首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

loss.sum().backward()中对于sum()的理解

一 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()就可以了。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OLZuLKeKI2nJPQtrrZ8-UT8Q0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com