一 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元无门槛券
私享最新 技术干货