您的位置首页 >人工智能 >

负采样 ID 报 “Expected indices to have scalar type Long”MovieLens 里那几个 id,得显式转成 long

这是一个 PyTorch 类型与索引报错的场景,我去核实 Embedding、gather 对索引 dtype 的官方要求,以及 MovieLens 数据集的真实字段口径。

官方文档对 Embedding 的输入 dtype 和 gather 的 LongTensor 索引已经说清楚了;我再补两条:经典报错文案的社区记录,以及负采样实现里索引怎么传。

这个报错的意思是:传给 Embedding 或 gather 的索引张量类型不对,不是整数张量。PyTorch 的查表类算子只接受整数索引,不做隐式类型转换,你给一个 float 它就原样拒绝。最典型的来源是 MovieLens 的读取链路:pd.read_csv 之后做了除法、归一化、np.random.random() 生成负样本、或者把 id 和一个 float 常量拼在一起,dtype 就悄悄变成了 float64,一路传到 nn.Embedding 的 argument 1 才炸。修法就三处:读入时就把 id 列钉成 int64、负采样器直接产出 torch.long、进模型前加一道 dtype 断言。别指望在 forward 里 .long() 一把糊过去,如果那个 float 是被算坏的,转换只会把错数据变成能跑通的错数据。

报错到底在说哪个参数

不同算子对索引入参的措辞不一样,认准参数名比背整句报错更有用:

报错里的参数出现在哪官方要求说明
argument #1 'indices'nn.Embedding / F.embedding输入为 IntTensor 或 LongTensor,任意形状,元素是 “要抽取的索引”索引不是特征值,必须是整数
indextorch.gather(input, dim, index)index (LongTensor) —— 要收集的元素的索引gather 的 index 明确写 LongTensor
targetnn.CrossEntropyLoss / NLLLoss类别下标,整型同类错误的另一个高发点

一条容易踩的口径差:报错文案里 “必须有 scalar type Long” 这句话,在旧版本里是硬要求,新版本文案已经扩成 “Long 或 Int 之一”。也就是说,某些版本上 IntTensor 能过,某些版本不能。工程上不要跟着文案走,统一用 long 最省事 —— 它跨版本、跨 CPU/CUDA 都稳。

MovieLens 的 id 是怎么变成 float 的

把常见来源摊开,基本能一眼认出自己踩的是哪条:

  1. pd.read_csv 读 u.data 时没指定 dtype,遇到含缺失值的列 pandas 会给 float64,整列跟着变。
  2. 做了 rating / 5、(id - mean) / std 这类运算,结果张量继承 float。
  3. 负采样用 np.random.random() 再乘 n_items 取整,中间是 float,忘了 .astype('int64')。
  4. 用 np.random.choice(n_items, size=...) 结果没问题,但之后又和 float 数组做了 hstack/concatenate,整块被提升为 float。
  5. 把 user id 和 item id 拼成一个 [B, 2] 的矩阵喂给模型,其中一列是 float,另一列是 int,拼接时统一提升。
  6. 从 DataFrame 取值时用了 .values 但列本身是 float(比如先做过 fillna(0) 再算平均)。

机制这一层:为什么必须是整数

Embedding 本质是一张 num_embeddings × embedding_dim 的查找表,前向就是 “按行号取一行”。行号天然是整数,没有第 1.5 行。gather 同理,它按 index 里的每个数字去 input 里挑元素,索引必须是可数的整数。所以 PyTorch 在这里选择直接报错,而不是替你四舍五入 —— 隐式转换会掩盖 “我传错东西了” 这件事。

三处一起改,改一处不够

环节要做的动作具体写法检查点
读入显式声明 id 列为整型pd.read_csv(..., dtype={'user id':'int64','item id':'int64','rating':'float32'})df.dtypes 里 id 是 int64
负采样采样器直接产出整型rng.integers(0, n_items, size=(B,k), dtype=np.int64)返回张量 dtype=torch.int64
传参转 tensor 时带上 dtypetorch.as_tensor(neg, dtype=torch.long)打印 .dtype 确认
前向加断言,早失败assert ids.dtype == torch.long报错位置从 embedding 前移到数据入口

负采样那段可以直接照这个骨架改:

import numpy as np, torch

def sample_negatives(n_users, n_items, batch_size, k=1, seed=0):

rng = np.random.default_rng(seed)

neg = rng.integers(0, n_items, size=(batch_size, k), dtype=np.int64)

return torch.as_tensor(neg, dtype=torch.long) 关键:dtype 明确写死

正样本那边同样要盯住。MovieLens 的 u.data 是 user id | item id | rating | timestamp 四列制表符分隔,用户和物品都从 1 开始连续编号,不是从 0。这件事和 dtype 是两个独立的坑,但经常一起爆:

口径事实后果处理
id 起点100K 数据集用户与物品从 1 开始连续编号直接当 0-based 索引用,会整体错位一行建表时 id - 1,或 num_embeddings = max_id + 1 留一行空位
数量100K:100,000 条评分、943 用户、1682 电影num_embeddings 写小了直接越界用 max(id)+1 而不是 “大致数量”
评分尺度100K 是 1–5 整数星;10M 版是 5 星半星递增把评分当连续值回归还是当类别,口径不同先定任务再定 loss,别混用
时间戳Unix 秒(1970-01-01 UTC 起算)当 relative 索引会溢出单独归一化,不要塞进 embedding

验证这四步,比反复重装有用

  1. 打印 dtype。在 DataLoader 出一个 batch 后立刻 print(batch['item_id'].dtype, batch['item_id'].shape),float 一眼就看见。
  2. 打印取值域。batch['item_id'].min(), .max() 必须落在 [0, num_embeddings-1],出现负数或超界,说明是索引构造的问题,不是 dtype 的问题。
  3. 加断言前移。在 forward 第一行写 assert x.dtype == torch.long,让错误发生在你的代码里,而不是埋在算子内部。
  4. 最小复现。拿 torch.tensor([[1,2],[3,4]], dtype=torch.long) 直接喂 nn.Embedding(10, 3),能过就说明模型侧没问题,问题在数据侧。

六个常见误判,避开能省一下午

做法为什么不行
在 forward 里对输入无脑 .long()掩盖了上游数据错误;float 转 long 是截断,1.7 变 1
只改负采样,正样本还是 float报错会从负样本分支挪到正样本分支,看着像 “没修好”
改 num_embeddings 大小类型错和越界错是两回事,混着改会引入新错误
升级 / 重装 torch类型校验是设计行为,不是 bug
把 IntTensor 当 “差不多能用”跨版本行为不一致,换机器就复现
用 .to(torch.int32) 代替 .long()部分算子只认 int64,省不了显存还留隐患

回头看:这个报错其实是好事

索引类型错误是所有深度学习报错里最好修的一类 —— 它有明确的参数名、明确的期望类型、明确的发生位置,不需要调梯度、不需要猜数值。真正难缠的是那种 “跑得通但指标不对” 的静默错误。比如你 .long() 了一个本该保留小数的特征,loss 会降、指标会动,但模型学到的东西是错的。这也是为什么建议把 dtype 校验放在数据入口,而不是模型内部:入口处报错,你知道是数据问题;模型内部报错,你只知道自己写错了一行。

【相关 FAQ】

问题 1:负采样的 ID 已经是整数了,为什么还会报 Expected indices to have scalar type Long?

回答 1:整数分两种 ——Python 的 int、numpy 的 int32/int64、张量的 int32,都和 PyTorch 索引默认要求的 int64(即 torch.long)不是同一个东西。MovieLens 的负采样如果先经过 np.random.random() n_items 再取整,或者 id 列被 pandas 提升成了 float64,落到张量上就是 float。判据很直接:打印 neg.dtype,只要不是 torch.int64,就按 torch.as_tensor(neg, dtype=torch.long) 显式指定。

问题 2:MovieLens 的物品 id 从 1 开始,负采样时要不要减 1,会不会影响 Long 类型报错?

回答 2:这两件事要分开处理。id 从 1 开始连续编号是索引构造问题,处理方式是建表时 id-1 或把 num_embeddings 开到 max_id + 1;Long 是 dtype 问题,靠 .long() 解决。两者会互相伪装:越界会报 IndexError 或 CUDA 断言失败,类型错才报 Expected indices to have scalar type Long。先看报错参数名是 indices 还是范围错误,再决定改哪一处,别同时动两个变量。

问题 3:在 forward 里对输入统一 .long() 行不行,为什么非要回到数据源头改?

回答 3:技术上能消除报错,工程上不建议。float → long 是截断转换,1.7 会变成 1,如果这个 float 是除法或归一化算出来的,你等于把一批算坏的数据强行塞进 Embedding,模型照样训练、loss 照样下降,但语义已经错了。正确顺序是先修数据口径(读入时定 dtype、采样器产出 int64),再在入口加断言,让 Expected indices to have scalar type Long 这类错误在数据管道里就暴露,而不是等它穿过三层网络。

问题 4:MovieLens 负采样除了 dtype,还有哪些容易连带出错的点?

回答 4:三类。一是采样范围,负样本必须从用户未交互集合里取,直接在全量物品里均匀采会抽到正样本,BPR 的 pairwise 假设就破了;二是数量口径,每个正样本配几个负样本要先定死,MovieLens 100K 只有 10 万条评分、1682 部电影,负样本比例过高会让梯度被负例主导;三是采样器的一致性,训练和验证要用不同的随机种子但不能用不同的采样逻辑,否则指标不可比。这三条和 Expected indices to have scalar type Long 无关,但同属负采样 ID 构造这一环,建议一起在数据管道的自检里固定下来。

【参考资料】

[2026-11-01](torch.nn.functional.embedding)PyTorch 官方文档

[2026-10-27](Heterophily-Aware Fair Recommendation using Graph Convolutional Networks)arXiv

[2026-10-21](词嵌入:编码词汇语义)PyTorch 教程

[2026-09-01](Embedding)PyTorch 2.13 官方文档

[2026-08-28](Embedding)PyTorch 2.6 中文文档

[2026-08-05](MovieLens 100K README)GroupLens

[2026-08-04](torch.gather)PyTorch 2.7 中文文档

[2026-07-07](MovieLens 10M/100K Data Set README)GroupLens

[2022-11-01](Expected tensor for argument 1 'indices' to have scalar type Long; but got CPUFloatTensor instead)PyTorch Forums

[2020-02-13](RuntimeError: Expected tensor for argument 1 'indices' to have scalar type Long)PyTorch Forums

[2019-06-20]([solved] Expected tensor for argument 1 'indices' to have scalar type Long; but got CPUDoubleTensor instead)PyTorch Forums

免责声明: 本文依据 PyTorch 官方文档中 torch.nn.Embedding、torch.nn.functional.embedding、torch.gather 的入参说明与词嵌入教程的表述,PyTorch 官方论坛关于 indices 类型报错的多个案例帖,以及 GroupLens 官方 MovieLens 100K / 10M 数据集 README 整理。信息属性需区分:Embedding 输入为 IntTensor 或 LongTensor、gather 的 index 为 LongTensor、id 从 1 开始连续编号、100K 为 943 用户 1682 电影 10 万条评分、时间戳为 Unix 秒,这些来自官方文档与数据集 README;报错文案中的可接受类型集合随版本变化(旧版仅 Long,较新版为 Long 或 Int 之一),此判断来自论坛不同年份的报错原文对比,未逐版本验证,具体以你所装版本实测为准;.long() 对 float 执行截断、以及 int32 在部分算子不被接受,属于依据类型转换语义与算子校验行为的工程经验,非官方明文规定。MovieLens 各版本字段、评分尺度与划分脚本存在差异,落地前请以你所下载版本的 README 为准。本文为技术排障说明,不构成对任何框架、模型结构或训练策略的推荐,也不保证按文中方法处理后模型效果符合预期;数据集使用需遵守 GroupLens 的许可条件,不得声称获得明尼苏达大学或 GroupLens 研究组的背书。

PyTorchPyTorchPyTorchPyTorchPyTorchGroupLensGroupLensPyTorchPyTorchPyTorcharXiv