[PYTHON] Présentation de DNC (Differentiable Neural Computers) + Implémentation par Chainer

Bonjour. Mon nom est @ yos1up. Ma nourriture préférée est les champignons verts.

12/10/2016 à DeepMind de papier ont publié dans Nature, [Hybrid calcul en utilisant un réseau de neurones avec une mémoire externe dynamique](http://www.nature.com/articles/nature20101.epdf?author_access_token=ImTXBI8aWbYxYQ51Plys8NRgN0jAjWel9jnR3ZoTv0MggmpDmwljGswxVdeocYSurJ3hxupzWuRNeGvvXnoO8o4jTJcnAyhGuZzXJ1GEaD-Z7E6X_a9R-xqJ9TfJWBqz J'ai mis en œuvre à la hâte le modèle de réseau neuronal ** DNC (Differentiable Neural Computers) ** proposé dans) avec Chainer.

À propos de DNC

DNC est un nouveau réseau neuronal proposé dans l'article ci-dessus, et sa haute capacité de traitement de l'information est attendue. Dans l'article, DNC peut être utilisé pour apprendre des tâches qui étaient auparavant considérées comme impossibles à apprendre avec un réseau neuronal, telles que la tâche de chemin le plus court sur un graphique et une petite tâche de puzzle, et sa capacité de traitement de l'information élevée peut être vue.

DNC a une structure dans laquelle la "mémoire" est attachée au RNN (réseau neuronal récurrent). Cette "mémoire" a une tête qui lit les informations et une tête qui écrit les informations, et il est possible de déplacer la tête (selon une méthode limitée) et de lire et écrire des informations sur la position de la tête.

Un RNN normal prend une entrée externe et produit une sortie (et met à jour son état interne) toutes les heures. D'autre part, le RNN dans le DNC reçoit à chaque instant des "données lues dans la" mémoire "à l'instant immédiatement précédent" en plus de l'entrée externe normale. Ensuite, en plus de la sortie normale, l '"instruction d'opération de" mémoire "" est également sortie. Selon cette instruction d'opération, la position de la tête de mémoire change, la mémoire à la position de la tête d'écriture est réécrite, et la mémoire à la position de la tête de lecture est lue. Les données lues seront entrées dans le RNN (avec l'entrée externe normale) à la prochaine fois.

Le RNN dans le DNC apprend le poids de couplage (par la méthode du gradient) de sorte qu'une relation d'entrée / sortie appropriée peut être réalisée dans la situation étant donné cet accessoire appelé "mémoire". La façon dont vous utilisez (ou n'utilisez pas) cet accessoire dépend de votre apprentissage.

Bien qu'il ait une forme particulière de «mémoire avec une tête», il doit s'agir de «l'état interne» du RNN, et en ce sens, le DNC est une extension du GRU et du LSTM, et «l'état interne est devenu considérablement compliqué». On peut dire que c'est "RNN" [^ 2]. Grâce à cette «mémoire», DNC peut réaliser ** des traitements d'informations complexes qui ne peuvent être gérés par les RNN conventionnels **.

[^ 2]: Cependant, ils appellent cette mémoire «mémoire externe» dans leurs articles. D'après l'article suivant: Le comportement du réseau est indépendant de la taille de la mémoire tant que la mémoire n'est pas remplie à sa capacité, c'est pourquoi nous considérons la mémoire comme `` externe '' (à moins que la mémoire n'atteigne sa capacité, nous Le comportement du réseau ne dépend pas de la taille de la mémoire, nous considérons donc cette mémoire comme une mémoire "externe".)

De plus, la "mémoire avec tête" qui semble raisonnablement pratique pour effectuer toutes sortes de traitements d'informations confère à DNC ** une polyvalence ** capable de gérer tous les types de tâches à sa manière. Je m'attends personnellement à ce qu'il soit là. La grande variété de genres de tâches que DNC résout dans le papier nous fait également nous attendre à une grande polyvalence.

Le réseau de neurones qu'ils ont proposé en décembre 2014 appelé NTM (Neural Turing Machine) a une structure similaire à ce DNC. Cependant, DNC est alimenté en ce sens qu'il est plus rationnel de déplacer la tête de mémoire que NTM. (Il y a un résumé des différences entre NTM et DNC dans les méthodes dans l'article.)

la mise en oeuvre

Le code implémenté cette fois (Python 2.7) est illustré ci-dessous. (GitHub)

main.py


import numpy as np
import math
import chainer
from chainer import functions as F
from chainer import links as L
from chainer import \
     cuda, gradient_check, optimizers, serializers, utils, \
     Chain, ChainList, Function, Link, Variable


def onehot(x,n):
    ret = np.zeros(n).astype(np.float32)
    ret[x] = 1.0
    return ret

def overlap(u, v): # u, v: (1 * -) Variable  -> (1 * 1) Variable
    denominator = F.sqrt(F.batch_l2_norm_squared(u) * F.batch_l2_norm_squared(v))
    if (np.array_equal(denominator.data, np.array([0]))):
        return F.matmul(u, F.transpose(v))
    return F.matmul(u, F.transpose(v)) / F.reshape(denominator,(1,1))


def C(M, k, beta):
    # (N * W), (1 * W), (1 * 1) -> (N * 1)
    # (not (N * W), ({R,1} * W), (1 * {R,1}) -> (N * {R,1}))
    W = M.data.shape[1]    
    ret_list = [0] * M.data.shape[0]
    for i in range(M.data.shape[0]):
        ret_list[i] = overlap(F.reshape(M[i,:], (1, W)), k) * beta # pick i-th row
    return F.transpose(F.softmax(F.transpose(F.concat(ret_list, 0)))) # concat vertically and calc softmax in each column



def u2a(u): # u, a: (N * 1) Variable
    N = len(u.data)
    phi = np.argsort(u.data.reshape(N)) # u.data[phi]: ascending
    a_list = [0] * N    
    cumprod = Variable(np.array([[1.0]]).astype(np.float32)) 
    for i in range(N):
        a_list[phi[i]] = cumprod * (1.0 - F.reshape(u[phi[i],0], (1,1)))
        cumprod *= F.reshape(u[phi[i],0], (1,1))
    return F.concat(a_list, 0) # concat vertically



class DeepLSTM(Chain): # too simple?
    def __init__(self, d_in, d_out):
        super(DeepLSTM, self).__init__(
            l1 = L.LSTM(d_in, d_out),
            l2 = L.Linear(d_out, d_out),)
    def __call__(self, x):
        self.x = x
        self.y = self.l2(self.l1(self.x))
        return self.y
    def reset_state(self):
        self.l1.reset_state()


    
class DNC(Chain):
    def __init__(self, X, Y, N, W, R):
        self.X = X # input dimension
        self.Y = Y # output dimension
        self.N = N # number of memory slot
        self.W = W # dimension of one memory slot
        self.R = R # number of read heads
        self.controller = DeepLSTM(W*R+X, Y+W*R+3*W+5*R+3)
        
        super(DNC, self).__init__(
            l_dl = self.controller,
            l_Wr = L.Linear(self.R * self.W, self.Y) # nobias=True ? 
            )# <question : should all learnable weights be here??>
        self.reset_state()
    def __call__(self, x):
        # <question : is batchsize>1 possible for RNN ? if No, I will implement calculations without batch dimension.>
        self.chi = F.concat((x, self.r))
        (self.nu, self.xi) = \
                  F.split_axis(self.l_dl(self.chi), [self.Y], 1)
        (self.kr, self.betar, self.kw, self.betaw,
         self.e, self.v, self.f, self.ga, self.gw, self.pi
         ) = F.split_axis(self.xi, np.cumsum(
             [self.W*self.R, self.R, self.W, 1, self.W, self.W, self.R, 1, 1]), 1)

        self.kr = F.reshape(self.kr, (self.R, self.W)) # R * W
        self.betar = 1 + F.softplus(self.betar) # 1 * R
        # self.kw: 1 * W
        self.betaw = 1 + F.softplus(self.betaw) # 1 * 1
        self.e = F.sigmoid(self.e) # 1 * W
        # self.v : 1 * W
        self.f = F.sigmoid(self.f) # 1 * R
        self.ga = F.sigmoid(self.ga) # 1 * 1
        self.gw = F.sigmoid(self.gw) # 1 * 1
        self.pi = F.softmax(F.reshape(self.pi, (self.R, 3))) # R * 3 (softmax for 3)

        # self.wr : N * R
        self.psi_mat = 1 - F.matmul(Variable(np.ones((self.N, 1)).astype(np.float32)), self.f) * self.wr # N * R
        self.psi = Variable(np.ones((self.N, 1)).astype(np.float32)) # N * 1
        for i in range(self.R):
            self.psi = self.psi * F.reshape(self.psi_mat[:,i],(self.N,1)) # N * 1

        # self.ww, self.u : N * 1
        self.u = (self.u + self.ww - (self.u * self.ww)) * self.psi
        
        self.a = u2a(self.u) # N * 1
        self.cw = C(self.M, self.kw, self.betaw) # N * 1
        self.ww = F.matmul(F.matmul(self.a, self.ga) + F.matmul(self.cw, 1.0 - self.ga), self.gw) # N * 1
        self.M = self.M * (np.ones((self.N, self.W)).astype(np.float32) - F.matmul(self.ww, self.e)) + F.matmul(self.ww, self.v) # N * W

        self.p = (1.0 - F.matmul(Variable(np.ones((self.N,1)).astype(np.float32)), F.reshape(F.sum(self.ww),(1,1)))) \
                  * self.p + self.ww # N * 1
        self.wwrep = F.matmul(self.ww, Variable(np.ones((1, self.N)).astype(np.float32))) # N * N
        self.L = (1.0 - self.wwrep - F.transpose(self.wwrep)) * self.L + F.matmul(self.ww, F.transpose(self.p)) # N * N
        self.L = self.L * (np.ones((self.N, self.N)) - np.eye(self.N)) # force L[i,i] == 0   

        self.fo = F.matmul(self.L, self.wr) # N * R
        self.ba = F.matmul(F.transpose(self.L), self.wr) # N * R

        self.cr_list = [0] * self.R
        for i in range(self.R):
            self.cr_list[i] = C(self.M, F.reshape(self.kr[i,:],(1, self.W)),
                                F.reshape(self.betar[0,i],(1, 1))) # N * 1
        self.cr = F.concat(self.cr_list) # N * R

        self.bacrfo = F.concat((F.reshape(F.transpose(self.ba),(self.R,self.N,1)),
                               F.reshape(F.transpose(self.cr),(self.R,self.N,1)),
                               F.reshape(F.transpose(self.fo) ,(self.R,self.N,1)),),2) # R * N * 3
        self.pi = F.reshape(self.pi, (self.R,3,1)) # R * 3 * 1
        self.wr = F.transpose(F.reshape(F.batch_matmul(self.bacrfo, self.pi), (self.R, self.N))) # N * R
            
        self.r = F.reshape(F.matmul(F.transpose(self.M), self.wr),(1, self.R * self.W)) # W * R (-> 1 * RW)
        
        self.y = self.l_Wr(self.r) + self.nu # 1 * Y
        return self.y
    def reset_state(self):
        self.l_dl.reset_state()
        self.u = Variable(np.zeros((self.N, 1)).astype(np.float32))
        self.p = Variable(np.zeros((self.N, 1)).astype(np.float32))
        self.L = Variable(np.zeros((self.N, self.N)).astype(np.float32))                           
        self.M = Variable(np.zeros((self.N, self.W)).astype(np.float32))
        self.r = Variable(np.zeros((1, self.R*self.W)).astype(np.float32))
        self.wr = Variable(np.zeros((self.N, self.R)).astype(np.float32))
        self.ww = Variable(np.zeros((self.N, 1)).astype(np.float32))
        # any variable else ?

X = 5
Y = 5
N = 10
W = 10
R = 2
mdl = DNC(X, Y, N, W, R)
opt = optimizers.Adam()
opt.setup(mdl)
datanum = 100000
loss = 0.0
acc = 0.0
for datacnt in range(datanum):
    lossfrac = np.zeros((1,2))
    # x_seq = np.random.rand(X,seqlen).astype(np.float32)
    # t_seq = np.random.rand(Y,seqlen).astype(np.float32)
    # t_seq = np.copy(x_seq)

    contentlen = np.random.randint(3,6)
    content = np.random.randint(0,X-1,contentlen)
    seqlen = contentlen + contentlen
    x_seq_list = [float('nan')] * seqlen
    t_seq_list = [float('nan')] * seqlen    
    for i in range(seqlen):
        if (i < contentlen):
            x_seq_list[i] = onehot(content[i],X)
        elif (i == contentlen):
            x_seq_list[i] = onehot(X-1,X)
        else:
            x_seq_list[i] = np.zeros(X).astype(np.float32)
            
        if (i >= contentlen):
            t_seq_list[i] = onehot(content[i-contentlen],X)    
    
    mdl.reset_state()
    for cnt in range(seqlen):
        x = Variable(x_seq_list[cnt].reshape(1,X))
        if (isinstance(t_seq_list[cnt], np.ndarray)):
            t = Variable(t_seq_list[cnt].reshape(1,Y))
        else:
            t = []
            
        y = mdl(x)
        if (isinstance(t,chainer.Variable)):
            loss += (y - t)**2
            print y.data, t.data, np.argmax(y.data)==np.argmax(t.data)
            if (np.argmax(y.data)==np.argmax(t.data)): acc += 1
        if (cnt+1==seqlen):
            mdl.cleargrads()
            loss.grad = np.ones(loss.data.shape, dtype=np.float32)
            loss.backward()
            opt.update()
            loss.unchain_backward()
            print '(', datacnt, ')', loss.data.sum()/loss.data.size/contentlen, acc/contentlen
            lossfrac += [loss.data.sum()/loss.data.size/seqlen, 1.]
            loss = 0.0
            acc = 0.0

Dans cet article, les détails du modèle sont fermement écrits dans Méthodes et matériel supplémentaire, et je me suis senti très gentil. En particulier, le Supplémentaire contient toutes les variables et les formules du modèle, et j'ai pu compléter le modèle simplement en "portant" toutes les formules écrites ici dans le code dans l'ordre du haut. (Les noms de variables dans le code ci-dessus correspondent presque aux noms de variables dans les formules supplémentaires.) De plus, lors de l'écriture du code, j'ai pu obtenir quelques détails sur les différentes fonctions qui actionnent la variable du chainer. (Je peux résumer si j'en ai l'occasion.)

Le code ci-dessus est un code qui permet à DNC d'apprendre une tâche très simple (une tâche qui fait écho à une courte chaîne de symboles avec un délai). Cela fonctionne sans aucune erreur. Vous pouvez apprendre avec environ 1000 données. (* Cette tâche n'est pas dans le papier.) Cependant, comme il a été mis en œuvre à la hâte, il n'y a aucune certitude que le DNC a été mis en œuvre correctement comme dans le document. Si vous constatez des erreurs, veuillez nous en informer.

À l'avenir, j'aimerais appliquer le DNC créé cette fois à diverses tâches d'apprentissage. Je veux voir l'article de DeepMind et essayer de résoudre des énigmes.

Journal des modifications

2016/10/30 …… J'ai trouvé que le découpage de Variable peut être écrit de manière concise, j'ai donc modifié une partie du code.

Recommended Posts

Présentation de DNC (Differentiable Neural Computers) + Implémentation par Chainer
Apprentissage des classements à l'aide d'un réseau neuronal (implémentation RankNet par Chainer)
Implémentation de réseaux neuronaux "flous" avec Chainer
Mise en œuvre de l'optimisation bayésienne des hyper paramètres du réseau de neurones (Chainer + GPyOpt)
Implémentation simple d'un réseau neuronal à l'aide de Chainer
Résumé de l'implémentation de base par PyTorch
Implémentation d'un réseau de neurones à deux couches 2
Mémo d'apprentissage Python pour l'apprentissage automatique par Chainer Chapitre 13 Bases du réseau neuronal
Implémentation d'un réseau neuronal à 3 couches (pas d'apprentissage)
Implémentation d'un système de dialogue utilisant Chainer [seq2seq]
Implémentation de SVM par méthode de descente de gradient probabiliste
[Chainer] Classification des documents par réseau de neurones convolutifs
Reconnaissance des nombres manuscrits par un réseau neuronal multicouche
Apprentissage profond appris par mise en œuvre (segmentation) ~ Mise en œuvre de SegNet ~