【深度学习笔记】2 自动求梯度

注:本文内容为《动手学深度学习》开源内容,仅为个人学习记录,无抄袭搬运意图

2.3 自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient)。PyTorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。本节将介绍如何使用autograd包来进行自动求梯度的有关操作。

前置概念-梯度

在机器学习和深度学习中,梯度是一个非常重要的概念。梯度是一个向量,它表示函数在某一点上的变化率或斜率方向。在深度学习中,梯度通常指的是损失函数(或成本函数)关于模型参数的偏导数

具体来说,如果我们有一个函数f(x1, x2, …, xn),其中x1, x2, …, xn是函数的输入变量,我们可以计算函数f在某一点(x1, x2, …, xn)处的梯度,记为∇f(x1, x2, …, xn)。这个梯度向量包含了函数f在该点上每个输入变量的偏导数,指示了函数在该点上沿着哪个方向变化最快。

在深度学习中,我们通常希望最小化损失函数来训练模型,因此需要计算损失函数关于模型参数的梯度。通过使用梯度下降等优化算法,我们可以沿着梯度的反方向更新模型参数,使损失函数逐渐减小,从而提高模型的性能。

总之,梯度在深度学习中扮演着至关重要的角色,它帮助我们理解函数的变化规律、优化模型参数以及实现模型的训练和优化过程。

梯度是一个向量,表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。

在二维空间中,梯度是一个二维向量,通常用 ∇ f ( x , y ) = [ ∂ f ∂ x   ∂ f ∂ y ] \nabla f(x,y) = \begin{bmatrix} \frac{\partial f}{\partial x} \ \frac{\partial f}{\partial y} \end{bmatrix} f(x,y)=[xf yf]表示。在三维空间中,梯度是一个三维向量,通常用 ∇ f ( x , y , z ) = [ ∂ f ∂ x   ∂ f ∂ y   ∂ f ∂ z ] \nabla f(x,y,z) = \begin{bmatrix} \frac{\partial f}{\partial x} \ \frac{\partial f}{\partial y} \ \frac{\partial f}{\partial z} \end{bmatrix} f(x,y,z)=[xf yf zf]表示。

在机器学习和深度学习中,梯度下降是沿着梯度所指出的方向一步一步向下走找出损失函数最小值的过程。而梯度是一个向量,方向代表了函数在这个方向上增长的方向,模代表了函数在这个方向上的增长速率。

2.3.1 概念

上一节介绍的Tensor是这个包的核心类,如果将其属性.requires_grad设置为True,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用.backward()来完成所有梯度计算。此Tensor的梯度将累积到.grad属性中。

注意在y.backward()时,如果y是标量,则不需要为backward()传入任何参数;否则,需要传入一个与y同形的Tensor。解释见 2.3.2 节。

如果不想要被继续追踪,可以调用.detach()将其从追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外,还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数(requires_grad=True)的梯度。

Function是另外一个很重要的类。TensorFunction互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor都有一个.grad_fn属性,该属性即创建该TensorFunction, 就是说该Tensor是不是通过某些运算得到的,若是,则grad_fn返回一个与这些运算相关的对象,否则是None。

下面通过一些例子来理解这些概念。

2.3.2 Tensor

创建一个Tensor并设置requires_grad=True:

x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)

输出:

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None

再做一下运算操作:

y = x + 2
print(y)
print(y.grad_fn)

输出:

tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x1100477b8>

注意x是直接创建的,所以它没有grad_fn, 而y是通过一个加法操作创建的,所以它有一个为<AddBackward>grad_fn

像x这种直接创建的称为叶子节点,叶子节点对应的grad_fnNone

在PyTorch中,张量(Tensor)对象具有一个属性(attribute)叫做.is_leaf,用于指示张量是否是叶子张量(leaf tensor)。

在PyTorch中,张量分为两种类型:叶子张量和非叶子张量。叶子张量是由用户直接创建的张量,而非叶子张量是通过对叶子张量进行操作得到的张量。叶子张量是计算图(computational graph)的起点,不会自动进行梯度计算,而非叶子张量会随着计算图的构建而自动求导。

print(x.is_leaf, y.is_leaf) # True False

再来点复杂度运算操作:
其中.mean()为计算张量的平均值(即所有元素之和除以元素总个数),以下例子中z的均值即为(27+27+27+27)/4=27

z = y * y * 3
out = z.mean()
print(z, out)

输出:

tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)

通过.requires_grad_()来用in-place的方式改变requires_grad属性(在2.3.1的概念部分有说明该属性的作用):

a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad) # False
a.requires_grad_(True)
print(a.requires_grad) # True
b = (a * a).sum()
print(b.grad_fn)

输出:

False
True
<SumBackward0 object at 0x118f50cc0>

2.3.3 梯度

因为out是一个标量,所以调用backward()时不需要指定求导变量:

out.backward() # 等价于 out.backward(torch.tensor(1.))

我们来看看out关于x的梯度 d ( o u t ) d x \frac{d(out)}{dx} dxd(out):

print(x.grad)

输出:

tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

我们令out o o o , 因为
o = 1 4 ∑ i = 1 4 z i = 1 4 ∑ i = 1 4 3 ( x i + 2 ) 2 o=\frac14\sum_{i=1}^4z_i=\frac14\sum_{i=1}^43(x_i+2)^2 o=41i=14zi=41i=143(xi+2)2
所以
∂ o ∂ x i ∣ x i = 1 = 9 2 = 4.5 \frac{\partial{o}}{\partial{x_i}}\bigr\rvert_{x_i=1}=\frac{9}{2}=4.5 xio xi=1=29=4.5
所以上面的输出是正确的。

数学上,如果有一个函数值和自变量都为向量的函数 y ⃗ = f ( x ⃗ ) \vec{y}=f(\vec{x}) y =f(x ), 那么 y ⃗ \vec{y} y 关于 x ⃗ \vec{x} x 的梯度就是一个雅可比矩阵(Jacobian matrix):
J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ) J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) J= x1y1x1ymxny1xnym
torch.autograd这个包就是用来计算一些雅克比矩阵的乘积的。例如,如果 v v v 是一个标量函数的 l = g ( y ⃗ ) l=g\left(\vec{y}\right) l=g(y ) 的梯度:
v = ( ∂ l ∂ y 1 ⋯ ∂ l ∂ y m ) v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right) v=(y1lyml)
那么根据链式法则我们有 l l l 关于 x ⃗ \vec{x} x 的雅克比矩阵就为:
v J = ( ∂ l ∂ y 1 ⋯ ∂ l ∂ y m ) ( ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ) = ( ∂ l ∂ x 1 ⋯ ∂ l ∂ x n ) v J=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right) \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)=\left(\begin{array}{ccc}\frac{\partial l}{\partial x_{1}} & \cdots & \frac{\partial l}{\partial x_{n}}\end{array}\right) vJ=(y1lyml) x1y1x1ymxny1xnym =(x1lxnl)

注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。

# 再来反向传播一次,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)

out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)

输出:

tensor([[5.5000, 5.5000],
        [5.5000, 5.5000]])
tensor([[1., 1.],
        [1., 1.]])

现在我们解释2.3.1节留下的问题,为什么在y.backward()时,如果y是标量,则不需要为backward()传入任何参数;否则,需要传入一个与y同形的Tensor?
简单来说就是为了避免向量(甚至更高维张量)对张量求导,而转换成标量对张量求导。举个例子,假设形状为 m x n 的矩阵 X 经过运算得到了 p x q 的矩阵 Y,Y 又经过运算得到了 s x t 的矩阵 Z。那么按照前面讲的规则,dZ/dY 应该是一个 s x t x p x q 四维张量,dY/dX 是一个 p x q x m x n的四维张量。问题来了,怎样反向传播?怎样将两个四维张量相乘???这要怎么乘???就算能解决两个四维张量怎么乘的问题,四维和三维的张量又怎么乘?导数的导数又怎么求,这一连串的问题,感觉要疯掉……
为了避免这个问题,我们不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形的张量。所以必要时我们要把张量通过将所有张量的元素加权求和的方式转换为标量,举个例子,假设y由自变量x计算而来,w是和y同形的张量,则y.backward(w)的含义是:先计算l = torch.sum(y * w),则l是个标量,然后求l对自变量x的导数。
参考

来看一些实际例子。

x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
y = 2 * x
z = y.view(2, 2)
print(z)

输出:

tensor([[2., 4.],
        [6., 8.]], grad_fn=<ViewBackward>)

现在 z 不是一个标量,所以在调用backward时需要传入一个和z同形的权重向量进行加权求和得到一个标量。

v = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
z.backward(v) #相当于loss = torch.sum(z*v)    loss.backward()
print(x.grad)

输出:

tensor([2.0000, 0.2000, 0.0200, 0.0020])

注意,x.grad是和x同形的张量。

再来看看中断梯度追踪的例子:

x = torch.tensor(1.0, requires_grad=True)
y1 = x ** 2 
with torch.no_grad():
    y2 = x ** 3
y3 = y1 + y2
    
print(x.requires_grad)
print(y1, y1.requires_grad) # True
print(y2, y2.requires_grad) # False
print(y3, y3.requires_grad) # True

输出:

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<ThAddBackward>) True

可以看到,上面的y2是没有grad_fn而且y2.requires_grad=False的,而y3是有grad_fn的。如果我们将y3x求梯度的话会是多少呢?

y3.backward()
print(x.grad)

输出:

tensor(2.)

为什么是2呢? y 3 = y 1 + y 2 = x 2 + x 3 y_3 = y_1 + y_2 = x^2 + x^3 y3=y1+y2=x2+x3,当 x = 1 x=1 x=1 d y 3 d x \frac {dy_3} {dx} dxdy3 不应该是5吗?事实上,由于 y 2 y_2 y2 的定义是被torch.no_grad():包裹的,所以与 y 2 y_2 y2 有关的梯度是不会回传的,只有与 y 1 y_1 y1 有关的梯度才会回传,即 x 2 x^2 x2 x x x 的梯度。

上面提到,y2.requires_grad=False,所以不能调用 y2.backward(),会报错:

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

此外,如果我们想要修改tensor的数值,但是又不希望被autograd记录(即不会影响反向传播),那么我么可以对tensor.data进行操作。

x = torch.ones(1,requires_grad=True)

print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外

y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播

y.backward()
print(x) # 更改data的值也会影响tensor的值
print(x.grad)

输出:

tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])

注: 本文主要参考PyTorch官方文档,与原书同一节有很大不同。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《鲁伟深度学习笔记》是一本关于深度学习的资料,提供了对深度学习的深入理解和实践指导。这本笔记以简明扼要的方式介绍了深度学习的基本原理和常用算法,并结合实例讲解了如何使用深度学习解决实际问题。 首先,笔记深度学习的基础概念入手,包括神经网络、激活函数、损失函数等,为读者打下坚实的理论基础。然后,笔记详细介绍了深度学习中常用的模型结构,如卷积神经网络、循环神经网络等,并对它们的原理和应用进行了剖析。 此外,笔记还重点介绍了深度学习中的优化算法和正则化方法,如梯度下降、随机梯度下降、批量归一化等,帮助读者了解如何提高模型的性能和减少过拟合。 在实践部分,笔记提供了丰富的代码示例和数据集,通过实际操作,读者可以学到如何使用深度学习框架搭建模型、训练和评估模型,并解决真实世界的问题。 总的来说,《鲁伟深度学习笔记》是一本深度学习入门的好资料,通过阅读笔记,读者可以初步掌握深度学习的基本原理和应用方法,为进一步深入学习和研究打下基础。 ### 回答2: 《鲁伟深度学习笔记》pdf可以在网络上找到,它是对深度学习领域的一本权威教材。这本书由知名的教育家鲁伟撰写,详细介绍了深度学习所涉及的各个方面和应用。该pdf经过了精心编辑和排版,使读者能够很方便地阅读。 这本笔记的内容包括深度学习的基本概念、神经网络的原理、常用的深度学习模型以及它们的应用领域等。书中详细介绍了卷积神经网络、循环神经网络和生成对抗网络等常用模型,并给出了它们的实际案例和代码实现。 阅读《鲁伟深度学习笔记》pdf对于深度学习初学者和从业者来说都是很有价值的。对于初学者来说,它提供了一个很好的入门教材,帮助他们快速了解深度学习的基本概念和应用。对于从业者来说,它提供了详细的技术指导和实践案例,帮助他们提高自己的技术水平和解决实际问题。 总而言之,《鲁伟深度学习笔记》pdf是一本权威且实用的深度学习教材,适合各个层次的读者参考学习。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值