[PYTHON] Apprentissage profond / Apprentissage profond à partir de zéro 2-Essayez de déplacer GRU

1.Tout d'abord

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.

2. Mise en œuvre du GRU

スクリーンショット 2020-06-02 18.24.01.png

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.

スクリーンショット 2020-06-02 18.35.09.png 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.

スクリーンショット 2020-06-03 08.49.21.png

        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.

スクリーンショット 2020-06-03 08.50.31.png

        # 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.

スクリーンショット 2020-06-03 09.08.57.png

        # 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.

スクリーンショット 2020-06-03 8.53.55.png

        # 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.

3. Mise en œuvre de TimeGRU

スクリーンショット 2020-06-03 17.20.13.png

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).

スクリーンショット 2020-06-03 17.00.02.png

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.

4. Correction de better_rnnlm

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.

5. Code d'apprentissage

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)

result.PNG 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

Apprentissage profond / Apprentissage profond à partir de zéro 2-Essayez de déplacer GRU
Apprentissage profond à partir de zéro
Apprentissage profond à partir de zéro 1 à 3 chapitres
Apprentissage profond à partir de zéro (calcul des coûts)
Mémo d'apprentissage profond créé à partir de zéro
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 7]
Apprentissage profond à partir de zéro (propagation vers l'avant)
Deep learning / Deep learning made from scratch Chapitre 6 Mémo
[Mémo d'apprentissage] Deep Learning fait de zéro [Chapitre 5]
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 6]
"Deep Learning from scratch" avec Haskell (inachevé)
Deep learning / Deep learning made from scratch Chapitre 7 Mémo
[Windows 10] Construction de l'environnement "Deep Learning from scratch"
Enregistrement d'apprentissage de la lecture "Deep Learning from scratch"
[Deep Learning from scratch] À propos de l'optimisation des hyper paramètres
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 12) Deep learning
[Mémo d'apprentissage] Deep Learning fait de zéro [~ Chapitre 4]
Mémo d'auto-apprentissage "Deep Learning from scratch" (n ° 13) Essayez d'utiliser Google Colaboratory
Mémo d'auto-apprentissage "Deep Learning from scratch" (glossaire illisible)
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 9) Classe MultiLayerNet
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
GitHub du bon livre "Deep Learning from scratch"
Deep Learning from scratch Chapter 2 Perceptron (lecture du mémo)
[Mémo d'apprentissage] Apprentissage profond à partir de zéro ~ Mise en œuvre de l'abandon ~
Résumé Python vs Ruby "Deep Learning from scratch"
Mémo d'auto-apprentissage «Deep Learning from scratch» (10) Classe MultiLayerNet
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 11) CNN
[Deep Learning from scratch] J'ai implémenté la couche Affine
Essayez l'apprentissage en profondeur avec TensorFlow
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 19) Augmentation des données
Apprentissage profond / code de travail LSTM
Essayez le Deep Learning avec FPGA
Application de Deep Learning 2 à partir de zéro Filtre anti-spam
[Deep Learning from scratch] J'ai essayé d'expliquer le décrochage
[Deep Learning from scratch] Implémentation de la méthode Momentum et de la méthode AdaGrad
Essayez de créer un réseau de neurones / d'apprentissage en profondeur avec scratch
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 2
Créez un environnement pour "Deep Learning from scratch" avec Docker
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 3
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 7
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 4
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 18) One! Miaou! Grad-CAM!
Un amateur a trébuché dans le Deep Learning à partir de zéro.
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 2
J'ai essayé d'implémenter Perceptron Part 1 [Deep Learning from scratch]
Mémo d'auto-apprentissage "Deep Learning from scratch" (n ° 15) Tutoriel pour débutants TensorFlow
Deep learning / Deep learning from scratch 2 Chapitre 4 Mémo
Essayez le Deep Learning avec les concombres FPGA-Select
Deep learning / Deep learning made from scratch Chapitre 3 Mémo
Essayez l'apprentissage en profondeur avec TensorFlow Partie 2
Essayez le modèle de régression de sklearn à partir de zéro
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 7 Mémo
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 8 Mémo
Essayez le tutoriel officiel de Django depuis le début