nn.Mdule
torch.nn是专门为深度学习设计的模块,它的核心数据结构是Module
以实现全连接层(仿射层)为例,它的输出y和输入x满足$y=wx+b$
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
|
import torch as t
from torch import nn
# 继承nn.Module
class Linear(nn.Module):
# 必须重写__init__()和前向传播函数forward
def __init__(self, in_feature, out_feature):
super().__init__() # 记得调用
# __init__()中可自行定义可学习参数,并封装成nn.Parameter(是一种特殊的tensor,默认徐亚求导)
# nn.Parameter内的参数是网络中的可学习参数
self.W = nn.Parameter(t.randn(in_feature, out_feature))
self.b = nn.Parameter(t.randn(out_feature))
# 比如一些层都可以定义在这里
def forward(self, x):
"""
实现了前向传播,它的输入可以是一个或者多个tensor
:param x:
:return:
"""
x = x.mm(self.W) # 矩阵乘法,相当于x@(self.W)
return x + self.b.expand_as(x)
# 反向传播无需手动编写,nn.Module能利用autograd自动实现反向传播,这笔Function简单许多
layer1 = Linear(4,3)
input = t.randn(2,4)
output = layer1(input)
print(output)
# 可学习参数会通过named_parameters返回一个迭代器
for name, parameter in layer1.named_parameters():
print(name, parameter) # w和b
|
实现多层感知机(MLP)
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
|
"""
多层感知机
"""
import torch as t
from torch import nn
# 继承nn.Module
class Linear(nn.Module):
# 必须重写__init__()和前向传播函数forward
def __init__(self, in_feature, out_feature):
super().__init__() # 记得调用
# __init__()中可自行定义可学习参数,并封装成nn.Parameter(是一种特殊的tensor,默认徐亚求导)
# nn.Parameter内的参数是网络中的可学习参数
self.W = nn.Parameter(t.randn(in_feature, out_feature))
self.b = nn.Parameter(t.randn(out_feature))
# 比如一些层都可以定义在这里
def forward(self, x):
"""
实现了前向传播,它的输入可以是一个或者多个tensor
:param x:
:return:
"""
x = x.mm(self.W) # 矩阵乘法,相当于x@(self.W)
return x + self.b.expand_as(x)
# 反向传播无需手动编写,nn.Module能利用autograd自动实现反向传播,这笔Function简单许多
class Perceptron(nn.Module):
"""
多层感知机MLP
"""
def __init__(self, in_feature, hidden_feature, out_feature):
super().__init__() # 记得调用
# 此处的Linear是前面自定义的全连接层
self.layer1 = Linear(in_feature, hidden_feature)
self.layer2 = Linear(in_feature, out_feature)
# 在forwar中加上各层之间的处理函数,并定义层与层之间的关系
def forward(self, x):
x = self.layer1(x)
x = t.sigmoid(x)
x = self.layer2(x)
return x
perception = Perceptron(3,4,1)
for name, parameter in perception.named_parameters():
print(name, parameter.size()) # w和b
|
MLP运行结果:
1
2
3
4
|
layer1.W torch.Size([3, 1])
layer1.b torch.Size([1])
layer2.W torch.Size([3, 1])
layer2.b torch.Size([1])
|
nn.Modlue还有很多其他层,可以自己翻看官方文档,有以下几个主注意的点:
1.关注构造参数的作用
2.关注属性、可学习的网络、和包含的子Module
3.输入输出的形状
常用的神经网络层
图像相关层
1.卷积层
卷积层的本质就是卷积层、池化层、激活层及其它层的叠加
https://pytorch.org/docs/stable/nn.html#convolution-layers
以二维卷积类Conv2d为例:
https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html#torch.nn.Conv2d
卷积输出结果的形状:
2.池化层
主要用于下采样。
增加池化层可以保留主要特征的同时降低参数量,从而在一定程度上防止过拟合。
池化层没有学习参数
https://pytorch.org/docs/stable/nn.html#pooling-layers
3.其他层
- Linear:全连接层
- BatchNorm:批标准化层
- Dropout:防止过拟合
激活函数
PyTorch实现了常见激活函数,他们可以作为独立的Layer使用
构建神经网络
前面的层都是前一层直接作为下一层的输入,这种称为前馈神经网络(FNN).
对于此类网络,重写forward比较麻烦,可以简化使用:
Sequential
:特殊的module,可以包含多个子module,在前向传播时候会将输入一层层传递下去
- 和
ModuleList
:特殊的module,可以包含多个子module,可以向list一样视同,但是不能直接将输入传给ModuleList
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import torch as t
from torch import nn
# 第一种
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3,3,3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('active', nn.ReLU())
# 第二种
net2 = nn.Sequential(
nn.Conv2d(3,3,3),
nn.BatchNorm2d(3),
nn.ReLU()
)
# 第三种
from collections import OrderedDict
net3 = nn.Sequential(OrderedDict([
('conv',nn.Conv2d(3,3,3)),
('batchnorm',nn.BatchNorm2d(3)),
('active', nn.ReLU()),
]))
print(net1)
"""
Sequential(
(conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
(batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(active): ReLU()
)
"""
# 可以根据名字或者序号取出子module
print(net1[1])
print(net2.conv)
|
为什么多此一举用ModuleList,而不用自带的list呢?
1
2
3
4
5
6
7
8
9
|
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.li = [nn.Linear(3,4), nn.Conv2d(3,3,(1,2)), nn.ReLU()] # 自定义的list不能被主module识别,在反向传播的时候将无法调整子module的参数
self.module_li = nn.ModuleList([nn.Linear(3,4), nn.Conv2d(3,3,(1,2)), nn.ReLU()]) #能被主module识别
def forward(self):
pass
|
除了ModuleList,还有ParameterList
如果在__init__()中需要构造list/tuple/dict等对象,那么一定要思考是够该用ModuleList和ParameterList代替
RNN
…
nn.functional
nn.module中大多数的layer在nn.functional中都有一个阈值对应的函数。
区别:
- 使用nn.module实现的layer是一个特殊的类,会自动提取可学习参数
- 使用nn.functional实现的layer更像是纯函数
使用:
如果模型具有可学习参数,那么最好使用nn.module,否则都行。
对于激活函数层、池化层等都没有可学习参数(Note:也不用放在构造函数__init__中),但还是建议使用nn.module中
优化器
常用的优化方法封装在torch.optim
中,所有优化方法都继承自optim.Optimizer
应学会使用:
- 优化方法的基本使用方法
- 如何对模型的不同部分设置不同的学习率
- 如歌调整学习率
这里以SGD为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# 以前面的多层感知机为例
# ...
from torch import optim
# 为一个网络设置学习率
optimizer = optim.SGD(params=perception.parameters(), lr=1)
optimizer.zero_grad() # 梯度清零,等价于 net.zero_grad()
input = t.rand(32,3)
out = perception(input)
out.backward(out) # 真正的反向传播过程在下一步执行
optimizer.step() # 执行优化
# 不同参数设置不同的学习率
bias_params = [param for name, param in perception.named_parameters() if name.endswith('.b')]
weight_params = [param for name, param in perception.named_parameters() if name.endswith('.W')]
optimizer = optim.SGD([
{'parms':bias_params},
{'parms':weight_params, lr = 0.0001}
], lr=0.1)
|
调整学习率有两种做法:
- 修改
optimizer.param_group
中对应学习率
- 新建一个优化器
1
2
3
4
5
6
7
8
9
|
# 新建一个优化器
optimizer = optim.SGD([
{'parms':bias_params},
{'parms':weight_params, lr = 0.0001}
], lr=0.1)
# 手动衰减学习率,保存动量
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1
|
PyTorch中保存模型
1
2
3
4
5
6
|
# 所有module对象都具有state_dict()函数,会返回当前module所有状态数据。保存后下次使用利用load_state_dict加载进来
t.save(perception.state_dict(), "多层感知机.pth")
perception2 = Perceptron(3,4,1)
perception2.load_state_dict(t.load("多层感知机.pth"))
|
在GPU上运行module
两步:
model=model.cuda
:将模型的所有参数转存到GPU上
inout.cuda
:将输入数据防止在GPU上
多块GPU上进行并行计算,PyTorch提供了2个函数:
nn.parallel.data_parallel()
直接利用多块GPU并行得到计算结果
- 和
nn.parallel.DataParallel()
返回一个新的module,能够自动在多块GPU上进行并行加速
1
2
3
4
5
6
7
8
|
# 方法1
new_net = nn.parallel.DataParallel(perception, device_ids=[0,1])
out = new_net(input)
# 将一个batch的数据均分成多分,分别送到对应的GPU上进行计算,然后将各块GPU得到的梯度进行累加...
# 方法2
nn.parallel.data_parallel(new_net, input,device_ids=[0,1] )
|
使用PyTorch实现线性回归(与前一篇文章作对比)
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
|
import torch
# 1.准备数据集
x_data = torch.Tensor([[1.0], [2.0], [3.0]]) # 3行1列的tensor
y_data = torch.Tensor([[2.0], [4.0], [6.0]])
# 2.使用类来设计模型
class LinearModel(torch.nn.Module): # Module构造出来的对象,会自动构建反向传播过程
def __init__(self):
super(LinearModel, self).__init__()
self.linear = torch.nn.Linear(1, 1) # torch.nn.Linear构造一个对象,参数是权重和偏差,也是继承子Module的会自动进行反向传播
# (in_features: size, out_features: size of each out feature, bias=True:要不要偏置量)
def forward(self, x): # 实现这个方法
# 计算 y heat
y_pred = self.linear(x) # linear是前面建立的对象,可调用的callable的对象(def __call__(self, *args, **kwargs))
return y_pred
model = LinearModel() # model是callable的 model(x)
# 3.构建损失和优化器
criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 梯度下降
# 可以尝试不同的优化器
# 4.训练
for epoch in range(100):
y_pred = model(x_data) # 1.前向传播,计算y heat
loss = criterion(y_pred, y_data) # 2.计算损失
print(epoch, loss) # 打印
optimizer.zero_grad() # 梯度会自动计算,务必梯度清零!
loss.backward() # 3.反向传播
optimizer.step() # 4.更新 update
# 打印权重和偏置
print("w=", model.linear.weight.item()) # model下面的linear,下面的weight
print("b=", model.linear.bias.item())
# 测试模型
x_test = torch.Tensor([[4.0]])
y_test = model(x_test)
print("y_pred=", y_test.data)
|