[PYTHON] J'ai essayé de mettre en œuvre le modèle de base du réseau neuronal récurrent

Je suis intéressé par le réseau neuronal récurrent, mais j'ai du mal à écrire du code. N'y a-t-il pas beaucoup de cas comme celui-ci? Il y a plusieurs raisons, mais dans mon cas, je peux penser aux suivantes.

  1. La configuration du réseau est simplement compliquée. De MLP (Multi-layer Perceptron) à CNN (Convolutional-NN), le flux de signal était uniquement vers l'avant, même s'il y avait une couche spéciale. (Hors calcul d'erreur.)
  2. Il y avait un exemple facile à comprendre dans MLP et CNN, «MNIST» (appelé «Hello World» dans Deep Learning), mais il n'y a pas d'exemple standard (standard) dans RNN.

À propos, le Deep Learning de Theano et le tutoriel de TensorFlow traitent des modèles de langage. Ceux qui sont familiers avec les modèles de langage peuvent commencer rapidement, mais les débutants doivent d'abord comprendre "ce que l'exemple tente de résoudre".

Cette fois, j'ai pris un exemple traitant d'une séquence de nombres plus simple qui n'est pas un modèle de langage, et j'ai décidé de mettre en œuvre un simple réseau neuronal récurrent (RNN).

(L'environnement de programmation utilisé est python 2.7.11, Theano 0.7.0.)

Structure RNN simple

En examinant RNN, j'ai d'abord essayé d'exécuter le tutoriel (ptb_word_lm.py) de "TensorFlow". On peut voir que la variable «perplexité» diminue à mesure que la valeur «époque» augmente. Cependant, je ne pouvais pas comprendre les détails de ce qu'il résolvait. Puisque LSTM (Long Short-term Memory) est également utilisé comme modèle de RNN, j'ai senti que l'introduction de RNN était un seuil élevé.

Elman Net est présenté comme un simple RNN dans le document "Deep Learning". Aussi, quand j'ai cherché "Elman RNN" comme mot-clé, j'ai trouvé un blog appelé "Peter's note" (http://peterroelants.github.io/) qui présente des RNN simples comme référence, j'ai donc créé un programme basé sur cela. enquêté.

Le chiffre de RNN est cité sur le site ci-dessus.

Fig. Simple RNN structure SRNmodel2.png

Les données entrent à partir de l'unité d'entrée x, et après avoir multiplié par le poids W_in, elles entrent dans l'unité de couche cachée s. Il y a un flux récursif pour la sortie de l'unité S, et le résultat de l'application du poids W_rec revient à l'unité s au moment suivant. De plus, il est généralement nécessaire de considérer le poids W_out pour la sortie, mais pour simplifier la structure, si W_out = 1.0 est fixe, l'état de l'unité S sera sorti tel quel.

Considérez l'état "étendu" sur le côté droit afin d'appliquer la méthode BPTT (propagation arrière dans le temps) à l'état indiqué sur la gauche. L'état de la valeur initiale s_0 de l'unité cachée change vers la droite en multipliant le poids W_rec au fur et à mesure que le temps avance. De plus, [x_1, x_2, ... x_n] est entré à chaque fois. L'état de s_n est émis vers l'unité y au moment final.

Le modèle illustré ci-dessus peut être converti en code Python comme suit. (Extrait de "Peter's note".)

def update_state(xk, sk, wx, wRec):

    return xk * wx + sk * wRec

def forward_states(X, wx, wRec):
    # Initialise the matrix that holds all states for all input sequences.
    # The initial state s0 is set to 0.
    S = np.zeros((X.shape[0], X.shape[1]+1))
    # Use the recurrence relation defined by update_state to update the 
    #  states trough time.
    for k in range(0, X.shape[1]):
        # S[k] = S[k-1] * wRec + X[k] * wx
        S[:,k+1] = update_state(X[:,k], S[:,k], wx, wRec)
    
    return S

Quel est le contenu de l'exemple?

En outre, en ce qui concerne "quel type de problème est traité par le modèle RNN ci-dessus", entrez une valeur numérique binaire de X_k = 0. ou 1. comme entrée. La sortie est un modèle de réseau qui génère la valeur totale de ces binaires. Par exemple Pour X = [0.1.0.0.0.0.0.0.0.0.0.0. 1.](car la valeur totale de cette liste X est 2.) Réglez la sortie de Y = 2. sur la valeur correcte. Bien entendu, le contenu de l'exemple est d'estimer par RNN (comprenant deux coefficients de pondération) sans utiliser «l'algorithme de comptage de valeurs numériques».

Puisque la valeur de sortie est une valeur numérique qui prend des valeurs continues, elle peut être considérée comme une sorte de problème de «retour», pas comme un problème de «classification». Par conséquent, MSE (erreur quadratique moyenne) est utilisée comme fonction de coût et les données unitaires sont transmises telles quelles sans passer la fonction d'activation.

Tout d'abord, la formation est effectuée à l'aide des données Train (créées à l'avance), et le coefficient de pondération de 2 [W_in, W_rec] est obtenu. Il peut être facilement estimé en regardant la figure ci-dessus, mais la réponse correcte est «[W_in, W_rec] = [1.0, 1.0] ».

Etude préliminaire de la mise en œuvre du modèle

Dans l'article "Peter's note" auquel j'ai fait référence, j'ai utilisé python (avec numpy) pour créer un notebook IPython sans utiliser la bibliothèque Deep Learning. Si cela est copié tel quel, le résultat comme dans l'article du blog peut être obtenu, mais compte tenu du développement, nous avons essayé de l'implémenter en utilisant la bibliothèque Deep Learning. Nous avons considéré les éléments suivants comme des options.

  1. Utilisez "TensorFlow".
  2. Utilisez "Theano".
  3. Utilisez des bibliothèques de niveau supérieur (abstraites) telles que "Keras" et "Pylearn2".

Au début, j'ai essayé de faire du code python original "un par un" dans la version TensorFlow,

    for k in range(0, X.shape[1]):
        # S[k] = S[k-1] * wRec + X[k] * wx
        S[:,k+1] = update_state(X[:,k], S[:,k], wx, wRec)
    
    return S

Il s'est avéré que le traitement en boucle de la partie de n'était pas bien corrigé (vers la version TensorFlow). Si vous vous référez au code du tutoriel de TensorFlow (ptb_word_lm.py etc.), vous devriez être en mesure d'implémenter ce modèle RNN simple comme une évidence, mais comme la bibliothèque de classes associée était compliquée et difficile à comprendre, j'ai décidé d'utiliser TensorFlow cette fois. passé.

De plus, les options 3, telles que «Keras» et «Pylearn2», n'ont pas été retenues cette fois parce qu'elles s'écartent de l'objectif de «comprendre l'implémentation de RNN».

En fin de compte, j'ai décidé d'écrire la version "Theano" du code pour l'option 2.

"Theano scan" pour RNN

Ce qui est commun au code RNN de Theano trouvé sur le net est que la plupart du code utilise "Theano scan". Theano scan est une fonction permettant d'effectuer des traitements de boucle (traitement d'itération) et d'itération (calcul de convergence) dans le cadre de Theano. Les spécifications sont compliquées, et il est difficile à comprendre immédiatement même si vous regardez la documentation originale (documentation Theano). Bien que les informations japonaises soient assez limitées, j'ai procédé à l'enquête sur le comportement de Theano scan en essayant un petit code avec Jupyter Notebook en me référant à l'article de blog de M. sinhrks.

n = T.iscalar('n')
result, updates = theano.scan(fn=lambda prior, nonseq: prior * 2,
                              sequences=None,
                              outputs_info=a, #Voir la valeur dans la boucle précédente--> prior
                              non_sequences=a, #Valeur non séquentielle--> nonseq
                              n_steps=n)

myfun1 = theano.function(inputs=[a, n], outputs=result, updates=updates)
myfun1(5, 3)
# array([10, 20, 40])
# return-1 = 5 * 2
# return-2 = return-1 * 2
# return-3 = return-2 * 2 

Résultat de l'exécution:

>>> array([10, 20, 40], dtype=int32)

Je ne peux pas l'expliquer en détail, je vais donc couvrir quelques exemples d'utilisation. Theano.scan () prend 5 types d'arguments comme décrit ci-dessus.

Key Word Contenu Exemple d'utilisation
fn Fonctions pour le traitement itératif fn=lambda prior, nonseq: prior * 2
sequences Répertoriez ces entrées tout en faisant avancer les éléments pendant le traitement séquentiel,Variable de type de matrice sequences=T.arange(x)
outputs_info Donne la valeur initiale du traitement séquentiel outputs_info=a
non_sequences Valeur fixe qui n'est pas une séquence (invariante avec le traitement itératif) non_sequences=a
n_steps Fonction itérative n_steps=n

Dans le code ci-dessus, theano.scan () reçoit une valeur initiale de 5 (pas une séquence) et un nombre de fois 3, et chaque itération est multipliée par 2 au résultat du processus précédent. Il y a. Première itération: 5 x 2 = 10 Deuxième itération: 10 x 2 = 20 Troisième itération: 20 x 2 = 40 En conséquence, result = [10, 20, 40] est calculé.

Ce qui suit est un test qui est un peu plus conscient RNN.

v = T.matrix('v')
s0 = T.vector('s0')
result, updates = theano.scan(fn=lambda seq, prior: seq + prior * 2,
                                             sequences=v,
                                             outputs_info=s0,
                                             non_sequences=None)
myfun2 = theano.function(inputs=[v, s0], outputs=result, updates=updates)

myfun2([[1., 0.], [0., 1.], [1., 1.]], [0.5, 0.5])

Résultat de l'exécution:

>>> array([[ 2.,  1.],
       [ 4.,  3.],
       [ 9.,  7.]], dtype=float32)

La valeur initiale [0,5, 0,5] est entrée dans la fonction. $ fn = \ texttt {lambda} \ seq, prior: \ seq + prior * Puisque nous l'avons défini comme 2 $, Premier processus itératif: [1., 0.] + [0.5, 0.5] x 2 = [2., 1.] Deuxième itération: [0., 1.] + [2., 1.] x 2 = [4., 3.] Troisième itération: [1., 1.] + [4., 3.] x 2 = [9., 7.] Il est calculé dans le flux.

"theano.scan ()" est une fonction qui prend en charge le contrôle de flux du traitement requis par RNN. Une fonctionnalité similaire pour TensorFlow n'est actuellement pas prise en charge,

Our white paper mentions a number of control flow operations that we've experimented with -- I think once we're happy with its API and confident in its implementation we will try to make it available through the public API -- we're just not quite there yet. It's still early days for us :)

(Extrait de la discussion dans le numéro 208 de GitHub TensorFlow.)

J'aimerais donc attendre un soutien futur.

(Je ne comprends pas quel type d'implémentation est fait pour le modèle RNN de TensorFlow, mais le fait que le calcul RNN ait déjà été réalisé signifie qu'une telle fonction semblable à "theano.scan ()" l'est " Cela signifie que ce n'est pas "essentiel". Je pense qu'il est nécessaire d'étudier un peu plus l'exemple de code de TenforFlow dans ce cas.)

Détails du code RNN simple utilisant Theano

Maintenant que nous connaissons Theano Scan (), regardons le code RNN simple. Tout d'abord, définissez une classe simpleRNN.

class simpleRNN(object):
    #   members:  slen  : state length
    #             w_x   : weight of input-->hidden layer
    #             w_rec : weight of recurrnce 
    def __init__(self, slen, nx, nrec):
        self.len = slen
        self.w_x = theano.shared(
            np.asarray(np.random.uniform(-.1, .1, (nx)),
            dtype=theano.config.floatX)
        )
        self.w_rec = theano.shared(
            np.asarray(np.random.uniform(-.1, .1, (nrec)),
            dtype=theano.config.floatX)
        )
    
    def state_update(self, x_t, s0):
        # this is the network updater for simpleRNN
        def inner_fn(xv, s_tm1, wx, wr):
            s_t = xv * wx + s_tm1 * wr
            y_t = s_t
            
            return [s_t, y_t]
        
        w_x_vec = T.cast(self.w_x[0], 'float32')
        w_rec_vec = T.cast(self.w_rec[0], 'float32')

        [s_t, y_t], updates = theano.scan(fn=inner_fn,
                                    sequences=x_t,
                                    outputs_info=[s0, None],
                                    non_sequences=[w_x_vec, w_rec_vec]
                                   )
        return y_t

En tant que membre de classe, une classe est définie en donnant la longueur et le poids (w_x, w_rec) de l'état. La méthode de classe state_update () met à jour l'état du réseau en fonction de la valeur initiale s0 de state et de la séquence d'entrée x_t, et calcule y_t (séquence de sortie). y_t est un vecteur, mais dans le traitement principal, seule la valeur finale est extraite et utilisée pour calculer la fonction de coût, telle que y = y_t [-1].

Dans le processus principal, tout d'abord, les données utilisées pour l'apprentissage sont créées. (Presque comme dans la "note de Peter" originale.)

    np.random.seed(seed=1)

    # Create Dataset by program
    num_samples = 20
    seq_len = 10
    
    trX = np.zeros((num_samples, seq_len))
    for row_idx in range(num_samples):
        trX[row_idx,:] = np.around(np.random.rand(seq_len)).astype(int)
    trY = np.sum(trX, axis=1)
    trX = trX.astype(np.float32)
    trX = trX.T                    # need 'List of vector' shape dataset
    trY = trY.astype(np.float32)
    # s0 is time-zero state 
    s0np = np.zeros((num_samples), dtype=np.float32)

trX est une série de données de longueur 10 et 20 échantillons. Le point ici est que la matrice est transposée comme trX = trX.T. En tant qu'ensemble de données d'apprentissage automatique général, il semble que les quantités de caractéristiques d'une donnée soient disposées dans la direction horizontale (colonne) et disposées dans la direction verticale (ligne) pour le nombre d'échantillons.

  Data Set Shape
                  feature1   feature2   feature3  ...
     sample1:        -          -          -
     sample2:        -          -          -
     sample3:        -          -          -
       .
       .

Cependant, cette fois, lors de la mise à jour des données de séries chronologiques avec theano.scan (), il était nécessaire de regrouper les données verticalement et de transmettre les données.

(En regroupant comme suit, theano.scan()Il est cohérent avec le fonctionnement de. )
  Data Set Shape (updated)
               [  time1[sample1,  time2[sample1,  time3[sample1 ...    ]
                        sample2,        sample2,        sample2,
                        sample3,        sample3,        sample3,
                         ...    ]         ...   ]         ...    ]

Afin de réaliser cela facilement, la matrice est transposée et traitée comme une entrée dans theano.scan ().

Après cela, le coût «perte» est calculé à partir du graphique de Theano, de la valeur de calcul du modèle «y_hypo» et de l'étiquette de données du train «y_».

    # Tensor Declaration
    x_t = T.matrix('x_t')
    x = T.matrix('x')
    y_ = T.vector('y_')
    s0 = T.vector('s0')
    y_hypo = T.vector('y_hypo')

    net = simpleRNN(seq_len, 1, 1)  
    y_t = net.state_update(x_t, s0)
    y_hypo = y_t[-1]
    loss = ((y_ - y_hypo) ** 2).sum()

Une fois que vous atteignez ce point, vous pouvez procéder à l'apprentissage d'une manière familière.

# Train Net Model
    params = [net.w_x, net.w_rec]
    optimizer = GradientDescentOptimizer(params, learning_rate=1.e-5)
    train_op = optimizer.minimize(loss)

    # Compile ... define theano.function 
    train_model = theano.function(
        inputs=[],
        outputs=[loss],
        updates=train_op,
        givens=[(x_t, trX), (y_, trY), (s0, s0np)],
        allow_input_downcast=True
    )
    
    n_epochs = 2001
    epoch = 0
    
    w_x_ini = (net.w_x).get_value()
    w_rec_ini = (net.w_rec).get_value()
    print('Initial weights: wx = %8.4f, wRec = %8.4f' \
                % (w_x_ini, w_rec_ini))
    
    while (epoch < n_epochs):
        epoch += 1
        loss = train_model()
        if epoch % 100 == 0:
            print('epoch[%5d] : cost =%8.4f' % (epoch, loss[0]))
    
    w_x_final = (net.w_x).get_value()
    w_rec_final = (net.w_rec).get_value()
    print('Final weights : wx = %8.4f, wRec = %8.4f' \
                % (w_x_final, w_rec_final))

Cette fois, nous avons préparé et utilisé deux optimiseurs, GradientDecent (méthode de descente de gradient) et RMSPropOptimizer (méthode RMSProp). (Le code de la partie optimiseur est omis cette fois. Pour la méthode RMSProp, reportez-vous au site Web affiché plus loin.)

Résultat d'exécution

La description selon laquelle "RNN est généralement difficile à faire progresser dans l'apprentissage" peut être trouvée à divers endroits, mais le résultat m'a fait comprendre.

Condition 1. Descente de gradient, taux d'apprentissage = 1,0e-5

Initial weights: wx =   0.0900, wRec =   0.0113
epoch[  100] : cost =529.6915
epoch[  200] : cost =504.5684
epoch[  300] : cost =475.3019
epoch[  400] : cost =435.9507
epoch[  500] : cost =362.6525
epoch[  600] : cost =  0.2677
epoch[  700] : cost =  0.1585
epoch[  800] : cost =  0.1484
epoch[  900] : cost =  0.1389
epoch[ 1000] : cost =  0.1300
epoch[ 1100] : cost =  0.1216
epoch[ 1200] : cost =  0.1138
epoch[ 1300] : cost =  0.1064
epoch[ 1400] : cost =  0.0995
epoch[ 1500] : cost =  0.0930
epoch[ 1600] : cost =  0.0870
epoch[ 1700] : cost =  0.0813
epoch[ 1800] : cost =  0.0760
epoch[ 1900] : cost =  0.0710
epoch[ 2000] : cost =  0.0663
Final weights : wx =   1.0597, wRec =   0.9863

Grâce à l'apprentissage, nous avons pu obtenir une valeur approximative de la bonne réponse [w_x, w_rec] = [1.0, 1.0]. La figure ci-dessous montre comment la fonction de coût est réduite.

Fig. Loss curve (GradientDescent) rnn_loss_log1.PNG

Condition 2. Méthode RMSProp, taux d'apprentissage = 0,001

Initial weights: wx =   0.0900, wRec =   0.0113
epoch[  100] : cost =  5.7880
epoch[  200] : cost =  0.3313
epoch[  300] : cost =  0.0181
epoch[  400] : cost =  0.0072
epoch[  500] : cost =  0.0068
epoch[  600] : cost =  0.0068
epoch[  700] : cost =  0.0068
epoch[  800] : cost =  0.0068
epoch[  900] : cost =  0.0068
epoch[ 1000] : cost =  0.0068
epoch[ 1100] : cost =  0.0068
epoch[ 1200] : cost =  0.0068
epoch[ 1300] : cost =  0.0068
epoch[ 1400] : cost =  0.0068
epoch[ 1500] : cost =  0.0068
epoch[ 1600] : cost =  0.0068
epoch[ 1700] : cost =  0.0068
epoch[ 1800] : cost =  0.0068
epoch[ 1900] : cost =  0.0068
epoch[ 2000] : cost =  0.0068
Final weights : wx =   0.9995, wRec =   0.9993

Fig. Loss curve (RMSProp) rnn_loss_log2.PNG

Dans ce modèle, la non-linéarité de la fonction de coût par rapport aux paramètres est très forte. Comme la valeur numérique diverge dès que le taux d'apprentissage est augmenté, il était nécessaire de fixer le taux d'apprentissage à 1,0e-5, ce qui est assez petit, dans la méthode Gradient Descent. En revanche, avec la méthode RMSProp, qui est dite adaptée pour RNN, l'apprentissage peut se dérouler sans problème même avec un taux d'apprentissage de 0,001.

(Supplément) Le blog de référence "Peter's note" a une explication détaillée de l'état de la fonction de coût et de RMSProp (nommé "Rprop" dans le blog source). La non-linéarité de la fonction de coût est visualisée avec des nuances de couleur, veuillez donc vous y référer si vous êtes intéressé. (Ce sera le lien ci-dessous.)

Références (site Web)

Recommended Posts

J'ai essayé de mettre en œuvre le modèle de base du réseau neuronal récurrent
J'ai essayé d'implémenter TOPIC MODEL en Python
Implémenter un réseau neuronal à 3 couches
J'ai essayé d'implémenter PCANet
J'ai essayé d'implémenter StarGAN (1)
J'ai essayé d'implémenter un pseudo pachislot en Python
J'ai essayé de résumer quatre méthodes d'optimisation de réseau neuronal
J'ai essayé d'implémenter une ligne moyenne mobile de volume avec Quantx
J'ai essayé d'implémenter Deep VQE
J'ai essayé d'implémenter un automate cellulaire unidimensionnel en Python
J'ai essayé de mettre en place une validation contradictoire
J'ai essayé de mettre en œuvre un réseau de neurones à deux couches
J'ai essayé d'implémenter Realness GAN
J'ai essayé de classer la musique en majeur / mineur sur Neural Network
J'ai essayé d'écrire dans un modèle de langage profondément appris
J'ai essayé d'implémenter SSD avec PyTorch maintenant (édition du modèle)
J'ai essayé de prédire le genre de musique à partir du titre de la chanson sur le réseau neuronal récurrent
[Python] J'ai essayé d'implémenter un tri stable, alors notez
J'ai essayé de mettre en œuvre un jeu de dilemme de prisonnier mal compris en Python
J'ai essayé de créer un linebot (implémentation)
J'ai essayé d'implémenter PLSA en Python
J'ai essayé d'implémenter Autoencoder avec TensorFlow
J'ai essayé d'implémenter la permutation en Python
J'ai essayé de créer un linebot (préparation)
J'ai essayé d'implémenter PLSA dans Python 2
J'ai essayé d'implémenter ADALINE en Python
J'ai essayé d'implémenter PPO en Python
J'ai essayé d'implémenter CVAE avec PyTorch
J'ai créé une API Web
J'ai essayé de toucher Python (syntaxe de base)
J'ai essayé de mettre en œuvre une blockchain qui fonctionne réellement avec environ 170 lignes
J'ai essayé d'améliorer la précision de mon propre réseau neuronal
Création d'un modèle de discrimination d'image (cifar10) à l'aide d'un réseau neuronal convolutif
J'ai essayé d'implémenter le jeu de cartes de Trump en Python
J'ai essayé de former le modèle RWA (Recurrent Weighted Average) dans Keras
J'ai essayé de créer une méthode de super résolution / ESPCN
J'ai essayé d'implémenter la lecture de Dataset avec PyTorch
Je souhaite créer facilement un modèle de bruit
J'ai essayé de créer une méthode de super résolution / SRCNN ①
J'ai essayé de générer une chaîne de caractères aléatoire
J'ai essayé de créer une méthode de super résolution / SRCNN ③
J'ai essayé de créer une méthode de super résolution / SRCNN ②
J'ai essayé d'implémenter le tri sélectif en python
J'ai essayé de mettre en œuvre le problème du voyageur de commerce
J'ai créé un jeu ○ ✕ avec TensorFlow
[Python] Deep Learning: J'ai essayé d'implémenter Deep Learning (DBN, SDA) sans utiliser de bibliothèque.
J'ai essayé un réseau de neurones Π-Net qui ne nécessite pas de fonction d'activation
J'ai essayé d'implémenter diverses méthodes d'apprentissage automatique (modèle de prédiction) en utilisant scicit-learn
J'ai essayé d'implémenter ce qui semble être un outil de snipper Windows avec Python
J'ai essayé de créer un modèle avec l'exemple d'Amazon SageMaker Autopilot
Implémenter un réseau neuronal convolutif
J'ai essayé de déboguer.
Introduction à la création d'IA avec Python! Partie 3 J'ai essayé de classer et de prédire les images avec un réseau de neurones convolutifs (CNN)
Implémenter le réseau neuronal à partir de zéro
Introduction à la création d'IA avec Python! Partie 2 J'ai essayé de prédire le prix de l'immobilier dans la ville de Boston avec un réseau neuronal
J'ai essayé de faire un "putain de gros convertisseur de littérature"
Je veux facilement implémenter le délai d'expiration en python