首页
学习
活动
专区
工具
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元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券