Как интерпретировать веса в слое LSTM в Керасе
В настоящее время я тренирую рекуррентную нейронную сеть для прогнозирования погоды, используя слой LSTM. Сама сеть довольно проста и выглядит примерно так:
model = Sequential()
model.add(LSTM(hidden_neurons, input_shape=(time_steps, feature_count), return_sequences=False))
model.add(Dense(feature_count))
model.add(Activation("linear"))
Веса слоя LSTM имеют следующие формы:
for weight in model.get_weights(): # weights from Dense layer omitted
print(weight.shape)
> (feature_count, hidden_neurons)
> (hidden_neurons, hidden_neurons)
> (hidden_neurons,)
> (feature_count, hidden_neurons)
> (hidden_neurons, hidden_neurons)
> (hidden_neurons,)
> (feature_count, hidden_neurons)
> (hidden_neurons, hidden_neurons)
> (hidden_neurons,)
> (feature_count, hidden_neurons)
> (hidden_neurons, hidden_neurons)
> (hidden_neurons,)
Короче говоря, похоже, что в этом слое LSTM есть четыре "элемента". Теперь мне интересно, как их интерпретировать:
Где
time_steps
параметр в этом представлении? Как это влияет на вес?Я читал, что LSTM состоит из нескольких блоков, таких как вход и ворота забывания. Если они представлены в этих весовых матрицах, то какая матрица принадлежит каким воротам?
Есть ли способ узнать, чему научилась сеть? Например, сколько это займет с последнего временного шага (
t-1
если мы хотим прогнозироватьt
) и сколько изt-2
так далее? Было бы интересно узнать, можем ли мы считать из весов, что входt-5
совершенно не имеет значения, например.
Разъяснения и подсказки будут с благодарностью.
2 ответа
Если вы используете Keras 2.2.0
Когда вы печатаете
print(model.layers[0].trainable_weights)
вы должны увидеть три тензора: lstm_1/kernel, lstm_1/recurrent_kernel, lstm_1/bias:0
Одно из измерений каждого тензора должно быть произведением
4 * число_единиц
где number_of_units - это количество нейронов. Пытаться:
units = int(int(model.layers[0].trainable_weights[0].shape[1])/4)
print("No units: ", units)
Это потому, что каждый тензор содержит веса для четырех единиц LSTM (в этом порядке):
i (вход), f (забыть), c (состояние ячейки) и o (выход)
Поэтому для извлечения весов вы можете просто использовать оператор среза:
W = model.layers[0].get_weights()[0]
U = model.layers[0].get_weights()[1]
b = model.layers[0].get_weights()[2]
W_i = W[:, :units]
W_f = W[:, units: units * 2]
W_c = W[:, units * 2: units * 3]
W_o = W[:, units * 3:]
U_i = U[:, :units]
U_f = U[:, units: units * 2]
U_c = U[:, units * 2: units * 3]
U_o = U[:, units * 3:]
b_i = b[:units]
b_f = b[units: units * 2]
b_c = b[units * 2: units * 3]
b_o = b[units * 3:]
Источник: код keras
Я, вероятно, не смогу ответить на все ваши вопросы, но я могу предоставить больше информации о ячейке LSTM и различных компонентах, из которых она сделана.
Этот пост на github предлагает способ увидеть имя параметров при их печати:
model = Sequential()
model.add(LSTM(4,input_dim=5,input_length=N,return_sequences=True))
for e in zip(model.layers[0].trainable_weights, model.layers[0].get_weights()):
print('Param %s:\n%s' % (e[0],e[1]))
Вывод выглядит так:
Param lstm_3_W_i:
[[ 0.00069305, ...]]
Param lstm_3_U_i:
[[ 1.10000002, ...]]
Param lstm_3_b_i:
[ 0., ...]
Param lstm_3_W_c:
[[-1.38370085, ...]]
...
Теперь вы можете найти здесь больше информации об этих различных весах. Они имеют имена W, U, V и b с разными индексами.
- W матрицы - это те, которые преобразуют входные данные в некоторые другие внутренние значения. Они имеют форму
[input_dim, output_dim]
, - Матрицы U - это те, которые преобразуют предыдущее скрытое состояние в другое внутреннее значение. Они имеют форму
[output_dim, output_dim]
, - Векторы b - это смещение для каждого блока. Все они имеют форму
[output_dim]
- V используется только в выходном вентиле, он выбирает, какие значения выводить из нового внутреннего состояния. Имеет форму
[output_dim, output_dim]
Короче говоря, у вас действительно есть 4 разных "блока" (или внутренних слоя).
gate gate: решает, основываясь на предыдущем скрытом состоянии (h_{t-1}) и входе (x), значения которого нужно забыть из предыдущего внутреннего состояния ячейки (C_ {t-1}):
f_t = сигмоид (W_f * x + U_f * h_ {t-1} + b_f)
f_t - это вектор значений между 0 и 1, который будет кодировать, что оставить (=1) и что забыть (=0) из предыдущего состояния ячейки.
Входной вентиль: он решает, основываясь на предыдущем скрытом состоянии (h_{t-1}) и входе (x), какие значения использовать из ввода (x):
i_t = сигмоид (W_i * x + U_i * h_ {t-1} + b_i)
i_t - это вектор значений между 0 и 1, который будет кодировать, какие значения использовать для обновления нового состояния ячейки.
Значение кандидата: Мы создаем новые значения кандидатов, чтобы обновить внутреннее состояние ячейки, используя input (x) и предыдущее скрытое состояние (h_{t-1}):
Ct_t = tanh (W_c * x + U_c * h_ {t-1} + b_c)
Ct_t - это вектор, содержащий потенциальные значения для обновления состояния ячейки (C_{t-1}).
Мы используем эти три значения для создания нового внутреннего состояния ячейки (C_t):
C_t = f_t * C_ {t-1} + i_t * Ct_t
как вы можете видеть, новое внутреннее состояние ячейки состоит из двух вещей: части, которую мы не забыли из последнего состояния, и того, что мы хотели узнать из ввода.
Выходные ворота: мы не хотим выводить состояние ячейки, так как это может рассматриваться как абстракция того, что мы хотим вывести (h_t). Итак, мы строим h_t, вывод для этого шага на основе всей имеющейся у нас информации:
h_t = W_o * x + U_o * h_ {t-1} + V_o * C_t + b_o
Я надеюсь, что это проясняет, как работает ячейка LSTM. Я приглашаю вас прочитать учебные пособия по LSTM, так как они используют красивые схемы, пошаговые примеры и так далее. Это относительно сложный слой.
Что касается ваших вопросов, у меня теперь есть идея, как отследить то, что использовалось из входных данных для изменения состояния. В конечном итоге вы можете посмотреть на различные W-матрицы, так как именно они обрабатывают ввод. W_c предоставит вам информацию о том, что потенциально может использоваться для обновления состояния ячейки. W_o может дать вам некоторую информацию о том, что используется для создания выходных данных... Но все это будет относительно других весов, так как предыдущие состояния также оказывают влияние.
Однако, если вы видите некоторые сильные веса в W_c, это может ничего не значить, потому что входные ворота (i_t) могут быть полностью закрыты и уничтожить обновление состояния ячейки... Это сложно, область математики, которая прослеживает то, что происходит в нейронной сети, действительно сложно.
Нейронные сети - это действительно черные ящики для наиболее общего случая. Вы можете найти в литературе некоторые случаи, когда они прослеживают информацию от вывода до ввода, но это в очень особых случаях из того, что я прочитал.
Надеюсь, это поможет:-)