一 l.sum().backward()
在动手学深度学习 3.2 线性回归的从零开始实现中有这样的代码:
import?torch
import?random
import?numpy?as?np
true_w?=?torch.tensor([2,?-3.4])
true_b?=?4.2
#?生成样本和标签
def?synthetic_data(w,?b,?num_examples):
X?=?torch.normal(0,?1,?(num_examples,?len(w)))
y?=?torch.matmul(X,?w)?+?b
y?+=?torch.normal(0,?0.01,?y.shape)
return?X,?y.reshape((-1,?1))
features,?labels?=?synthetic_data(true_w,?true_b,?1000)
def?data_iter(batch_size,?features,?labels):
num_examples?=?len(features)?#?获取样本数量
indices?=?list(range(num_examples))?#?生成数列,数字指向样本
#?打乱样本读取顺序
random.shuffle(indices)
for?i?in?range(0,?num_examples,?batch_size):
batch_indices?=?torch.tensor(indices[i:?min(i+batch_size,?num_examples)])
yield?features[batch_indices],?labels[batch_indices]
#?初始化权重和偏置
w?=?torch.normal(0,?0.01,?size=(2,1),?requires_grad=True)
b?=?torch.zeros(1,?requires_grad=True)
#?定义模型
def?linreg(X,?w,?b):
return?torch.matmul(X,?w)?+?b
#?定义损失函数
def?squred_loss(y_hat,?y):
return?(y_hat?-?y.reshape(y_hat.shape))?**?2?/?2
#?定义优化算法
def?sgd(params,?lr,?batch_size):
with?torch.no_grad():
for?param?in?params:
param?-=?lr?*?param.grad?/?batch_size?#?多个样本的梯度取平均值,不至于一次迈出多个梯度
param.grad.zero_()?#?清空梯度
#?训练
lr?=?0.03
num_epochs?=?3?#?迭代次数
batch_size?=?10
net?=?linreg
loss?=?squred_loss
#?训练模型
for?epoch?in?range(num_epochs):
for?X,?y?in?data_iter(batch_size,?features,?labels):
l?=?loss(net(X,?w,?b),?y)?#?计算损失函数
l.sum().backward()?#?计算各个参数的梯度
sgd([w,?b],?lr,?batch_size)?#?更新参数
一直想不明白训练模型时计算反向传播为什么要写成 l.sum().backward(), 为什么先要求和呢 ?
二 导数
先说明一下向量的导数,假设 y=f(x)。
2.1 x是标量,y是标量
如果x是标量,y为标量,那么y对x的导数为标量。
2.2 x是标量,y是一维列向量
如果 x 是标量,y 为一维列向量,那么 y 对 x 的导数为 1 维列向量。此时可以转为两个标量函数分别求导,然后拼接。
2.3 x是一维行向量,y是标量
如果 x 是1维行向量,y 为标量,那么 y 对 x 的导数是1维行向量。
2.4 x是一维行向量,y是一维列向量
如果x是1维行向量,y为1维列向量,那么y对x的导数是2维矩阵。
三 backword()函数调用
backward是对标量的操作,没办法对向量进行操作。
3.1 y是标量
y=f(x), 当 y 为标量时, 可以直接调用 y.backward() 。
3.1.1 x是标量,y是标量
3.1.2 x是一维行向量,y是标量
3.2 y是向量
当y为向量时,调用 backward 需要传入一个 gradient参数。
对于《动手学深度学习》第二版中2.5小节
作者说,本例只想求偏导数的和,所以传递一个1的梯度最合适。就是将上述式子中的 gradient 参数
赋值为1。而
则
的导数为上面推导的
。所以 y.sum().backward()
的导数 等价于 y.backward(torch.ones(len(x)))
的导数。
.sum()函数主要有两个作用,一个是用来求和,一个是用来降维。而在这里是用到了降维的作用。
PyTorch进行梯度的计算,只能对标量进行梯度计算,若直接使用 y.backward() 会报错:grad can be implicitly created only for scalar outputs。这一问题的解决方法就是先使用.sum()再反向传播。例如
是一个标量,是能够进行梯度计算的,而例如
这是二维的,pytorch并不能进行梯度反向传播计算梯度,所以我们需要使用sum进行降维处理,变成
,对于多元函数便能计算偏微分,求梯度了。例子如下 y_hat 和
是多维的,所以先要 sum 再 backward:
X?=?X.reshape((1,?1,?6,?8))
Y?=?Y.reshape((1,?1,?6,?7))
lr?=?3e-2??#?Learning?rate
for?i?in?range(10):
Y_hat?=?conv2d(X)
l?=?(Y_hat?-?Y)?**?2
conv2d.zero_grad()
l.sum().backward()
#?Update?the?kernel
conv2d.weight.data[:]?-=?lr?*?conv2d.weight.grad
if?(i?+?1)?%?2?==?0:
print(f'epoch?{i?+?1},?loss?{l.sum():.3f}')
print(conv2d.weight.data.reshape((1,?2)))
一个向量是不能进行backward操作的,而sum()后,由于梯度为1,所以对结果不产生影响。
四 总结
PyTorch backward() 进行梯度计算时,只能对标量进行梯度计算。
.sum() 函数主要有两个作用,一个是用来求和,一个是用来降维。
在深度学习中,损失函数都是标量,所以一般情况下可以直接调用backward()就可以了。
领取专属 10元无门槛券
私享最新 技术干货