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:
nn.Embedding
nn.LSTM
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))
?
Votre compréhension de la plupart des concepts est exacte, mais il y a des points manquants ici et là.
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.
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
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)
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.
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.