Tensor的自动求导

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
a = torch.randn(2,3,requires_grad = True)
b = torch.randn(2,3)
print(b.requires_grad)
# 只要被计算中的任意一个变量需要求导,那么返回的变量也需要求导
c = a + b
# 分离变量,返回的变量不需要求导
d = a.detach()
b.requires_grad_()
# 显示变量c经过了什么操作
print(c.grad_fn)
print(a.requires_grad)
print(b.requires_grad)
print(c.requires_grad)
print(d.requires_grad)

'''
result:

False
<AddBackward0 object at 0x000002672E8FDC88>
True
True
True
False

grad:该Tensor对应的梯度,类型为Tensor,并与Tensor同维度。
grad_fn:指向function对象,即该Tensor经过了什么样的操作,用作反向传播的梯度计算,如果该Tensor由用户自己创建,则该grad_fn为None。

  • require_grad参数表示是否需要对该Tensor进行求导,默认为False;设置为True则需要求导,并且依赖于该Tensor的之后的所有节点都需要求导。
  • 默认的Tensor是不需要求导的

计算图

计算图是PyTorch对于神经网络的具体实现形式,包括每一个数据Tensor及Tensor之间的函数function。
Autograd的基本原理是随着每一步Tensor的计算操作,逐渐生成计算图,并将操作的function记录在Tensor的grad_fn中。在前向计算完后,只需对根节点进行backward函数操作,即可从当前根节点自动进行反向传播与梯度计算,从而得到每一个叶子节点的梯度,梯度计算遵循链式求导法则。
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import torch
# 自己生成的,因此都为叶节点
x = torch.randn(1)
w = torch.ones(1, requires_grad=True)
b = torch.ones(1, requires_grad=True)
print(b)
print(x.is_leaf)
print(w.is_leaf)
print(b.is_leaf)
# 进行前向计算,由计算生成的变量都不是叶节点
y = x * w
z = y = b
print(y.is_leaf)
print(z.is_leaf)
print(w.grad)
print(b.grad)
# 对根节点调用backward()函数,进行梯度反传
z.backward(retain_graph = True)
print(w.grad)
print(b.grad)

'''
result:

True
True
True
True
True
None
None
None
tensor([1.])
'''
  • backward()函数还有一个需要传入的参数grad_variabels,其代表了根节点的导数,也可以看做根节点各部分的权重系数。因为PyTorch不允许Tensor对Tensor求导,求导时都是标量对于Tensor进行求导,因此,如果根节点是向量,则应配以对应大小的权重,并求和得到标量,再反传。如果根节点的值是标量,则该参数可以省略,默认为1。
  • 当有多个输出需要同时进行梯度反传时,需要将retain_graph设置为True,从而保证在计算多个输出的梯度时互不影响。

nn.Module类

nn.Module是PyTorch提供的神经网络类,并在类中实现了网络各层的定义及前向计算与反向传播机制。在实际使用时,如果想要实现某个神经网络,只需继承nn.Module,在初始化中定义模型结构与参数,在函数forward()中编写网络前向过程即可。
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from torch import nn
# 首先建立一个全连接的子module,继承nn.Module
class Linear(nn.Module):
def __init__(self, in_dim, out_dim):
super(Linear, self).__init__() # 调用nn.Module的构造函数
# 使用nn.Parameter来构造需要学习的参数
self.w = nn.Parameter(torch.randn(in_dim, out_dim))
self.b = nn.Parameter(torch.randn(out_dim))
# 在forward中实现前向传播过程
def forward(self, x):
x = x.matmul(self.w) # 使用Tensor.matmul实现矩阵相乘
y = x + self.b.expand_as(x) # 使用Tensor.expand_as()来保证矩阵形状一致
return y
class Perception(nn.Module):
def __init__(self, in_dim, hid_dim, out_dim):
super(Perception, self).__init__()
self.layer1 = Linear(in_dim, hid_dim)
self.relu = nn.ReLU(inplace=True)
self.layer2 = Linear(hid_dim, out_dim)
self.fc = nn.Linear(3, 3)
def forward(self, x):
x = self.layer1(x)
y = torch.relu(x) # 使用torch中的relu作为激活函数
y = self.layer2(y)
y = torch.softmax(y,dim = 1) # 使用torch中的softmax作为激活函数来进行多分类
return y
# 实例化一个网络,并赋值全连接中的维数,最终输出三维代表了三分类
pcp = Perception(3, 3, 3)
print(pcp)
# named_parameters()可以返回学习参数的迭代器,分别为参数名与参数值
for name, parameter in pcp.named_parameters():
print(name, parameter)
data = torch.randn(4,3)
print(pcp(data))

'''
result:
感知器结构:
Perception(
(layer1): Linear()
(relu): ReLU(inplace=True)
(layer2): Linear()
)
各个层的参数:
layer1.w Parameter containing:
tensor([[-0.1038, -0.8919, 0.5721],
[ 1.0064, -0.2040, 1.1663],
[-1.6619, 0.6891, 0.9040]], requires_grad=True)
layer1.b Parameter containing:
tensor([-0.8985, -0.9285, 0.4434], requires_grad=True)
layer2.w Parameter containing:
tensor([[-1.2363, 0.2189, 0.2963],
[-0.1473, 1.8698, 0.7392],
[-0.7365, 1.0238, -0.2207]], requires_grad=True)
layer2.b Parameter containing:
tensor([ 2.0176, -0.2706, 1.5695], requires_grad=True)
测试结果:
tensor([[0.5746, 0.0583, 0.3671],
[0.3566, 0.2445, 0.3988],
[0.3658, 0.2336, 0.4006],
[0.4451, 0.1455, 0.4094]], grad_fn=<SoftmaxBackward>)
  • nn.Parameter()函数定义了全连接中的ω和b,这是一种特殊的Tensor的构造方法,默认需要求导,即requires_grad为True。
  • 在PyTorch中,还有一个库为nn.functional,同样也提供了很多网络层与函数功能,但与nn.Module不同的是,利用nn.functional定义的网络层不可自动学习参数,还需要使用nn.Parameter封装。nn.functional的设计初衷是对于一些不需要学习参数的层,如激活层、BN(Batch Normalization)层,可以使用nn.functional,这样这些层就不需要在nn.Module中定义了。
  • 当模型中只是简单的前馈网络时,即上一层的输出直接作为下一层的输入,这时可以采用nn.Sequential()模块来快速搭建模型,而不必手动在forward()函数中一层一层地前向传播。因此,如果想快速搭建模型而不考虑中间过程的话,推荐使用nn.Sequential()模块。
    例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    from torch import nn
    class Perception(nn.Module):
    def __init__(self, in_dim, hid_dim, out_dim):
    super(Perception, self).__init__()
    self.layer = nn.Sequential(
    nn.Linear(in_dim, hid_dim),
    nn.Sigmoid(),
    nn.Linear(hid_dim, out_dim),
    nn.Sigmoid()
    )
    def forward(self, x):
    y = self.layer(x)
    return y
    pcp = Perception(1000,10000 ,10)
    print(pcp)

    '''
    result:

    Perception(
    (layer): Sequential(
    (0): Linear(in_features=1000, out_features=10000, bias=True)
    (1): Sigmoid()
    (2): Linear(in_features=10000, out_features=10, bias=True)
    (3): Sigmoid()
    )
    )

损失函数的选择

PyTorch在torch.nn及torch.nn.functional中都提供了各种损失函数,通常来讲,由于损失函数不含有可学习的参数,因此这两者在功能上基本没有区别。

问题类型 损失函数
二分类 nn.CrossEntropyLoss()
多类别分类 nn.CrossEntropyLoss()
多标签分类 nn.CrossEntropyLoss()
回归 MSE
向量回归 MSE

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from torch import nn
import torch
import torch.nn.functional as F
# 首先建立一个全连接的子module,继承nn.Module
class Linear(nn.Module):
def __init__(self, in_dim, out_dim):
super(Linear, self).__init__() # 调用nn.Module的构造函数
# 使用nn.Parameter来构造需要学习的参数
self.w = nn.Parameter(torch.randn(in_dim, out_dim))
self.b = nn.Parameter(torch.randn(out_dim))
# 在forward中实现前向传播过程
def forward(self, x):
x = x.matmul(self.w) # 使用Tensor.matmul实现矩阵相乘
y = x + self.b.expand_as(x) # 使用Tensor.expand_as()来保证矩阵形状一致
return y
class Perception(nn.Module):
def __init__(self, in_dim, hid_dim, out_dim):
super(Perception, self).__init__()
self.layer1 = Linear(in_dim, hid_dim)
self.relu = nn.ReLU(inplace=True)
self.layer2 = Linear(hid_dim, out_dim)
self.fc = nn.Linear(3, 3)
def forward(self, x):
x = self.layer1(x)
y = torch.relu(x) # 使用torch中的relu作为激活函数
y = self.layer2(y)
y = torch.softmax(y,dim = 1) # 使用torch中的softmax作为激活函数来进行多分类
return y
# 实例化一个网络,并赋值全连接中的维数,最终输出三维代表了三分类
pcp = Perception(3, 3, 3)
print(pcp)
# named_parameters()可以返回学习参数的迭代器,分别为参数名与参数值
for name, parameter in pcp.named_parameters():
print(name, parameter)
data = torch.randn(4,3)
output = pcp(data)
print(output)
# 设置标签,每个数为0、1或2三个类别
label = torch.Tensor([0, 1, 2, 0]).long()
# 实例化nn中的交叉熵损失类
criterion = nn.CrossEntropyLoss()
loss_functional = F.cross_entropy(output, label)
print(criterion(output, label), loss_functional)

'''
result:

Perception(
(layer1): Linear()
(relu): ReLU(inplace=True)
(layer2): Linear()
(fc): Linear(in_features=3, out_features=3, bias=True)
)
layer1.w Parameter containing:
tensor([[-0.5057, -0.3412, 1.3228],
[ 0.2286, 0.7570, 1.0803],
[-0.1114, -0.8957, 0.1538]], requires_grad=True)
layer1.b Parameter containing:
tensor([ 0.6814, -0.4924, -0.3002], requires_grad=True)
layer2.w Parameter containing:
tensor([[ 2.3430, 1.0380, 1.0289],
[ 0.5373, 0.5528, -1.4240],
[-1.2860, 0.2747, 1.0854]], requires_grad=True)
layer2.b Parameter containing:
tensor([-0.5483, -0.8220, -0.1983], requires_grad=True)
fc.weight Parameter containing:
tensor([[-0.5237, -0.1501, 0.5000],
[ 0.2985, 0.4648, -0.1471],
[-0.4871, -0.3018, 0.2251]], requires_grad=True)
fc.bias Parameter containing:
tensor([0.4509, 0.1335, 0.0446], requires_grad=True)
tensor([[0.0069, 0.1075, 0.8856],
[0.0141, 0.1204, 0.8655],
[0.4050, 0.2080, 0.3870],
[0.0056, 0.0956, 0.8988]], grad_fn=<SoftmaxBackward>)
两者求得的损失值相同
tensor(1.3640, grad_fn=<NllLossBackward>) tensor(1.3640, grad_fn=<NllLossBackward>)
'''

优化器

  1. SGD
    例:
    optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.002, momentum=0.9,weight_decay=1e-5)
  2. Adam
    例:
    optimizer_ft = optim.Adam(model_ft.parameters(),lr=0.002,weight_decay=1e-4)
  3. RMSprop
    例:
    optimizer_ft = torch.optim.RMSprop(model_ft.parameters(),lr=0.002,alpha=0.9)

Reference

深度学习之PyTorch物体检测实战 - 董洪义
Subramanian, V., 2018. Deep Learning with PyTorch: A practical approach to building neural network models using PyTorch. Packt Publishing Ltd.