Ich lese ein Meisterwerk, ** "Deep Learning from Zero 2" **. ** GRU ** wird im Anhang dieses Buches vorgestellt, aber es gibt keine tatsächliche Szene, um es zu verschieben. Dieses Mal habe ich, wie in Kapitel 6, die ** BetterRnnlm-Klasse ** verwendet, um ** GRU ** die Reihenfolge der Wörter im ** PTB-Datensatz ** und den Grad des Lernens mit Ratlosigkeit lernen zu lassen. Ich würde gerne messen.
Dies ist ein GRU-Berechnungsdiagramm. Es gibt keine Speicherzellen im LSTM und nur h im verborgenen Zustand breitet sich in Zeitrichtung aus. Es gibt zwei Gates, ** Reset Gate ** und ** Update Gate **.
Das ** Reset-Gate ** bestimmt, wie viel vergangene versteckte Zustände ignoriert werden sollen. Wenn r Null ist, wird $ h_ {hat} $ nur aus der Eingabe bestimmt, wobei vergangene verborgene Zustände ignoriert werden.
Das ** Update-Gate ** fungiert gleichzeitig als LSTM-Vergessens-Gate und Eingangs-Gate. Der Teil, der als Vergessensgatter fungiert, ist $ (1-z) \ odot h_ {t-1} $. Diese Berechnung löscht Informationen, die aus früheren verborgenen Zuständen vergessen werden sollten.
Und es ist der Teil $ z \ odot h_ {hat} $, der als Eingangsgatter fungiert. Diese Berechnung gewichtet die neu hinzugefügten Informationen.
Lassen Sie uns nun die Gewichte und Verzerrungen vor der Implementierung sortieren.
Wxz, Wxr, Wxh zusammen ** Wx ** (T × 3H), Whz, Whr, Whh zusammen ** Wh ** (H × 3H), bz, br, bh zusammen ** b * * (3H).
from common.np import * # import numpy as np (or import cupy as np)
from common.layers import *
from common.functions import softmax, sigmoid
class GRU:
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
H = Wh.shape[0]
Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:]
Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:]
bz, br, bh = b[:H], b[H:2 * H], b[2 * H:]
z = sigmoid(np.dot(x, Wxz) + np.dot(h_prev, Whz) + bz)
r = sigmoid(np.dot(x, Wxr) + np.dot(h_prev, Whr) + br)
h_hat = np.tanh(np.dot(x, Wxh) + np.dot(r*h_prev, Whh) + bh)
h_next = (1-z) * h_prev + z * h_hat
self.cache = (x, h_prev, z, r, h_hat)
return h_next
Die Parameter werden in Form von ** self.params ** und die Farbverläufe in Form von ** self.grad ** behandelt, damit sie so behandelt werden können, wie sie in der BetterRnnlm-Klasse sind. Einzelne Werte können durch die Breite H geteilt werden.
Jetzt ist es eine etwas komplizierte Rückausbreitung. Erstens ist es der Teil, der ** self.params ** zerlegt, um jeden Wert zu erhalten, und die anderen Zustände aus dem ** Cache ** wiederherstellt.
def backward(self, dh_next):
Wx, Wh, b = self.params
H = Wh.shape[0]
Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:]
Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:]
x, h_prev, z, r, h_hat = self.cache
Von hier aus werden wir die Backpropagation in vier Teilen implementieren. Erstens der Teil, der nichts mit Tanh und den beiden Sigmoiden zu tun hat.
dh_hat =dh_next * z
dh_prev = dh_next * (1-z)
Es ist eine einfache Kombination von + und ×. Als nächstes ist um Tanh.
# tanh
dt = dh_hat * (1 - h_hat ** 2)
dbh = np.sum(dt, axis=0)
dWhh = np.dot((r * h_prev).T, dt)
dhr = np.dot(dt, Whh.T)
dWxh = np.dot(x.T, dt)
dx = np.dot(dt, Wxh.T)
dh_prev += r * dhr
Da dh_prev früher berechnet wurde, werden wir es von hier aus mit dh_prev + = zum Ergebnis hinzufügen. Als nächstes ist ungefähr z des Update-Gates.
# update gate(z)
dz = dh_next * h_hat - dh_next * h_prev
dt = dz * z * (1-z)
dbz = np.sum(dt, axis=0)
dWhz = np.dot(h_prev.T, dt)
dh_prev += np.dot(dt, Whz.T)
dWxz = np.dot(x.T, dt)
dx += np.dot(dt, Wxz.T)
Da dx bereits mit tanh berechnet wurde, wird von nun an dx + = zum Ergebnis hinzugefügt. Weiter ist um r des Reset-Gates.
# rest gate(r)
dr = dhr * h_prev
dt = dr * r * (1-r)
dbr = np.sum(dt, axis=0)
dWhr = np.dot(h_prev.T, dt)
dh_prev += np.dot(dt, Whr.T)
dWxr = np.dot(x.T, dt)
dx += np.dot(dt, Wxr.T)
Nachdem die Berechnung jedes Gradienten abgeschlossen ist, werden wir ihn in Grad zusammenfassen.
self.dWx = np.hstack((dWxz, dWxr, dWxh))
self.dWh = np.hstack((dWhz, dWhr, dWhh))
self.db = np.hstack((dbz, dbr, dbh))
self.grads[0][...] = self.dWx
self.grads[1][...] = self.dWh
self.grads[2][...] = self.db
return dx, dh_prev
Zu diesem Zeitpunkt ist die GRU-Implementierung abgeschlossen.
Für die Vorwärtsausbreitung von ** TimeGRU ** werden 3D-Daten ** xs ** ausgeschnitten und stündlich in ** GRU ** eingegeben, und die Ausgabe von ** GRU ** wird erneut in 3D-Daten ** ausgegeben. Es ist in hs ** zusammengefasst.
class TimeGRU:
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
H = Wh.shape[0]
N, T, D = xs.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 = GRU(*self.params)
self.h = layer.forward(xs[:, t, :], self.h)
hs[:, t, :] = self.h
self.layers.append(layer)
return hs
Bereiten Sie eine Box ** hs ** (N, T, H) vor, um die Ausgabe zu speichern. Bereiten Sie bei Bedarf auch eine Nullmatrix ** self.h ** (N, H) vor. Schneiden Sie dann eine Stunde aus den Daten ** xs ** aus, geben Sie sie in ** GRU ** ein und speichern Sie die Ausgabe ** self.h ** von GRU in ** hs **. Fügen Sie gleichzeitig die Ebene für die Zeit T Minuten hinzu (dies wird rückwärts verwendet).
Nun die Rückausbreitung von TimeGRU. Während der Backpropagation wird $ dh_t + dh_ {next} $ in die GRU-Ebene eingegeben.
def backward(self, dhs):
Wx, Wh, b = self.params
N, T, H = dhs.shape
D = Wx.shape[0]
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)
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
Bereiten Sie eine Box *** dxs *** (N, T, D) vor, um die Backpropagation-Ausgabe zu speichern. Bereiten Sie außerdem eine Liste ** Absolventen ** vor, um die Farbverläufe vorübergehend zu speichern.
** Eine Stunde von dhs ausschneiden + Gradient von einem zukünftigen dh ** wird eingegeben, und die durch vorwärts angehängte GRU-Ebene wird in umgekehrter Reihenfolge aufgerufen und rückwärts angewendet. Dann wird das Rückwärtsergebnis ** dx ** in ** dxs ** gespeichert.
Im Ausdruck dx, dh = layer.backward (dhs [:, t,:] + dh)
ist dh auf der rechten Seite $ dh_ {next} $ und dh auf der linken Seite ist $ dh_ {prev} $.
Fügen Sie dann die Gewichtsverläufe bei jeder Schicht hinzu und fassen Sie das Endergebnis in ** self.grads ** zusammen.
Nachdem die Implementierung von GRU und TimeGRU abgeschlossen ist, erstellen Sie einen Ordner mit dem Namen "ch09" und speichern Sie ihn unter dem Dateinamen "time_layers_gru.py".
Ändern Sie als Nächstes better_rnnlm.py
, um das Netzwerkmodell zu generieren.
import sys
sys.path.append('..')
from common.time_layers import TimeEmbedding, TimeAffine, TimeSoftmaxWithLoss, TimeDropout #Geben Sie die zu lesende Ebene an
from time_layers_gru import * #Von hier wird nur GRU gelesen
from common.np import * # import numpy as np
from common.base_model import BaseModel
class BetterRnnlm(BaseModel):
def __init__(self, vocab_size=10000, wordvec_size=650,
hidden_size=650, dropout_ratio=0.5):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
embed_W = (rn(V, D) / 100).astype('f')
gru_Wx1 = (rn(D, 3 * H) / np.sqrt(D)).astype('f')
gru_Wh1 = (rn(H, 3 * H) / np.sqrt(H)).astype('f')
gru_b1 = np.zeros(3 * H).astype('f')
gru_Wx2 = (rn(H, 3 * H) / np.sqrt(H)).astype('f')
gru_Wh2 = (rn(H, 3 * H) / np.sqrt(H)).astype('f')
gru_b2 = np.zeros(3 * H).astype('f')
affine_b = np.zeros(V).astype('f')
self.layers = [
TimeEmbedding(embed_W),
TimeDropout(dropout_ratio),
TimeGRU(gru_Wx1, gru_Wh1, gru_b1, stateful=True),
TimeDropout(dropout_ratio),
TimeGRU(gru_Wx2, gru_Wh2, gru_b2, stateful=True),
TimeDropout(dropout_ratio),
TimeAffine(embed_W.T, affine_b)
]
self.loss_layer = TimeSoftmaxWithLoss()
self.gru_layers = [self.layers[2], self.layers[4]]
self.drop_layers = [self.layers[1], self.layers[3], self.layers[5]]
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def predict(self, xs, train_flg=False):
for layer in self.drop_layers:
layer.train_flg = train_flg
for layer in self.layers:
xs = layer.forward(xs)
return xs
def forward(self, xs, ts, train_flg=True):
score = self.predict(xs, train_flg)
loss = self.loss_layer.forward(score, 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):
for layer in self.gru_layers:
layer.reset_state()
Zu Beginn wird nur die angegebene Ebene aus "common / time_layers.py" importiert, und die GRU wird so geändert, dass sie aus "time_layers_gru.py" importiert wird, das zuvor im aktuellen Verzeichnis gespeichert wurde.
Ändern Sie danach den LSTM-Teil des Codes in GRU. Das Gewicht wird von 4 auf 3 reduziert, so zum Beispiel "(D, 3 *) in" gru_Wx1 = (rn (D, 3 * H) / np.sqrt (D)). Astype ("f") " Vergessen Sie nicht, den Teil in Bezug auf die Anzahl der Gewichte wie H) `zu ändern.
Speichern Sie diesen Code im Ordner ch09
als better_rnnlm_gru.py
.
Ändern Sie basierend auf dem Lerncode in Kapitel 6 das von better_rnnlm import BetterRnnlm
am Anfang in von better_rnnlm_gru import BetterRnnlm
und speichern Sie es im Ordner ch09
mit dem Dateinamen train_better_rnnlm.py
.
Wenn ich es mit dem Hyperparameter lr = 20 ausführte, gab es in den frühen Stadien große Unterschiede in der Perplixität, also habe ich es in lr = 10 geändert und es erneut ausgeführt.
import sys
sys.path.append('..')
from common import config
#Wenn Sie mit einer GPU arbeiten, löschen Sie den folgenden Kommentar (Cupy erforderlich).
# ==============================================
config.GPU = True
# ==============================================
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from common.util import eval_perplexity, to_gpu
from dataset import ptb
from better_rnnlm_gru import BetterRnnlm #Veränderung
#Hyper-Parametereinstellungen
batch_size = 20
wordvec_size = 650
hidden_size = 650
time_size = 35
lr = 10
max_epoch = 40
max_grad = 0.25
dropout = 0.5
#Trainingsdaten lesen
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_val, _, _ = ptb.load_data('val')
corpus_test, _, _ = ptb.load_data('test')
if config.GPU:
corpus = to_gpu(corpus)
corpus_val = to_gpu(corpus_val)
corpus_test = to_gpu(corpus_test)
vocab_size = len(word_to_id)
xs = corpus[:-1]
ts = corpus[1:]
model = BetterRnnlm(vocab_size, wordvec_size, hidden_size, dropout)
optimizer = SGD(lr)
trainer = RnnlmTrainer(model, optimizer)
best_ppl = float('inf')
for epoch in range(max_epoch):
trainer.fit(xs, ts, max_epoch=1, batch_size=batch_size,
time_size=time_size, max_grad=max_grad)
model.reset_state()
ppl = eval_perplexity(model, corpus_val)
print('valid perplexity: ', ppl)
if best_ppl > ppl:
best_ppl = ppl
model.save_params()
else:
lr /= 4.0
optimizer.lr = lr
model.reset_state()
print('-' * 50)
#Auswertung mit Testdaten
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('test perplexity: ', ppl_test)
Die Testverwirrung des LSTM-Modells in Kapitel 6 lag in den hohen 70er Jahren, aber das GRU-Modell scheint in den niedrigen 80er Jahren zu bleiben. Für einen langen Korpus von über 900.000 Wörtern wie diesen Datensatz scheint das LSTM-Modell mit Speicherzellen vorteilhafter zu sein.
Recommended Posts