PyTorch训练RNN实现域名二分类时Loss不随Epoch更新问题咨询
DGA域名二分类RNN模型训练Loss停滞问题排查
问题背景
使用PyTorch训练RNN模型对DGA域名进行二分类,数据集样例如下:
m644136d0.tmodns.ne | 0 dfg.ca.gov | 0 c4w6wpg81xsbopy8a67.ddns.net | 1
其中标签1代表DGA生成域名,采用独热编码做嵌入,但训练50轮后Loss始终维持0.6931(约等于ln2,对应模型随机预测),无任何下降。
问题排查与修复方案
1. 损失函数与输出层的冲突
- 问题本质:
CrossEntropyLoss内部已经整合了LogSoftmax和NLLLoss的计算逻辑,而模型中额外添加了Softmax层,导致输出被两次归一化,最终模型输出趋近于均匀分布(两类概率各0.5),对应Loss固定为ln2≈0.6931。 - 修复:移除模型中的Softmax层:
class rnnModel(nn.Module): def __init__(self, vocab_size, embedding_dim, num_hid_neurons, num_out, nun_hid_layers=1): super(rnnModel, self).__init__() self.nn = nn.RNN(embedding_dim, num_hid_neurons, nun_hid_layers, batch_first=True) self.fc = nn.Linear(num_hid_neurons, 2) # 移除self.softmax = nn.Softmax(dim=1) self.num_out = num_out self.nun_hid_layers = nun_hid_layers self.num_hid_neurons = num_hid_neurons def forward(self, input, hidden): if hidden is None : batch_size = input.size(0) hidden = self.init_hidden(batch_size) output, hidden = self.nn(input, hidden) output = self.fc(output) # 移除output = self.softmax(output) return output, hidden
2. 梯度清零时机错误
- 问题本质:当前代码将
optimizer.zero_grad()放在epoch循环外部,导致每轮所有batch的梯度累加,梯度会出现爆炸或失效,模型无法有效更新参数。 - 修复:将梯度清零操作移至每个batch循环内部:
for epoch in range(1, n_epochs + 1): total_loss = 0.0 for input, target in train_data_loader : optimizer.zero_grad() # 每个batch前清零梯度 batch_size = input.size(0) hidden = model.init_hidden(batch_size) output, hidden = model(input, hidden) output = output[:, -1, :] loss = criterion(output, target) loss.backward() optimizer.step() total_loss += loss.item() losses.append(total_loss / len(train_data_loader)) print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ') print("Loss: {:.4f}".format(total_loss / len(train_data_loader)))
3. RNN输入处理与前向传播逻辑错误
- 问题本质:手动循环输入的每个时间步喂给RNN不仅低效,还导致输入维度冗余(多了一个无用的维度),且没有正确利用整个序列的信息做分类。
- 修复:
- 调整
domain2tensor,移除冗余维度:def domain2tensor(domain): tensor = torch.zeros(len(domain), vocab_size) # 改为(序列长度, 词汇表大小) for i, c in enumerate(domain): tensor[i, char2token[c]] = 1 return tensor - 调整Dataset的pad逻辑:
def __getitem__(self, index) : temp_text = self.Xtr.iloc[index] not_padded = domain2tensor(temp_text) # 调整pad维度:(seq_len, vocab_size) -> (padded_size, vocab_size) padded = F.pad(not_padded, pad=(0,0, self.padded_size - len(not_padded), 0)) x = self.ytr.iloc[index] return padded, torch.tensor(x, dtype=torch.long) # 标签改为类索引(long类型) - 训练循环中直接喂入整个序列,并取最后一个时间步的输出做分类:
for input, target in train_data_loader : optimizer.zero_grad() batch_size = input.size(0) hidden = model.init_hidden(batch_size) output, hidden = model(input, hidden) # 取RNN最后一个时间步的输出用于分类 output = output[:, -1, :] loss = criterion(output, target) loss.backward() optimizer.step()
- 调整
4. 标签格式不符合损失函数要求
- 问题本质:
CrossEntropyLoss要求目标是类别的索引值(0或1),但当前代码返回的是one-hot格式的向量,导致损失计算逻辑错误,模型无法学习。 - 修复:如上述Dataset修改所示,直接返回原始标签的long类型张量即可。
5. 学习率设置过低
- 问题本质:当前学习率
lr=1e-5对于Adam优化器来说过小,参数更新幅度微乎其微,模型无法有效收敛。 - 修复:将学习率调整至
1e-4或1e-3,后续可根据训练曲线再微调:optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
6. 隐藏层初始化的设备不匹配问题
- 问题本质:如果使用GPU训练,
init_hidden生成的隐藏状态张量默认在CPU上,而模型参数在GPU上,会导致设备不匹配报错,或训练无效。 - 修复:让隐藏状态使用模型所在的设备:
def init_hidden(self, batch_size): hidden = torch.zeros(self.nun_hid_layers, batch_size, self.num_hid_neurons, device=self.nn.weight.device) return hidden
内容的提问来源于stack exchange,提问作者SlavaG
相关产品推荐
相关产品推荐

