web-dev-qa-db-fra.com

Comment donner correctement des entrées aux couches Embedding, LSTM et Linear dans PyTorch?

J'ai besoin d'un peu de clarté sur la façon de préparer correctement les entrées pour la formation par lots en utilisant différents composants du module torch.nn. Plus précisément, je cherche à créer un réseau encodeur-décodeur pour un modèle seq2seq.

Supposons que j'ai un module avec ces trois couches, dans l'ordre:

  1. nn.Embedding
  2. nn.LSTM
  3. nn.Linear

nn.Embedding

Entrée:batch_size * seq_length
Sortie:batch_size * seq_length * embedding_dimension

Je n'ai aucun problème ici, je veux juste être explicite sur la forme attendue de l'entrée et de la sortie.

nn.LSTM

Entrée:seq_length * batch_size * input_size (embedding_dimension Dans ce cas)
Sortie:seq_length * batch_size * hidden_size
last_hidden_state:batch_size * hidden_size
last_cell_state:batch_size * hidden_size

Pour utiliser la sortie de la couche Embedding comme entrée pour la couche LSTM, je dois transposer les axes 1 et 2.

De nombreux exemples que j'ai trouvés en ligne font quelque chose comme x = embeds.view(len(sentence), self.batch_size , -1), mais cela me confond. Comment cette vue garantit-elle que les éléments d'un même lot restent dans le même lot? Que se passe-t-il lorsque la taille len(sentence) et self.batch Sont de la même taille?

nn.Linear

Entrée:batch_size X input_size (Taille_cachée de LSTM dans ce cas ou ??)
Sortie:batch_size X output_size

Si j'ai seulement besoin du last_hidden_state De LSTM, alors je peux le donner comme entrée à nn.Linear.

Mais si je veux utiliser Output (qui contient également tous les états cachés intermédiaires), je dois changer la taille d'entrée de nn.Linear En seq_length * hidden_size Et utiliser Output comme entrée pour Linear module J'ai besoin de transposer les axes 1 et 2 de sortie et ensuite je peux voir avec Output_transposed(batch_size, -1).

Ma compréhension ici est-elle correcte? Comment effectuer ces opérations de transposition en tenseurs (tensor.transpose(0, 1))?

18
Silpara

Votre compréhension de la plupart des concepts est exacte, mais il y a des points manquants ici et là.

Interfaçage d'intégration à LSTM (ou à toute autre unité récurrente)

Vous avez incorporé la sortie sous la forme de (batch_size, seq_len, embedding_size). Il existe maintenant différentes manières de transmettre ces informations au LSTM.
* Vous pouvez le transmettre directement au LSTM, si LSTM accepte l'entrée comme batch_first. Ainsi, lors de la création de votre LSTM argument de passe batch_first=True.
* Ou, vous pouvez passer une entrée sous la forme de (seq_len, batch_size, embedding_size). Ainsi, pour convertir votre sortie d'intégration dans cette forme, vous devrez transposer les première et deuxième dimensions à l'aide de torch.transpose(tensor_name, 0, 1), comme vous l'avez mentionné.

Q. Je vois de nombreux exemples en ligne qui font quelque chose comme x = embeds.view (len (phrase), self.batch_size, -1) ce qui m'embrouille.
UNE. C'est faux. Il mélangera des lots et vous essaierez d'apprendre une tâche d'apprentissage désespérée. Partout où vous voyez cela, vous pouvez dire à l'auteur de modifier cette déclaration et d'utiliser la transposition à la place.

Il y a un argument en faveur de ne pas utiliser batch_first, Qui indique que l'API sous-jacente fournie par Nvidia CUDA s'exécute considérablement plus rapidement en utilisant batch comme secondaire.

Utilisation de la taille du contexte

Vous alimentez directement la sortie d'intégration à LSTM, cela fixera la taille d'entrée de LSTM à la taille de contexte de 1. Cela signifie que si votre entrée est des mots à LSTM, vous lui donnerez toujours un mot à la fois. Mais ce n'est pas ce que nous voulons tout le temps. Vous devez donc étendre la taille du contexte. Cela peut être fait comme suit -

# Assuming that embeds is the embedding output and context_size is a defined variable
embeds = embeds.unfold(1, context_size, 1)  # Keeping the step size to be 1
embeds = embeds.view(embeds.size(0), embeds.size(1), -1)

Déplier la documentation
Maintenant, vous pouvez procéder comme indiqué ci-dessus pour alimenter ceci dans le LSTM, rappelez-vous simplement que seq_len Est maintenant changé en seq_len - context_size + 1 Et embedding_size (qui est la taille d'entrée du LSTM) est maintenant changé en context_size * embedding_size

Utilisation de longueurs de séquence variables

La taille d'entrée des différentes instances d'un lot ne sera pas toujours la même. Par exemple, une partie de votre phrase peut être de 10 mots et une partie de 15 et une partie de 1000. Donc, vous voulez certainement une entrée de séquence de longueur variable dans votre unité récurrente. Pour ce faire, certaines étapes supplémentaires doivent être effectuées avant de pouvoir transmettre vos entrées au réseau. Vous pouvez suivre ces étapes -
1. Triez votre lot de la plus grande séquence à la plus petite.
2. Créez un tableau seq_lengths Qui définit la longueur de chaque séquence du lot. (Cela peut être une simple liste python liste)
3. Remplissez toutes les séquences pour qu'elles soient de longueur égale à la plus grande séquence.
4. Créez une variable LongTensor de ce lot.
5. Maintenant, après avoir passé la variable ci-dessus par incorporation et créé la bonne taille de contexte, vous devrez emballer votre séquence comme suit -

# Assuming embeds to be the proper input to the LSTM
lstm_input = nn.utils.rnn.pack_padded_sequence(embeds, [x - context_size + 1 for x in seq_lengths], batch_first=False)

Comprendre la sortie de LSTM

Maintenant, une fois que vous avez préparé votre lstm_input Acc. Pour vos besoins, vous pouvez appeler lstm as

lstm_outs, (h_t, h_c) = lstm(lstm_input, (h_t, h_c))

Ici, (h_t, h_c) Doit être fourni comme état caché initial et il affichera l'état caché final. Vous pouvez voir pourquoi la séquence de longueur variable est requise, sinon LSTM exécutera également les mots remplis non requis.
Maintenant, lstm_outs Sera une séquence compactée qui est la sortie de lstm à ​​chaque étape et (h_t, h_c) Sont les sorties finales et l'état final des cellules respectivement. h_t Et h_c Auront la forme (batch_size, lstm_size). Vous pouvez les utiliser directement pour d'autres entrées, mais si vous souhaitez également utiliser les sorties intermédiaires, vous devrez d'abord déballer le lstm_outs Comme ci-dessous.

lstm_outs, _ = nn.utils.rnn.pad_packed_sequence(lstm_outs)

Maintenant, votre lstm_outs Aura la forme (max_seq_len - context_size + 1, batch_size, lstm_size). Maintenant, vous pouvez extraire les sorties intermédiaires de lstm en fonction de vos besoins.

N'oubliez pas que la sortie décompressée aura 0 après la taille de chaque lot, qui est juste un remplissage pour correspondre à la longueur de la plus grande séquence (qui est toujours la première, car nous avons trié l'entrée du plus grand au plus petit).

Notez également que h_t sera toujours égal au dernier élément pour chaque sortie de lot.

Interfaçage de lstm à ​​linéaire

Maintenant, si vous souhaitez utiliser uniquement la sortie du lstm, vous pouvez directement alimenter h_t Dans votre couche linéaire et cela fonctionnera. Mais, si vous souhaitez également utiliser des sorties intermédiaires, vous devrez déterminer comment allez-vous entrer cela dans la couche linéaire (via un réseau d'attention ou une mise en commun). Vous ne voulez pas entrer la séquence complète dans le calque linéaire, car différentes séquences seront de longueurs différentes et vous ne pouvez pas fixer la taille d'entrée du calque linéaire. Et oui, vous devrez transposer la sortie de lstm pour une utilisation ultérieure (encore une fois, vous ne pouvez pas utiliser l'affichage ici).

Remarque de fin: j'ai délibérément laissé certains points, tels que l'utilisation de cellules récurrentes bidirectionnelles, l'utilisation de la taille des pas dans le dépliage et l'interface de l'attention, car elles peuvent devenir assez lourdes et seront hors de portée de cette réponse.

29
layog