Die Datengenerierung mithilfe eines wiederkehrenden neuronalen Netzwerks wird immer beliebter und wird in vielen Bereichen der Informatik eingesetzt. Seit dem Beginn der Geburt des seq2seq-Konzepts im Jahr 2014 sind nur fünf Jahre vergangen, aber die Welt hat viele Anwendungen gesehen, angefangen bei klassischen Modellen der Übersetzung und Spracherkennung bis hin zur Erstellung von Beschreibungen von Objekten in Fotografien.
Andererseits gewann die von Google speziell für die Entwicklung neuronaler Netze veröffentlichte Tensorflow-Bibliothek im Laufe der Zeit an Popularität. Natürlich konnten Google-Entwickler ein so beliebtes Paradigma wie seq2seq nicht ignorieren, daher bietet die Tensorflow-Bibliothek Klassen für die Entwicklung innerhalb dieses Paradigmas. Dieser Artikel beschreibt dieses Klassensystem.
Wiederkehrende Netzwerke
Gegenwärtig sind wiederkehrende Netze einer der bekanntesten und praktischsten Formalismen für den Aufbau tiefer neuronaler Netze. Rekursive Netzwerke sind für die Verarbeitung serieller Daten ausgelegt. Daher enthält eine rekursive Zelle im Gegensatz zu einer normalen Zelle (Neuron), die Daten als Eingabe empfängt und das Ergebnis von Berechnungen ausgibt, zwei Eingaben und zwei Ausgaben.
Eine der Eingaben repräsentiert die Daten des aktuellen Elements der Sequenz, und die zweite Eingabe wird als Zustand bezeichnet und als Ergebnis der Zellenberechnungen für das vorherige Element der Sequenz übertragen.

Die Abbildung zeigt Zelle A, für die die Daten eines Sequenzelements eingegeben werden sowie den hier nicht angegebenen Zustand . Bei der Ausgabe gibt Zelle A den Zustand an und das Ergebnis der Berechnung .
In der Praxis wird die Datensequenz normalerweise in Teilsequenzen einer bestimmten festen Länge unterteilt und von ganzen Teilmengen (Chargen) an die Berechnung übergeben. Mit anderen Worten, Teilsequenzen sind Beispiele für das Lernen. Die Ein-, Ausgänge und Zellzustände eines rekursiven Netzwerks sind Folgen von reellen Zahlen. Zur Eingabeberechnung Es ist erforderlich, einen Status zu verwenden, der nicht das Ergebnis einer Berechnung für eine bestimmte Datensequenz war. Solche Zustände werden Anfangszustände genannt. Wenn die Sequenz lang genug ist, ist es sinnvoll, den Kontext der Berechnungen für jede Teilsequenz beizubehalten. In diesem Fall ist es möglich, den zuletzt berechneten Zustand in der vorherigen Sequenz als Ausgangszustand zu übertragen. Wenn die Sequenz nicht so lang ist oder die Teilsequenz das erste Segment ist, können Sie den Anfangszustand mit Nullen initialisieren.
Derzeit wird für das Training neuronaler Netze fast überall der Algorithmus der Rückausbreitung von Fehlern verwendet . Das Ergebnis der Berechnung für den übertragenen Satz von Beispielen (in unserem Fall den Satz von Teilsequenzen) wird mit dem erwarteten Ergebnis (markierte Daten) verglichen. Die Differenz zwischen dem tatsächlichen und dem erwarteten Wert wird als Fehler bezeichnet, und dieser Fehler wird in entgegengesetzter Richtung auf die Netzwerkgewichte übertragen. Somit passt sich das Netzwerk an beschriftete Daten an und das Ergebnis dieser Anpassung funktioniert in der Regel gut für die Daten, die das Netzwerk in den ersten Trainingsbeispielen nicht erfüllt hat (Generalisierungshypothese).
Im Fall eines rekursiven Netzwerks haben wir mehrere Optionen, um den Fehler zu berücksichtigen. Wir werden hier zwei Hauptpunkte beschreiben:
- Sie können den Fehler berücksichtigen, indem Sie die Ausgabe der letzten Zelle der Teilsequenz mit der erwarteten Ausgabe vergleichen. Dies funktioniert gut für die Klassifizierungsaufgabe. Zum Beispiel müssen wir die emotionale Färbung eines Tweets bestimmen. Dazu wählen wir Tweets aus und markieren sie in drei Kategorien: negativ, positiv und neutral. Die Ausgabe der Zelle besteht aus drei Zahlen - dem Gewicht der Kategorien. Der Tweet wird auch mit drei Zahlen markiert - den Wahrscheinlichkeiten des Tweets, die zur entsprechenden Kategorie gehören. Nachdem Sie den Fehler für eine Teilmenge der Daten berechnet haben, können Sie ihn nach Belieben über die Ausgabe oder den Status weitergeben.
- Sie können den Fehler sofort an den Ausgängen der Zellenberechnung für jedes Element der Teilsequenz lesen. Dies ist gut geeignet für die Aufgabe, das nächste Element einer Sequenz aus vorherigen vorherzusagen. Ein solcher Ansatz kann beispielsweise bei dem Problem der Bestimmung von Anomalien in Zeitreihen von Daten oder bei der Vorhersage des nächsten Zeichens in einem Text verwendet werden, um es später zu generieren. Die Fehlerausbreitung ist auch über Zustände oder Ausgänge möglich.
Im Gegensatz zu einem regulären vollständig verbundenen neuronalen Netzwerk ist ein rekursives Netzwerk tief in dem Sinne, dass sich der Fehler nicht nur von den Ausgängen des Netzwerks zu seinen Gewichten, sondern auch nach links durch Verbindungen zwischen Zuständen ausbreitet. Die Tiefe des Netzwerks wird somit durch die Länge der Teilsequenz bestimmt. Um den Fehler durch den Zustand des rekursiven Netzwerks zu verbreiten, gibt es einen speziellen Algorithmus . Sein Merkmal ist, dass sich die Gradienten der Gewichte miteinander multiplizieren, wenn sich der Fehler von rechts nach links ausbreitet. Wenn der anfängliche Fehler größer als Eins ist, kann der Fehler infolgedessen sehr groß werden. Wenn umgekehrt der anfängliche Fehler kleiner als eins ist, kann der Fehler irgendwo am Anfang der Sequenz verschwinden. Diese Situation in der Theorie der neuronalen Netze wird als Karussell des Standardfehlers bezeichnet. Um solche Situationen während des Trainings zu vermeiden, wurden spezielle Zellen erfunden, die solche Nachteile nicht aufweisen. Die erste derartige Zelle war LSTM , jetzt gibt es eine breite Palette von Alternativen, von denen die beliebteste GRU .
Eine gute Einführung in Wiederholungsnetzwerke finden Sie in diesem Artikel . Eine weitere bekannte Quelle ist ein Artikel aus dem Blog von Andrey Karpaty.
Die Tensorflow-Bibliothek verfügt über viele Klassen und Funktionen zum Implementieren rekursiver Netzwerke. Hier ist ein Beispiel für die Erstellung eines dynamischen rekursiven Netzwerks basierend auf einer Zelle vom Typ GRU:
cell = tf.contrib.rnn.GRUCell(dimension) outputs, state = tf.nn.dynamic_rnn(cell, input, sequence_length=input_length, dtype=tf.float32)
In diesem Beispiel wird eine GRU-Zelle erstellt, mit der dann ein dynamisches rekursives Netzwerk erstellt wird. Der Eingangsdatentensor und die tatsächlichen Längen der Teilsequenzen werden an das Netzwerk übertragen. Eingabedaten werden immer durch einen Vektor reeller Zahlen angegeben. Für einen einzelnen Wert, zum Beispiel einen Symbolcode oder ein Wort, das sogenannte Einbetten - Zuordnung dieses Codes zu einer Folge von Zahlen. Die Funktion zum Erstellen eines dynamischen rekursiven Netzwerks gibt ein Wertepaar zurück: eine Liste der Netzwerkausgaben für alle Werte der Sequenz und den zuletzt berechneten Status. Als Eingabe nimmt die Funktion eine Zelle, Eingabedaten und einen Teilsequenzlängen-Tensor.
Ein dynamisches rekursives Netzwerk unterscheidet sich von einem statischen dadurch, dass es kein Netzwerk von Netzwerkzellen für die Teilsequenz im Voraus erstellt (in der Phase der Bestimmung des Berechnungsdiagramms), sondern die Zellen an den Eingaben dynamisch während der Berechnung des Diagramms auf den Eingabedaten startet. Daher muss diese Funktion die Länge der Teilsequenzen der Eingabedaten kennen, um zum richtigen Zeitpunkt anzuhalten.
Generieren von Modellen basierend auf Wiederholungsnetzwerken
Wiederholungsnetzwerke generieren
Zuvor haben wir zwei Methoden zur Berechnung der Fehler rekursiver Netzwerke betrachtet: bei der letzten Ausgabe oder bei allen Ausgaben für eine bestimmte Sequenz. Hier betrachten wir das Problem der Erzeugung von Sequenzen. Das Generator-Netzwerk-Training basiert auf der zweiten Methode der oben genannten Methode.
Im Detail versuchen wir, ein rekursives Netzwerk zu trainieren, um das nächste Element einer Sequenz vorherzusagen. Wie oben erwähnt, ist die Ausgabe einer Zelle in einem rekursiven Netzwerk einfach eine Folge von Zahlen. Dieser Vektor ist für das Lernen nicht sehr praktisch, daher führen sie eine andere Ebene ein, die diesen Vektor am Eingang empfängt und am Ausgang das Gewicht der Vorhersagen angibt. Diese Ebene wird als Projektionsebene bezeichnet und ermöglicht es Ihnen, die Ausgabe der Zelle für ein bestimmtes Element der Sequenz mit der erwarteten Ausgabe in den beschrifteten Daten zu vergleichen.
Betrachten Sie zur Veranschaulichung die Aufgabe, Text zu generieren, der als Folge von Zeichen dargestellt wird. Die Länge des Ausgabevektors der Projektionsebene entspricht der Größe des Alphabets des Quelltextes. Die Größe des Alphabets überschreitet normalerweise nicht 150 Zeichen, wenn Sie die Zeichen der russischen und englischen Sprache sowie die Satzzeichen zählen. Die Ausgabe der Projektionsebene ist ein Vektor mit der Länge des Alphabets, wobei jedes Symbol einer bestimmten Position in diesem Vektor entspricht - dem Index dieses Symbols. Beschriftete Daten sind auch Vektoren, die aus Nullen bestehen, wobei man an der Position des Zeichens steht, das der Sequenz folgt.
Für das Training verwenden wir zwei Datensequenzen:
- Eine Folge von Zeichen im Quelltext, an deren Anfang ein Sonderzeichen hinzugefügt wird, das nicht Teil des Quelltextes ist. Es wird normalerweise als go bezeichnet .
- Die Zeichenfolge des Quelltextes wie sie ist, ohne Zusätze.
Beispiel für den Text "Mama hat den Rahmen gewaschen":
['<go>', '', '', ', '', ' ', '', '', '', '', ' ', '', '', '', ''] ['', '', ', '', ' ', '', '', '', '', ' ', '', '', '', '']
Für das Training werden normalerweise Minibatches gebildet, die aus einer kleinen Anzahl von Beispielen bestehen. In unserem Fall sind dies Zeichenfolgen, die unterschiedlich lang sein können. Der unten beschriebene Code verwendet die folgende Methode, um das Problem unterschiedlicher Länge zu lösen. Aus den vielen Zeilen in diesem Minipaket wird die maximale Länge berechnet. Alle anderen Zeilen sind mit einem Sonderzeichen (Polsterung) gefüllt, sodass alle Beispiele im Minipaket gleich lang sind. Im folgenden Codebeispiel wird die Pad- Zeichenfolge als solches Zeichen verwendet. Fügen Sie zur besseren Generierung am Ende des Beispiels das Ende des Satzsymbols hinzu - eos . In der Realität sehen die Daten aus dem Beispiel also etwas anders aus:
['<go>', '', '', ', '', ' ', '', '', '', '', ' ', '', '', '', '', '<eos>', '<pad>', '<pad>', '<pad>'] ['', '', ', '', ' ', '', '', '', '', ' ', '', '', '', '', '<eos>', '<pad>', '<pad>', '<pad>', '<pad>']
Die erste Sequenz wird dem Netzwerkeingang zugeführt, und die zweite Sequenz wird als markierte Daten verwendet. Das Vorhersage-Training basiert auf der Verschiebung der ursprünglichen Sequenz um ein Zeichen nach links.
Training und Laichen
Schulung
Der Lernalgorithmus ist recht einfach. Für jedes Element der Eingabesequenz berechnen wir den Ausgabevektor seines Projektionspegels und vergleichen ihn mit dem markierten. Die Frage ist nur, wie der Fehler berechnet wird. Sie können den quadratischen Mittelwertfehler verwenden, aber um den Fehler in dieser Situation zu berechnen, ist es besser, die Kreuzentropie zu verwenden . Die Tensorflow-Bibliothek bietet mehrere Funktionen für ihre Berechnung, obwohl nichts die Implementierung der Berechnungsformel direkt im Code aufhält.
Zur Verdeutlichung führen wir eine Notation ein. Mit symbol_id bezeichnen wir die Kennung des Symbols (seine Seriennummer im Alphabet). Der Begriff Symbol ist hier eher willkürlich und bedeutet einfach ein Element des Alphabets. Das Alphabet enthält möglicherweise keine Symbole, sondern Wörter oder sogar einige komplexere Sätze von Attributen. Der Begriff symbol_embedding wird verwendet, um den Vektor von Zahlen zu bezeichnen, die einem bestimmten Element des Alphabets entsprechen. In der Regel werden solche Zahlengruppen in einer Größentabelle gespeichert, die der Größe des Alphabets entspricht.
Tensorflow bietet eine Funktion, mit der Sie auf die Einbettungstabelle zugreifen und Zeichenindizes durch ihre Einbettungsvektoren ersetzen können. Zuerst definieren wir eine Variable zum Speichern der Tabelle:
embedding_table = tf.Variable(tf.random_uniform([alphabet_size, embedding_size]))
Danach können Sie die Eingangstensoren in Einbettungstensoren konvertieren:
input_embeddings = tf.nn.embedding_lookup(embedding_table, input_ids)
Das Ergebnis des Funktionsaufrufs ist ein Tensor derselben Dimension, der an die Eingabe übertragen wurde. Infolgedessen werden jedoch alle Zeichenindizes durch die entsprechenden Einbettungssequenzen ersetzt.
Spawn
Zur Berechnung benötigt eine Zelle eines rekursiven Netzwerks einen Status und das aktuelle Zeichen. Das Ergebnis der Berechnung ist ein Exit und ein neuer Zustand. Wenn wir die Projektionsstufe auf die Ausgabe anwenden, können wir einen Vektor von Gewichten erhalten, bei dem das Gewicht an der entsprechenden Position (sehr bedingt) als die Wahrscheinlichkeit betrachtet werden kann, dass dieses Symbol an der nächsten Position in der Sequenz erscheint.
Verschiedene Strategien können verwendet werden, um das nächste Symbol basierend auf dem von der Projektionsebene erzeugten Gewichtsvektor auszuwählen:
- Gierige Suchstrategie. Jedes Mal, wenn wir das Symbol mit dem höchsten Gewicht auswählen, d.h. am wahrscheinlichsten in dieser Situation, aber nicht unbedingt am geeignetsten im Kontext der gesamten Sequenz.
- Strategie zur Auswahl der besten Sequenz (Strahlensuche). Wir wählen nicht sofort ein Symbol aus, sondern erinnern uns an mehrere Varianten der wahrscheinlichsten Symbole. Nachdem alle diese Optionen für alle Elemente der generierten Sequenz berechnet wurden, wählen wir die wahrscheinlichste Zeichenfolge unter Berücksichtigung des Kontexts der gesamten Sequenz aus. Üblicherweise wird dies mittels einer Matrix implementiert, deren Breite gleich der Länge der Sequenz und die Höhe der Anzahl der Strahlerzeugungsbreiten ist. Nachdem die Erzeugung der Sequenzvarianten abgeschlossen ist, wird eine der Varianten des Viterbi- Algorithmus verwendet , um die wahrscheinlichste Sequenz auszuwählen.
System vom Typ Tensorflow-Bibliothek seq2seq
In Anbetracht dessen ist es klar, dass die Implementierung von generativen Modellen, die auf Wiederholungsnetzwerken basieren, eine ziemlich schwierige Aufgabe für die Codierung ist. Daher wurden natürlich Klassensysteme vorgeschlagen, um die Lösung dieses Problems zu erleichtern. Eines dieser Systeme heißt seq2seq, dann beschreiben wir die Funktionalität seiner Haupttypen.
Aber zuallererst ein paar Worte zum Namen der Bibliothek. Der Name seq2seq ist die Abkürzung für Sequenz zu Sequenz (von Sequenz zu Sequenz). Die ursprüngliche Idee, eine Sequenz zu erzeugen, wurde zur Implementierung eines Übersetzungssystems vorgeschlagen. Die Eingabesequenz von Wörtern wurde der Eingabe eines rekursiven Netzwerks zugeführt, das in diesem System als Codierer bezeichnet wird. Die Ausgabe dieses rekursiven Netzwerks war der Zustand der Zellenberechnung für das letzte Zeichen der Sequenz. Dieser Zustand wurde als Anfangszustand des zweiten rekursiven Netzwerks, des Decoders, dargestellt, der darauf trainiert wurde, das nächste Wort zu erzeugen. Die Wörter wurden in beiden Netzwerken als Symbole verwendet. Fehler am Dekorator wurden durch den übertragenen Zustand an den Codierer weitergegeben. Der Zustandsvektor selbst wurde in dieser Terminologie als Gedankenvektor bezeichnet. Die Zwischenpräsentation wurde in traditionellen Übersetzungsmodellen verwendet und war in der Regel ein Diagramm, das die Struktur des für die Übersetzung eingegebenen Textes darstellt. Das Übersetzungssystem erzeugte Ausgabetext basierend auf dieser Zwischenstruktur.
Tatsächlich gehört die Implementierung von seq2seq in Tensorflow zum Decoderteil, ohne den Encoder zu beeinflussen. Daher wäre es richtig, die 2seq-Bibliothek zu nennen, aber die Stärke der Tradition und die Trägheit des Denkens überwiegen hier offensichtlich gegenüber dem gesunden Menschenverstand.
Die beiden Hauptmetatypen in der seq2seq-Bibliothek sind:
- Helferklasse .
- Klassendecoder .
Die Bibliotheksentwickler identifizierten diese Typen anhand der folgenden Überlegungen. Betrachten wir den Lernprozess und den Generierungsprozess, die wir oben beschrieben haben, aus einem etwas anderen Blickwinkel.
Für das Training benötigen Sie:
- Geben Sie für jedes Zeichen die Berechnung des aktuellen Status und die Einbettung des aktuellen Zeichens weiter.
- Merken Sie sich den für die Ausgabe berechneten Ausgabestatus und die Projektion.
- Holen Sie sich das nächste Zeichen in der Sequenz und fahren Sie mit Schritt 1 fort.
Danach können Sie beginnen, Fehler zu zählen, indem Sie die Ergebnisse der Berechnungen mit den folgenden Zeichen der Sequenz vergleichen.
Um es zu generieren ist notwendig:
- Geben Sie für jedes Zeichen die Berechnung des aktuellen Status und die Einbettung des aktuellen Zeichens weiter.
- Merken Sie sich den für die Ausgabe berechneten Ausgabestatus und die Projektion.
- Berechnen Sie das nächste Zeichen als Maximum der Projektionspegelindizes und fahren Sie mit Schritt 1 fort.
Wie aus der Beschreibung ersichtlich ist, sind die Algorithmen sehr ähnlich. Aus diesem Grund haben die Entwickler der Bibliothek beschlossen, das Verfahren zum Abrufen des nächsten Zeichens in der Helper-Klasse zu kapseln. Für das Training wird lediglich das nächste Zeichen aus der Sequenz gelesen und zum Generieren das Zeichen mit dem maximalen Gewicht ausgewählt (natürlich für die gierige Suche).
Dementsprechend implementiert die Helper-Basisklasse die next_inputs-Methode, um das nächste Zeichen aus dem aktuellen und dem aktuellen Status abzurufen, sowie die Beispielmethode, um Zeichenindizes von der Projektionsebene abzurufen. Für die Implementierung des Trainings wird die TrainingHelper- Klasse bereitgestellt, und für die Implementierung der Generierung durch die Greedy-Suchmethode wird die GreedyEmbeddingHelper- Klasse bereitgestellt . Leider passt das Strahlensuchmodell nicht in dieses Typsystem, daher ist hierfür eine spezielle Klasse BeamSearchDecoder in der Bibliothek implementiert. Helper nicht verwenden.
Die Decoder-Klasse bietet eine Schnittstelle zum Implementieren eines Decoders. Tatsächlich bietet die Klasse zwei Methoden:
- initialisieren, um zu Beginn der Arbeit zu initialisieren.
- Schritt, um einen Lernschritt oder eine Generation zu implementieren. Der Inhalt dieses Schritts wird vom entsprechenden Helfer bestimmt.
Die Bibliothek implementiert die BasicDecoder- Klasse, die sowohl für das Training als auch für die Zucht mit den Assistenten TrainingHelper und GreedyEmbeddingHelper verwendet werden kann. Diese drei Klassen reichen normalerweise aus, um Generierungsmodelle zu implementieren, die auf Wiederholungsnetzwerken basieren.
Schließlich werden dynamic_decode- Funktionen verwendet, um den Durchgang durch eine Eingabe oder eine generierte Sequenz zu organisieren.
Als nächstes betrachten wir ein veranschaulichendes Beispiel, das Methoden zum Erstellen von Generierungsmodellen für verschiedene Arten von seq2seq-Bibliotheken zeigt.
Illustratives Beispiel
Zunächst sollte gesagt werden, dass alle Beispiele in Python 2.7 implementiert sind. Eine Liste zusätzlicher Bibliotheken finden Sie in der Datei require.txt.
Betrachten Sie als anschauliches Beispiel einen Teil der Daten für den Wettbewerb Text Normalization Challenge - Russian Language , der 2017 von Kaggle by Google durchgeführt wurde. Ziel dieses Wettbewerbs war es, den russischen Text in eine lesbare Form umzuwandeln. Der Text für den Wettbewerb wurde in typisierte Ausdrücke unterteilt. Die Trainingsdaten wurden in einer CSV-Datei des folgenden Formulars angegeben:
"sentence_id","token_id","class","before","after" 0,0,"PLAIN","","" 0,1,"PLAIN","","" 0,2,"PLAIN","","" 0,3,"DATE","1862 "," " 0,4,"PUNCT",".","." 1,0,"PLAIN","","" 1,1,"PLAIN","","" 1,2,"PLAIN","","" 1,3,"PLAIN","","" 1,4,"PLAIN","","" 1,5,"PLAIN","","" 1,6,"PLAIN","","" 1,7,"PLAIN","","" 1,8,"PLAIN","","" 1,9,"PUNCT",".","." ...
Im obigen Beispiel ist ein Ausdruck vom Typ DATE interessant; darin wird "1862" in "eintausendachthundertzweiundsechzigstes Jahr" übersetzt. Zur Veranschaulichung betrachten wir Daten vom Typ DATE nur als Paare der Form (Ausdruck vor, Ausdruck nach). Beginn der Datendatei:
before,after 1862 , 1811 , 12 2013, 15 2013, 1905 , 17 2014, 7 2010 , 1 , 1843 , 30 2007 , 1846 , 1996 , 9 , ...
Wir werden das generierende Modell unter Verwendung der seq2seq-Bibliothek erstellen, in der der Codierer auf Symbolebene implementiert wird (d. H. Die Elemente des Alphabets sind Symbole), und der Decodierer verwendet die Wörter als Alphabet. Beispielcode ist wie Daten im Repository von Github verfügbar.
Die Trainingsdaten sind in drei Untergruppen unterteilt: train.csv, test.csv und dev.csv für Training, Test und Überprüfung der Umschulung. Die Daten befinden sich im Datenverzeichnis. Im Repository sind drei Modelle implementiert: seq2seq_greedy.py, seq2seq_attention.py und seq2seq_beamsearch.py. Hier sehen wir uns den Code für das grundlegende Modell der gierigen Suche an.
Alle Modelle verwenden die Estimator- Klasse zur Implementierung. Mit dieser Klasse können Sie die Codierung vereinfachen, ohne von Nichtmodellteilen abgelenkt zu werden. Es ist beispielsweise nicht erforderlich, einen Datenübertragungszyklus für Schulungen zu implementieren, Sitzungen für die Arbeit mit Tensorflow zu erstellen, Daten an Tensorboard zu übertragen usw. Estimator benötigt für seine Implementierung nur zwei Funktionen: für die Datenübertragung und für die Erstellung eines Modells. In den Beispielen wird auch die Dataset- Klasse verwendet, um Daten für die Verarbeitung zu übergeben. Diese moderne Implementierung ist viel schneller als herkömmliche Wörterbücher zum Übertragen von Daten der Form feed_dict.
Betrachten Sie einen Datengenerierungscode für Training und Generierung.
def parse_fn(line_before, line_after):
Die Funktion input_fn wird verwendet, um eine Sammlung von Daten zu erstellen, die Estimator dann an Training und Generierung weitergibt. Der Datentyp wird zuerst festgelegt. Dies ist ein Paar der Form ((Codierersequenz, Länge), (Decodersequenz, Decodersequenz mit einem Präfix, Länge)). Die Zeichenfolge "" wird als Präfix verwendet, jede Encodersequenz endet mit einem speziellen Wort "". Aufgrund der Tatsache, dass die Sequenzen (sowohl Eingabe als auch Ausgabe) eine ungleiche Länge haben, wird auch das Füllsymbol mit dem Wert "" verwendet.
Der Datenvorbereitungscode liest die Datendatei, unterteilt die Encoder-Zeichenfolge in Zeichen und die Decoder-Zeichenfolge in Wörter, wobei die nltk-Bibliothek verwendet wird. Eine auf diese Weise verarbeitete Zeile ist ein Beispiel für Trainingsdaten. Die generierte Sammlung ist in Minipakete unterteilt, und die Datenmenge wird entsprechend der Anzahl der Trainingsperioden geklont (jede Epoche besteht aus einem Datenpass).
Arbeiten Sie mit Wörterbüchern
Wörterbücher werden als Liste in Dateien gespeichert, eine Zeile für ein Wort oder ein Zeichen. Verwenden Sie zum Erstellen von Wörterbüchern das Skript build_vocabs.py. Die generierten Wörterbücher befinden sich im Datenverzeichnis als Dateien des Formularvokabulars. *. Txt.
Code zum Lesen von Wörterbüchern:
Hier ist wahrscheinlich die Funktion index_table_from_file interessant, die Wörterbuchelemente aus einer Datei liest, und ihr Parameter num_oov_buckets ist die Anzahl der Körbe außerhalb des Wortschatzes. Standardmäßig ist diese Zahl gleich eins, d.h. Alle Wörter, die nicht im Wörterbuch enthalten sind, haben denselben Index, der der Größe des Wörterbuchs + 1 entspricht. Wir haben drei unbekannte Wörter: "", "" und "", für die wir unterschiedliche Indizes haben möchten. Setzen Sie diesen Parameter daher auf die Nummer drei. Leider müssen Sie die Eingabedatei erneut lesen, um die Anzahl der Wörter im Wörterbuch als Zeitkonstante für die Einstellung des Modellgraphen zu erhalten.
Wir müssen noch eine Tabelle erstellen, um die Einbettung - _source_embedding - zu implementieren und Wortzeichenfolgen in Bezeichnerzeichenfolgen zu übersetzen:
Implementierung des Encoders
Für den Encoder verwenden wir ein bidirektionales rekursives Netzwerk mit mehreren Ebenen. , , .
GRU, MultiRNNCell, , rnn.Cell. ,
sequence_length — , , .
, , , . , 128, 256. , , 128. .
. Weil , , bidirectional_dynamic_rnn, , . , . , .. . , , . , , .
, . .
Schulung
TrainingHelper + BasicDecoder.
.
GreedyEmbeddingHelper "", "". . , , dynamic_decode . , , . , , .
, seq2seq.
, , sequence_mask.
Adam , .
optimizer = tf.train.AdamOptimizer(learning_rate=params.get('lr', .001)) grads, vs = zip(*optimizer.compute_gradients(loss)) grads, gnorm = tf.clip_by_global_norm(grads, params.get('clip', .5)) train_op = optimizer.apply_gradients(zip(grads, vs), global_step=tf.train.get_or_create_global_step())
. 0.9 . , , , . , .
24 1944 1 2003 1992 . 11 1927 1969 1 2016 1047 1863 17 22 2014
. — , — , — .
, — . . , ( ), . . , .
Fazit
seq2seq. , , . , .
. Tensorflow , , . , , . , . , , padding , embedding ? , , . — . , , . , , , . , . , , , , .