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

Python实现带负采样Word2Vec时梯度报错及训练不收敛问题排查

问题根因与修正方案

1. tensor have no grad_fn 报错与requires_grad_()的错误用法

你在forward返回值上手动调用.requires_grad_()是完全错误的操作,这个操作没有解决根本问题,反而直接切断了反向传播的梯度通路,是后续模型训练不动的核心原因之一。

这个报错的本质是你的前向传播代码意外切断了自动微分的计算图:

  • nn.Module中定义的nn.Linear层的权重,默认已经开启了requires_grad=True,只要前向过程全程使用PyTorch支持自动微分的张量操作,最终输出的预测张量会自动绑定grad_fn,不需要手动给任何张量开梯度。
  • 你在循环中写的results.append(torch.tensor([forward_result[out_index] for out_index in output_indices_batch[i]]))存在严重问题:用torch.tensor()包裹从已有计算图张量中取出的值生成新张量,会直接斩断新张量和前面线性层权重的计算关联,新生成的是独立的无计算历史的叶子张量,自然没有grad_fn。
  • 你手动给最终输出加.requires_grad_(),相当于告诉autograd“这个张量是独立的叶子节点,梯度只算到这里就行”,反向传播时梯度根本不会传到前面的输入、输出层权重,参数全程不会更新。

2. 训练时损失、准确率无变化的其他诱因

除了上述计算图断裂的问题,你的代码还有几个硬伤直接导致训练失效:

  • 训练循环缩进错误:你的for epoch循环体里只有model.train()一行代码,后面的数据集生成、遍历dataloader、反向传播、参数更新、指标计算全在循环外部,不管设置多少个epoch,实际只会执行一次训练流程。
  • 逐样本Python循环效率极低:你用for循环逐样本生成one-hot、逐样本索引得分,不仅速度慢,也更容易出现意外断图的问题,应该使用批量化的张量操作实现。
  • 数值类型与计算逻辑错误:BCEWithLogitsLoss要求输入标签为浮点型,你直接传入整型标签会出现隐式类型问题;计算平均损失时你直接累加带计算图的损失张量、且除以的步数值少算1(i_step从0开始计数),会导致显存泄漏、损失值计算错误。
  • 指标累加逻辑问题:你直接累加张量类型的正确样本数,会长期持有计算图引用浪费显存,应该转成普通Python数值再累加。

修正后的可运行代码

模型前向传播修正

class Word2VecNegativeSamples(nn.Module):
    def __init__(self, num_tokens, wordvec_dim=10):
        super(Word2VecNegativeSamples, self).__init__()
        self.input = nn.Linear(num_tokens, wordvec_dim, bias=False)
        self.output = nn.Linear(wordvec_dim, num_tokens, bias=False)
        self.num_tokens = num_tokens

    def forward(self, input_index_batch, output_indices_batch):
        '''
        实现带负采样的前向传播
        参数:
        input_index_batch: 整型张量,形状(batch_size,),批次内中心词索引
        output_indices_batch: 整型张量,形状(batch_size, num_negative_samples+1),每个样本对应的正样本+负样本索引
        返回:
        predictions: 浮点张量,形状(batch_size, num_negative_samples+1),对应位置的词得分
        '''
        batch_size = input_index_batch.shape[0]
        # 批量生成one-hot编码,替代逐样本循环
        input_one_hot = torch.zeros(batch_size, self.num_tokens, device=input_index_batch.device)
        input_one_hot.scatter_(1, input_index_batch.unsqueeze(1), 1.0)
        # 计算中心词嵌入
        center_embed = self.input(input_one_hot)
        # 计算所有词的得分
        all_word_scores = self.output(center_embed)
        # 用gather取对应正/负样本位置的得分,不会切断计算图
        predictions = torch.gather(all_word_scores, 1, output_indices_batch)
        return predictions

训练循环修正

def train_neg_sample(model, dataset, train_loader, optimizer, scheduler, num_epochs):    
    loss_fn = nn.BCEWithLogitsLoss()
    loss_history = []
    train_history = []
    for epoch in range(num_epochs):
        model.train()
        dataset.generate_dataset()        
        loss_accum = 0
        correct_samples = 0
        total_samples = 0
        for i_step, (inp, out, lab) in enumerate(train_loader):
            optimizer.zero_grad()
            prediction = model(inp, out)
            # 标签转float匹配BCEWithLogitsLoss输入要求
            loss_value = loss_fn(prediction, lab.float())
            loss_value.backward()
            optimizer.step()
            
            # 计算准确率:正样本在每个样本组的第0位,取预测得分最高的位置判断
            pred_pos = torch.argmax(prediction, dim=1)
            correct_samples += torch.sum(pred_pos == 0).item()
            total_samples += lab.shape[0]
            # 取item()转普通数值累加,避免持有计算图浪费显存
            loss_accum += loss_value.item()
        
        scheduler.step()
        # i_step从0开始,总步数为i_step+1
        ave_loss = loss_accum / (i_step + 1)
        train_accuracy = correct_samples / total_samples
        
        loss_history.append(ave_loss)
        train_history.append(train_accuracy)
        print(f"Epoch#: {epoch}, Average loss: {ave_loss:.6f}, Train accuracy: {train_accuracy:.6f}")
        
    return loss_history, train_history

额外优化建议

  • 用nn.Embedding替代nn.Linear+手动生成one-hot的实现,不需要构造巨大的one-hot张量,训练速度会提升数倍,本质上两者的计算结果是完全等价的。
  • 注意保证模型、输入张量、损失函数在同一个设备(CPU/GPU)上,避免设备不匹配报错。
  • 负采样训练时标签的构造要保证每个样本组的第0位是正样本(标签为1),其余位置是负样本(标签为0),和你现在的准确率判断逻辑对齐。

内容的提问来源于stack exchange,提问作者Даниил Гурин

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 20:09:19