You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyTorch情感分析FFNN模型零准确率问题求助

电影评论情感分析FFNN模型准确率始终为0的问题排查

我基于电影评论数据集,用PyTorch构建了二分类情感分析的前馈神经网络(FFNN),任务仅区分正类(1)和负类(0)。模型流程为:生成词嵌入→对词嵌入取均值→经过两层网络与激活函数→通过sigmoid得到预测概率。但模型能正常运行,准确率却始终为0。

已尝试的解决方案:

  • 更换损失函数
  • 调整模型结构顺序
  • 修改嵌入维度

相关代码

模型代码

from collections import OrderedDict
import torch
from typing import List
import torch.nn as nn

class FeedForwardNeuralNetClassifier(nn.Module):
    """
    前馈神经网络情感分类器。
    """
    def __init__(self, vocab_size, emb_dim, n_hidden_units):
        """
        在__init__函数中定义FFNN的模块。
        :param vocab_size: 词汇表大小
        :param emb_dim: 嵌入向量维度
        :param n_hidden_units: 隐藏层单元维度
        """
        super(FeedForwardNeuralNetClassifier, self).__init__()
        self.vocab_size = vocab_size
        self.emb_dim = emb_dim
        self.n_hidden_units = n_hidden_units
       
        # 使用nn.Embedding实现随机初始化的词嵌入矩阵
        # 尺寸应为(vocab_size x emb_dim)
        self.word_embeddings = nn.Embedding(self.vocab_size, self.emb_dim)

        # 定义模型剩余部分
        self.layer1 = nn.Linear(self.emb_dim, self.n_hidden_units)
        self.layer2 = nn.Linear(self.n_hidden_units, 1)
        self.activation = nn.ReLU()
        self.sigmoid = nn.Sigmoid()

    def forward(self, batch_inputs: torch.Tensor, batch_lengths: torch.Tensor) -> torch.Tensor:
        """
        前向传播函数,定义FFNN在给定一批输入及实际句子长度(填充前)时的运行逻辑
        :param batch_inputs: 尺寸为(n_examples, max_sent_length_in_this_batch)的torch.Tensor对象,即索引化的输入
        :param batch_lengths: 尺寸为(n_examples)的torch.Tensor对象,描述每个样本的实际句子长度(填充前)
        :return FFNN的logits(即sigmoid之前的未归一化隐藏单元),形状为(n_examples)
        """

        # 查找词嵌入
        embeddings = self.word_embeddings(batch_inputs)

        # 逐元素平均层
        averaged = torch.mean(embeddings, dim=1)

        # 隐藏层ReLU激活
        hidden = self.activation(self.layer1(averaged))

        # 输出层
        return self.layer2(hidden)

    def batch_predict(self, batch_inputs: torch.Tensor, batch_lengths: torch.Tensor) -> List[int]:
        """
        对一批输入进行预测。该函数可直接调用forward(将输入传入FFNN并返回输出logits)

        :param batch_inputs: 尺寸为(n_examples, max_sent_length_in_this_batch)的torch.Tensor对象,即索引化的输入
        :param batch_lengths: 尺寸为(n_examples)的torch.Tensor对象,描述每个样本的实际句子长度(填充前)
        :return: 该批数据的预测类别列表,0代表负类,1代表正类
        """
        output = self.forward(batch_inputs, batch_lengths)
         
        # Sigmoid激活
        probability = self.sigmoid(output)

        # 将logits转换为预测标签
        predicted_labels = (probability > 0.5).int().tolist()

        return predicted_labels

FFNN实例化代码

model = FeedForwardNeuralNetClassifier(vocab_size=len(vocab), emb_dim=300, n_hidden_units=300)

# FeedForwardNeuralNetClassifier(
# (word_embeddings): Embedding(4818, 300)
# (layer1): Linear(in_features=300, out_features=300, bias=True)
# (layer2): Linear(in_features=300, out_features=1, bias=True)
#  (activation): ReLU()
#  (sigmoid): Sigmoid()
# )

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
model = model.to(device)

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

训练代码

import time

BATCH_SIZE=32
N_EPOCHS=20

# 为训练数据创建批次迭代器
batch_iterator = SentimentExampleBatchIterator(
    train_exs, batch_size=BATCH_SIZE, PAD_idx=PAD_IDX, shuffle=True)

# 损失函数
loss_func = nn.BCEWithLogitsLoss()

# 训练过程
best_epoch = -1
best_acc = -1
start_time = time.time()
for epoch in range(N_EPOCHS):
    print("第%i轮训练" % epoch)

    batch_iterator.refresh() # 为当前轮次初始化新的迭代器

    model.train() # 开启训练模式
    batch_loss = 0.0
    batch_example_count = 0
    batch_data = batch_iterator.get_next_batch()
    while batch_data is not None:
        batch_inputs, batch_lengths, batch_labels = batch_data
        # 将数据移至指定设备
        batch_inputs = batch_inputs.to(device)
        batch_lengths = batch_lengths.to(device)
        batch_labels = batch_labels.unsqueeze(1)
        batch_labels = batch_labels.to(device)
        
        # 清除当前批次的梯度
        optimizer.zero_grad()
        
        # 调用模型并计算损失
        outputs = model(batch_inputs, batch_lengths).float()
        
        batch_labels = batch_labels.float()
        loss = loss_func(outputs, batch_labels)
        # 记录损失和样本数量,用于统计
        batch_example_count += len(batch_labels)
        batch_loss += loss.item() * len(batch_labels)

        # 基于损失进行反向传播
        loss.backward()

        # 更新模型参数
        optimizer.step()

        # 获取下一批数据
        batch_data = batch_iterator.get_next_batch()

    print("平均损失: %.5f" % (batch_loss / batch_example_count))

    # 在验证集上评估
    model.eval() # 开启评估模式
    acc, _, _, _ = evaluate(model, dev_exs, return_metrics=True)
    if acc > best_acc:
        best_acc = acc
        best_epoch = epoch
        print("第%d轮训练取得新的最佳准确率%.3f!" % (best_epoch, best_acc))
        
        # 保存当前最佳模型参数
        print("将最佳模型 checkpoint 保存为best_model.ckpt!")
        torch.save(model.state_dict(), "best_model.ckpt")
    
    print("已耗时: %s" % time.strftime("%Hh%Mm%Ss", time.gmtime(time.time()-start_time)))
    print("-" * 10)

print("训练结束! 最佳准确率%.3f出现在第%d轮训练。" % (best_acc, best_epoch))

# 加载验证集上的最佳checkpoint
model.load_state_dict(torch.load("best_model.ckpt"))

问题排查与修复建议

1. 词嵌入平均未忽略PAD token

当前模型对整个序列的词嵌入取均值,包含填充的PAD token。PAD token的随机初始化嵌入会稀释有效词的特征信息,导致模型无法学习到有用的情感特征。

修复代码:在forward函数中替换原平均逻辑,基于batch_lengths生成掩码,仅对有效token的嵌入取平均:

# 替换原averaged = torch.mean(embeddings, dim=1)
mask = (batch_inputs != PAD_IDX).unsqueeze(-1).float()
sum_embeddings = torch.sum(embeddings * mask, dim=1)
# 避免样本长度为0时除以0
lengths = batch_lengths.clamp(min=1).unsqueeze(-1).float()
averaged = sum_embeddings / lengths

2. PAD token嵌入未做特殊处理

PAD token的嵌入不需要参与学习,建议将其固定为0向量,避免干扰模型训练:

# 在模型__init__中添加
self.word_embeddings = nn.Embedding(self.vocab_size, self.emb_dim)
# 固定PAD token嵌入为0
self.word_embeddings.weight.data[PAD_IDX] = torch.zeros(self.emb_dim)
# 禁止PAD token嵌入的梯度更新
self.word_embeddings.weight.requires_grad = True
self.word_embeddings.weight.register_hook(lambda grad: grad.index_fill_(0, torch.tensor(PAD_IDX, device=grad.device), 0.0))

3. 数据与训练流程检查

  • 确认训练/验证集的标签分布是否均衡,若某一类占比极高,模型可能会偏向预测多数类导致准确率为0
  • 检查SentimentExampleBatchIterator是否正确生成输入、长度和标签,确保标签没有被错误统一为0或1
  • 在训练过程中打印部分预测结果,确认模型是否始终输出同一类别,以此判断是模型学习问题还是数据问题

4. 评估函数验证

检查evaluate函数是否正确调用batch_predict,且标签匹配逻辑无误,避免因评估逻辑错误导致准确率计算异常


内容的提问来源于stack exchange,提问作者nojibe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 22:54:54