Я читаю 14-ю главу практического машинного обучения с Scikit-Learn и TensorFlow. Это говорит:
Хотя использование OutputProjectionWrapper
является самым простым решением для уменьшения размерности выходных последовательностей RNN до одного значения за шаг по времени (для каждого экземпляра), оно не является наиболее эффективным. Существует более хитрое, но более эффективное решение: вы можете изменить форму выходов RNN, а затем применить один полностью подключенный слой с соответствующим размером выходного сигнала. [...] Это может обеспечить значительное повышение скорости, поскольку существует только один полностью связанный слой вместо одного за шаг по времени.
Это не имеет смысла для меня. В случае OutputProjectionWrapper
нам нужно выполнить 2 операции за временной шаг:
- Рассчитать новое скрытое состояние на основе предыдущего скрытого состояния и ввода.
- Рассчитать вывод, применяя плотный слой к вычисленному скрытому состоянию.
Конечно, когда мы используем простой BasicRNNCell
+ плотный слой сверху, нам нужно делать только одну операцию на каждом временном шаге (первый), но затем нам нужно пропустить каждый выходной тензор через наш плотный слой. Поэтому нам нужно выполнить одинаковое количество операций в обоих случаях.
Кроме того, я не могу понять следующую часть:
Это может обеспечить значительный прирост скорости, поскольку имеется только один полностью подключенный слой вместо одного за шаг по времени.
Разве у нас нет только одного полностью связного слоя в обоих случаях? Насколько я понимаю, OutputProjectionWrapper
использует один и тот же общий слой на каждом временном шаге. Я даже не знаю, как он может создавать разные слои для каждого временного шага, потому что OutputProjectionWrapper
не имеет информации о количестве временных шагов, которые мы будем использовать.
Буду очень признателен, если кто-нибудь сможет объяснить разницу между этими подходами.
UPD Вот псевдокод для вопроса. Я что-то упустил?
# 2 time steps, x1 and x2 - inputs, h1 and h2 - hidden states, y1 and y2 - outputs.
# OutputProjectionWrapper
h1 = calc_hidden(x1, 0)
y1 = dense(h1)
h2 = calc_hidden(x2, h1)
y2 = dense(h2)
# BasicRNNCell + dense layer on top of all time steps
h1 = calc_hidden(x1, 0)
y1 = h1
h2 = calc_hidden(x2, h1)
y2 = h2
y1 = dense(y1)
y2 = dense(y2)
UPD 2 Я создал два небольших фрагмента кода (один с OutputProjectionWrapper
, а другой с BasicRNNCell
и tf.layers.dense
сверху) - оба создали 14 переменных с одинаковой формой. Таким образом, между этими подходами нет никакой разницы в памяти.