Tensor是autograd包的核心类,若将其属性.requires_grad设置为True,它将开始追踪在其上的所有操作。完成计算后,可以调用 .backward()来完成所有梯度计算。此Tensor的梯度将累计到.grad属性中。若要停止追踪,则方法如下:
示例1:
import torch
def my_grad():
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn) #该tensor若为计算所得,则输出一个与这些计算相关的对象,否则为None
y = x + 2
print(y)
print(y.grad_fn)
if __name__ == "__main__":
my_grad()
运行结果:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x00000178B6D15C18>
像x这种直接创建的的称为 叶子节点。
示例2:计算指定函数关于x的梯度
import torch
def my_grad():
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
z_mean = z.mean()
z_mean.backward() #等价z_mean.backward(torch.tensor(1.))
print(x.grad)
if __name__ == "__main__":
my_grad()
运行结果:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
需要注意的是.backward(),如果当前tensor是标量,则不需要为backward()传入参数;否则,需要传入一个与当前tensor同行的tensor。这样做的好处在于避免tensor对tensor求导,只允许标量对tensor求导,则求导结果是和自变量同形的tensor。
当然,grad在反向传播的过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前把梯度清零,如以下:
示例3:梯度清零
import torch
def my_grad():
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
z_mean = z.mean()
z_mean.backward()
z_sum1 = x.sum()
z_sum1.backward()
print(x.grad)
z_sum2 = x.sum()
x.grad.data.zero_() #梯度清零
z_sum2.backward()
print(x.grad)
if __name__ == "__main__":
my_grad()
运行结果:
tensor([[5.5000, 5.5000],
[5.5000, 5.5000]])
tensor([[1., 1.],
[1., 1.]])
示例4:当前tensor非标量
import torch
def my_grad():
x = torch.tensor([[1.0, 2.0], [3.0, 4.0]], requires_grad=True)
y = x * 2
print(y)
z = torch.tensor([[1.0, 1.0], [1.0, 1.0]]) #传入与y同形的权重向量
y.backward(z)
print(x.grad)
if __name__ == "__main__":
my_grad()
运行结果:
tensor([[2., 4.],
[6., 8.]], grad_fn=<MulBackward0>)
tensor([[2., 2.],
[2., 2.]])
需要注意的是:
(1)z = torch.tensor([[1.0, 1.0], [1.0, 1.0]]) 为权重向量,代表求梯度时的偏置;
(2)x.grad与x同形。
示例5:中断梯度追踪
import torch
def my_grad():
x = torch.tensor(1.0, requires_grad=True)
y = x ** 2
with torch.no_grad():
y2 = x ** 2
y3 = y + y2
print(x.requires_grad)
print(y, y.requires_grad)
print(y2, y2.requires_grad)
print(y3, y3.requires_grad)
y3.backward()
print(x.grad)
if __name__ == "__main__":
my_grad()
运行结果:
True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<AddBackward0>) True
tensor(2.)
可以看到y2没有grad_fn,y2.requires_grad=False,且y3对x求梯度为2.,原因在于y2被with torch.no_grad():包裹,与y2相关的梯度没有回传。
示例6:操作tensor.data
对tensor.data操作的好处在于,既可以修改tensor的值,又不会被autograd记录,即不会反向传播。
import torch
def my_grad():
x = torch.tensor(1.0, requires_grad=True)
print(x.data) #依旧是一个tensor
print(x.data.requires_grad) #但不会影响反向传播
y = x * 2
x.data *= 100 #只是改变了值
y.backward()
print(x) #更改data的值也会影响tensor的值
print(x.grad)
if __name__ == "__main__":
my_grad()
运行结果:
tensor(1.)
False
tensor(100., requires_grad=True)
tensor(2.)