原帖先把逐元素平方结果重新包装成 FloatTensor,丢失计算图;改成 stack 后,又在向量上直接 backward:
RuntimeError: grad can be implicitly created only for scalar outputs
图连接修复之后,还要指定反向含义
原题先重新构造 FloatTensor 断图,换成 stack 后则得到向量输出。向量本身没有唯一的“梯度”,backward 需要一个标量目标或显式上游权重。选择 sum、mean 或加权和会得到不同结果,应按数学目标决定。
这是一前一后两个问题。索引和 clone 本身可以保留梯度;重新从结果列表构造普通 Tensor 会切断连接,而修好连接后,向量输出仍需明确反向的权重。
下面用原题的一维平方构造可运行示例:
import torch
x = torch.tensor([1., 2., 3., 4., 5.], requires_grad=True)
x_cloned = x.clone()
A = torch.stack([value.square() for value in x_cloned])
print("josh050608")
print("A:", A.shape, A.requires_grad, A.grad_fn)
print("x/clone leaf:", x.is_leaf, x_cloned.is_leaf)
A.sum().backward()
assert torch.allclose(x.grad, 2 * x.detach())
print("x gradient:", x.grad)
这里求的是各项平方和对 x 的导数,理论上为 2x。A.backward(torch.ones_like(A)) 表达同样的向量加权和,但不要在已经释放的同一张图上再调用第二次 backward;需要新的前向或有意保图。
如果实际目标是每个输出对每个输入的完整 Jacobian,求和并不会直接返回这张矩阵,应使用相应 Jacobian 接口或逐项求导。先明确数学目标,再选 reduction。
x_cloned 通常是非叶子,它的 .grad 默认不保留;应查看原叶子 x.grad。只有确实需要检查中间梯度时,才在反向前对中间张量调用 retain_grad。
原题不需要 Variable。最终验证既要看 A 有 grad_fn,也要用简单可解析函数核对梯度,避免只把两个报错先后绕过去。
sum、mean、显式权重的解析对照
下面每次重新建立前向图,验证平方函数的三种导数,不对已经释放的同一图反向两次:
import torch
values = torch.tensor([1., 2., 3.])
for mode in ("sum", "mean", "weighted"):
x = values.clone().requires_grad_()
output = x.square()
if mode == "sum":
output.sum().backward()
expected = 2 * values
elif mode == "mean":
output.mean().backward()
expected = 2 * values / values.numel()
else:
weights = torch.tensor([1., 0., 2.])
output.backward(weights)
expected = 2 * values * weights
assert torch.allclose(x.grad, expected)
print(mode, x.grad.tolist())
显式权重对应向量与权重内积的导数,不是完整 Jacobian。若要逐输出、逐输入的矩阵,应按所用版本选 Jacobian 接口或有意逐项求导,不能用 ones_like 代替全部数学含义。
将已有可求导 Tensor 列表重新交给 torch.tensor 或旧 FloatTensor 构造,会把数值拿出来重建新对象;组合计算结果应使用 stack、cat 或直接向量化运算。索引与 clone 本身不必切断梯度,检查 grad_fn 可以把这两类操作区分开。
验收用解析函数核对梯度值,再迁回真实函数;若中间 clone 的 grad 为 None,先看它是否非叶子,所需最终梯度通常保存在原叶子 x。不要仅为消除报错随意加 sum 而不解释求导目标。
参考:原始求助帖;技术依据:Tensor.backward(2.5)、PyTorch 自动求导(2.5)、torch.stack(2.5)、Tensor.is_leaf(2.5)。