学习场景:
最近在B站上跟着李沐大佬学【动手学深度学习v2】,大佬讲的很好,但是有些东西对我这种小白来说还是有一些不太好理解,所以在我想明白之后做个笔记。或许能帮助别人理解,也欢迎大佬指出我存在的问题。
问题描述
在【动手学深度学习v2】第八节课的 “P3线性回归的从零开始实现 ” 这个视频里,讲到一段代码:
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward() #这里的求和不太理解
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
里面的 l.sum().backward() 卡了我好久都没理解。
原因分析:
主要是输出了 l 看了一下 ,就是一个N*1的列向量(这么说好理解),形式如下:
我看到一些解释,和李沐大佬的意思是对 I 求和(sum())就变成了一个数字累加和,也就是变成了一个标量,符合了.backward() 的只对标量求梯度的标准,能够用.backward() 进行梯度计算。但是我就是觉得这一堆数加起来再对w和b求梯度就感觉很怪,不知道应该怎么理解这里。
理解思路:
其实看上面这张图反而更难以理解,上面的列向量里面的值都为确定的是因为计算的式子里没有未知数,但是后面我们其实需要把w和b当成未知数去求梯度,所以 l = loss(net(X, w, b), y) 中我们获得的返回值,即 (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 ,我们不要把他算出来,我们把它看成 一个 f(w,b)的函数,这样我们认为输出的 l 可以看成显示的结果为:
tensor([[ f1(w,b)],
[ f2(w,b)],
[ f3(w,b)],
[ f4(w,b)],
[ f5(w,b)],
[ f6(w,b)],
[ f7(w,b)],
[ f8(w,b)],
[ f9(w,b)],
[ f10(w,b)]])
那么我们再进行 l.sum() 的相加操作时得到的就是 [f1(w,b)+f2(w,b)+f3(w,b)+f4(w,b)+f5(w,b)+f6(w,b)+f7(w,b)+f8(w,b)+f9(w,b)+f10(w,b)] 这样的一个标量,对他就可以使用 .backward() 求梯度(也就是对于各个f(w,b)式子求导再加起来),最后得到的是各个梯度的和。
而后续的程序中函数sgd(params, lr, batch_size) 是对于w和b进行优化的函数,里面会对 l.sum().backward() 得到的梯度和进行处理,除一个梯度个数,得到平均梯度。