[PYTHON] Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer

Hallo. Ich heiße @ yos1up. Mein Lieblingsessen sind grüne Pilze.

2016.10.12 zu Papier DeepMind haben in Nature veröffentlicht [Hybrid Berechnung eines neuronalen Netzes mit dynamischen externen Speicher mit](http://www.nature.com/articles/nature20101.epdf?author_access_token=ImTXBI8aWbYxYQ51Plys8NRgN0jAjWel9jnR3ZoTv0MggmpDmwljGswxVdeocYSurJ3hxupzWuRNeGvvXnoO8o4jTJcnAyhGuZzXJ1GEaD-Z7E6X_a9R-xqJ9TfJWBqz Ich habe das in vorgeschlagene neuronale Netzwerkmodell ** DNC (Differentiable Neural Computers) ** eilig mit Chainer implementiert.

Über DNC

DNC ist ein neues neuronales Netzwerk, das im vorherigen Artikel vorgeschlagen wurde, und es wird erwartet, dass es eine hohe Informationsverarbeitungsfähigkeit aufweist. In diesem Artikel kann DNC verwendet werden, um Aufgaben zu lernen, von denen bisher angenommen wurde, dass sie mit einem neuronalen Netzwerk nicht zu erlernen sind, z. B. die Aufgabe mit dem kürzesten Pfad in einem Diagramm und eine kleine Puzzle-Aufgabe, und ihre hohe Fähigkeit zur Informationsverarbeitung wird sichtbar.

DNC hat eine Struktur, in der "Speicher" an RNN (wiederkehrendes neuronales Netzwerk) angeschlossen ist. Dieser "Speicher" hat einen Kopf, der Informationen liest, und einen Kopf, der Informationen schreibt, und es ist möglich, den Kopf (gemäß einer begrenzten Methode) zu bewegen und Informationen über die Position des Kopfes zu lesen und zu schreiben.

Ein normaler RNN nimmt eine externe Eingabe entgegen und erzeugt stündlich eine Ausgabe (und aktualisiert seinen internen Status). Andererseits empfängt die RNN in der DNC zusätzlich zu dem normalen externen Eingang zu jedem Zeitpunkt "Daten, die zum unmittelbar vorhergehenden Zeitpunkt aus dem" Speicher "gelesen wurden". Dann wird zusätzlich zur normalen Ausgabe auch die "Betriebsanweisung von" Speicher "" ausgegeben. Gemäß dieser Operationsanweisung ändert sich die Position des Speicherkopfs, der Speicher an der Position des Schreibkopfs wird neu geschrieben und der Speicher an der Position des Lesekopfs wird gelesen. Die gelesenen Daten werden beim nächsten Mal (zusammen mit dem normalen externen Eingang) in das RNN eingegeben.

Die RNN in der DNC lernt das Kopplungsgewicht (durch die Gradientenmethode), so dass in der Situation, in der diese Stütze als "Speicher" bezeichnet wird, eine geeignete Eingabe / Ausgabe-Beziehung realisiert werden kann. Wie Sie diese Requisite verwenden (oder nicht verwenden), hängt von Ihrem Lernen ab.

Obwohl es eine spezielle Form von "Speicher mit Kopf" hat, muss es der "interne Zustand" des RNN sein, und in diesem Sinne ist die DNC eine Erweiterung der GRU und des LSTM, und der "interne Zustand ist erheblich kompliziert geworden". Man kann sagen, dass es "RNN" ist [^ 2]. Dank dieses "Speichers" kann DNC eine komplexe Informationsverarbeitung realisieren, die von herkömmlichen RNNs nicht verarbeitet werden kann.

[^ 2]: Sie bezeichnen diesen Speicher in ihren Arbeiten jedoch als "externen Speicher". Aus dem folgenden Artikel: Das Verhalten des Netzwerks ist unabhängig von der Speichergröße, solange der Speicher nicht voll ist. Aus diesem Grund betrachten wir den Speicher als "extern". (Sofern der Speicher nicht die Kapazität erreicht, werden wir Das Verhalten des Netzwerks hängt nicht von der Speichergröße ab, daher betrachten wir diesen Speicher als "externen" Speicher.)

Darüber hinaus bietet der "Speicher mit Kopf", der für die Durchführung aller Arten der Informationsverarbeitung einigermaßen praktisch zu sein scheint, DNC ** Vielseitigkeit **, die alle Arten von Aufgaben auf ihre eigene Weise bewältigen kann. Ich persönlich erwarte, dass es da ist. Die Vielzahl der Aufgabengenres, die DNC in diesem Artikel löst, lässt uns auch eine hohe Vielseitigkeit erwarten.

Das im Dezember 2014 vorgeschlagene neuronale Netzwerk mit dem Namen NTM (Neural Turing Machine) hat eine ähnliche Struktur wie diese DNC. DNC wird jedoch insofern eingeschaltet, als es rationaler ist, den Speicherkopf zu bewegen als NTM. (Das Papier enthält eine Zusammenfassung der Unterschiede zwischen NTM und DNC bei den Methoden.)

Implementierung

Der diesmal implementierte Code (Python 2.7) ist unten dargestellt. (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

In diesem Artikel sind die Details des Modells fest in Methoden und ergänzendem Material geschrieben, und ich fühlte mich sehr nett. Insbesondere enthält das Supplementary alle Variablen und Formeln im Modell, und ich konnte das Modell vervollständigen, indem ich einfach alle hier geschriebenen Formeln in der Reihenfolge von oben in den Code "portierte". (Variablennamen im obigen Code entsprechen fast den Variablennamen in Zusatzformeln.) Außerdem konnte ich beim Schreiben des Codes einige Details zu den verschiedenen Funktionen erfahren, die die Variable des Kettenführers bedienen. (Ich kann zusammenfassen, wenn ich die Gelegenheit habe.)

Der obige Code ist ein Code, mit dem DNC eine sehr einfache Aufgabe lernen kann (eine Aufgabe, die eine kurze Symbolzeichenfolge mit einer Verzögerung wiedergibt). Es funktioniert ohne Fehler. Sie können mit etwa 1000 Daten lernen. (* Diese Aufgabe steht nicht im Papier.) Da es jedoch in Eile implementiert wurde, gibt es keine Gewissheit, dass die DNC korrekt wie in dem Papier implementiert wurde. Wenn Sie Fehler finden, lassen Sie es uns bitte wissen.

In Zukunft möchte ich die diesmal erstellte DNC auf verschiedene Lernaufgaben anwenden. Ich möchte DeepMinds Artikel sehen und versuchen, Rätsel zu lösen.

Änderungsprotokoll

30.10.2016 …… Ich habe festgestellt, dass das Schneiden von Variablen präzise geschrieben werden kann, daher habe ich einen Teil des Codes geändert.

Recommended Posts

Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer
Rank Learning über ein neuronales Netzwerk (RankNet-Implementierung von Chainer)
Implementierung von "verschwommenen" neuronalen Netzen mit Chainer
Bayesianische Optimierungsimplementierung von Hyperparametern des neuronalen Netzwerks (Chainer + GPyOpt)
Einfache Implementierung eines neuronalen Netzwerks mit Chainer
Zusammenfassung der grundlegenden Implementierung von PyTorch
Implementierung eines zweischichtigen neuronalen Netzwerks 2
Python-Lernnotiz für maschinelles Lernen von Chainer Kapitel 13 Grundlagen des neuronalen Netzwerks
Implementierung eines 3-Schicht-Neuronalen Netzwerks (kein Lernen)
Implementierung eines Dialogsystems mit Chainer [seq2seq]
Implementierung von SVM durch probabilistische Gradientenabstiegsmethode
[Chainer] Dokumentklassifizierung nach Faltungsnetzwerk
Erkennung handgeschriebener Zahlen durch ein mehrschichtiges neuronales Netzwerk
Tiefes Lernen durch Implementierung (Segmentierung) ~ Implementierung von SegNet ~