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


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

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

До сих пор я всегда использовал подход без минибатча градиентного спуска, в котором я передавал обучающие элементы один за другим один к обновлению градиента. Теперь я хочу реализовать обновление градиента через мини-пакет, начиная, скажем, с мини-пакетов, состоящих из 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 элемент; как следует изменить ее, чтобы она могла управлять minibatch ?

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

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

Что вы, ребята, думаете?

1 5

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-й входной таблицы. Таким образом, если вы хотите, чтобы он обрабатывал пакет, каждый вход[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? Можно ли использовать его вместо этого? Да, но это крайне не рекомендуется . Секвенсор берет таблицу последовательностей и применяет модуль к каждому элементу таблицы независимо, не обеспечивая ускорения. Кроме того, он должен делать копии этого модуля, поэтому такой "пакетный режим" значительно менее эффективен, чем онлайн (непакетный) тренинг. Секвенсор был разработан, чтобы быть частью рекуррентных сетей, нет смысла использовать его в вашем дело.