Ich lese ein Meisterwerk, ** "Deep Learning from Zero 2" **. Diesmal ist ein Memo von Kapitel 5. Um den Code auszuführen, laden Sie den gesamten Code von Github herunter und verwenden Sie jupyter notebook in ch05.
2.RNNLM Versuchen Sie zunächst, den folgenden Code ch05 / train_custom_loop.py auszuführen, der die Wortreihenfolge des PTB-Datensatzes lernt.
import sys
sys.path.append('..')
import matplotlib.pyplot as plt
import numpy as np
from common.optimizer import SGD
from dataset import ptb
from simple_rnnlm import SimpleRnnlm
#Hyper-Parametereinstellungen
batch_size = 10
wordvec_size = 100
hidden_size = 100
time_size = 5 #Zeitgröße für die Bereitstellung von abgeschnittenem BPTT
lr = 0.1
max_epoch = 100
#Trainingsdaten lesen (Datensatz verkleinern)
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_size = 1000
corpus = corpus[:corpus_size]
vocab_size = int(max(corpus) + 1)
xs = corpus[:-1] #Eingang
ts = corpus[1:] #Ausgabe (Lehreretikett)
data_size = len(xs)
print('corpus size: %d, vocabulary size: %d' % (corpus_size, vocab_size))
#Während des Lernens verwendete Variablen
max_iters = data_size // (batch_size * time_size)
time_idx = 0
total_loss = 0
loss_count = 0
ppl_list = []
#Modellgenerierung
model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size)
optimizer = SGD(lr)
#Berechnen Sie die Startposition für das Laden jeder Probe in der Mini-Charge
jump = (corpus_size - 1) // batch_size
offsets = [i * jump for i in range(batch_size)]
for epoch in range(max_epoch):
for iter in range(max_iters):
#Holen Sie sich eine Mini-Charge
batch_x = np.empty((batch_size, time_size), dtype='i')
batch_t = np.empty((batch_size, time_size), dtype='i')
for t in range(time_size):
for i, offset in enumerate(offsets):
batch_x[i, t] = xs[(offset + time_idx) % data_size]
batch_t[i, t] = ts[(offset + time_idx) % data_size]
time_idx += 1
#Finden Sie den Farbverlauf und aktualisieren Sie die Parameter
loss = model.forward(batch_x, batch_t)
model.backward()
optimizer.update(model.params, model.grads)
total_loss += loss
loss_count += 1
#Bewertung der Ratlosigkeit für jede Epoche
ppl = np.exp(total_loss / loss_count)
print('| epoch %d | perplexity %.2f'
% (epoch+1, ppl))
ppl_list.append(float(ppl))
total_loss, loss_count = 0, 0
#Zeichnen eines Diagramms
x = np.arange(len(ppl_list))
plt.plot(x, ppl_list, label='train')
plt.xlabel('epochs')
plt.ylabel('perplexity')
plt.show()
Die vertikale Achse des Graphen ist ein Index namens ** Ratlosigkeit **, der die Wahrscheinlichkeit des nächsten Wortes vorhersagt, und $ Ratlosigkeit = e ^ L \ (L = - \ frac {1} {N} \ sum_n \ sum_k t_ { Es wird durch die Formel nk} log y_ {nk}) $ ausgedrückt. Je näher der Ratlosigkeitswert an 1 liegt, desto höher ist die Vorhersagegenauigkeit. Einfach ausgedrückt ist Ratlosigkeit die Anzahl der Auswahlmöglichkeiten für das nächste Wort. Werfen wir einen kurzen Blick auf den Teil, der die Daten aufbereitet.
** Korpus ** verwendet nur die ersten 1.000 Wörter des PTB-Datensatzes, und ** Trainingsdaten xs ** und ** Lehrerdaten ts ** erhalten jeweils 999 Wörter, jeweils ein Wort.
Verwenden Sie dann "Offsets", um die Lesepositionen (10 Positionen) für die Stapelgröße zu bestimmen, und erstellen Sie einen Mini-Stapel, indem Sie alle Daten für die Zeitgröße (5) teilen. Wenn "Offsets + time_idx" die Datengröße von 999 oder mehr erreicht, beginnt es wieder bei 0 und die Daten werden erfasst.
Werfen wir einen Blick auf die ** Klasse SimpleRnnlm **, die das Modell generiert.
3.SimpleRnnlm
class SimpleRnnlm:
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
#Gewichtsinitialisierung
embed_W = (rn(V, D) / 100).astype('f')
rnn_Wx = (rn(D, H) / np.sqrt(D)).astype('f')
rnn_Wh = (rn(H, H) / np.sqrt(H)).astype('f')
rnn_b = np.zeros(H).astype('f')
affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
affine_b = np.zeros(V).astype('f')
#Schichterzeugung
self.layers = [
TimeEmbedding(embed_W),
TimeRNN(rnn_Wx, rnn_Wh, rnn_b, stateful=True),
TimeAffine(affine_W, affine_b)
]
self.loss_layer = TimeSoftmaxWithLoss()
self.rnn_layer = self.layers[1]
#Listen Sie alle Gewichte und Verläufe auf
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def forward(self, xs, ts):
for layer in self.layers:
xs = layer.forward(xs)
loss = self.loss_layer.forward(xs, ts)
return loss
def backward(self, dout=1):
dout = self.loss_layer.backward(dout)
for layer in reversed(self.layers):
dout = layer.backward(dout)
return dout
def reset_state(self):
self.rnn_layer.reset_state()
** Klasse SimpleRnnlm ** ist ein Stapel von vier ** Zeitschichten **: ** TimeEmbedding, Time RNN, Time Affine, Time Softmax With Loss **. Schauen wir uns die Zeitebene der Reihe nach an.
class TimeEmbedding:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.layers = None
self.W = W
def forward(self, xs):
N, T = xs.shape
V, D = self.W.shape
out = np.empty((N, T, D), dtype='f')
self.layers = []
for t in range(T):
layer = Embedding(self.W)
out[:, t, :] = layer.forward(xs[:, t])
self.layers.append(layer)
return out
def backward(self, dout):
N, T, D = dout.shape
grad = 0
for t in range(T):
layer = self.layers[t]
layer.backward(dout[:, t, :])
grad += layer.grads[0]
self.grads[0][...] = grad
return None
Die ** Time Embedding-Ebene ** schneidet Daten spaltenweise aus xs aus, gibt sie in die ** Embedding-Ebene ** ein und speichert die Ausgabe in ** out (N, T, D) **. Es wiederholt T-mal in einer Schleife.
Bevor wir uns die TimeRNN-Schicht ansehen, schauen wir uns die RNN-Schicht an, die für die TimeRNN-Schicht verwendet wird.
class RNN:
def __init__(self, Wx, Wh, b):
self.params = [Wx, Wh, b]
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
self.cache = None
def forward(self, x, h_prev):
Wx, Wh, b = self.params
t = np.dot(h_prev, Wh) + np.dot(x, Wx) + b
h_next = np.tanh(t)
self.cache = (x, h_prev, h_next)
return h_next
Die RNN-Schicht hat zwei Gewichte. Sie sind das Gewicht ** W_x **, das die Eingabe x_t und das innere Produkt (MatMul) nimmt, und das Gewicht ** W_h **, das die Eingabe h_prev und das innere Produkt (MatMUl) nimmt.
def backward(self, dh_next):
Wx, Wh, b = self.params
x, h_prev, h_next = self.cache
dt = dh_next * (1 - h_next ** 2)
db = np.sum(dt, axis=0)
dWh = np.dot(h_prev.T, dt)
dh_prev = np.dot(dt, Wh.T)
dWx = np.dot(x.T, dt)
dx = np.dot(dt, Wx.T)
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
return dx, dh_prev
Backpropagation sieht so aus. Es ist eine modifizierte Version von Affine, daher ist nichts kompliziert.
class TimeRNN:
def __init__(self, Wx, Wh, b, stateful=False):
self.params = [Wx, Wh, b]
self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
self.layers = None
self.h, self.dh = None, None
self.stateful = stateful
def forward(self, xs):
Wx, Wh, b = self.params
N, T, D = xs.shape
D, H = Wx.shape
self.layers = []
hs = np.empty((N, T, H), dtype='f')
if not self.stateful or self.h is None:
self.h = np.zeros((N, H), dtype='f')
for t in range(T):
layer = RNN(*self.params)
self.h = layer.forward(xs[:, t, :], self.h)
hs[:, t, :] = self.h
self.layers.append(layer)
return hs
** TimeRNN-Schicht ** ist ein Netzwerk von T RNN-Schichten, die miteinander verbunden sind. Hier können Sie anpassen, ob der Status h zwischen Blöcken mit dem Argument "stateful" geerbt werden soll.
Bereiten Sie für die Vorwärtsausbreitung zuerst den Ausgabecontainer "hs (N, T, H)" vor. Während die for-Schleife gedreht wird, werden die t-ten Daten durch "xs [:, t ,:]" ausgeschnitten und in die normale RNN eingegeben, und die Ausgabe ist der von "hs [:, t ,:]" vorbereitete Container. Registrieren Sie die Ebene in Ebenen, während Sie sie an der angegebenen Position von speichern.
Mit anderen Worten ist die TimeRNN-Schicht die Eingabe / Ausgabe der RNN-Schicht mit den Funktionen der Datenextraktion und Datenzusammenfassung.
def backward(self, dhs):
Wx, Wh, b = self.params
N, T, H = dhs.shape
D, H = Wx.shape
dxs = np.empty((N, T, D), dtype='f')
dh = 0
grads = [0, 0, 0]
for t in reversed(range(T)):
layer = self.layers[t]
dx, dh = layer.backward(dhs[:, t, :] + dh) #Summierter Gradient
dxs[:, t, :] = dx
for i, grad in enumerate(layer.grads):
grads[i] += grad
for i, grad in enumerate(grads):
self.grads[i][...] = grad
self.dh = dh
return dxs
def set_state(self, h):
self.h = h
def reset_state(self):
self.h = None
Die TimeRNN-Vorwärtsausbreitung hat zwei Ausgänge. Im Fall der Rückwärtsausbreitung wird also $ dh_t + dh_ {next} $, die Summe der beiden, eingegeben.
Erstellen Sie zunächst einen Container dxs, der stromabwärts fließt, ermitteln Sie den Gradienten dx jedes Mal mit backward () der RNN-Schicht in umgekehrter Reihenfolge der Vorwärtsausbreitung und weisen Sie ihn dem entsprechenden Index von dxs zu. Der Parameter weight fügt die Gewichtsverläufe für jede Ebene hinzu und überschreibt das Endergebnis in self.grads.
class TimeAffine:
def __init__(self, W, b):
self.params = [W, b]
self.grads = [np.zeros_like(W), np.zeros_like(b)]
self.x = None
def forward(self, x):
N, T, D = x.shape
W, b = self.params
rx = x.reshape(N*T, -1)
out = np.dot(rx, W) + b
self.x = x
return out.reshape(N, T, -1)
def backward(self, dout):
x = self.x
N, T, D = x.shape
W, b = self.params
dout = dout.reshape(N*T, -1)
rx = x.reshape(N*T, -1)
db = np.sum(dout, axis=0)
dW = np.dot(rx.T, dout)
dx = np.dot(dout, W.T)
dx = dx.reshape(*x.shape)
self.grads[0][...] = dW
self.grads[1][...] = db
return dx
Die ** Zeitaffine Ebene ** ist die Eingabe und Ausgabe der ** Affinen Ebene ** mit hinzugefügter Umformung, so dass sie T in Richtung der Zeitachse entsprechen kann.
8.TimeSoftmaxWithLoss
class TimeSoftmaxWithLoss:
def __init__(self):
self.params, self.grads = [], []
self.cache = None
self.ignore_label = -1
def forward(self, xs, ts):
N, T, V = xs.shape
if ts.ndim == 3: #Das Lehrerlabel ist eins-Für heißen Vektor
ts = ts.argmax(axis=2)
mask = (ts != self.ignore_label)
#Stapel- und Zeitreihen sammeln (umformen)
xs = xs.reshape(N * T, V)
ts = ts.reshape(N * T)
mask = mask.reshape(N * T)
ys = softmax(xs)
ls = np.log(ys[np.arange(N * T), ts])
ls *= mask # ignore_Daten, die dem Etikett entsprechen, setzen den Verlust auf 0
loss = -np.sum(ls)
loss /= mask.sum()
self.cache = (ts, ys, mask, (N, T, V))
return loss
def backward(self, dout=1):
ts, ys, mask, (N, T, V) = self.cache
dx = ys
dx[np.arange(N * T), ts] -= 1
dx *= dout
dx /= mask.sum()
dx *= mask[:, np.newaxis] # ignore_Setzen Sie den Farbverlauf für die der Beschriftung entsprechenden Daten auf 0
dx = dx.reshape((N, T, V))
return dx
** Time Softmax mit Verlustschicht ** ist eine Schicht, die T von Sotmax mit einem Verlust von $ x_t und t_t $ addiert und durch T dividiert.
Versuchen wir es mit einem japanischen Datensatz, um das Ganze besser zu verstehen. Wenn Sie jedoch versuchen, dies Wort für Wort zu tun, ist eine morphologische Analyse erforderlich, also ** Zeicheneinheit **. Dieses Mal habe ich "Alter Mann und das Meer" von Aozora Bunko heruntergeladen und verwendet.
import numpy as np
import io
def load_data():
# file_UTF die ersten 1000 Zeichen des Namens-Lesen Sie in Text im 8-Format
file_name = './data_rojinto_umi.txt'
length = 1000
with io.open(file_name, encoding='utf-8') as f:
text = f.read().lower()
text = text[:length]
# word_to_id, id_to_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 text, corpus, word_to_id, id_to_word
Liest 1000 Zeichen vom Anfang der durch Dateiname
angegebenen Textdatei im UTF-8-Format. Eine Funktion, die Text, Korpus, word_to_id, id_to_word zurückgibt. Bewegen wir es ein wenig.
text, corpus, word_to_id, id_to_word = load_data()
print('text_length = ', len(text))
print(text)
Die Länge von "Text" beträgt 1000 wie angegeben. Wenn Sie es in Zeicheneinheiten vorbereiten, ist der Text sehr kompakt.
print('vocab_size = ', len(word_to_id))
print(word_to_id)
Es ist word_to_id
. vocab_size
ist nicht so groß wie ich dachte es war 236. Dies wird verwendet, um jedes Zeichen von "Text" durch ID zu ersetzen, um "Korpus" zu erstellen.
print('corpus_length = ', len(corpus))
print(corpus[:500])
Es ist Korpus. Die Anzeige ist von Anfang an auf 500 Zeichen begrenzt.
text2 = ''.join([id_to_word[id] for id in corpus])
print(text2)
Das Konvertieren der ID von "Korpus" in ein Zeichen mit "id_to_word" kehrt zum ersten Text wie diesem zurück.
Jetzt, da es eine große Sache ist, möchte ich Testdaten und Antworten vorbereiten und prüfen, wie viel Vorhersage für jede Epoche gemacht werden kann.
#Probe aus dem Korpus
x = corpus[:50]
t = corpus[1:51]
print('x = ', x)
print('t = ', t)
#Bestätigung per Text
text_x = ''.join([id_to_word[id] for id in x])
text_t = ''.join([id_to_word[id] for id in t])
print('text_x = ', text_x)
print('text_t = ', text_t)
#In Stapelformat konvertieren
test_x = x.reshape(10, 5)
test_t = t.reshape(10, 5)
print(test_x)
print(test_t)
Holen Sie sich x, t aus "Korpus", indem Sie 50 Zeichen um ein Zeichen verschieben. Zur Zeit konvertiere ich es in Zeichen und überprüfe den Inhalt. Anschließend wird es in die im Modell verwendete Form (10, 5) konvertiert und die Vorhersagedaten test_x und test_t zum Testen werden erstellt.
def generate(self, xs):
for layer in self.layers:
xs = layer.forward(xs)
return xs
Später werde ich diesen Code am Ende von simple_rnnlm.py hinzufügen, um Vorhersagen für jede Epoche zu treffen.
Ändern und fügen Sie dann basierend auf dem zuerst ausgeführten Code ch05 / train_custom_loop.py die beiden Teile ** hinzu, lesen Sie die Trainingsdaten und führen Sie die Inferenz der Testdaten aus **. Die Anzahl der Epochen beträgt das 1000-fache.
import sys
sys.path.append('..')
import matplotlib.pyplot as plt
import numpy as np
from common.optimizer import SGD
from dataset import ptb
from simple_rnnlm import SimpleRnnlm
#Hyper-Parametereinstellungen
batch_size = 10
wordvec_size = 100
hidden_size = 100
time_size = 5 #Zeitgröße für die Bereitstellung von abgeschnittenem BPTT
lr = 0.1
max_epoch = 1000
# -----------Trainingsdaten lesen-------------
text, corpus, word_to_id, id_to_word = load_data()
corpus_size = 1000
vocab_size = int(max(corpus) + 1)
# ----------------------------------------------
xs = corpus[:-1] #Eingang
ts = corpus[1:] #Ausgabe (Lehreretikett)
data_size = len(xs)
print('corpus size: %d, vocabulary size: %d' % (corpus_size, vocab_size))
#Während des Lernens verwendete Variablen
max_iters = data_size // (batch_size * time_size)
time_idx = 0
total_loss = 0
loss_count = 0
ppl_list = []
#Modellgenerierung
model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size)
optimizer = SGD(lr)
#Berechnen Sie die Startposition für das Laden jeder Probe in der Mini-Charge
jump = (corpus_size - 1) // batch_size
offsets = [i * jump for i in range(batch_size)]
for epoch in range(max_epoch):
for iter in range(max_iters):
#Holen Sie sich eine Mini-Charge
batch_x = np.empty((batch_size, time_size), dtype='i')
batch_t = np.empty((batch_size, time_size), dtype='i')
for t in range(time_size):
for i, offset in enumerate(offsets):
batch_x[i, t] = xs[(offset + time_idx) % data_size]
batch_t[i, t] = ts[(offset + time_idx) % data_size]
time_idx += 1
#Finden Sie den Farbverlauf und aktualisieren Sie die Parameter
loss = model.forward(batch_x, batch_t)
model.backward()
optimizer.update(model.params, model.grads)
total_loss += loss
loss_count += 1
#Bewertung der Ratlosigkeit für jede Epoche
ppl = np.exp(total_loss / loss_count)
print('| epoch %d | perplexity %.2f'
% (epoch+1, ppl))
ppl_list.append(float(ppl))
total_loss, loss_count = 0, 0
# ----------Mit Testdaten vorhergesagt------------
pred= model.generate(test_x)
predict = np.argmax(pred, axis = 2)
print(predict)
# ------------------------------------------------
#Zeichnen eines Diagramms
x = np.arange(len(ppl_list))
plt.plot(x, ppl_list, label='train')
plt.xlabel('epochs')
plt.ylabel('perplexity')
plt.show()
Nach 1000 Epochen fiel die Ratlosigkeit auf 1,08. Da das auf den Testdaten basierende Vorhersageergebnis für jede Epoche angezeigt wird, sind alle 5 (Hiraganas "ta"), wenn Sie das Vorhersageergebnis nach 1 Epoche betrachten. Wenn Sie gerade gelernt haben, sieht es so aus. Dies ist das Vorhersageergebnis nach 1000 Epochen ziemlich ähnlich. Lassen Sie uns nun das endgültige Vorhersageergebnis überprüfen.
Die richtige Antwort wird nach dem Abgleichen der Vorhersageergebnisse rot eingekreist. Da es 24/50 ist, beträgt die korrekte Antwortrate 48%, was nicht so hoch ist, wie ich erwartet hatte. Liegt es daran, dass die korrekte Antwortrate in der zweiten Hälfte der fünf Zeichen hoch ist, weil sie anhand der Zeichen in der ersten Hälfte vorhergesagt werden kann? Wenn Sie versuchen, das Vorhersageergebnis der ersten Zeile mit einem Modell auszudrücken,
Das war's. Hast du so einen Fehler gemacht?
Recommended Posts