Так что я строю RNN с нуля, используя numpy, просто чтобы понять, как они работают внутри. Мое обратное распространение через время здесь:
def backprop_through_time(self, X, Y):
assert(len(X.shape) == 3)
seq_length = Y.shape[1] if self.return_sequences else 1
_, (Z_states, States, Z_outs, Outs) = self.feed_forward(X, cache=True)
if not self.return_sequences:
Outs = Outs[:,-1,:]
# setup gradients
dLdU = np.zeros(self.U.shape)
dLdV = np.zeros(self.V.shape)
dLdW = np.zeros(self.W.shape)
dLdB_state = np.zeros(self.B_state.shape)
dLdB_out = np.zeros(self.B_out.shape)
dLdOuts = self.loss_function_prime(Outs, Y)
if not self.return_sequences:
# we need dLdOuts to have a seq_length dim at axis 1
dLdOuts = np.expand_dims(dLdOuts, axis=1)
for t in range(seq_length):
adjusted_t = seq_length-1 if not self.return_sequences else t
# print("adjusted_t {}".format(adjusted_t))
dOuts_tdZ_out = self.output_activation_function_prime(Z_outs[:,adjusted_t,:])
dLdZ_out = np.multiply(dLdOuts[:, adjusted_t, :], dOuts_tdZ_out)
# Z_state = dot(X_t, self.U) + dot(State_{t-1}, self.W) + self.B_state
# State_t = f(Z_state)
# Z_out = dot(State_t, self.V) + self.B_out
# Out_t = g(Z_out)
dLdV += np.dot(States[:,adjusted_t,:].T, dLdZ_out)
dLdB_out += np.sum(dLdZ_out, axis=0, keepdims=True)
dLdZ_state = np.multiply(np.dot(dLdZ_out, self.V.T),
self.hidden_activation_function_prime(Z_states[:,adjusted_t,:]))
for t_prev in range(max(0, adjusted_t-self.backprop_through_time_limit), adjusted_t+1)[::-1]:
dLdB_state += np.sum(dLdZ_state, axis=0, keepdims=True)
dLdW += np.dot(States[:,t_prev-1,:].T, dLdZ_state)
dLdU += np.dot(X[:,t_prev,:].T, dLdZ_state)
dLdZ_state = np.multiply(np.dot(dLdZ_state, self.W.T),
self.hidden_activation_function_prime(States[:,t_prev-1,:]))
return (dLdU, dLdV, dLdW), (dLdB_state, dLdB_out)
Однако мне все еще не удается проверить градиент для параметров `dLdU, dLdW, dLdB_state`. Я прошел математику около десятка раз, и я не могу найти, что не так с моей реализацией.
Я предполагаю, что X и Y являются трехмерными матрицами с X, имеющим форму: X.shape := (batch_size, seq_length, input_dim)
в то время как Y имеет форму: Y.shape := (batch_size, seq_length, output_dim)
Кэшируя операцию feed_forward, я возвращаю Z_states с формой Z_states.shape := (batch_size, seq_length, hidden_dim)
, Z_outs и Outs с формой Z_outs.shape, Outs.shape := (batch_size, seq_length, output_dim)
и состояния как States.shape := (batch_size, seq_length+1, hidden_dim)
. Состояния [:, - 1 ,:] - это исходные нули формы States[:,-1,:].shape := (batch_size, hidden_dim)
, с которыми инициализируется состояние RNN. Кто-нибудь может мне помочь?
EDIT
Я нашел свой ответ. Моя математика верна, но я вызывал не ту переменную. Когда я обновляю dLdZ_state во 2-м внутреннем цикле (обратная часть времени), я умножаю на self.hidden_activation_function_prime(States[:,t_prev-1,:])
Этот шут вместо этого будет self.hidden_activation_function_prime(Z_states[:,t_prev-1,:])