Im April dieses Jahres wurde ** "Deep Learning3 Framework von Grund auf neu" ** veröffentlicht. Ich habe ** Zero Work ** als ** 1.2 ** gelesen und viel gelernt, deshalb habe ich mich entschlossen, diesmal die Framework Edition herauszufordern.
Also habe ich kürzlich ein Buch gekauft, aber bevor ich Schritt für Schritt anfing zu studieren, habe ich beschlossen, Code zu schreiben, um einen schnellen Überblick über das ** Framework ** zu erhalten.
Weitere Informationen finden Sie in der ** Bibliothek ** und im ** Beispiel ** von ** DeZero ** unter Github. Ich habe beim Durchsuchen ** dieses ** einen einfachen Code für die Verarbeitung natürlicher Sprache in Google Colab geschrieben, daher werde ich ihn als Memorandum belassen.
Der von mir erstellte ** Google Colab-Code ** wird auf ** Github ** veröffentlicht. Wenn Ihnen also [** dieser Link **] gefällt (https://github.com/cedro3/google_colab/blob/master/generate_japanese_text) Klicken Sie auf .ipynb), um es zu verschieben.
Als ich versuchte, Japanisch in natürlicher Sprache zu verarbeiten, hielt ich es für praktisch, wenn es etwas gab, das einfach zu verwenden war, wie z. B. ** MNIST ** für die Bildverarbeitung, also machte ich etwas Ähnliches.
Laden Sie für die zu erstellende ** Datensatzklasse ** ** "Ich bin eine Katze" ** von Aozora Bunko herunter. Nachdem Sie den unnötigen Teil gelöscht haben, schreiben Sie mit ** janome **, erstellen Sie ein Wörterbuch und einen Korpus und erstellen Sie dann ** Zeitreihendaten ** und ** die nächsten richtigen Antwortdaten **.
Installieren Sie als vorläufige Vorbereitung das ** Framework dezero ** mit ! Pip install dezero
und die ** morphologische Analysebibliothek janome ** mit! Pip install janome
.
Setzen Sie den Klassennamen des Datensatzes auf ** Neko **, erben Sie ** Dataset-Klasse ** gemäß der Methode von dezero, schreiben Sie ** Verarbeitungsinhalt ** in def prepare ()
und verarbeiten Sie dann ** Schreiben Sie die gewünschte Funktion ** in.
import numpy as np
import dezero
from dezero.datasets import Dataset
from dezero.utils import get_file, cache_dir
import zipfile
import re
from janome.tokenizer import Tokenizer
class Neko(Dataset):
def prepare(self):
url = 'https://www.aozora.gr.jp/cards/000148/files/789_ruby_5639.zip'
file = get_file(url)
data = self.unzip(cache_dir + '/' + '789_ruby_5639.zip')
self.text = self.preprocess(cache_dir + '/' + 'wagahaiwa_nekodearu.txt')
self.wakati = self.keitaiso(self.text)
self.corpus, self.word_to_id, self.id_to_word = self.process(self.wakati)
self.data = np.array(self.corpus[:-1])
self.label = np.array(self.corpus[1:])
def unzip(self, file_path):
with zipfile.ZipFile(file_path) as existing_zip:
existing_zip.extractall(cache_dir)
def preprocess(self, file_path):
binarydata = open(file_path, 'rb').read()
text = binarydata.decode('shift_jis')
text = re.split(r'\-{5,}', text)[2] #Header löschen
text = re.split('Unteres Buch:',text)[0] #Fußzeile entfernen
text = re.sub('|', '', text) # |Löschen
text = re.sub('[.+?]', '', text) #Eingabenotiz löschen
text = re.sub(r'《.+?》', '', text) #Rubinentfernung
text = re.sub(r'\u3000', '', text) #Leerzeichen entfernen
text = re.sub(r'\r\n', '', text) #Zeilenumbrüche entfernen
text = text[1:] #Löschen Sie das erste Zeichen (Anpassung)
return text
def keitaiso(self, text):
t = Tokenizer()
output = t.tokenize(text, wakati=True)
return output
def process(self, text):
# word_to_id, id_to_Ein Wort erstellen
word_to_id, id_to_word = {}, {}
for word in text:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
#Korpus erstellen
corpus = np.array([word_to_id[W] for W in text])
return corpus, word_to_id, id_to_word
Der ** Konstruktor ** (bei def __init __ ()
) der ** geerbten ** Dataset-Klasse ** sagt self.prepare ()
, daher ist die Neko-Klasse eine ** Instanz. Dann wird def prepare ()
** funktionieren **.
def prepare ()
verwendetget_file (url)
in der dezero-Bibliothek, um die Datei von der angegebenen url
herunterzuladen und in cache_dir
zu speichern. Für Google Colab ist "cache_dir" "/ root / .dezero".
Danach werden vier Funktionen nacheinander aufgerufen, um die Verarbeitung durchzuführen. Ersetzen Sie schließlich ** corpus ** gemäß der Methode durch self.data
(Zeitreihendaten) und self.label
(nächstbeste Antwortdaten).
Die Variablen "text, wakati, corpus, word_to_id, id_to_word" werden jeweils "self" gegeben, so dass sie als ** Attribute ** aufgerufen werden können, sobald die Neko-Klasse ** instanziiert ** ist. ..
def unzip ()
ist eine Funktion, die die heruntergeladene ** Zip-Datei ** entpackt. def preprocess ()
ist eine Funktion, die die dekomprimierte Datei liest und den Text zurückgibt, wobei ** unnötige Teile wie Ruby und Zeilenumbrüche ** entfernt werden. def keitaiso ()
ist eine Funktion, die Text morphologisch analysiert und ** Fraktionierung ** zurückgibt. def process ()
ist eine Funktion, die ** Wörterbücher ** und ** Korpus ** aus Brüchen erstellt.
Bewegen wir es tatsächlich.
** Instanziiere ** die Neko-Klasse mit neko = Neko ()
, um die Datei herunterzuladen und ** den Prozess zu starten **. Die Fertigstellung dauert einige zehn Sekunden, da die Verarbeitung von Janomes Division etwas Zeit in Anspruch nimmt. Wenn Sie fertig sind, lassen Sie es uns sofort verwenden.
neko.text
kann ** Text ** anzeigen, neko.wakati
kann ** Brüche ** anzeigen und neko.corpus
kann ** Korpus ** anzeigen. Der Text ist so genannt fest, die Unterteilung ist eine wortweise Liste und der Korpus ist die Zahl vom Anfang des Unterteilungswortes (keine Vervielfältigung). Schauen wir uns übrigens das Wörterbuch an.
neko.waord_to_id []
ist ein Wörterbuch, das ** Wörter in Wörter umwandelt **, und neko.id_to_word []
ist ein Wörterbuch, das Zahlen ** in Wörter ** umwandelt. Schauen wir uns die Trainingsdaten an.
Sie können sehen, dass neko.data
und neko.label
um eins versetzt sind. Schauen wir uns zum Schluss die Länge der Daten und die Anzahl der Wörter im Wörterbuch an.
Die ** Datenlänge ** beträgt 205.815 und die Anzahl der Wörter im Wörterbuch ** vovab_size ** beträgt 13.616.
Schreiben wir nun den Code des Hauptteils.
import numpy as np
import dezero
from dezero import Model
from dezero import SeqDataLoader
import dezero.functions as F
import dezero.layers as L
import random
from dezero import cuda
import textwrap
max_epoch = 70
batch_size = 30
vocab_size = len(neko.word_to_id)
wordvec_size = 650
hidden_size = 650
bptt_length = 30
class Lstm_nlp(Model):
def __init__(self, vocab_size, wordvec_size, hidden_size, out_size):
super().__init__()
self.embed = L.EmbedID(vocab_size, wordvec_size)
self.rnn = L.LSTM(hidden_size)
self.fc = L.Linear(out_size)
def reset_state(self): #Zustand zurücksetzen
self.rnn.reset_state()
def __call__(self, x): #Beschreiben Sie den Verbindungsinhalt der Ebene
y = self.embed(x)
y = self.rnn(y)
y = self.fc(y)
return y
Das Modell hat eine einfache Struktur aus ** Einbettungsschicht + LSTM-Schicht + Lineare Schicht **. Geben Sie die EmbedID als wortbasierte Zahl (Ganzzahl) ein.
Die Größe der Worteinbettungsmatrix für EmbedID beträgt ** vocal_size x wordvec_size **, also 13616 x 650. Die hidden_size
von LSTM ist 650, was mit wordvec_size identisch ist. Und die lineare Ausgabegröße "out_size" ist 13616, was mit vocab_size identisch ist.
Beschreibe ** den Verbindungsinhalt jeder Schicht ** in def __call __ ()
. Der hier beschriebene Inhalt kann aufgerufen werden, indem der erstellten Instanz wie eine Funktion Argumente gegeben werden. Wenn Sie beispielsweise mit "model = Lstm_nlp (....)" instanziieren, können Sie den Teil von "def __call __ ()" mit "y = model (x)" verschieben. Mit anderen Worten kann damit eine sogenannte Vorhersage erreicht werden. Das ist klug.
model = Lstm_nlp(vocab_size, wordvec_size, hidden_size, vocab_size) #Modellgenerierung
dataloader = SeqDataLoader(neko, batch_size=batch_size) #Generierung von Datenladern
seqlen = len(neko)
optimizer = dezero.optimizers.Adam().setup(model) #Die Optimierungsmethode ist Adam
#Anwesenheits- / Abwesenheitsbeurteilung und Verarbeitung der GPU
if dezero.cuda.gpu_enable: #Wenn die GPU aktiviert ist, gehen Sie wie folgt vor
dataloader.to_gpu() #Datenlader zur GPU
model.to_gpu() #Modell zur GPU
Der Datenlader verwendet "SeqDataLoader" für Zeitreihendaten. Da sich die Reihenfolge der Zeitreihendaten beim Mischen ändert, wird das Verfahren zum Extrahieren mehrerer Daten durch Teilen der Zeitreihendaten in regelmäßigen Intervallen angewendet.
Wenn die GPU verfügbar ist, ist if dezero.cuda.gpu_enable:
True. In diesem Fall werden der Datenlader und das Modell an die GPU gesendet.
#Lernschleife
for epoch in range(max_epoch):
model.reset_state()
loss, count = 0, 0
for x, t in dataloader:
y = model(x) #Vorwärtsausbreitung
#Erscheinungsgrad des nächsten Wortes y(vocab_Größe dimensionale Vektor)Ist softmax verarbeitet und korrekt(Ein heißer Vektor)Verlustberechnung mit
#Die Eingabe t ist jedoch die Indexnummer, in der 1 des einen heißen Vektors steht.(ganze Zahl)
loss += F.softmax_cross_entropy_simple(y, t)
count += 1
if count % bptt_length == 0 or count == seqlen:
model.cleargrads() #Initialisierung der Differenzierung
loss.backward() #Backpropagation
loss.unchain_backward() #Kehren Sie zum Berechnungsdiagramm zurück und trennen Sie die Verbindung
optimizer.update() #Gewichtsaktualisierung
avg_loss = float(loss.data) / count
print('| epoch %d | loss %f' % (epoch + 1, avg_loss))
#Satzerzeugung
model.reset_state() #Zustand zurücksetzen
with dezero.no_grad(): #Gewichte nicht aktualisieren
text = []
x = random.randint(0,vocab_size) #Wählen Sie zufällig die erste Wortnummer
while len(text) < 100: #Wiederholen Sie bis 100 Wörter
x = np.array(int(x))
y = model(x) #y ist der Grad des Auftretens des nächsten Wortes(vocab_Größe dimensionale Vektor)
p = F.softmax_simple(y, axis=0) #Mit softmax multiplizieren, um die Erscheinungswahrscheinlichkeit zu erhalten
xp = cuda.get_array_module(p) #XP mit GPU=xp ohne cp=np
sampled = xp.random.choice(len(p.data), size=1, p=p.data) #Zahlen unter Berücksichtigung der Wahrscheinlichkeit des Auftretens(Index)Wählen
word = neko.id_to_word[int(sampled)] #Zahlen in Wörter umwandeln
text.append(word) #Fügen Sie dem Text ein Wort hinzu
x = sampled #Stellen Sie Sampling auf den nächsten Eingang ein
text = ''.join(text)
print(textwrap.fill(text, 60)) #Anzeige mit einer Pause von 60 Zeichen
Es ist eine Lernschleife. ** Vorwärtsausbreitung ** mit y = Modell (x)
und Verlust mit Verlust + = F.softmax_cross_entropy_simple (y, t)
berechnen.
Zu diesem Zeitpunkt ist y ein ** Vektor ** (vocab_size-Dimension), der den ** Erscheinungsgrad ** des nächsten Wortes darstellt, der mit softmax multipliziert wird, um die ** Erscheinungswahrscheinlichkeit ** zu erhalten, und ** einen heißen nächsten. Der Verlust wird aus den richtigen Antwortdaten berechnet **. Die Eingabe t ist jedoch die ** Zahl (Ganzzahl) ** des One-Hot-Vektors, in dem 1 steht.
Wenn count ein ganzzahliges Vielfaches von bptt_length ist oder mit if count% bptt_length == 0 oder count == seqlen:
zum Ende geht, wird eine Backpropagation durchgeführt und das Gewicht aktualisiert.
Als nächstes werden 100 Wörter für jede Eopch generiert. Verwenden Sie zuerst model.reset_state ()
, um den Status zurückzusetzen, und with dezero.no_grad ():
, um die Gewichte unverändert zu lassen. Dann wird mit "x = random.randint (0, voc_size)" der Anfangswert des Wortes zufällig aus einer ganzen Zahl von 0 bis vocal_size bestimmt und das nächste Wort vorhergesagt. Ein Satz wird erzeugt, indem eine weitere Vorhersage basierend auf dem vorhergesagten Wort wiederholt wird.
p = F.softmax_simple (y, axis = 0)
multipliziert y mit softmax, um die Eintrittswahrscheinlichkeit des nächsten Wortes zu ermitteln, und xp.random.choice ()
folgt zufällig der Eintrittswahrscheinlichkeit. Ist ausgewählt.
Der Grund, warum xp.random.choice ()
mit ** xp ** beginnt, ist ** np ** (numpy), wenn das erste Zeichen von der CPU verschoben wird, und ** cp, wenn es von der GPU verschoben wird. Dies liegt daran, dass es in ** (cupy) geändert werden muss. Beurteilen Sie daher nach "xp = cuda.get_array_module (p)" und ersetzen Sie die CPU durch xp = np und die GPU durch xp = cp.
Bewegen wir nun die Haupteinheit.
Wenn Sie den Hauptteilcode ausführen, lernt er die Wortreihenfolge "Ich bin eine Katze" und generiert für jede Epoche einen Satz. Es dauert ungefähr 1 bis 2 Minuten pro Epoche. Nach dem Lernen sieht es so aus. Es macht auch Spaß zu sehen, wie die Sätze nach und nach mehr so werden.
Der Eindruck, den Code durch Nachahmung zu schreiben, ist, dass es sich um ein ** einfaches Framework ** handelt, das in ** ganz Python ** geschrieben ist, sodass der Inhalt leicht zu verstehen ist ** und der Code leicht zu schreiben ist, aber der Freiheitsgrad hoch ist * Ich hatte einen guten Eindruck von *. In dieser Zeit möchte ich den Inhalt des DeZero-Frameworks untersuchen.
Recommended Posts