动手学深度学习学习笔记(4)

自动求梯度
PyTorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。

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

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

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

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

创建一个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=<AddBackward0>)
<AddBackward0 object at 0x000001CFA56F8608>

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

像x这种直接创建的称为叶子节点,叶子节点对应的grad_fn是None。

print(x.is_leaf,y.is_leaf)

输出

True False

进行一步复杂的运算操作

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

输出:

tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

通过.requires_grad_()来用in-place的方式改变requires_grad属性

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 0x000001CFA5730508>

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

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

输出

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

数学上,如果有一个函数值和自变量都为向量的函数,那么y关于x的梯度就是一个雅克比矩阵:
而torch.autograd这个包就是用来计算一些雅克比矩阵的乘积的。根据链式法则求得梯度。
重点: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.]])

为了避免链式求导时出现多维张量运算困难,不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形的张量,所以必要时需要通过把张量所有元素加权求和的方式转换为标量。
例: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)
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)
print(y2,y2.requires_grad)
print(y3,y3.requires_grad)

输出

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<AddBackward0>) True
y3.backward()
print(x.grad)

输出

tensor(2.)

因为y2的定义是被torch.no_grad()包裹的,所以没有与y2有关的梯度,只有和y1有关的梯度才会回传
不能调用y2.backward()会报错

如果想要修改tensor的数值,又不想被autograd记录(即不影响反向传播),那么可以对tensor.data操作

x = torch.ones(1,requires_grad = True)
print(x.data)
print(x.data.requires_grad)
#仍是一个tensor,但是已经独立于计算图之外

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.])

你可能感兴趣的:(动手学深度学习学习笔记(4))