手动实现RNN训练Loss异常:先持平后逐epoch上升
手动实现RNN训练时Loss异常问题
我参考仓库手动实现RNN,训练时最初每个epoch的Loss始终保持不变。已确认参数在更新,且梯度未出现极小值,单步迭代中Loss会下降,但整个epoch的总Loss反而上升。修改损失函数后解决了Loss持平问题,但现在每个epoch的Loss持续上升。
训练循环代码如下:
# Hyper-parameters num_epochs = 1000 # Initialize a new network params = init_rnn(hidden_size=hidden_size, vocab_size=vocab_size) # Initialize hidden state as zeros hidden_state = np.zeros((hidden_size, 1)) # Track loss training_loss, validation_loss = [], [] def check_if_params_updated(old_params, new_params): # This function checks if two sets of parameters are different for old_param, new_param in zip(old_params, new_params): if not np.array_equal(old_param, new_param): return True # Parameters have been updated return False # Parameters have not been updated # For each epoch for i in range(num_epochs): # Track loss epoch_training_loss = 0 epoch_validation_loss = 0 # For each sentence in validation set for inputs, targets in val_loader: # One-hot encode input and target sequence inputs_one_hot = one_hot_encode_sequence(inputs, vocab_size) targets_one_hot = one_hot_encode_sequence(targets, vocab_size) # Re-initialize hidden state hidden_state = np.zeros_like(hidden_state) # Forward pass outputs, hidden_states = forward_pass(inputs_one_hot, hidden_state, params) # Backward pass loss, _ = backward_pass(inputs_one_hot, outputs, hidden_states, targets_one_hot, params) # Update loss epoch_validation_loss += loss # For each sentence in training set for inputs, targets in train_loader: # One-hot encode input and target sequence inputs_one_hot = one_hot_encode_sequence(inputs, vocab_size) targets_one_hot = one_hot_encode_sequence(targets, vocab_size) # Re-initialize hidden state hidden_state = np.zeros_like(hidden_state) # Forward pass outputs, hidden_states = forward_pass(inputs_one_hot, hidden_state, params) # Backward pass loss, grads = backward_pass(inputs_one_hot, outputs, hidden_states, targets_one_hot, params) print(inputs_one_hot.shape) if np.isnan(loss): raise ValueError('Gradients have vanished/exploded!') # Update parameters params = update_parameters(params, grads, lr=1e-3) # Update loss epoch_training_loss += loss # Save loss for plot training_loss.append(epoch_training_loss/len(training_set)) validation_loss.append(epoch_validation_loss/len(validation_set)) # Print loss every 100 epochs if i % 100 == 0: print(f'Epoch {i}, training loss: {training_loss[-1]}, validation loss: {validation_loss[-1]}') # Get first sentence in test set inputs, targets = test_set[1] # One-hot encode input and target sequence inputs_one_hot = one_hot_encode_sequence(inputs, vocab_size) targets_one_hot = one_hot_encode_sequence(targets, vocab_size) # Initialize hidden state as zeros hidden_state = np.zeros((hidden_size, 1)) # Forward pass outputs, hidden_states = forward_pass(inputs_one_hot, hidden_state, params) output_sentence = [idx_to_word[np.argmax(output)] for output in outputs] print('Input sentence:') print(inputs) print('\nTarget sequence:') print(targets) print('\nPredicted sequence:') print([idx_to_word[np.argmax(output)] for output in outputs]) # Plot training and validation loss epoch = np.arange(len(training_loss)) plt.figure() plt.plot(epoch, training_loss, 'r', label='Training loss',) plt.plot(epoch, validation_loss, 'b', label='Validation loss') plt.legend() plt.xlabel('Epoch'), plt.ylabel('NLL') plt.show()
排查方向
- 学习率过高:当前使用的
lr=1e-3对RNN来说可能偏大,尝试降低至1e-4或更小,观察Loss变化。 - 损失平均逻辑错误:若
backward_pass返回的是单样本的损失总和,按样本数量len(training_set)平均并不合理,应改为按所有样本的总序列步数来平均,因为不同样本的序列长度可能不同。 - 梯度更新方向错误:检查
update_parameters函数,确认是执行参数 -= 学习率 * 梯度,如果写成了加号,会导致Loss持续上升。 - 反向传播逻辑漏洞:重点检查隐藏层梯度的传递是否正确,比如tanh激活函数的导数计算是否准确,是否遗漏了前一时刻隐藏状态的梯度累积。
- 数据加载问题:确认训练集每个epoch是否打乱,固定顺序的样本可能导致模型学习异常。
内容的提问来源于stack exchange,提问作者Dhanush Binu
相关产品推荐
相关产品推荐

