Je lis un chef-d'œuvre, ** "Deep Learning from Zero 2" **. Cette fois, c'est un mémo du chapitre 6. Pour exécuter le code, téléchargez le code complet depuis Github et utilisez le notebook jupyter dans ch06.
Le code pour ch06 / train_rnnlm.py qui apprend l'ordre des mots de l'ensemble de données PTB. Dans le modèle RNN simple du chapitre 5, nous n'avons appris que les 1 000 premiers mots de l'ensemble de données d'apprentissage, mais cette fois, nous en apprendrons environ 900 000 pour l'ensemble de l'ensemble de données d'entraînement.
Exécutez avec time_size = 35, batch_size = 20, word_size = hidden_size = 100, max_eopch = 4.
import sys
sys.path.append('..')
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from common.util import eval_perplexity
from dataset import ptb
from rnnlm import Rnnlm
#Paramètres des hyper paramètres
batch_size = 20
wordvec_size = 100
hidden_size = 100 #Nombre d'éléments dans le vecteur d'état caché de RNN
time_size = 35 #La taille pour déployer RNN
lr = 20.0
max_epoch = 4
max_grad = 0.25
#Lecture des données d'entraînement
corpus, word_to_id, id_to_word = ptb.load_data('train')
corpus_test, _, _ = ptb.load_data('test')
vocab_size = len(word_to_id)
xs = corpus[:-1]
ts = corpus[1:]
#Génération de modèle
model = Rnnlm(vocab_size, wordvec_size, hidden_size)
optimizer = SGD(lr)
trainer = RnnlmTrainer(model, optimizer)
#Apprenez en appliquant un découpage en dégradé
trainer.fit(xs, ts, max_epoch, batch_size, time_size, max_grad,
eval_interval=20)
trainer.plot(ylim=(0, 500))
#Évaluer avec des données de test
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('test perplexity: ', ppl_test)
#Enregistrer les paramètres
model.save_params()
Il a fallu 33 minutes pour terminer sur mon Macbook Air. La perplexité après 4 époques était de 111,47 et la perplexité selon les données de test était de 136,3. Si vous prédisez le mot suivant, vous avez 136 choix. Les conditions sont complètement différentes de ce que j'ai fait au chapitre 5, donc je ne peux pas le comparer, mais c'est plus grand que ce à quoi je m'attendais.
Dans le modèle RNN simple du chapitre 5, une explosion de gradient est susceptible de se produire, donc dans le modèle LSTM, afin de la supprimer, la classe d'entraînement est utilisée.Détourage de dégradé( -threshold < ||
import sys
sys.path.append('..')
from common.time_layers import *
from common.base_model import BaseModel
class Rnnlm(BaseModel):
def __init__(self, vocab_size=10000, wordvec_size=100, hidden_size=100):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
#Initialisation du poids
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b = np.zeros(4 * H).astype('f')
affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
affine_b = np.zeros(V).astype('f')
#Génération de couches
self.layers = [
TimeEmbedding(embed_W),
TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True),
TimeAffine(affine_W, affine_b)
]
self.loss_layer = TimeSoftmaxWithLoss()
self.lstm_layer = self.layers[1]
#Lister tous les poids et dégradés
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def predict(self, xs):
for layer in self.layers:
xs = layer.forward(xs)
return xs
def forward(self, xs, ts):
score = self.predict(xs)
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):
self.lstm_layer.reset_state()
La structure de couche de ** Rnnlm ** remplace simplement la couche Time RNN du modèle du chapitre 5 par la couche Time LSTM. Avant de regarder Time LSTM, examinons d'abord la couche LSTM utilisée ici.
class LSTM:
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, c_prev):
Wx, Wh, b = self.params
N, H = h_prev.shape
A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b
f = A[:, :H]
g = A[:, H:2*H]
i = A[:, 2*H:3*H]
o = A[:, 3*H:]
f = sigmoid(f)
g = np.tanh(g)
i = sigmoid(i)
o = sigmoid(o)
c_next = f * c_prev + g * i
h_next = o * np.tanh(c_next)
self.cache = (x, h_prev, c_prev, i, f, g, o, c_next)
return h_next, c_next
Il s'agit de la partie propagation directe de la ** couche LSTM **. Étant donné que les formules pour le stockage des cellules et la propagation vers l'avant des trois portes ont toutes la même forme, les poids $ W_x, W_h, b $ calculent A ensemble, puis coupent A pour l'activer. Il est efficace de passer la fonction.
def backward(self, dh_next, dc_next):
Wx, Wh, b = self.params
x, h_prev, c_prev, i, f, g, o, c_next = self.cache
tanh_c_next = np.tanh(c_next)
ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2)
dc_prev = ds * f
di = ds * g
df = ds * c_prev
do = dh_next * tanh_c_next
dg = ds * i
di *= i * (1 - i)
df *= f * (1 - f)
do *= o * (1 - o)
dg *= (1 - g ** 2)
dA = np.hstack((df, dg, di, do))
dWh = np.dot(h_prev.T, dA)
dWx = np.dot(x.T, dA)
db = dA.sum(axis=0)
self.grads[0][...] = dWx
self.grads[1][...] = dWh
self.grads[2][...] = db
dx = np.dot(dA, Wx.T)
dh_prev = np.dot(dA, Wh.T)
return dx, dh_prev, dc_prev
Pour la rétropropagation, dA peut être obtenu en trouvant respectivement df, dg, di et do et en les connectant avec hstach. Après cela, puisqu'il s'agit de la propagation arrière de MatMul, dWx, dWh, db, dx, dh_prev peuvent être obtenus.
5.TimeLSTM
class TimeLSTM:
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.c = None, None
self.dh = None
self.stateful = stateful
def forward(self, xs):
Wx, Wh, b = self.params
N, T, D = xs.shape
H = Wh.shape[0]
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')
if not self.stateful or self.c is None:
self.c = np.zeros((N, H), dtype='f')
for t in range(T):
layer = LSTM(*self.params)
self.h, self.c = layer.forward(xs[:, t, :], self.h, self.c)
hs[:, t, :] = self.h
self.layers.append(layer)
return hs
C'est fondamentalement la même chose que pour TimeRNN. ** La couche Time LSTM ** est un réseau qui relie les couches T LSTM. Vous permet d'ajuster s'il faut hériter de l'état h entre les blocs avec l'argument avec état. Il y a aussi la cellule C pour stocker la mémoire.
Pour la propagation vers l'avant, préparez d'abord un conteneur hs (N, T, H) pour la sortie. Ensuite, tout en tournant la boucle for, les t-ièmes données sont coupées par xs [:, t ,:] et entrées dans le LSTM normal, et la sortie est à la position spécifiée du conteneur préparé par hs [:, t ,:]. Au fur et à mesure que nous le stockons, nous enregistrerons les couches par couches.
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, dc = 0, 0
grads = [0, 0, 0]
for t in reversed(range(T)):
layer = self.layers[t]
dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc)
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, c=None):
self.h, self.c = h, c
def reset_state(self):
self.h, self.c = None, None
La propagation vers l'avant TimeLSTM a trois sorties, donc dans le cas de la propagation arrière, $ dh_t + dh_ {next} + dc_ {next} $, qui est la somme de ces trois, est entrée.
Tout d'abord, créez un conteneur dxs qui coule en aval, trouvez le gradient dx à chaque fois avec backward () de la couche LSTM dans l'ordre inverse de la propagation directe, et remplacez-le par l'indice correspondant de dxs. Le paramètre poids ajoute les gradients de poids pour chaque calque et écrase le résultat final dans self.grads.
Maintenant, exécutons le modèle amélioré train_better_rnnlm.py. Comme prévu, c'est difficile avec le processeur seul, j'ai donc installé cupy sur ma machine Windows.
Exécutez avec time_size = 35, batch_size = 20, ** word_size = hidden_size = 650, max_epoch = 40 **.
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 import BetterRnnlm
#Paramètres des hyper paramètres
batch_size = 20
wordvec_size = 650
hidden_size = 650
time_size = 35
lr = 20.0
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)
Il a été réalisé en moins de 3 heures sur la machine Windows (GTX1060). L'évaluation par les données de test après 40eopch s'est améliorée jusqu'à la perplexité = 76-79.
Dans ce code, la perplexité des données de test est calculée pour chaque epch, et le coefficient d'apprentissage lr n'est abaissé que lorsque la valeur se détériore. Jetons un coup d'œil à Better Rnnlm.
7.BetterRnnlm
import sys
sys.path.append('..')
from common.time_layers import *
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')
lstm_Wx1 = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh1 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b1 = np.zeros(4 * H).astype('f')
lstm_Wx2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_Wh2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b2 = np.zeros(4 * H).astype('f')
affine_b = np.zeros(V).astype('f')
self.layers = [
TimeEmbedding(embed_W),
TimeDropout(dropout_ratio),
TimeLSTM(lstm_Wx1, lstm_Wh1, lstm_b1, stateful=True),
TimeDropout(dropout_ratio),
TimeLSTM(lstm_Wx2, lstm_Wh2, lstm_b2, stateful=True),
TimeDropout(dropout_ratio),
TimeAffine(embed_W.T, affine_b) # weight tying!!
]
self.loss_layer = TimeSoftmaxWithLoss()
self.lstm_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.lstm_layers:
layer.reset_state()
Structure de couche BetterRnnlm. Il existe trois fonctionnalités: couche LSTM multicouche (2), utilisation de Dropout (2) et partage de poids de Time Embedding et Time Affine.
Tout en augmentant l'expressivité en superposant LSTM, Time Dropout (le contenu est fondamentalement le même que Dropout) est inclus pour supprimer le surapprentissage. Pour le partage de poids, le poids de la couche Time Embeddinh est (V, H) et le poids de la couche Affine est (H, v). , Supprime le surapprentissage et facilite l'apprentissage.
Recommended Posts