Je lis un chef-d'œuvre, ** "Deep Learning from Zero 2" **. ** GRU ** est présenté dans l'annexe de ce livre, mais il n'y a pas de scène réelle pour le déplacer. Cette fois, comme je l'ai fait au chapitre 6, j'ai utilisé la classe ** BetterRnnlm ** pour laisser ** GRU ** apprendre l'ordre des mots dans le ** jeu de données PTB **, et le degré d'apprentissage avec perplexité. Je voudrais mesurer.
common / time_layers.py
, mais ils ne peuvent pas être utilisés tels qu'ils sont dans la classe BetterRnnlm car ils sont implémentés avec la priorité sur la compréhensibilité. De plus, le terme de biais n'est pas pris en compte.Ceci est un graphique de calcul GRU. Il n'y a pas de cellules de stockage dans le LSTM, et seul h dans l'état caché se propage dans le sens du temps. Il y a deux portes, ** porte de réinitialisation ** et ** porte de mise à jour **.
La ** porte de réinitialisation ** détermine combien d'états cachés passés sont ignorés. Si r est zéro, $ h_ {hat} $ est déterminé à partir de l'entrée uniquement, en ignorant les états cachés passés.
La ** porte de mise à jour ** sert également de porte d'oubli et d'entrée LSTM. La partie qui agit comme la porte de l'oubli est $ (1-z) \ odot h_ {t-1} $. Ce calcul efface les informations qui devraient être oubliées des états cachés passés.
Et c'est la partie $ z \ odot h_ {hat} $ qui sert de porte d'entrée. Ce calcul pondère les informations nouvellement ajoutées.
Maintenant, trions les poids et les biais avant de mettre en œuvre.
Wxz, Wxr, Wxh ensemble ** Wx ** (P × 3H), Whz, Whr, Whh ensemble ** Wh ** (H × 3H), bz, br, bh ensemble ** 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
Gérez les paramètres sous la forme ** self.params ** et les dégradés sous la forme ** self.grad ** afin que la classe BetterRnnlm puisse les gérer tels quels. Les valeurs individuelles peuvent être divisées par la largeur H.
Maintenant, c'est une propagation arrière un peu compliquée. Tout d'abord, c'est la partie qui décompose ** self.params ** pour obtenir chaque valeur, et restaure les autres états de ** cache **.
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
De là, nous allons implémenter la rétropropagation en quatre parties. Tout d'abord, la partie qui n'a rien à voir avec Tanh et les deux sigmoids.
dh_hat =dh_next * z
dh_prev = dh_next * (1-z)
C'est une simple combinaison de + et de ×. Ensuite, c'est autour de 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
Puisque dh_prev a été calculé plus tôt, à partir de maintenant, ajoutez-le au résultat avec dh_prev + =. Vient ensuite environ z de la porte de mise à jour.
# 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)
Puisque dx a déjà été calculé avec tanh, à partir de maintenant, dx + = sera ajouté au résultat. Le suivant est à environ r de la porte de réinitialisation.
# 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)
Maintenant que le calcul de chaque gradient est terminé, nous allons le résumer en grades.
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
À ce stade, la mise en œuvre de GRU est terminée.
Pour la propagation avant de ** TimeGRU **, les données 3D ** xs ** sont coupées et entrées dans ** GRU ** toutes les heures, et la sortie de ** GRU ** est à nouveau sortie vers les données 3D **. Il est résumé en hs **.
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
Préparez une boîte ** hs ** (N, T, H) pour stocker la sortie. Préparez également une matrice zéro ** self.h ** (N, H) si nécessaire. Ensuite, coupez une heure des données ** xs **, saisissez-la dans ** GRU ** et stockez la sortie ** self.h ** de GRU dans ** hs **. Dans le même temps, ajoutez la couche pour le temps T minutes (ceci est utilisé à l'envers).
Maintenant, la propagation arrière de TimeGRU. Lors de la rétropropagation, $ dh_t + dh_ {next} $ est entré dans la couche GRU.
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
Préparez une boîte *** dxs *** (N, T, D) pour stocker la sortie de rétropropagation. Préparez également une liste ** diplômés ** pour stocker temporairement les dégradés.
** Découpez une heure de dhs + Gradient dh ** à partir d'un futur en entrée, appelez la couche GRU ajoutée par avant dans l'ordre inverse et multipliez par arrière. Ensuite, le résultat arrière ** dx ** est stocké dans ** dxs **.
Dans la formule dx, dh = layer.backward (dhs [:, t ,:] + dh)
, dh sur le côté droit est ce qu'on appelle $ dh_ {next} $ et dh sur le côté gauche est ce qu'on appelle $ dh_ {prev} $.
Ajoutez ensuite les gradients de poids à chaque couche et résumez le résultat final en ** self.grads **.
Maintenant que l'implémentation de GRU et TimeGRU est terminée, créez un dossier appelé ch09
et enregistrez-le avec le nom de fichier time_layers_gru.py
.
Ensuite, modifiez better_rnnlm.py
pour générer le modèle de réseau.
import sys
sys.path.append('..')
from common.time_layers import TimeEmbedding, TimeAffine, TimeSoftmaxWithLoss, TimeDropout #Spécifiez le calque à lire
from time_layers_gru import * #Seul GRU est lu à partir d'ici
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()
Au début, seule la couche spécifiée est importée de common / time_layers.py
, et GRU est changé pour importer de time_layers_gru.py
sauvegardé dans le répertoire courant plus tôt.
Après cela, changez la partie LSTM du code en GRU. Le poids est réduit de 4 à 3, donc par exemple (D, 3 * in
gru_Wx1 = (rn (D, 3 * H) /np.sqrt(D)). Astype ('f')N'oubliez pas de modifier la partie liée au nombre de poids comme H)
.
Enregistrez ce code dans le dossier ch09
sous le nom better_rnnlm_gru.py
.
En vous basant sur le code d'apprentissage du chapitre 6, changez le from better_rnnlm import BetterRnnlm
au début en from better_rnnlm_gru import BetterRnnlm
et enregistrez-le dans le dossier ch09
avec le nom de fichier train_better_rnnlm.py
.
Si je l'ai exécuté avec l'hyper paramètre lr = 20, il y avait beaucoup de variation dans la perplixité dans les premiers stades, donc je l'ai changé en lr = 10 et l'ai relancé.
import sys
sys.path.append('..')
from common import config
#Lors de l'exécution sur GPU, supprimez le commentaire ci-dessous (cupy requis)
# ==============================================
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 #Changement
#Paramètres des hyper paramètres
batch_size = 20
wordvec_size = 650
hidden_size = 650
time_size = 35
lr = 10
max_epoch = 40
max_grad = 0.25
dropout = 0.5
#Lecture des données d'entraînement
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)
#Évaluation avec données de test
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('test perplexity: ', ppl_test)
La perplexité de test du modèle LSTM au chapitre 6 était dans les années 70, mais le modèle GRU semble rester dans les années 80. Pour un long corpus de plus de 900 000 mots, comme ce jeu de données, le modèle LSTM avec cellules mémoire semble plus avantageux.
Recommended Posts