2016-02-17 1 views
5

Je travaille toujours sur l'implémentation de la mise à jour du gradient mini-batch sur mon réseau neuronal siamois. Auparavant, j'avais un problème d'implémentation, c'était correctly solved here.Torch/Lua, quelle structure de réseau neuronal pour la formation de mini-lot?

Maintenant, j'ai réalisé qu'il y avait aussi une erreur dans l'architecture de mon réseau de neurones, c'est lié à ma compréhension incomplète de la bonne implémentation. Jusqu'ici, j'ai toujours utilisé une approche de descente de gradient non-minibatch, dans laquelle je passais les éléments d'entraînement un par un à la mise à jour de gradient. Maintenant, je veux mettre en œuvre une mise à jour de gradient par mini-batch, en commençant par dire avec des minibatches faites de N = 2 éléments.

Ma question est: comment changer l'architecture de mon réseau de neurones siamois pour le rendre capable de gérer un mini-lot de N = 2 éléments au lieu d'un seul élément?

C'est le (simplifié) l'architecture de mon réseau de neurones Siamois:

nn.Sequential { 
    [input -> (1) -> (2) -> output] 
    (1): nn.ParallelTable { 
    input 
     |`-> (1): nn.Sequential { 
     |  [input -> (1) -> (2) -> output] 
     |  (1): nn.Linear(6 -> 3) 
     |  (2): nn.Linear(3 -> 2) 
     | } 
     |`-> (2): nn.Sequential { 
     |  [input -> (1) -> (2) -> output] 
     |  (1): nn.Linear(6 -> 3) 
     |  (2): nn.Linear(3 -> 2) 
     | } 
     ... -> output 
    } 
    (2): nn.CosineDistance 
} 

Je:

  • 2 réseaux de neurones Siamois identiques (supérieure et inférieure)
  • 6 unités d'entrée
  • 3 unités cachées
  • 2 unités de sortie
  • fonction
  • distance cosinus qui compare la sortie des deux réseaux de neurones parallèles

Voici mon code:

perceptronUpper= nn.Sequential() 
perceptronUpper:add(nn.Linear(input_number, hiddenUnits)) 
perceptronUpper:add(nn.Linear(hiddenUnits,output_number)) 
perceptronLower= perceptronUpper:clone('weight', 'gradWeights', 'gradBias', 
'bias') 

parallel_table = nn.ParallelTable() 
parallel_table:add(perceptronUpper) 
parallel_table:add(perceptronLower) 

perceptron = nn.Sequential() 
perceptron:add(parallel_table) 
perceptron:add(nn.CosineDistance()) 

Cette architecture fonctionne très bien si j'ai une fonction de mise à jour de gradient qui prend 1 élément; comment devrait le modifier pour le laisser gérer un minibatch?

EDIT: Je devrais probablement utiliser le nn.Sequencer() class, en modifiant les deux dernières lignes de mon code:

perceptron:add(nn.Sequencer(parallel_table)) 
perceptron:add(nn.Sequencer(nn.CosineDistance())). 

Qu'est-ce que vous en pensez?

Répondre

2

Chaque module nn peut fonctionner avec des mini-piles. Certains fonctionnent uniquement avec des minibatches, par ex. (Spatial)BatchNormalization. Un module sait combien de dimensions son entrée doit contenir (disons D) et si le module reçoit un tenseur dimensionnel D + 1, il suppose que la première dimension est la dimension du lot. Par exemple, jetez un oeil à nn.Linear module documentation:

Le tenseur d'entrée donné en avant (entrée) doivent être soit un vecteur (tenseur 1D ) ou d'une matrice (tenseur 2D). Si l'entrée est une matrice, chaque ligne est supposée être un échantillon d'entrée du lot donné.

function table_of_tensors_to_batch(tbl) 
    local batch = torch.Tensor(#tbl, unpack(tbl[1]:size():totable())) 
    for i = 1, #tbl do 
     batch[i] = tbl[i] 
    end 
    return batch 
end 

inputs = { 
    torch.Tensor(5):fill(1), 
    torch.Tensor(5):fill(2), 
    torch.Tensor(5):fill(3), 
} 
input_batch = table_of_tensors_to_batch(inputs) 
linear = nn.Linear(5, 2) 
output_batch = linear:forward(input_batch) 

print(input_batch) 
1 1 1 1 1 
2 2 2 2 2 
3 3 3 3 3 
[torch.DoubleTensor of size 3x5] 

print(output_batch) 
0,3128 -1,1384 
0,7382 -2,1815 
1,1637 -3,2247 
[torch.DoubleTensor of size 3x2] 

Ok, mais qu'en est-conteneurs (nn.Sequential, nn.Paralel, nn.ParallelTable et autres)?Le conteneur lui-même ne gère pas une entrée, il envoie simplement l'entrée (ou sa partie correspondante) au module correspondant qu'il contient. ParallelTable, par exemple, applique simplement le module i-ème élément au i-ème élément de table d'entrée. Ainsi, si vous voulez gérer un lot, chaque entrée [i] (entrée est une table) doit être un tenseur avec la dimension du lot comme décrit ci-dessus.

input_number = 5 
output_number = 2 

inputs1 = { 
    torch.Tensor(5):fill(1), 
    torch.Tensor(5):fill(2), 
    torch.Tensor(5):fill(3), 
} 
inputs2 = { 
    torch.Tensor(5):fill(4), 
    torch.Tensor(5):fill(5), 
    torch.Tensor(5):fill(6), 
} 
input1_batch = table_of_tensors_to_batch(inputs1) 
input2_batch = table_of_tensors_to_batch(inputs2) 

input_batch = {input1_batch, input2_batch} 
output_batch = perceptron:forward(input_batch) 

print(input_batch) 
{ 
    1 : DoubleTensor - size: 3x5 
    2 : DoubleTensor - size: 3x5 
} 
print(output_batch) 
0,6490 
0,9757 
0,9947 
[torch.DoubleTensor of size 3] 


target_batch = torch.Tensor({1, 0, 1}) 
criterion = nn.MSECriterion() 
err = criterion:forward(output_batch, target_batch) 
gradCriterion = criterion:backward(output_batch, target_batch) 
perceptron:zeroGradParameters() 
perceptron:backward(input_batch, gradCriterion) 

Pourquoi est-il nn.Sequencer alors? Peut-on l'utiliser à la place? Oui, mais c'est hautement déconseillé. Sequencer prend une table de séquence et applique le module à chaque élément de la table de manière indépendante, sans aucune accélération. En outre, il doit faire des copies de ce module, de sorte que ce "mode batch" est considérablement moins efficace que la formation en ligne (non-batch). Séquenceur a été conçu pour faire partie de réseaux récurrents, inutile de l'utiliser dans votre cas.

+0

Bonjour @Alexander, merci d'avoir répondu. J'essaie d'implémenter votre solution, mais je suis coincé dans l'instruction de mise à jour de gradient 'perceptron: backward (input_batch, targets)'. 'targets' devrait contenir les cibles de mon entraînement, par exemple' 0,1'. Si 'input_batch' est une liste de 2 DoubleTensors dont la taille est 3x5, quelles devraient être les bonnes dimensions de' target'? Merci –

+0

@ DavideChicco.it, le but est de minimiser la distance entre les paires d'entrées, non? Quels sont vos objectifs alors? Je suppose que c'est des zéros. D'où vient 0,1? –

+0

Je compare des paires de vecteurs. Chaque vecteur est composé de 6 valeurs réelles. Chaque paire peut être vraie (cible = 1) ou fausse (cible = 0). Pendant l'entraînement, j'appelle 'perceptron: forward (input_batch)' et ensuite 'perceptron: zeroGradParameters()' et 'perceptron: backward (input_batch, targets)'. J'ai des problèmes avec les dimensions des 'target's, que je dois adapter aux nouveaux paramètres. Un vecteur de #input_batch DoubleTensors de taille 1 ne fonctionne pas, que dois-je utiliser? Merci –