我在使用Pytorch库运行一个RNN模型来对电影评论进行情感分析,但不知为何,训练损失和验证损失在整个训练过程中保持不变。我查阅了不同的在线资源,但仍然卡住了。
请有人能帮助我并查看我的代码吗?
一些参数是由作业指定的:
embedding_dim = 64n_layers = 1n_hidden = 128dropout = 0.5batch_size = 32
我的主要代码
txt_field = data.Field(tokenize=word_tokenize, lower=True, include_lengths=True, batch_first=True)label_field = data.Field(sequential=False, use_vocab=False, batch_first=True)train = data.TabularDataset(path=part2_filepath+"train_Copy.csv", format='csv', fields=[('label', label_field), ('text', txt_field)], skip_header=True)validation = data.TabularDataset(path=part2_filepath+"validation_Copy.csv", format='csv', fields=[('label', label_field), ('text', txt_field)], skip_header=True)txt_field.build_vocab(train, min_freq=5)label_field.build_vocab(train, min_freq=2)device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')train_iter, valid_iter, test_iter = data.BucketIterator.splits( (train, validation, test), batch_size=32, sort_key=lambda x: len(x.text), sort_within_batch=True, device=device)n_vocab = len(txt_field.vocab)embedding_dim = 64n_hidden = 128n_layers = 1dropout = 0.5model = Text_RNN(n_vocab, embedding_dim, n_hidden, n_layers, dropout)optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)criterion = torch.nn.BCELoss().to(device)N_EPOCHS = 15best_valid_loss = float('inf')for epoch in range(N_EPOCHS): train_loss, train_acc = RNN_train(model, train_iter, optimizer, criterion) valid_loss, valid_acc = evaluate(model, valid_iter, criterion)
我的模型
class Text_RNN(nn.Module): def __init__(self, n_vocab, embedding_dim, n_hidden, n_layers, dropout): super(Text_RNN, self).__init__() self.n_layers = n_layers self.n_hidden = n_hidden self.emb = nn.Embedding(n_vocab, embedding_dim) self.rnn = nn.RNN( input_size=embedding_dim, hidden_size=n_hidden, num_layers=n_layers, dropout=dropout, batch_first=True ) self.sigmoid = nn.Sigmoid() self.linear = nn.Linear(n_hidden, 2) def forward(self, sent, sent_len): sent_emb = self.emb(sent) outputs, hidden = self.rnn(sent_emb) prob = self.sigmoid(self.linear(hidden.squeeze(0))) return prob
训练函数
def RNN_train(model, iterator, optimizer, criterion): epoch_loss = 0 epoch_acc = 0 model.train() for batch in iterator: text, text_lengths = batch.text predictions = model(text, text_lengths) batch.label = batch.label.type(torch.FloatTensor).squeeze() predictions = torch.max(predictions.data, 1).indices.type(torch.FloatTensor) loss = criterion(predictions, batch.label) loss.requires_grad = True acc = binary_accuracy(predictions, batch.label) optimizer.zero_grad() loss.backward() optimizer.step() epoch_loss += loss.item() epoch_acc += acc.item() return epoch_loss / len(iterator), epoch_acc / len(iterator)
我在10个测试评论+5个验证评论上运行的输出
Epoch [1/15]: Train Loss: 15.351 | Train Acc: 44.44% Val. Loss: 11.052 | Val. Acc: 60.00%Epoch [2/15]: Train Loss: 15.351 | Train Acc: 44.44% Val. Loss: 11.052 | Val. Acc: 60.00%Epoch [3/15]: Train Loss: 15.351 | Train Acc: 44.44% Val. Loss: 11.052 | Val. Acc: 60.00%Epoch [4/15]: Train Loss: 15.351 | Train Acc: 44.44% Val. Loss: 11.052 | Val. Acc: 60.00%...
如果有人能指出正确的方向,我将不胜感激,我认为是训练代码的问题,因为大部分我都遵循了这篇文章:https://www.analyticsvidhya.com/blog/2020/01/first-text-classification-in-pytorch/
回答:
在你的训练循环中,你使用了最大操作的索引,这是不可以微分的,因此你无法通过它来追踪梯度。因为它不可微分,所以之后的所有操作也无法追踪梯度。调用loss.backward()
将会失败。
# 最大操作的索引是不可以微分的predictions = torch.max(predictions.data, 1).indices.type(torch.FloatTensor)loss = criterion(predictions, batch.label)# 设置requires_grad为True以使.backward()工作,尽管不正确。loss.requires_grad = True
你可能希望通过设置requires_grad
来解决这个问题,但那并不会达到你期望的效果,因为没有梯度传播到你的模型中,因为你的计算图中唯一的东西就是损失本身,并且从那里开始无处可去。
你使用索引来得到0或1,因为你的模型的输出基本上是两个类,你想要那个概率较高的类。对于二元交叉熵损失,你只需要一个类,其值在0和1之间(连续的),你可以通过应用sigmoid函数来获得这个值。
因此,你需要将最终线性层的输出通道更改为1:
self.linear = nn.Linear(n_hidden, 1)
并且在你的训练循环中,你可以移除torch.max
调用以及requires_grad
。
# 压缩模型的输出以去除单个类维度predictions = model(text, text_lengths).squeeze()batch.label = batch.label.type(torch.FloatTensor).squeeze()loss = criterion(predictions, batch.label)acc = binary_accuracy(predictions, batch.label)optimizer.zero_grad()loss.backward()
由于你最后只有一个类,一个实际的预测将是0或1(没有中间值),要实现这一点,你可以简单地使用0.5作为阈值,因此一切低于这个值的都被认为是0,一切高于这个值的都被认为是1。如果你使用的是你所遵循的文章中的binary_accuracy
函数,这会自动为你完成。他们通过torch.round
来实现这一点。