数据已经转成 Tensor,调用 model(X_trainTensor) 却立刻失败;还没执行 loss.backward(),就看到 Float 和 Double 不一致。遇到下面这条错误,先检查第一层矩阵乘法的输入和权重:
RuntimeError: Expected object of scalar type Float but got scalar type Double for argument #4 'mat1'
先确定要改的是哪两个对象
对应的原始问题使用 47 个特征,模型开头是 Linear(47,33),输入由 torch.from_numpy(X_train) 构造。失败行是:
y_pred = model(X_trainTensor)
这时还没有计算 NLLLoss,先改标签类型解决不了这次前向错误。第一层 Linear 实际计算的是 输入 @ 权重的转置 + 偏置:输入的最后一维应为 47,权重形状为 [33,47],浮点类型也应一致。
下面构造四行示例数据,保留这个 47→33→2 的结构。它不读取原帖的数据,不需要 GPU,可以在已经安装 NumPy、PyTorch 的环境中直接运行:
import numpy as np
import torch
from torch import nn
torch.manual_seed(7)
X_train = np.arange(4 * 47, dtype=np.float64).reshape(4, 47) / 100
X_trainTensor = torch.from_numpy(X_train)
model = nn.Sequential(
nn.Linear(47, 33), nn.ReLU(),
nn.Linear(33, 2), nn.LogSoftmax(dim=1),
)
first = model[0]
print("josh050608")
print("input:", X_trainTensor.shape, X_trainTensor.dtype, X_trainTensor.device)
print("weight:", first.weight.shape, first.weight.dtype, first.weight.device)
print("numpy dtype:", X_train.dtype)
try:
model(X_trainTensor)
except RuntimeError as exc:
print("forward error:", exc)
这个例子会打印 input 为 float64、weight 为 float32。不同 PyTorch 版本的最后一句错误可能不同,但这两个检查值足以解释此例的失败。
回到自己的工程,把 first = model[0] 和下面的检查放在失败调用前。如果模型不是 Sequential,改成堆栈中实际失败的层,例如 first = model.fc1;如果失败的是中间层,应打印送到该层的中间张量,而不是只看最初的图片。
NumPy 转成 Tensor,为什么还会留下 Double
torch.from_numpy 不会自动把 float64 降成 float32。上例明确创建了 float64 数组,转成 Tensor 后仍是 torch.float64;默认创建的浮点 Linear 权重通常是 torch.float32。
这里的 Float、Double 是类型名称,对应 float32、float64。不能根据“输入已经是 Tensor”判断类型一致,也不应根据打印出来的小数位数判断。
先读检查值,再选修复方向:
| 检查结果 | 应该处理的位置 |
|---|---|
| 输入 float64,权重 float32,项目按 float32 训练 | 在数据转换或进入模型前把特征转成 float32 |
| 输入 float64,模型有意按 float64 训练 | 把模型及其他浮点输入统一为 float64 |
| 两者 dtype 已一致,但设备不同 | 继续统一 device;这不是同一个类型问题 |
| 第一层检查一致,仍在后续层失败 | 到真正失败层前检查中间激活与该层权重 |
方案一:保留 float32 模型,转换特征
继续使用前面的示例,在前向前执行:
weight = model[0].weight
X_trainTensor = X_trainTensor.to(dtype=weight.dtype, device=weight.device)
对于明确采用 float32 的项目,也可以更早写成 X_train.astype(np.float32) 后再 from_numpy。这两种写法选择一处作为统一入口即可;训练集、验证集和测试集都应遵循相同规则。
注意 .float() 和 .to() 的返回值要接住。只写一行 X_trainTensor.float(),再把原变量送入模型,并没有替换后续使用的对象。
方案二:项目需要 float64,就转换模型
如果双精度是项目的明确要求,不要为了消除报错把输入降为 float32。改用下面这组代码;它是上一方案的替代方案:
model = model.to(dtype=torch.float64)
X_trainTensor = X_trainTensor.to(
dtype=torch.float64, device=next(model.parameters()).device,
)
在创建优化器、恢复优化器状态之前确定模型类型更清楚。若还有自定义损失、其他浮点输入或手工创建的状态张量,也应按实际运算要求检查;不要只给某一层换类型,留下前后冲突。
不止验证前向,还要走完一个训练 step
任选上面一个方案后,继续运行这段示例。targets 是给四行示例数据配的合成二分类标签,不是原帖真实标注:
targets = torch.tensor([0, 1, 0, 1], dtype=torch.long,
device=next(model.parameters()).device)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer.zero_grad()
log_probs = model(X_trainTensor)
loss = nn.NLLLoss()(log_probs, targets)
assert log_probs.shape == (4, 2)
assert torch.isfinite(loss)
loss.backward()
assert model[0].weight.grad is not None
assert torch.isfinite(model[0].weight.grad).all()
optimizer.step()
print("output:", log_probs.shape, log_probs.dtype)
print("gradient finite:", bool(torch.isfinite(model[0].weight.grad).all()))
特征与模型统一为 float64 时,类别标签仍然是 long。这里 NLLLoss 使用每行一个类别索引,不能把所有张量一起转成 double。
回到真实数据后,再检查输出是否为 [样本数,2]、标签是否为同样数量的 0/1 编号。类型修复应同时保留模型精度要求和分类目标的含义。