Ein von DeepMind bei Nature eingereichtes Papier Hybrid computing using a neural network with dynamic external memory Beschreibt das "Differentiable Neural Computing (DNC)", das in verwendet wird. Die Erklärung der Logik ist die wichtigste, aber auch das Implementierungsbeispiel von Python - Chainer wird vorgestellt.
Der Wunsch, sequentielle Daten mit Neural Net zu verarbeiten, scheint schon lange zu bestehen, und der Standard ist Recurrent Neural Net (RNN). RNN hat jedoch ein "Gradienten-Verschwinden-Problem", und das Modell, das es überwindet, heißt Long Short Term Memory (LSTM). Kurz und lang ist, dass die Eingabedaten bis zur Ausgabe im Neuronalen Netz bleiben. Sie können sich das in gewisser Weise als "Erinnerung" vorstellen. Es kann jedoch nur für eine "kurze Zeit" von der Eingabe zur Ausgabe gespeichert werden. Daher wird es "kurzfristige Mutterschaft" genannt. Es scheint, dass es als Langzeitkurzgedächtnis in dem Sinne bezeichnet wird, dass "dieses Kurzzeitgedächtnis für eine lange Zeit gespeichert werden kann". Dann habe ich das Gefühl, dass das wirklich lange Meomory außerhalb der Lernmaschine statt innerhalb sein kann. Ich weiß nicht, ob es aus einem solchen Gedanken entstanden ist, aber Differentiable Neural Computing (DNC) besteht darin, ein Gedächtnis außerhalb der Lernmaschine vorzubereiten und es lernen zu lassen, einschließlich der Verwendung des Gedächtnisses. Es bedeutet "Differenzierbar", aber wenn Sie mit Rückausbreitung trainieren möchten, müssen Sie in der Lage sein, das Differential zu berechnen. Daher bedeutet dies, dass "Differentialberechnung möglich ist", einschließlich Operationen am externen Speicher.
Die Komponenten der DNC sind der Hauptkörper-Controller und der externe Speicher. Dem Papier zufolge kann der gesamte Datenfluss wie in der folgenden Abbildung dargestellt dargestellt werden. DNC behandelt sequentielle Daten, sodass der aktuelle Zeitschritt als $ t $ dargestellt wird. Der vorherige Schritt ist $ t-1 $, der nächste Schritt ist $ t + 1 $ und der Index $ t $ in der Abbildung gibt an, in welchem Zeitschritt jede Variable generiert wurde.
Schauen wir uns den Datenfluss Schritt für Schritt an. Die Nummern (1) bis (4) sind in der Abbildung zugeordnet. Befolgen Sie sie daher in dieser Reihenfolge. (1): Eingabedaten $ x_t $ werden aus "Datensatz" eingegeben. $ \ Boldsymbol {\ chi} \ _ t = [\ boldsymbol {x} \ _ t, \ boldsymbol, kombiniert mit der Ausgabe aus dem Speicher im vorherigen Schritt $ \ boldsymbol {r} \ _ {t-1} $ Sei {r} \ _ {t-1}] $ die Eingabe in den Controller. (2): Da die Ausgabe $ \ boldsymbol {h} \ _t $ vom Controller erhalten wird, teilen Sie sie in zwei Routen. Eines ist $ \ boldsymbol {v} \ _t $ für "Out Put" und das andere ist "Schnittstellenvektor" $ \ boldsymbol {\ xi} \ _t $ für die Steuerung des Speichers. In der Arbeit sind dies die linearen Transformationen von $ \ boldsymbol {h} \ _t $ $ \ boldsymbol {v} \ _t = W \ _y \ boldsymbol {h} \ _t $ und $ \ boldsymbol {\ xi} \ _ t = Es wird als W \ _ {\ xi} \ boldsymbol {h} \ _ t $ geschrieben. (3): Daten werden basierend auf dem "Schnittstellenvektor" $ \ boldsymbol {\ xi} \ _t $ in den Speicher geschrieben, und der Speicherstatus wird aktualisiert. Es liest auch aus dem Speicher und gibt Ihnen einen "Lesevektor" $ \ boldsymbol {r} _t $. (4): "Lesevektor" $ \ boldsymbol {r} \ _ t $ wird der Ausgabe zu "Out Put" hinzugefügt, während sie im nächsten Schritt an die Eingabe an die Steuerung gesendet wird. (5): Kombinieren Sie die Ausgabe vom Controller und die Ausgabe vom Speicher und gehen Sie zu "Ausgabe" $ \ boldsymbol {y} \ _ t = \ boldsymbol {v} \ _ t + W \ _r \ boldsymbol {r} \ _ t $ Wird ausgegeben. Wie oben erwähnt, wird 1 Schritt durch (1) bis (5) abgeschlossen.
Der Controller kann eine beliebige Lernmaschine sein, die mehrdimensionale Eingaben empfängt und mehrdimensionale Ausgaben zurückgibt. Es scheint jedoch, dass häufig (wiederkehrende) neuronale Netze und LSTM verwendet werden. (Wiederkehrend) Ich werde die Erklärung von Neural Net und LSTM anderen überlassen, aber in diesem Artikel werde ich das "Lesen und Schreiben von externem Speicher" erklären, was das größte Merkmal von DNC ist.
Lassen Sie uns nun über das Lesen und Schreiben von externem Speicher sprechen. Es ist ein Ärger, also werde ich es unten einfach Speicher nennen.
Lassen Sie uns zunächst die Struktur des Gedächtnisses verstehen. Als Speicher wird eine Matrix von $ N $ × $ W $ verwendet. Beachten Sie also, dass Adressen von $ 1 $ bis $ N $ zugewiesen werden und jede Adresse einen Steckplatz hat, in dem ein numerischer Vektor mit der Dimension $ W $ gespeichert werden kann. Da der Speicherstatus jeden Moment aktualisiert wird, schreiben wir die Matrix, die den Speicher im Zeitschritt $ t $ darstellt, als $ M \ _t $. Es wird jedoch angenommen, dass die Dimensionen der Matrix $ N $ × $ W $ fest sind, wobei $ N $ immer die Gesamtzahl der Speicherschlitze (Gesamtzahl der Adressen) und $ W $ die Länge der Schlitze (des zu speichernden numerischen Vektors) ist. Abmessungen).
Wie unter "Gesamtdatenfluss" erwähnt, ist der Betrieb des Speichers durch die Steuerung Dies erfolgt über den "Schnittstellenvektor" $ \ boldsymbol {\ xi} \ _t $. Da mehrere Aufgaben berücksichtigt werden können, z. B. Lesen / Schreiben / Adressieren, wird $ \ boldsymbol {\ xi} \ _t $ nach Funktionen in jede Komponente zerlegt, selbst wenn Sie Speicheroperation in einem Biss sagen.
\begin{align}
\boldsymbol{\xi}_t = \Big[ \boldsymbol{k}_t^{r, 1},...,\boldsymbol{k}_t^{r, R}, \; \hat{\beta}_t^{r, 1},...,\hat{\beta}_t^{r, R}, \; \boldsymbol{k}_t^w, \; \hat{\beta_t}^w, \hat{\boldsymbol{e}}_t, \; \boldsymbol{\nu}_t, \; \hat{f}_t^1,...,\hat{f}_t^R, \; \hat{g}_t^a, \; \hat{g}_t^w, \; \hat{\boldsymbol{\pi}}_t^1,...,\hat{\boldsymbol{\pi}}_t^R \Big]
\end{align}
Diejenigen mit dem Zeichen '$ \ hat {} $' unterliegen einer weiteren Skalierungskonvertierung. Es gibt viele Arten und es ist kompliziert, deshalb werde ich es einmal zusammenfassen. Der Wert in (,) ist (Dimension, Zahl). ・ "Leseschlüssel" $ (W, R) ;: ; \ boldsymbol {k} \ _ t ^ {r, 1}, ..., \ boldsymbol {k} \ _ t ^ {r, R} $ ・ "Lesestärke" $ (1, R) ;: ; \ beta \ _ t ^ {r, 1}, ..., \ beta \ _ t ^ {r, R} ; ; \ big (\ beta) \ _ t ^ {r, i} = \ text {oneplus} (\ hat {\ beta} \ _ t ^ {r, i}) \ big) $ ・ "Schreibschlüssel" $ (W, 1) ;: ; \ boldsymbol {k} \ _ t ^ w $ ・ "Schreibstärke" $ (1,1) ;: ; \ beta \ _ t ^ w = \ text {oneplus} (\ hat {\ beta} \ _ t ^ w) $ ・ "Vektor löschen" $ (W, 1) ;: ; \ boldsymbol {e} \ _ t = \ sigma (\ hat {\ boldsymbol {e}} _t) $ ・ "Schreibvektor" $ (W, 1) ;: ; \ boldsymbol {\ nu} \ _t $ ・ "Freies Tor" $ (1, R) ;: ; f \ _ t ^ 1, ..., f \ _ t ^ R ; ; \ big (f \ _ t ^ i = \ sigma (\ hat { f} \ _ t ^ i) \ big) $ ・ "Allocation Gate" $ (1, 1) ;: ; g ^ a \ _ t = \ sigma (\ hat {g} ^ a \ _ t) $ ・ "Write gate" $ (1, 1) ;: ; g ^ w \ _ t = \ sigma (\ hat {g} ^ w \ _t) $ ・ "Lesemodus" $ (3, R) ;: ; \ boldsymbol {\ pi} \ _ t ^ 1, ..., \ boldsymbol {\ pi} \ _ t ^ R ; ; \ big (\ boldsymbol {\ pi} \ _ t ^ i = \ text {softmax} (\ hat {\ boldsymbol {\ pi}} \ _ t ^ i) \ big) $ Die für die Skalenkonvertierung verwendete Funktion ist
\begin{align}
& \text{oneplus}(x) = 1+\text{log}(1+e^x) \in [1, \infty) \\
& \sigma(x) = \frac{1}{1+e^{-x}} \in [0, 1] \\
& \text{softmax}(\boldsymbol{x}) = \frac{e^\boldsymbol{x}}{\sum_ie^{x_i}} \in [0, 1]
\end{align}
Ist definiert als. Beachten Sie, dass wenn Sie einen Vektorwert als Argument für $ \ text {oneplus} (x) $, $ \ sigma (x) $, $ \ text {exp} (x) $ verwenden, dies die Aktion jeder Komponente bedeutet. Bitte.
Ich liste hier nur die Definitionen auf, also glaube ich nicht, dass Sie etwas wissen. Im Folgenden wird erläutert, wie diese Komponenten zur Steuerung des Speichers verwendet werden.
Bevor wir fortfahren, möchte ich noch einen Punkt hinzufügen. $ R $ steht für die Anzahl der Lesevorgänge aus dem Speicher. Die DNC im Papier ist so eingestellt, dass sie in einem Schritt mehrmals aus dem Speicher liest, und die Häufigkeit beträgt $ R $. Andererseits wird das Schreiben nur einmal pro Schritt eingestellt. Sie können auch sehen, dass die Abmessungen des Schnittstellenvektors durch $ W $ und $ R $ bestimmt werden, was $ WR + 3W + 5R + 3 $ ist.
Um Speicher zu lesen oder zu schreiben, müssen Sie die Adresse des Speichersteckplatzes angeben, der gelesen oder geschrieben werden soll. Um jedoch durch Rückausbreitung lernen zu können, ist eine Differenzierbarkeit, zumindest die Kontinuität der Operationen, erforderlich. Anstatt nur eine bestimmte Adresse anzugeben, geben wir daher eine Breite und ein Gewicht an, welche Adresse intensiv gelesen / geschrieben wird. Im Folgenden wird diese Gewichtung als "Lese- / Schreibgewichtung" bezeichnet, aber es geht darum, wie diese zu finden sind.
Sobald Sie nach "Lese- / Schreibgewichtung" gefragt haben, ist der Lese- / Schreibvorgang selbst einfach. Ich werde die Details später hinterlassen, aber lassen Sie uns einen Überblick bekommen. Betrachten Sie zunächst den Fall des Lesens. Angenommen, Sie erhalten "Lesegewichtung" $ \ boldsymbol {w} ^ r \ _t $. Die Dimension dieses Vektors ist $ N $, was der Gesamtzahl der Speichersteckplätze entspricht. Jede Komponente gibt an, wie "stark" die Informationen im Speichersteckplatz der entsprechenden Adresse gelesen werden. Das heißt, die gelesenen Informationen werden als $ M \ _t ^ T \ boldsymbol {w} ^ r \ _t $ als Produkt der Speichermatrix und des Gewichtsvektors ausgedrückt. Wie bereits erwähnt, wird in der Veröffentlichung festgelegt, dass in einem Schritt $ R $ mal gelesen wird, sodass "Lesegewichtung" auch $ R $ $ \ {\ boldsymbol {w} \ _ t ^ {r, i} ist. \} \ _ {i = 1, ..., R} $ Besteht. Auch als natürliche Aufforderung, eine Verstärkung durch Lesen zu verhindern.
\begin{align}
& 0 \leqq \boldsymbol{w}_t^{r,i}[n] \leqq 1 \;\; (n=1,2,...,N)\\
& \sum_{n=1}^N \boldsymbol{w}_t^{r,i}[n] \leqq 1 \;\; (i=1,2,...,R)
\end{align}
Soll auferlegen. Betrachten Sie auch das Schreiben. Angenommen, Sie erhalten "Schreibgewichtung" $ \ boldsymbol {w_t ^ w} $. Wir haben auch einen Vektor $ \ boldsymbol {\ nu} \ _t $, der die Informationen darstellt, die wir schreiben möchten. Die Dimension von $ \ boldsymbol {w_t ^ w} $ ist $ N $, und jede Komponente bedeutet, wie "stark" $ \ boldsymbol {\ nu} \ _t $ in den entsprechenden Speichersteckplatz geschrieben wird. Mit anderen Worten, das Schreiben wird ausgeführt und der Speicher wird aktualisiert, indem die Matrix $ \ boldsymbol {w_t ^ w} \ boldsymbol {\ nu} \ _t ^ T $ zur Speichermatrix $ M \ _ {t-1} $ hinzugefügt wird. Für jede Zutat
\begin{align}
& 0 \leqq \boldsymbol{w}_t^w[n] \leqq 1 \;\; (n=1,2,...,N)\\
& \sum_{n=1}^N \boldsymbol{w}_t^w[n] \leqq 1
\end{align}
Der Punkt des Auferlegens ist der gleiche.
In diesem Artikel werden die folgenden vier Schritte ausgeführt, einschließlich Lesen und Schreiben in den Speicher selbst. ① Schreibgewichtung aktualisieren ② In den Speicher schreiben ③ Aktualisieren Sie die Lesegewichtung ④ Aus dem Speicher lesen
Im Folgenden werden wir diesen Schritt erläutern, aber selbst im Implementierungsbeispiel ist die Verarbeitung in dieser Reihenfolge zusammengefasst. Bitte beziehen Sie sich darauf. Zusätzlich Lese- / Schreibgewichtung $ \ {\ boldsymbol {w} \ _ {t-1} ^ {r, i} \} \ _ {i = 1, ..., R} im vorherigen Zeitschritt Es wird angenommen, dass $ / $ \ boldsymbol {w} \ _ {t-1} ^ {w} $ erhalten wurde.
Es gibt zwei Möglichkeiten, die Adresse auszuwählen, an die geschrieben werden soll, und die Schreibgewichtung besteht aus zwei Elementen. Das heißt, (1) Wählen Sie den Schreibzielschlitz basierend auf der "Schlüssel" -Eingabe über den Schnittstellenvektor aus, und (2) wählen Sie den Schlitz aus, in dem die verwendeten Informationen basierend auf dem Lesestatus bis zum vorherigen Zeitschritt verbleiben. ,,.
Beginnen wir zunächst mit (1). Die Ähnlichkeit wird berechnet, indem der "Schreibschlüssel" $ \ boldsymbol {k} \ _t ^ w $ im Schnittstellenvektor $ \ boldsymbol {\ xi} \ _t $ mit den Informationen in jedem Speichersteckplatz abgeglichen wird. Es verwendet auch "Schreibstärke" $ \ beta_t ^ w $ als Parameter, um die Schärfe des Gewichtungspeaks anzupassen. Diese Berechnung wird auch häufig zum Ermitteln der Lesegewichtung verwendet. Für die Matrix $ N $ × $ W $ $ M $, den Ordnungsvektor $ W $ $ \ boldsymbol {k} $ und den Skalarwert $ \ beta $ Definieren Sie die Operation.
\begin{align}
\mathcal{C}(M, \boldsymbol{k}, \beta)[n] = \frac{\text{exp}\big(\mathcal{D}(\boldsymbol{k}, M[n,:])\beta \big)}{\sum_m\text{exp}\big(\mathcal{D}(\boldsymbol{k}, M[m,:])\beta \big)}
\end{align}
Hier ist $ \ mathcal {D} $ der Abstand zwischen zwei Vektoren, und es gibt verschiedene Möglichkeiten, ihn zu nehmen, aber die Kosinusähnlichkeit entspricht dem Papier.
\begin{align}
\mathcal{D}(\boldsymbol{u}, \boldsymbol{v}) = \frac{\boldsymbol{u} \cdot \boldsymbol{v}}{||\boldsymbol{u}|| \; ||\boldsymbol{v}||}
\end{align}
Ich werde es als belassen. An der Grenze von $ \ beta \ rightarrow \ infty $ hat $ \ mathcal {C} $ nur einen scharfen Peak. Übrigens ist unter Verwendung der hier definierten Operation die Schreibgewichtung nach der Methode von (1)
\begin{align}
\boldsymbol{c}_t^w=\mathcal{C}(M_{t-1}, \boldsymbol{k}_t^w, \beta_t^w)
\end{align}
Ist berechnet.
Als nächstes folgt die Berechnung nach der Methode (2). Es ist etwas kompliziert, aber wir werden in der folgenden Reihenfolge danach fragen.
(2) -1. Konfiguration des "Retentionsvektors" $ \ boldsymbol {\ psi} \ _t $ Es ist natürlich, den Speichersteckplatz, dessen Informationen im vorherigen Zeitschritt $ t-1 $ gelesen wurden, als verwendeten Steckplatz zum Schreiben verwenden zu wollen. Wenn Sie jedoch noch über die benötigten Informationen verfügen, überschreiben Sie diese nicht. Auf diese Weise ist das Flag (Gewicht, weil es tatsächlich ein kontinuierlicher Wert von 0 bis 1 ist) "freies Tor" $ f \ _ t ^ i , ob der im vorherigen Zeitschritt gelesene Speicherschlitz wirklich freigegeben werden kann. ; (i = 1, ..., R) $. Das heißt, betrachten Sie $ f \ _t ^ i \ boldsymbol {w} \ _ {t-1} ^ {r, i} $ in Komponenteneinheiten und betrachten Sie "$ f \ _t ^ iw \ _ {t-1} ^ { r, i} $ $ \ simeq 1 $ $ \ Leftrightarrow f \ _t ^ i \ simeq 1 $ und $ w \ _ {t-1} ^ {r, i} \ simeq 1 $ "wird gelesen und geöffnet OK Also überschreib es. Auch "$ f \ _t ^ iw \ _ {t-1} ^ {r, i} $ $ \ simeq 0 $ $ \ Leftrightarrow f \ _t ^ i \ simeq 0 $ oder $ w \ _ {t-1} Wenn es "^ {r, i} \ simeq 0 $" ist, kann es nicht überschrieben werden, da es im vorherigen Zeitschritt nicht gelesen wurde, oder selbst wenn es gelesen wurde, wurde das Release-Flag nicht gesetzt. Jetzt habe ich über jede Lesung nachgedacht, aber ich habe den "Aufbewahrungsvektor" $ \ boldsymbol {\ psi} \ _t $ gesetzt, der das verwendete (nicht überschreibbare) Flag für alle $ R $ -Zeiten ist.
\begin{align}
\boldsymbol{\psi}_t = \prod_{i=1}^R\big(1-f_t^i\boldsymbol{w}_{t-1}^{r, i}\big)
\end{align}
Definiert in. Das Produkt ist das Produkt jeder Komponente. Da das Produkt nach Berechnung der Differenz von 1 berechnet wird, wird der Speicherplatz, der selbst in $ R $ als überschrieben beurteilt wird, als überschrieben beurteilt. Andererseits wird beurteilt, dass es nur dann verwendet wird (nicht überschrieben) $ \ psi \ _t \ simeq 1 $, wenn beurteilt wird, dass es nicht für alle $ R $ -Zeiten überschrieben werden kann.
(2) -2. Konfiguration des "Verwendungsvektors" $ \ boldsymbol {u} \ _t $ In (2) -1 dachte ich an $ \ boldsymbol {\ psi} \ _t $ als ein in Gebrauch befindliches Flag (korrektes Gewicht), aber ich dachte nur daran, im vorherigen Zeitschritt zu lesen. In der Realität sollte ein Schreibvorgang häufiger verwendet werden, und Sie sollten die Situation im Zeitschritt vor dem letzten Zeitschritt berücksichtigen. Basierend auf diesen wird das Gewicht "Speichernutzungsvektor" $ \ boldsymbol {u} _t $, das den Nutzungsgrad jedes Speichersteckplatzes darstellt, durch die folgende Aktualisierungsformel definiert.
\begin{align}
\boldsymbol{u}_t &= \big(\boldsymbol{u}_{t-1} + \boldsymbol{w}_{t-1}^w - \boldsymbol{u}_{t-1} \circ \boldsymbol{w}_{t-1}^w \big) \circ \boldsymbol{\psi}_t \\
\boldsymbol{u}_0 &= 0, \;\; \boldsymbol{w}_0^w = 0
\end{align}
$ \ Circ $ repräsentiert das Produkt jeder Komponente. Das dritte Element in (...) ist eine Korrektur, um jede Komponente von $ \ boldsymbol {u} \ _t $ so anzupassen, dass sie $ 1 $ nicht überschreitet. Wenn $ u \ _t = 1 $ erreicht ist, wird die Gewichtsaktualisierung durch Schreiben gestoppt, und selbst wenn $ u \ _t> 1 $ ist, wird der Wert durch die Aktualisierung reduziert.
(2) -3. Konfiguration der "Zuordnungsgewichtung" Bestimmt die Adresse des Speichersteckplatzes, in den geschrieben werden soll, basierend auf dem Nutzungsgrad $ \ boldsymbol {u} \ _t $. Grundsätzlich wird es an der Stelle geschrieben, an der der Nutzungsgrad gering ist, aber es ist geneigt. Zunächst sei $ \ boldsymbol {\ phi} _t $ der Vektor, der aus den Indizes besteht, wenn die Komponenten von $ \ boldsymbol {u} \ _t $ in aufsteigender Reihenfolge des Werts angeordnet sind. Mit anderen Worten
\begin{align}
\boldsymbol{u}_t[\boldsymbol{\phi}_t[1]] \leqq \boldsymbol{u}_t[\boldsymbol{\phi}_t[2]] \leqq
\boldsymbol{u}_t[\boldsymbol{\phi}_t[3]] \leqq \cdots \leqq
\boldsymbol{u}_t[\boldsymbol{\phi}_t[N]]
\end{align}
ist. Verwenden Sie diese Option, um $ \ boldsymbol {a} \ _t $ zu "gewichten"
\begin{align}
\boldsymbol{a}_t[\boldsymbol{\phi}_t[n]] = \big(1-\boldsymbol{u}_t[\boldsymbol{\phi}_t[n]]\big) \prod_{m=1}^{n-1}\boldsymbol{u}_t[\boldsymbol{\phi}_t[m]]
\end{align}
Definiert in. Das Produkt ist das Produkt jeder Komponente. Grundsätzlich ist es $ 1- \ boldsymbol {u} \ _t $, also ist es ein Gewicht, das den Grad der Verwendung angibt (beschreibbar).
Wie oben erwähnt, werden die Ergebnisse von (1) und (2) integriert und schreiben die Gewichtung $ \ boldsymbol {w} \ _t ^ w $
\begin{align}
\boldsymbol{w}_t^w = g_t^w\big(g_t^a\boldsymbol{a}_t+(1-g_t^a)\boldsymbol{c}_t^w\big)
\end{align}
Update als. Wobei $ g_t ^ a $ und $ g_t ^ w $ über den Schnittstellenvektor $ \ boldsymbol {\ xi} _t $ eingegeben wurden. Sie fungieren als Gates, die steuern, "ob basierend auf dem verwendeten Flag oder" Schlüssel "geschrieben werden soll" und "ob überhaupt geschrieben werden soll".
Nachdem die Aktualisierung der Schreibgewichtung abgeschlossen ist, schreiben Sie in den Speicher. Alte Informationen werden jedoch gleichzeitig mit dem Schreiben gelöscht. Die Gewichtung des Speicherplatzes erfolgt mit "Schreibgewichtung" $ \ boldsymbol {w} \ _t ^ w $. Die zu schreibenden Daten werden durch "Schreibvektor" $ \ boldsymbol {\ nu} \ _t $ angegeben, und das Muster zum Löschen der Daten im Schlitz wird durch den Löschvektor $ \ boldsymbol {e} \ t $ angegeben. Die beiden werden über den Schnittstellenvektor eingegeben. Die Speichermatrix $ M {t-1} $ wird durch Löschen / Schreiben gemäß der folgenden Formel aktualisiert.
\begin{align}
& M_t[n,s] = M_{t-1}[n,s] \big(1-\boldsymbol{w}_t^w[n] \boldsymbol{e}_t[s] \big) + \boldsymbol{w}_t^w[n] \boldsymbol{\nu}_t[s] \\
& \Leftrightarrow M_t = M_{t-1} \circ \big(1-\boldsymbol{w}_t^w \boldsymbol{e}_t^T \big) + \boldsymbol{w}_t^w \boldsymbol{\nu}_t^T
\end{align}
Es gibt zwei Möglichkeiten, die Zieladresse auszuwählen. Mit anderen Worten (1) Wählen Sie den Lesezielschlitz basierend auf dem durch den Schnittstellenvektor eingegebenen "Schlüssel" aus, und (2) Wählen Sie den Schlitz gemäß der Schreibreihenfolge aus.
In Bezug auf (1) "Leseschlüssel" $ \ {\ boldsymbol {k} \ _ t ^ {r, i} \} \ _ {i = 1,2, .., wie im Fall der Schreibgewichtung Verwenden von R} $ und "Lesestärke" $ \ {\ beta \ _t ^ {r, i} \} \ _ {i = 1,2, .., R} $,
\begin{align}
\boldsymbol{c}_t^{r, i} = \mathcal{C}\big(M_t, \boldsymbol{k}_t^{r,i}, \beta_t^{r,i}\big) \;\; (i=1,2,...,R)
\end{align}
Berechnen.
(2) ist etwas lang, aber ich werde es der Reihe nach erklären. Zur Vereinfachung der Erklärung werden wir in der Reihenfolge (2) -2 → (2) -1 erklären, aber bei der Implementierung ist der Fluss (2) -1 → (2) -2.
(2) -2. Zusammensetzung der "Vorranggewichtung" Definieren Sie die Prioritätsgewichtung $ \ boldsymbol {p} _t $ mit dem folgenden Aktualisierungsausdruck.
\begin{align}
& \boldsymbol{p}_0 = 0 \\
& \boldsymbol{p}_t = \Big(1-\sum_{n=1}^N \boldsymbol{w}_t^w[n]\Big)\boldsymbol{p}_{t-1} + \boldsymbol{w}_t^w
\end{align}
Bitte beachten Sie, dass $ \ boldsymbol {w} \ _ t ^ w $ bereits in ① aktualisiert wurde. Wenn in ② "stark" geschrieben wird, ist dies $ \ sum \ boldsymbol {w} \ _ t ^ w \ simeq 1 $, also die Information des vorherigen Zeitschritts $ \ boldsymbol {p} \ _ {t- 1} $ wird gelöscht und speichert dort, wo im aktuellen Zeitschritt geschrieben wurde. Wenn keine Schreibvorgänge durchgeführt wurden, wurde das Gewicht des letzten Schreibvorgangs beibehalten. Sie können auch leicht erkennen, dass es sich um $ \ boldsymbol {p} \ _ t [n] \ leq 1 $, $ \ sum \ _n \ boldsymbol {p} _t [n] \ leq 1 $ handelt.
(2) -1. Konfiguration der "Temporal Link Matrix" Konstruiert eine $ N $ × $ N $ -Matrix $ L_t $, die die Reihenfolge des Schreibens in den Speichersteckplatz darstellt. In Komponenteneinheiten bedeutet $ L_t [n, m] \ simeq 1 $, dass "im Speicher $ M_t $ die Informationen in Slot'n''nach den Informationen in Slot'm 'geschrieben werden. Die Aktualisierungsformel ist unten angegeben, damit die Beziehung "ist" ausgedrückt werden kann.
\begin{align}
& L_0[n,m]=0 \\
& L_t[n,n]=0 \\
& L_t[n,m]=\big(1-\boldsymbol{w}_t^w[n]-\boldsymbol{w}_t^w[m]\big)L_{t-1}[n,m] + \boldsymbol{w}_t^w[n]\boldsymbol{p}_{t-1}[m]
\end{align}
Die diagonale Komponente macht keinen Sinn, also fixieren Sie sie auf $ 0 $. $ 0 \ leqq L \ _t [n, m] \ leqq 1 $ und $ \ sum_m L \ _t [n, m] \ leqq 1 $ können sofort bestätigt werden. Ich habe versucht, $ \ sum_n L \ _t [n, m] \ leqq 1 $ zu überprüfen, konnte es aber nicht anzeigen. Wenn jemand es zeigen kann, würde ich es begrüßen, wenn Sie einen Kommentar abgeben könnten.
(2) -3. Konfiguration der "Vorwärts- / Rückwärtsgewichtung" Vorwärts- / Rückwärtsgewichtung $ \ {\ boldsymbol {f} \ _t ^ i \} \ _ {i = 1, .., R} $ / $ \ {\ boldsymbol {b } \ _t ^ i \} \ _ {i = 1, .., R} $ ist etwas rau
\begin{align}
&\boldsymbol{f}_t^i[n]=\sum_{m=1}^NL_t[n,m]\boldsymbol{w}_{t-1}^{r,i}[m] \; \Leftrightarrow \; \boldsymbol{f}_t^i=L_t\boldsymbol{w}_{t-1}^{r,i} \\
&\boldsymbol{b}_t^i[m]=\sum_{n=1}^N\boldsymbol{w}_{t-1}^{r,i}[n]L_t[n,m] \; \Leftrightarrow \; \boldsymbol{b}_t^i=L_t^T\boldsymbol{w}_{t-1}^{r,i} \\
\end{align}
Es besteht aus. Wenn $ \ sum_n L \ _t [n, m] \ leqq 1 $ gilt, dann sind $ 0 \ leqq \ boldsymbol {f} \ _t ^ i [n] \ leqq 1 $ und $ \ sum_n \ boldsymbol {f} \ _t ^ i [n] \ leqq1 $ gilt. Gleiches gilt für $ \ boldsymbol {b} \ _ t ^ i $.
Wie oben erwähnt, lesen Sie durch Integrieren von (1) und (2) die Gewichtung $ \ {\ boldsymbol {w} \ _ t ^ {i, r} \} \ _ {i = 1, ..., R} $ Aktualisieren.
\begin{align}
\boldsymbol{w}_t^{r,i}=\boldsymbol{\pi}_t^i[1]\boldsymbol{b}_t^i + \boldsymbol{\pi}_t^i[2]\boldsymbol{c}_t^i + \boldsymbol{\pi}_t^i[3]\boldsymbol{f}_t^i \;\; (i=1,2,...,R)
\end{align}
$ \ {\ pi_t ^ i \} \ _ {i = 1, ..., R} $ wird über den Schnittstellenvektor eingegeben.
Lesen aus dem Speicher ist einfach. Das Leseergebnis "Lesevektor" $ \ {\ boldsymbol {r} \ _t ^ i \} \ _ {i = 1, .., R} $ ist
\begin{align}
\boldsymbol{r}_t^i[s] = \sum_{n=1}^N \boldsymbol{w}_t^{r,i}[n]M_t[n,s] \; \Leftrightarrow \; \boldsymbol{r}_t^i = M_t^T\boldsymbol{w}_t^{r,i} \;\; (i=1,2,...,R)
\end{align}
Ist berechnet.
Damit ist das Lesen und Schreiben des Speichers für den Zeitschritt $ t $ abgeschlossen.
Hier ist ein Beispiel für die Python-Implementierung der oben beschriebenen Logik. Der ursprüngliche Code ist Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer Es ist in. Die Verarbeitung ist in Funktionen gruppiert und die Variablennamen werden zum einfachen Vergleich mit der obigen Erklärung geändert, aber der Inhalt ist fast der gleiche.
Zu beachten ist, dass die Gesamtausgabe des Controllers, $ \ boldsymbol {h} \ _t $, nicht nur die endgültige Ausgabe des Controllers, sondern die gesamte Ausgabe der verborgenen Ebene im Papier integriert. Es sieht so aus. Der Einfachheit halber ist der hier verwendete Controller jedoch auf einen einfachen mit etwa zwei Schichten beschränkt, und die Ausgabe der verborgenen Schicht wird nicht aus dem Controller herausgenommen.
dnc.py
import numpy as np
import chainer
from chainer import functions as F
from chainer import links as L
from chainer import optimizers, Chain, Link, Variable
# controller of DNC
class SimpleLSTM(Chain):
def __init__(self, d_in, d_hidden, d_out):
super(SimpleLSTM, self).__init__(
l1 = L.LSTM(d_in, d_hidden),
l2 = L.Linear(d_hidden, d_out),
)
def __call__(self, x):
return self.l2(self.l1(x))
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.d_ctr_in = W*R+X # input dimension into the controller
self.d_ctr_out = Y+W*R+3*W+5*R+3 # output dimension from the controller
self.d_ctr_hidden = self.d_ctr_out # dimension of hidden unit of the controller
self.d_interface = W*R+3*W+5*R+3 # dimension of interface vector
self.controller = SimpleLSTM(self.d_ctr_in, self.d_ctr_hidden, self.d_ctr_out)
super(DNC, self).__init__(
l_ctr = self.controller,
l_Wy = L.Linear(self.d_ctr_out, self.Y),
l_Wxi = L.Linear(self.d_ctr_out, self.d_interface),
l_Wr = L.Linear(self.R * self.W, self.Y),
)
self.reset_state()
def reset_state(self):
# initialize all the recurrent state
self.l_ctr.reset_state() # controller
self.u = Variable(np.zeros((self.N, 1)).astype(np.float32)) # usage vector (N, 1)
self.p = Variable(np.zeros((self.N, 1)).astype(np.float32)) # precedence weighting (N, 1)
self.L = Variable(np.zeros((self.N, self.N)).astype(np.float32)) # temporal memory linkage (N, N)
self.Mem = Variable(np.zeros((self.N, self.W)).astype(np.float32)) # memory (N, W)
self.r = Variable(np.zeros((1, self.R*self.W)).astype(np.float32)) # read vector (1, R * W)
self.wr = Variable(np.zeros((self.N, self.R)).astype(np.float32)) # read weighting (N, R)
self.ww = Variable(np.zeros((self.N, 1)).astype(np.float32)) # write weighting (N, 1)
# utility functions
def _cosine_similarity(self, u, v):
# cosine similarity as a distance of two vectors
# 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(self, Mem, k, beta):
# similarity between rows of matrix Mem and vector k
# Mem:(N, W) Variable, k:(1, W) Variable, beta:(1, 1) Variable -> (N, 1) Variable
N, W = Mem.shape
ret_list = [0] * N
for i in range(N):
# calculate distance between i-th row of Mem and k
ret_list[i] = self._cosine_similarity(F.reshape(Mem[i,:], (1, W)), k) * beta
# concat horizontally because softmax operates along the direction of axis=1
return F.transpose(F.softmax(F.concat(ret_list, 1)))
def _u2a(self, u):
# convert usage vector u to allocation weighting a
# u, a: (N, 1) Variable
N = u.shape[0]
phi = np.argsort(u.data.flatten()) # 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]], (1, 1)))
cumprod *= F.reshape(u[phi[i]], (1, 1))
return F.concat(a_list, 0)
# operations of the DNC system
def _controller_io(self, x):
# input data from the Data Set : x (1, X) Variable
# out-put from the controller h is split into two ways : v (1, Y), xi(1, W*R+3*W+5*R+3) Variable
chi = F.concat([x, self.r], 1) # total input to the controller
h = self.l_ctr(chi) # total out-put from the controller
self.v = self.l_Wy(h)
self.xi = self.l_Wxi(h)
# interface vector xi is split into several components
(self.kr, self.beta_r, self.kw, self.beta_w,
self.e, self.nu, 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) # details of the interface vector
# rescale components
self.kr = F.reshape(self.kr, (self.R, self.W)) # read key (R, W)
self.beta_r = 1 + F.softplus(self.beta_r) # read strength (1, R)
# self.kw : write key (1, W)
self.beta_w = 1 + F.softplus(self.beta_w) # write strength (1, 1)
self.e = F.sigmoid(self.e) # erase vector (1, W)
# self.nu : write vector (1, W)
self.f = F.sigmoid(self.f) # free gate (1, R)
self.ga = F.sigmoid(self.ga) # allcation gate (1, 1)
self.gw = F.sigmoid(self.gw) # write gate (1, 1)
self.pi = F.softmax(F.reshape(self.pi, (self.R, 3))) # read mode (R, 3)
def _up_date_write_weighting(self):
# calculate retention vector : psi (N, 1)
# here, read weighting : wr (N, R) must retain state one step former
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))
for i in range(self.R):
self.psi = self.psi * F.reshape(psi_mat[:,i],(self.N,1)) # (N, 1)
# up date usage vector : u (N, 1)
# here, write weighting : ww (N, 1) must retain state one step former
self.u = (self.u + self.ww - (self.u * self.ww)) * self.psi
# calculate allocation weighting : a (N, 1)
self.a = self._u2a(self.u)
# calculate write content weighting : cw (N, 1)
self.cw = self._C(self.Mem, self.kw, self.beta_w)
# up date write weighting : ww (N, 1)
self.ww = F.matmul(F.matmul(self.a, self.ga) + F.matmul(self.cw, 1.0 - self.ga), self.gw)
def _write_to_memory(self):
# erase vector : e (1, W) deletes information on the Memory : Mem (N, W)
# and write vector : nu (1, W) is written there
# write weighting : ww (N, 1) must be up-dated before this step
self.Mem = self.Mem * (np.ones((self.N, self.W)).astype(np.float32) - F.matmul(self.ww, self.e)) + F.matmul(self.ww, self.nu)
def _up_date_read_weighting(self):
# up date temporal memory linkage : L (N, N)
ww_mat = F.matmul(self.ww, Variable(np.ones((1, self.N)).astype(np.float32))) # (N, N)
# here, precedence wighting : p (N, 1) must retain state one step former
self.L = (1.0 - ww_mat - F.transpose(ww_mat)) * 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)) # constrain L[i,i] == 0
# up date prcedence weighting : p (N, 1)
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
# calculate forward weighting : fw (N, R)
# here, read wighting : wr (N, R) must retain state one step former
self.fw = F.matmul(self.L, self.wr)
# calculate backward weighting : bw (N, R)
self.bw = F.matmul(F.transpose(self.L), self.wr)
# calculate read content weighting : cr (N, R)
self.cr_list = [0] * self.R
for i in range(self.R):
self.cr_list[i] = self._C(self.Mem, F.reshape(self.kr[i,:], (1, self.W)), F.reshape(self.beta_r[0,i],(1, 1))) # (N, 1)
self.cr = F.concat(self.cr_list, 1) # (1, N * R)
# compose up-dated read weighting : wr (N, R)
bcf_tensor = F.concat([
F.reshape(F.transpose(self.bw), (self.R, self.N, 1)),
F.reshape(F.transpose(self.cr), (self.R, self.N, 1)),
F.reshape(F.transpose(self.fw), (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(bcf_tensor, self.pi), (self.R, self.N))) # (N, R)
def _read_from_memory(self):
# read information from the memory : Mem (N, W) and compose read vector : r (W, R) to reshape (1, W * R)
# read weighting : wr (N, R) must be up-dated before this step
self.r = F.reshape(F.matmul(F.transpose(self.Mem), self.wr), (1, self.R * self.W))
def __call__(self, x):
self._controller_io(x) # input data is processed through the controller
self._up_date_write_weighting()
self._write_to_memory() # memory up-date
self._up_date_read_weighting()
self._read_from_memory() # extract information from the memory
self.y = self.l_Wr(self.r) + self.v # compose total out put y : (1, Y)
return self.y
Ein Verwendungsbeispiel wird ebenfalls bereitgestellt, der Inhalt entspricht jedoch dem in Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer. Eine zufällige Anzahl von One-Hot-Vektoren fester Länge wird eingegeben, und nachdem die Eingabe abgeschlossen ist, werden die Eingabedaten als Ausgabe wiedergegeben. Das Training wird für jeden Satz von Eingaben durchgeführt, wobei der quadratische Fehler zwischen den Echo-Daten und den Eingabedaten als Verlust gilt. Wenn Sie mit dem Training fertig sind, geben Sie den nächsten Satz von One-Hot-Vektoren ein.
dnc_echo_test.py
import dnc
import numpy as np
import chainer
from chainer import functions as F
from chainer import links as L
from chainer import optimizers, Chain, Link, Variable
def onehot(x, n):
ret = np.zeros(n).astype(np.float32)
ret[x] = 1.0
return ret
X = 5
Y = 5
N = 10
W = 10
R = 2
model = dnc.DNC(X, Y, N, W, R)
optimizer = optimizers.Adam()
optimizer.setup(model)
n_data = 10000 # number of input data
loss = 0.0
acc = 0.0
acc_bool = []
for data_cnt in range(n_data):
loss_frac = np.zeros((1, 2))
# prepare one pair of input and target data
# length of input data is randomly set
len_content = np.random.randint(3, 6)
# generate one input data as a sequence of randam integers
content = np.random.randint(0, X-1, len_content)
len_seq = len_content + len_content # the former is for input, the latter for the target
x_seq_list = [float('nan')] * len_seq # input sequence
t_seq_list = [float('nan')] * len_seq # target sequence
for i in range(len_seq):
# convert a format of input data
if (i < len_content):
x_seq_list[i] = onehot(content[i], X)
elif (i == len_content):
x_seq_list[i] = onehot(X-1, X)
else:
x_seq_list[i] = np.zeros(X).astype(np.float32)
# convert a format of output data
if (i >= len_content):
t_seq_list[i] = onehot(content[i - len_content], X)
model.reset_state() # reset reccurent state per input data
# input data is fed as a sequence
for cnt in range(len_seq):
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 = model(x)
if (isinstance(t, chainer.Variable)):
loss += (y - t)**2
acc_bool.append(np.argmax(y.data)==np.argmax(t.data))
if (np.argmax(y.data)==np.argmax(t.data)): acc += 1
if (cnt+1==len_seq):
# training by back propagation
model.cleargrads()
loss.grad = np.ones(loss.shape, dtype=np.float32)
loss.backward()
optimizer.update()
loss.unchain_backward()
# print loss and accuracy
if data_cnt < 50 or data_cnt >= 9950:
print('(', data_cnt, ')', acc_bool, ' :: ', loss.data.sum()/loss.data.size/len_content, ' :: ', acc/len_content)
loss_frac += [loss.data.sum()/loss.data.size/len_seq, 1.]
loss = 0.0
acc = 0.0
acc_bool = []
Dies ist das Ergebnis, wenn es 10000 Mal wiederholt wird. Der Bool-Wert in [] ist der Bool-Wert, der angibt, ob der Maximalwert im Ausgabevektor auf 1 gesetzt ist und die anderen auf 0 geändert werden, was dem Eingabewert entspricht.
・ Ergebnisse der ersten 20 Male
( 0 ) [True, False, False] :: 0.197543557485 :: 0.3333333333333333
( 1 ) [False, False, False, False] :: 0.209656882286 :: 0.0
( 2 ) [True, False, False] :: 0.172263367971 :: 0.3333333333333333
( 3 ) [False, True, True] :: 0.185363880793 :: 0.6666666666666666
( 4 ) [True, True, True, True] :: 0.157090616226 :: 1.0
( 5 ) [False, False, False, False, False] :: 0.191528530121 :: 0.0
( 6 ) [True, False, False, False, False] :: 0.175649337769 :: 0.2
( 7 ) [False, False, False, True, True] :: 0.173387451172 :: 0.4
( 8 ) [True, False, True, True] :: 0.150813746452 :: 0.75
( 9 ) [False, True, False] :: 0.163899072011 :: 0.3333333333333333
( 10 ) [False, False, False, False, False] :: 0.183468780518 :: 0.0
( 11 ) [True, False, True, False] :: 0.152743542194 :: 0.5
( 12 ) [False, False, True, False] :: 0.170574557781 :: 0.25
( 13 ) [False, True, False, True, False] :: 0.161617393494 :: 0.4
( 14 ) [False, False, False, False] :: 0.168220555782 :: 0.0
( 15 ) [False, False, False] :: 0.167814588547 :: 0.0
( 16 ) [False, True, False, False] :: 0.158575570583 :: 0.25
( 17 ) [False, False, False, False] :: 0.165678012371 :: 0.0
( 18 ) [False, False, False] :: 0.165241924922 :: 0.0
( 19 ) [False, True, False] :: 0.143808253606 :: 0.3333333333333333
・ Ergebnisse der letzten 20 Male
( 9980 ) [True, True, True, True] :: 0.000208107382059 :: 1.0
( 9981 ) [True, True, True, True, True] :: 0.000164349582046 :: 1.0
( 9982 ) [True, True, True, True, True] :: 0.000122650777921 :: 1.0
( 9983 ) [True, True, True] :: 0.000181751077374 :: 1.0
( 9984 ) [True, True, True, True, True] :: 0.000318505689502 :: 1.0
( 9985 ) [True, True, True, True, True] :: 0.00023639023304 :: 1.0
( 9986 ) [True, True, True, True, True] :: 0.000988183766603 :: 1.0
( 9987 ) [True, True, True, True, True] :: 0.000226851813495 :: 1.0
( 9988 ) [True, True, True] :: 0.000401457709571 :: 1.0
( 9989 ) [True, True, True, True] :: 0.000256504747085 :: 1.0
( 9990 ) [True, True, True, True, True] :: 0.000165695995092 :: 1.0
( 9991 ) [True, True, True, True] :: 0.000123940082267 :: 1.0
( 9992 ) [True, True, True, True, True] :: 0.000351718552411 :: 1.0
( 9993 ) [True, True, True, True] :: 0.000147357559763 :: 1.0
( 9994 ) [True, True, True, True] :: 0.000173216045368 :: 1.0
( 9995 ) [True, True, True, True] :: 0.000108330522198 :: 1.0
( 9996 ) [True, True, True, True] :: 0.00016659933608 :: 1.0
( 9997 ) [True, True, True] :: 0.000255667418242 :: 1.0
( 9998 ) [True, True, True] :: 0.000280433737983 :: 1.0
( 9999 ) [True, True, True, True, True] :: 0.000443447269499 :: 1.0
Die Antwort ist vollkommen richtig. Ich habe nur 20 Mal gepostet, aber False war in den letzten 100 Mal 0 Mal. Ich habe es jedoch nicht mit anderen Methoden verglichen, daher ist unklar, ob es an DNC liegt.
Dieses Mal war der Zweck, die Logik einzuführen, also das war's.
・ Ich habe das Beispiel von Chainer auf dieser Seite verwendet. Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer
・ Ich habe diesmal nicht über GPU, Stapelverarbeitung usw. nachgedacht, aber ich denke, die folgende Seite wird hilfreich sein. Lernen und Generieren von Strings mit (Chainer) DNC (Differentiable Neural Computers)
・ Es scheint eine Implementierung in Tensor Flow zu geben. https://github.com/Mostafa-Samir/DNC-tensorflow
・ Die Erklärung von LSTM ist hier leicht zu verstehen. LSTM mit den neuesten Trends verstehen
Recommended Posts