Avril 2018
Volume 33, numéro 4
Cet article a fait l'objet d'une traduction automatique.
Série de tests - Comprendre les cellules LSTM à l’aide de C#
Par James McCaffrey
Une cellule long à court terme de la mémoire (LSTM) est un petit composant logiciel qui peut être utilisé pour créer un réseau neuronal ponctuelle que vous pouvez effectuer des prévisions relatives aux séquences de données. Réseau LSTM a été responsable des innovations majeures dans plusieurs domaines de l’apprentissage. Dans cet article, je montrent comment implémenter une cellule LSTM à l’aide de c#. Il est peu probable que vous devez toujours créer un réseau neuronal ponctuelle à partir de zéro, comprendre exactement comment fonctionnent les cellules LSTM vous aidera si vous devez créer un réseau LSTM à l’aide d’une bibliothèque de code tels que Microsoft CNTK ou Google TensorFlow.
La capture d’écran de Figure 1 montre où cet article est menée. Le programme de démonstration crée une cellule LSTM qui accepte un vecteur d’entrée de taille n = 2 et génère un vecteur de sortie explicite de taille m = 3 et un vecteur d’état de cellule de taille m = 3. Une caractéristique clé de cellules LSTM est qu’elles conservent un état.
Figure 1 LSTM cellule d’entrée-sortie démonstration
Une cellule LSTM a (4 * n * m) + (4 * m * m) poids et (4 * m) écarts. Poids et des écarts sont simplement des constantes, avec des valeurs comme 0,1234, qui définissent le comportement de la cellule LSTM. La démonstration a 60 poids et des 12 écarts définis avec des valeurs arbitraires.
La démonstration envoie d’entrée (1.0, 2.0) à la cellule LSTM. La sortie calculée est (0.0629, 0.0878, 0.1143) et le nouvel état de la cellule est (0.1143, 0.1554, 0.1973). Je vais expliquer ce que ces valeurs numériques peuvent représenter peu de temps, mais pour l’instant, le point est qu’une cellule LSTM accepte l’entrée, génère la sortie et met à jour son état. Les valeurs de poids et d’écarts n’ont pas changé.
Ensuite, les flux de démonstration (3.0, 4.0) à la cellule LSTM. La nouvelle sortie calculée est (0.1282, 0.2066, 0.2883). Le nouvel état de la cellule est (0.2278, 0.3523, 0.4789). Bien qu’il n’est pas visible, la nouvelle valeur d’état de cellule contient des informations sur toutes les précédente valeurs d’entrée et sortie. Il s’agit de la partie « longue » de la mémoire long à court terme.
Cet article suppose que vous avez intermédiaires ou meilleures compétences de programmation, mais ne suppose pas que vous savez rien sur les cellules LSTM. La démonstration est codée à l’aide de c#, mais vous devez être en mesure de refactoriser le code à un autre langage, tels que Python ou Visual Basic, si vous le souhaitez. Le code de démonstration est juste un peu trop long pour être affichée dans son intégralité, mais le code complet est disponible dans le téléchargement de fichier associé.
Présentation des cellules LSTM
Il existe trois façons pour décrire les cellules LSTM : à l’aide d’un diagramme d’architecture comme indiqué dans Figure 2; à l’aide des équations mathématiques, comme indiqué dans Figure 3; et à l’aide de code présenté dans cet article. Votre impression initiale du diagramme d’architecture dans Figure 2 est probablement quelque chose dans les lignes de « Quelles donc ? » Les idées clés sont qu’une cellule LSTM accepte un vecteur d’entrée x(t), où t représente une heure. La sortie explicite est h(t). L’utilisation inhabituelle de h (plutôt que o) pour représenter la sortie est historique et vient du fait que les systèmes NEURONALES ont été souvent décrits comme h et fonctions g.
La cellule LSTM utilise également h(t-1) et c(t-1) en tant qu’entrées. Ici, le t-1 signifie à partir de l’étape précédente. C représente l’état de la cellule, h(t-1) et c(t-1) sont les valeurs précédentes de la sortie et les valeurs d’état précédent. L’intérieur de l’architecture de cellule LSTM semble compliquée. Et il n’est pas, mais aussi complexes que vous pouvez deviner.
Les équations mathématiques dans Figure 3 définissent le comportement de la cellule LSTM de programme de démonstration. Si vous ne travaillez pas régulièrement avec des définitions mathématiques, votre réaction à Figure 3 est moins probable, à nouveau, « quelles donc ? » Équations (1), (2) et (3) définir trois portails : une porte d’oublier, une porte d’entrée et une porte de sortie. Chaque grille est un vecteur de valeurs comprises entre 0,0 et 1,0, qui sont utilisées pour déterminer la quantité d’informations à oublier (ou son équivalent, n’oubliez pas) dans chaque cycle d’entrée-sortie. Équation (4) calcule le nouvel état de la cellule, et l’équation (5) calcule la nouvelle sortie.
Architecture de cellule LSTM figure 2
Figure 3 LSTM cellule mathématiques équations
Ces équations sont plus simples qu’ils apparaissent. Par exemple, l’équation (1) est implémentée par le code de démonstration en tant que :
float[][] ft = MatSig(MatSum(MatProd(Wf, xt),
MatProd(Uf, h_prev), bf));
Ici, MatSig est une fonction définie par le programme s’applique logistique sigmoïde à chaque valeur dans une matrice. MatSum ajoute trois matrices. MatProd multiplie deux matrices. Une fois que vous comprenez la matrice de base et les opérations de vecteur, il est relativement facile de mise en œuvre d’une cellule LSTM.
Structure de programme de démonstration globale
La structure du programme de démonstration, avec quelques modifications mineures pour économiser l’espace, est présentée dans Figure 4. La démonstration utilise une approche de la méthode statique plutôt qu’une approche de programmation orientée objet pour garder les idées principales plus clair possible.
Structure de programme de démonstration de la figure 4
using System;
namespace LSTM_IO
{
class LSTM_IO_Program
{
static void Main(string[] args)
{
Console.WriteLine("Begin LSTM IO demo");
// Set up inputs
// Set up weights and biases
float[][] ht, ct; // Outputs, new state
float[][][] result;
result = ComputeOutputs(xt, h_prev, c_prev,
Wf, Wi, Wo, Wc, Uf, Ui, Uo, Uc, bf, bi, bo, bc);
ht = result[0]; // Outputs
ct = result[1]; // New state
Console.WriteLine("Output is:");
MatPrint(ht, 4, true);
Console.WriteLine("New cell state is:");
MatPrint(ct, 4, true);
// Set up new inputs
// Call ComputeOutputs again
Console.WriteLine("End LSTM demo");
}
static float[][][] ComputeOutputs(float[][] xt,
float[][] h_prev, float[][] c_prev,
float[][] Wf, float[][] Wi, float[][] Wo, float[][] Wc,
float[][] Uf, float[][] Ui, float[][] Uo, float[][] Uc,
float[][] bf, float[][] bi, float[][] bo, float[][] bc)
{ . . }
// Helper matrix functions defined here
}
}
Pour créer la démonstration, j’ai lancé Visual Studio et créé une nouvelle application de console c# nommée LSTM_IO. J’ai utilisé Visual Studio 2015, mais la démonstration n’a aucune dépendance significative de .NET Framework, pour n’importe quelle version de Visual Studio fonctionne correctement.
Une fois le code du modèle est chargé, dans la fenêtre de l’Explorateur de solutions je renommé le fichier Program.cs pour LSTM_IO_Program.cs et autorisé Visual Studio afin de renommer automatiquement la classe Program pour moi. En haut de la fenêtre d’éditeur, j’ai supprimé toutes les références inutiles aux espaces de noms, en laissant uniquement à celui de l’espace de noms système niveau supérieur. Tout le travail est exécuté par la fonction ComputeOutputs.
À l’aide de c# de matrices
Pour implémenter une cellule LSTM, vous devez avoir appréhendé solide de l’utilisation des matrices c#. En c#, une matrice est un tableau de tableaux. Dans l’apprentissage automatique, il est courant d’utiliser float de types 32 bits au lieu d’octet 64 bits en double.
La démonstration définit un programme d’assistance pour créer une matrice en tant que :
static float[][] MatCreate(int rows, int cols)
{
float[][] result = new float[rows][];
for (int i = 0; i < rows; ++i)
result[i] = new float[cols];
return result;
}
La première instruction crée un tableau avec le nombre spécifié de lignes, où chaque ligne est un tableau de type float. L’instruction de boucle for attribue chaque ligne sous forme de tableau avec le nombre spécifié de colonnes. Notez que contrairement à la plupart des langages de programmation c# prend en charge un type de matrice true, mais à l’aide d’une approche de tableau de baie est beaucoup plus courant.
Dans l’apprentissage, le vecteur de colonne terme ou simplement vecteur pour la forme courte, fait référence à une matrice avec une seule colonne. La plupart du code machine inaperçu fonctionne avec les vecteurs plutôt que des tableaux unidimensionnels. La démonstration définit une fonction pour générer un matrice/vecteur à partir d’un tableau unidimensionnel :
static float[][] MatFromArray(float[] arr, int rows, int cols)
{
float[][] result = MatCreate(rows, cols);
int k = 0;
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
result[i][j] = arr[k++];
return result;
}
La fonction peut être appelée pour créer un 1 x 3 (3 lignes, 1 colonne) vecteur comme suit :
float[][] v = MatFromArray(new float[] {1.0f, 9.0f, 5.0f}, 3,1);
La démonstration définit une fonction MatCopy qui permet de dupliquer une matrice. MatCopy peut être appelée :
float[][] B = MatCopy(A);
Ici, B est une matrice de nouveau et indépendante avec les mêmes valeurs qu’a. Veillez à du code similaire à :
float[][] B = A;
Cette opération crée B comme une référence à A, par conséquent, toute modification apportée à une matrice a une incidence sur l’autre. Cela peut être le comportement souhaité, mais probablement pas.
Opérations élément par élément de matrice
Une implémentation de la cellule LSTM utilise plusieurs fonctions d’élément par élément de matrices, où chaque valeur dans une matrice est utilisée ou modifiée. Par exemple, la fonction que mattanh est définie :
static float[][] MatTanh(float[][] m)
{
int rows = m.Length; int cols = m[0].Length;
float[][] result = MatCreate(rows, cols);
for (int i = 0; i < rows; ++i) // Each row
for (int j = 0; j < cols; ++j) // Each col
result[i][j] = Tanh(m[i][j]);
return result;
}
La fonction traverse la matrice d’entrée m et applique la tangente hyperbolique (tanh) à chaque valeur. Tanh (fonction) d’assistance est définie :
static float Tanh(float x)
{
if (x < -10.0) return -1.0f;
else if (x > 10.0) return 1.0f;
return (float)(Math.Tanh(x));
}
La démonstration définit également une fonction MatSigmoid qui est identique à celle MatTanh sauf logistique sigmoïde est appliquée à chaque valeur. La fonction logistique sigmoïde est étroitement liée à tanh et retourne une valeur entre 0,0 et 1,0, au lieu d’entre -1,0 et + 1.0.
La démonstration définit une fonction MatSum qui additionne les valeurs de deux matrices de la même forme. Si vous examinez l’équation mathématique (1) Figure 3, vous verrez qu’une LSTM ajoute trois matrices. Les surcharges de la démonstration MatSum pour travailler avec deux ou trois matrices.
Fonction MatHada multiplie les valeurs correspondantes dans les deux matrices qui ont la même forme :
static float[][] MatHada(float[][] a, float[][] b)
{
int rows = a.Length; int cols = a[0].Length;
float[][] result = MatCreate(rows, cols);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
result[i][j] = a[i][j] * b[i][j];
return result;
}
Multiplication élément par élément est parfois appelée la fonction Hadarmard. Les équations mathématiques (4) et (5) dans Figure 3, la fonction Hadarmard est indiquée par le symbole de point. Ne confondez pas la fonction Hadarmard élément par élément avec la multiplication de matrice, qui est une fonction très différente.
Multiplication des matrices
Si vous n’avez pas lu la multiplication des matrices avant, l’opération n’est pas du tout évidente. Supposons que A est une matrice 3 x 2 :
1.0, 2.0
3.0, 4.0
5.0, 6.0
Et supposons que B est une matrice 2 x 4 :
10.0, 11.0, 12.0, 13.0
14.0, 15.0, 16.0, 17.0
Le résultat de C = AB (multiplication A et B) est une matrice 3 x 4 :
38.0 41.0 44.0 47.0
86.0 93.0 100.0 107.0
134.0 145.0 156.0 167.0
La démonstration implémente la multiplication des matrices en tant que fonction MatProd. Notez que lorsque vous utilisez c#, pour les très grandes matrices vous pouvez utiliser l’instruction Parallel.For dans la bibliothèque parallèle de tâches.
Pour résumer, mise en œuvre d’une cellule LSTM à l’aide de c# nécessite plusieurs fonctions d’assistance qui créent et opèrent sur des matrices (tableaux de tableaux) et les vecteurs (matrices avec une colonne). Le code de démonstration définit les fonctions MatCreate, MatFromArray, MatCopy(m), MatSig(m), MatTanh(m), MatHada (a, b), MatSum (a, b), MatSum (a, b, c) et MatProd (a, b). Mais non essentiel pour la création d’une cellule LSTM, il est utile de disposer d’une fonction pour afficher une matrice de c#. La démonstration définit à la fonction de MatPrint.
Implémentation et l’appel LSTM d’entrée-sortie
Le code de fonction ComputeOutputs est présenté dans Figure 5. La fonction a 15 paramètres, mais 12 d'entre eux sont essentiellement les mêmes.
Figure 5 fonction ComputeOutputs
static float[][][] ComputeOutputs(float[][] xt,
float[][] h_prev, float[][] c_prev,
float[][] Wf, float[][] Wi, float[][] Wo, float[][] Wc,
float[][] Uf, float[][] Ui, float[][] Uo, float[][] Uc,
float[][] bf, float[][] bi, float[][] bo, float[][] bc)
{
float[][] ft = MatSig(MatSum(MatProd(Wf, xt),
MatProd(Uf, h_prev), bf));
float[][] it = MatSig(MatSum(MatProd(Wi, xt),
MatProd(Ui, h_prev), bi));
float[][] ot = MatSig(MatSum(MatProd(Wo, xt),
MatProd(Uo, h_prev), bo));
float[][] ct = MatSum(MatHada(ft, c_prev),
MatHada(it, MatTanh(MatSum(MatProd(Wc, xt),
MatProd(Uc, h_prev), bc))));
float[][] ht = MatHada(ot, MatTanh(ct));
float[][][] result = new float[2][][];
result[0] = MatCopy(ht);
result[1] = MatCopy(ct);
return result;
}
Test exploratoire : Vector est l’entrée, tel que (1.0, 2.0). C_prev et les vecteurs h_prev sont le vecteur de sortie précédent et l’état de cellule précédente. Les matrices W quatre sont les poids de porte associées aux valeurs d’entrée, où f est une porte d’oublier i est une porte d’entrée et o est une porte de sortie. Les matrices de U quatre sont les poids associés de sortie de la cellule. Les quatre vecteurs de b sont des écarts.
Dans le corps de la fonction, les cinq premières instructions sont les équations cinq mathématiques dans un mappage Figure 3. Notez que le texte intégral, il et ot portes tous utiliser la fonction de MatSig. Par conséquent, les trois sont des vecteurs avec des valeurs comprises entre 0,0 et 1,0. Vous pouvez considérer ces filtres sont appliqués à l’entrée, sortie ou état, où la valeur de la porte est le pourcentage de retenue. Par exemple, si une des valeurs de texte intégral est 0,75, 75 % de la valeur correspondante dans l’entrée combinée et d’un vecteur de sortie précédente sont conservée. Ou, de façon équivalente, 25 pour cent de l’information est oublié.
Le calcul du nouvel état de la cellule, ct, est simple à implémenter mais conceptuellement très profond. À un niveau élevé, le nouvel état de la cellule dépend une combinaison contrôlée de xt le vecteur d’entrée et l’état précédent de sortie vecteur et cellule h_prev et c_prev. La nouvelle sortie, ht, dépend du nouvel état de la cellule et la porte de sortie. Tout à fait remarquable.
La fonction retourne la nouvelle sortie et le nouvel état de la cellule dans un tableau. Cela nous mène à un type de retour de type float [] [] [], où le résultat [0] est une matrice de tableau de tableaux contenant la sortie et résultat [1] conserve le nouvel état de la cellule.
Appel de ComputeOutputs est principalement de la définition de valeurs de paramètre. La démonstration commence la préparation avec :
float[][] xt = MatFromArray(new float[] {
1.0f, 2.0f }, 2, 1);
float[][] h_prev = MatFromArray(new float[] {
0.0f, 0.0f, 0.0f }, 3, 1);
float[][] c_prev = MatFromArray(new float[] {
0.0f, 0.0f, 0.0f }, 3, 1);
La sortie précédente et l’état de la cellule sont explicitement initialisées à zéro. Ensuite, deux ensembles de poids arbitraire, les valeurs sont créées :
float[][] W = MatFromArray(new float[] {
0.01f, 0.02f,
0.03f, 0.04f,
0.05f, 0.06f }, 3, 2);
float[][] U = MatFromArray(new float[] {
0.07f, 0.08f, 0.09f,
0.10f, 0.11f, 0.12f,
0.13f, 0.14f, 0.15f }, 3, 3);
Notez que les deux matrices ont différentes formes. Les valeurs de pondération sont copiés dans les paramètres d’entrée :
float[][] Wf = MatCopy(W); float[][] Wi = MatCopy(W);
float[][] Wo = MatCopy(W); float[][] Wc = MatCopy(W);
float[][] Uf = MatCopy(U); float[][] Ui = MatCopy(U);
float[][] Uo = MatCopy(U); float[][] Uc = MatCopy(U);
Étant donné que les poids ne changent pas la démonstration peut avoir attribué par référence plutôt que MatCopy. Les écarts sont configurés à l’aide du même modèle :
float[][] b = MatFromArray(new float[] {
0.16f, 0.17f, 0.18f }, 3, 1);
float[][] bf = MatCopy(b); float[][] bi = MatCopy(b);
float[][] bo = MatCopy(b); float[][] bc = MatCopy(b);
Fonction ComputeOutputs est appelée comme suit :
float[][] ht, ct;
float[][][] result;
result = ComputeOutputs(xt, h_prev, c_prev,
Wf, Wi, Wo, Wc, Uf, Ui, Uo, Uc,
bf, bi, bo, bc);
ht = result[0]; // Output
ct = result[1]; // New cell state
Il est tout l’intérêt d’une cellule LSTM pour introduire une séquence de vecteurs d’entrée, afin que la démonstration configure et envoie un second vecteur d’entrée :
h_prev = MatCopy(ht);
c_prev = MatCopy(ct);
xt = MatFromArray(new float[] {
3.0f, 4.0f }, 2, 1);
result = ComputeOutputs(xt, h_prev, c_prev,
Wf, Wi, Wo, Wc, Uf, Ui, Uo, Uc,
bf, bi, bo, bc);
ht = result[0];
ct = result[1];
Notez que la démonstration explicitement envoie la sortie précédente et les vecteurs d’état à ComputeOutputs. Une alternative consiste à simplement le vecteur d’entrée de nouveau, de flux, car la sortie précédente et l’état de la cellule sont toujours stockées dans ht et ct.
La connexion à la taille en points
Par conséquent, ce qui est le point ? Une cellule LSTM permettre servir à construire un réseau neuronal récurrent de LSTM : une cellule LSTM avec certains éléments supplémentaires. Ces réseaux sont à l’origine de progrès considérables dans les systèmes de prédiction qui fonctionnent avec des données de séquence. Par exemple, supposons que vous deviez deviner le mot suivant dans la phrase, « En 2017, le championnat a abouti par __ ». Avec ces informations uniquement, vous serait difficile qui permet d’élaborer une prédiction. Mais supposons que votre système avait l’état et rappeler que la partie d’une phrase précédente a été, « la NBA occupe un jeu championship depuis 1947. » Maintenant, vous serez en mesure de prédire un des équipes NBA 30.
Il existe des dizaines de variations des architectures LSTM. En outre, étant donné que les cellules LSTM sont complexes, il existe des dizaines de variations d’implémentation pour chaque architecture. Mais si vous comprenez le mécanisme de cellule LSTM base, vous pouvez facilement comprendre les variations.
Le programme de démonstration définit le poids LSTM et les écarts sur des valeurs arbitraires. Les poids et biais d’un réseau LSTM réel sont déterminés par la formation du réseau. Vous obtiendriez un jeu de données d’apprentissage avec des valeurs d’entrée connus et les valeurs de sortie connus, correct. Pour rechercher des valeurs pour le poids et les écarts qui minimisent erreur entre des sorties calculées et les sorties correctes, utilisez un algorithme de rétropropagation.
Récupération d’urgence. James McCaffreyfonctionne pour Microsoft Research à Redmond, Washington Il a travaillé sur plusieurs produits Microsoft, y compris Internet Explorer et Bing. Dr. McCaffrey peut être atteint à jamccaff@microsoft.com.
Grâce aux experts techniques Microsoft suivants ayant révisé cet article : Ricky Loynd et Adith Swaminathan