Torch/Lua, какая структура нейронной сети для мини-пакетного обучения?

Я все еще работаю над реализацией мини-пакетного обновления градиента в моей сиамской нейронной сети. Раньше у меня была проблема с реализацией, которая была n">правильно решено здесь.

Теперь я понял, что в архитектуре моей нейросети тоже была ошибка, связанная с моим неполным пониманием правильной реализации.

До сих пор я всегда использовал подход градиентного спуска без мини-пакетов, в котором я передавал обучающие элементы один за другим в обновление градиента. Теперь я хочу реализовать обновление градиента с помощью мини-пакета, начиная, скажем, с мини-пакетов, состоящих из элементов N = 2.

Мой вопрос: как мне изменить архитектуру моей сиамской нейронной сети, чтобы она могла обрабатывать мини-партию из N=2 элементов вместо одного элемента?

Это (упрощенная) архитектура моей сиамской нейронной сети:

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
}

У меня есть:

  • 2 одинаковые сиамские нейронные сети (верхняя и нижняя)
  • 6 входных единиц
  • 3 скрытых юнита
  • 2 выходных блока
  • функция косинусного расстояния, которая сравнивает выходные данные двух параллельных нейронных сетей

Вот мой код:

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())

Эта архитектура работает очень хорошо, если у меня есть функция обновления градиента, которая принимает 1 элемент; как изменить его, чтобы он мог управлять мини-пакетом?

EDIT: мне, вероятно, следует использовать nn. Sequencer(), изменив последние две строки моего кода:

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

Ребята, что вы думаете?


person DavideChicco.it    schedule 17.02.2016    source источник


Ответы (1)


Каждый модуль nn может работать с минипакетами. Некоторые работают только с мини-пакетами, например. (Spatial)BatchNormalization. Модуль знает, сколько измерений должны содержать его входные данные (скажем, D), и если модуль получает размерный тензор D+1, он предполагает, что первое измерение является пакетным измерением. Например, взгляните на nn.Linear документацию по модулю:

Входной тензор, заданный в forward(input), должен быть либо вектором (1D-тензор), либо матрицей (2D-тензор). Если вход представляет собой матрицу, то предполагается, что каждая строка является входной выборкой данной партии.

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]

Хорошо, а как насчет контейнеров (nn.Sequential, nn.Paralel, nn.ParallelTable и других)? Сам контейнер не обрабатывает ввод, он просто отправляет ввод (или его соответствующую часть) соответствующему модулю, который он содержит. ParallelTable, например, просто применяет i-й модуль-член к i-му входному элементу таблицы. Таким образом, если вы хотите, чтобы он обрабатывал пакет, каждый input[i] (вход — это таблица) должен быть тензором с размерностью пакета, как описано выше.

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)

Почему тогда nn.Sequencer? Можно ли использовать его вместо этого? Да, но это настоятельно не рекомендуется. Sequencer берет таблицу последовательности и применяет модуль к каждому элементу в таблице независимо, не обеспечивая ускорения. Кроме того, он должен делать копии этого модуля, поэтому такой «пакетный режим» значительно менее эффективен, чем онлайн-обучение (непакетное). Секвенсор был разработан, чтобы быть частью рекуррентных сетей, нет смысла использовать его в вашем случае.

person Alexander Lutsenko    schedule 29.02.2016
comment
Привет @Александр, спасибо за ответ. Я пытаюсь реализовать ваше решение, но застреваю в инструкции по обновлению градиента perceptron:backward(input_batch, targets). targets должен содержать цели моего обучения, например 0,1. Если input_batch представляет собой список из двух двойных тензоров размером 3x5, какими должны быть правильные размеры target? Спасибо - person DavideChicco.it; 29.02.2016
comment
@DavideChicco.it, цель состоит в том, чтобы минимизировать расстояние между парами входных данных, верно? Каковы ваши цели тогда? Я бы предположил, что это нули. Откуда 0,1? - person Alexander Lutsenko; 29.02.2016
comment
Я сравниваю пары векторов. Каждый вектор состоит из 6 действительных значений. Каждая пара может быть истинной (цель = 1) или ложной (цель = 0). Во время обучения я звоню perceptron:forward(input_batch), а затем perceptron:zeroGradParameters() и perceptron:backward(input_batch, targets). У меня проблемы с размерами targets, которые я должен адаптировать к новым настройкам. Вектор #input_batch DoubleTensors размера 1 не работает, что мне использовать? Спасибо - person DavideChicco.it; 29.02.2016
comment
[torch.DoubleTensor размера BatchSize] должен работать. Я использую MSECriterion, и это работает. Каков ваш критерий? - person Alexander Lutsenko; 29.02.2016
comment
Я использую функцию обновления градиента, разработанную мной. Не могли бы вы взглянуть на мой короткий код сценария? Вам просто нужно скачать его и запустить th cosine_similarity_minibatch3_momentum_withoutSequencer.lua Проблема в строке #104: perceptron:backward() Спасибо! bit.ly/1LQ5BPf - person DavideChicco.it; 29.02.2016
comment
@DavideChicco.it, я обновил свой ответ и добавил пример того, как обучить сеть с помощью MSECriterion. Проверь это. Также сиамские сети описаны в документации: github.com/ torch/nn/blob/master/doc/table.md#cosinedistance - person Alexander Lutsenko; 29.02.2016
comment
Это было очень трудоемко реализовать (мне пришлось переключиться с вектора из N пар тензоров на пару N тензоров), но, в конце концов, я думаю, что смог сделать это правильно. Спасибо Александр! - person DavideChicco.it; 01.03.2016