PyTorch快速入门
基本介绍
Tensor
t.Tensor
- 传入维度 - 分配空间不初始化
- 传入具体数据 - 舒适化
t.rand
- 传入维度 - 指定维度的随机初始化数据,复合[0,1]分布
- Add
x+y
t.add(x,y)
t.add(x,y,out=result)
- 指定输出目标x.add(y)
- x不变x.add_(y)
- x改变 =x+=y
函数名后面带下划线
_
会修改Tensor本身
- 与Numpy转换:
- Tensor -> numpy:
a.numpy()
- Numpy -> tensor:
t.from_numpy(a)
- Tensor -> numpy:
一个对象的tensor和numpy共享内存,一个改变,另一个随之而变
获得元素值:
- 下标访问tensor -
tensor[index...]
获得0-dim tensor - 获得值 -
scalar.item()
- 下标访问tensor -
scalar和tensor的区别:
1
2
3
4
5
6
7
8
9tensor = t.tensor([2])
scalar = tensor[0]
scalar0 = t.tensor(2)
# tensor 1-dim
tensor.size() # torch.Size([1])
# scalar 0-dim
scalar.size() # torch.Size([])
# scalar0 0-dim
scalar0.size() # torch.Size([])数据拷贝和共享内存
t.tensor()
- 会进行数据拷贝t.from_numpy()
/tensor.detach()
新建的tensor与原tensor共享内存
GPU加速
t.cuda
Autograd: 自动微分
使用autograd功能
```python
x = t.ones(2, 2, requires_grad=True)
result = doSomething(x)1
2
3
4
5
- 反向传播
- ```python
result.backward()反向传播会累加之前的梯度,反向传播前把梯度清零
梯度清零
- ```python
x.grad.data.zero_()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
### 神经网络
定义网络
- 继承`nn.Module`,实现`forward`,把网络中具有**可学习参数**的层放在`__init__`中。不具有可学习参数使用`forward`的`nn.functional`代替
- `__init__`函数先执行父类构造函数`super(CLASSNAME, self).__init__()`
##### 卷积层
- `nn.Conv2d(inputC, outputC, k)`
- 输入图片通道数
- 输出通道数
- 卷积核
##### 全连接层
- `nn.Linear(input, output)`
- 输入样本数
- 输出样本数
> y = Wx + b
##### forward
- `F.relu(input)` - 激活
- 上一层的结果
- `F.max_pool2d(input, kernel size)`
- kernel size
- `x.view(ONEDIMENSION,-1`
- -1 自适应维度
#### 训练网络
- 定义了forward函数,backward就会自动被实现
- `net.parameters()`返回网络的**可学习参数**
- `·net.named_parameters()`返回可学习的参数及名称
> torch.nn不支持一次只输入一个样本:
>
> 1. 用 `input.unsqueeze(0)`将batch_size设为1
> 2. Conv2d输入时将nSample设为1
#### 损失函数
- `nn.MSELoss(output, target)`计算均方误差
- `nn.CrossEntropyLoss(output target)`计算交叉熵损失
#### 优化器
- 反向传播计算参数的梯度,使用优化方法**更新网络的权重和参数**
- e.g. SGD:
- ```python
weight = weight - learning_rate * gradient
- ```python
```python
learning_rate = 0.01
for f in net.parameters():f.data.sub_(f.grad.data * learning_rate)# inplace 减法
1
2
3
4
5
6
7
##### 构造优化器
> optim <= torch.optim
- ```
optimizer = optim.SGD(net.parameters(), lr = 0.01)要调整的参数为网络中的可学习参数
lr为学习率
训练步骤:
清零优化器梯度
1
optimizer.zero_grad()
过网络
1
output = net(input)
计算损失
1
loss = criterion(output, target)
反向传播
1
loss.backward()
更新参数
1
optimizer.step()
Tensor 和 Autograd
Tensor
基础操作
创建
函数 功能 arange(s,e,step) 从s到e,步长为step linspace(s,e,steps) 从s到e,均匀切分成steps份 rand/randn(*sizes) 均匀/标准分布 normal(mean,std)/uniform(from,to) 正态分布/均匀分布 randperm(m) 随机排列 创建的时候可以指定数据类型(e.g.
dtype = t.int
)和存放device(e.g.device = t.device('cpu')
tensor转化为list -
tensor.tolist()
tensor的数据类型为
tensor([[1,2],[3,4]])
; list数据类型为[[1,2],[3,4]]
获得tensor的维度 -
tensor.size()
或tensor.shape
返回torch.Size对象 -torch.Size([第一维, 第二维, ..])
获得tensor元素的总数 -
tensor.numel()
传参为数据和维度的区别:
1
2t.Tensor(2,3) # tensor([[x, x, x],[x, x, x]])
t.Tensor((2,3)) # tensor([2,3])
使用维度作为参数构造tensor的时候不会马上分配空间,使用到tensor才分配;其他的构造函数都是马上分配
- 使用
eye
构造对角矩阵,不要求行列数一致
常用操作
调整tensor形状,调整前后元素总数一致。view返回的新tensor与原tensor共享内存。
view
的参数为修改后的每个维度,如果有一个为-1
则该维度根据元素总数不变的原则自动计算unsqueeze(index)
- 第index维的维度+1- 即之前维度为[x, y, z, …]
- index = 0 -> 维度变为 [1, x, y, z, …]
- index = 1 -> 维度变为 [x, 1, y, z, …]
- index = 2 -> 维度变为 [x, y, 1, z, …]
- 参数为负数的时候表示倒数
- 即之前维度为[x, y, z, …]
squeeze(index)
- 压缩第index维的1,如果不输入index,则把所有维度为1的压缩resize
可以修改tensor的大小,如果新大小超过了原大小,自动分配新空间,大小小于原大小,之前数据仍然保留
索引操作
索引出来的结果与原tensor共享内存
None
- 新增轴```pythob
a[:,None,:,None,None].shape
torch.Size([3, 1, 4, 1, 1])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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
- 筛选tensor元素 - `tensor[CONDITION]`
> 返回结果与原tensor不共享内存空间
- 常用选择函数
| 函数 | 功能 |
| ------------------------------: | ----------------------------------------------------: |
| index_select(input, dim, index) | 在指定维度dim上选取,比如选取某些行、某些列 |
| masked_select(input, mask) | 例子如上,a[a>0],使用ByteTensor进行选取 |
| non_zero(input) | 非0元素的下标 |
| gather(input, dim, index) | 根据index,在dim维度上选取数据,输出的size与index一样 |
> 对tensor的任何索引操作仍是一个**tensor**,想要获取标准的python对象数值,需要调用`tensor.item()`, 这个方法**只对包含一个元素的tensor适用**
- 索引访问:
- 维度为3:[[a,b], [c,d], [e,f]] => [[a,c,e], [b,d,f]]
- [[a,b,c], [d], [e]] => [[a,d,e], [b,d,e], [c,d,e]]
##### 逐元素操作
- `clamp` - 控制元素范围,用于比较大小
![1561187765542](C:\Users\Sherry\AppData\Roaming\Typora\typora-user-images\1561187765542.png)
##### 归并操作
- 使得输出形状小于输入形状,e.g. 均值、和...
- 指定对第几维度的元素操作,`keepdim = True`保存被操作的维度为1,否则不保留这一维度
##### 线性代数
| 函数 | 功能 |
| -------------------------------- | --------------------------------- |
| trace | 对角线元素之和(矩阵的迹) |
| diag | 对角线元素 |
| triu/tril | 矩阵的上三角/下三角,可指定偏移量 |
| mm/bmm | 矩阵乘法,batch的矩阵乘法 |
| addmm/addbmm/addmv/addr/badbmm.. | 矩阵运算 |
| t | 转置 |
| dot/cross | 内积/外积 |
| inverse | 求逆矩阵 |
| svd | 奇异值分解 |
#### Tensor和Numpy
> 当numpy的数据类型和Tensor的类型不一样时,数据会被复制,不共享内存
- 调用`t.tensor`进行构建的时候都会进行数据拷贝
##### 广播法则
###### N
- 所有输入数组像其中shape最长的看齐,shape不足通过前面加1
- 两个数组要么在某一个维度长度一致,要么其中一个为1,否则不能计算
- 输入某个长度为1,计算时沿此维度扩充成和另一个输入一样的维度
###### Pytorch中的使用
- `unsqueeze`或者`view`或者`tensor[NONE]`,实现补齐1
- `expand`或者`expand_as`重复数组,不会占用空间
> repeat和expand相似,repeat占用额外空间,expand不占用
#### 内部结构
- Tensor分为头信息区tensor和存储区storage
- tensor保存size, stride, type
- 共享内存的情况下,即共享storage
- 下标访问只是对于原storage地址增加了偏移量进行映射的
> 大部分操作不修改tensor,只修改tensor的头
>
> contiguous方法将离散的tensor变成连续数据
>
> 高级索引一般不共享storage,普通索引共享storage
#### 其他问题
##### GP
- 推荐使用`tensor.to(device)`使得程序同时兼容CPU和GPU,避免频繁在内存和显存中传输数据
##### 持久化
- `t.save`,
##### 向量化
- python的for循环十分低效
- `t.set_num_threads`可以设置PyTorch进行CPU多线程并行计算时候所占用的线程数,这个可以用来限制PyTorch所占用的CPU数目
### AutoGrad
- 自动求导引擎
#### 计算图
- 有向无环图
- 有向无环图的叶子节点由用户自己创建,不依赖其他变量,根节点为计算图的最终目标
- 链式的中间导数在前向传播过程中保存为buffer,在计算完梯度后会自动清空
> 需要多次反向传播则指定`retain_graph`保存buffer
- 变量的`requires_grad`属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点`requires_grad`都是True。
- 修改tensor数值,不希望被autograd记录,则修改tensor.data或者tensor.detach()
- 反向传播非叶子结点的导数会在计算完后被清空,查看梯度可以使用autograd.grad函数或者hood
- hook需要register和remove
##### 总结
- `autograd`根究用户对variable的操作构建计算图。对变量的操作抽象为Function
- 某个变量不是Function的输出,且由用户创建则为叶子节点(用户输入),其`grad_fn`为None. 叶子节点中需要求导的Variable,具有`AccumulateGrad`标识,因为梯度累加
- Variable默认不求导,如果一个节点的`require_grad`设置为True,所有依赖它的节点`require_grad`为True
- Variable的`volatile`默认为True,volatile为True的节点不会求导,volatile优先级比`require_grad`高
- 多次反向传播梯度累加,若需要保存反向传播中间变量的值,需要设置`retain_graph = True`
- 非叶子节点梯度计算完之后被清空,使用`autogtad.grad`或`hook`获取
#### 扩展autograd
- 自己定义函数继承`Function`,并且实现`forward`, `backward`静态方法(def之前写`@staticmethod`)
- 主函数中调用时:
- 前向传播:
- ```
z = MultiplyAdd.apply(w, x, b)反向传播:
- ```
z.backward()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 神经网络工具箱nn
- Module - 神经网络中的某层/包含很多层的神经网络
##### 全连接层
- 继承`nn.Module`
- 构造函数`__init__`自己定义可学习参数
- ```python
# infeatures和outfeatures为输入输出特征的维度
def __init__(self, infeatures, outfeatures):
super...
# 自己定义可学习参数如下:
self.para1 = nn.Parameter..
self.para2 = ...
...
- ```
nn.Parameter
- tensor,默认requires_grad = True
不需要写反向传播函数
调用:
1
2layer = Linear(INFEATURES, OUTFEATURES)
y = layer(x)返回可学习参数 -
named_parameters()
子模块sub module
named_parameters()
返回可学习参数为:模块名.参数名
常用神经网络层
图像相关层
卷积层
nn.Conv2d
池化层
- 没有可学习参数,权重固定
其他
Linear
全连接BatchNorm
- 批量规范化,风格迁移instanceNorm
- 初始化通道数
- 权重初始化为单位阵
- 偏差初始化为0
Dropout
- 防止过拟合- 输出每个元素的舍弃概率
激活函数
Relu
- $ReLU(x) = max(0, x)$
- 其inplace参数为True会将输出覆盖到输入,节省内存,一般不使用Inplace
前馈传播网络 feedfoward neural network: 将每一层的输出直接作为下一层的输入。简化forward -> ModuleList & Sequential
Sequential
- 包含几个sub module - 前向传播一层一层传递
- 声明,添加module
- ```python
net1 = nn.Sequential()
net1.add_module(‘conv’, nn.Conv2d(3,3,3))
net1.add_module(‘batchnorm’, nn.BatchNorm2d(3))
net1.add_module(‘activation_layer’, nn.ReLU())1
2
3
4
5
6
7
8
9
2. 将模块作为参数传递
- ```python
net2 = nn.Sequential(
nn.Conv2d(3, 3, 3),
nn.BatchNorm2d(3),
nn.ReLU()
)
- 将模块作为有序字典传入
- ```python
from collections import OrderedDict
net3= nn.Sequential(OrderedDict([('conv1', nn.Conv2d(3, 3, 3)), ('bn1', nn.BatchNorm2d(3)), ('relu1', nn.ReLU()) ]))
1
2
3
4
5
6
7
8
9
10
11
- 其中方法1和方法3可以按照模块名称访问子模块,而方法2按照下标获取子模块(从0)
---
- 模块数组`nn.ModuleList`,其中的元素可以被主(父)module识别,即其参数可以自动加入到主module的参数中
- ```python
modellist = nn.ModuleList([nn.Linear(3,4), nn.ReLU(), nn.Linear(4,2)])
损失函数
```
nn.LOSS_FUNCTION_NAME()1
2
3
4
5
6
7
8
9
10
11
12
### 优化器
- 优化方法来自包`torch.optim`,所有的优化方法继承基类`optim.Optimizer`
- 优化器可以对于不同的子网络(如在主网络/主Module中定义了多个子模块或者Sequential)
- ```python
optimizer =optim.SGD([
{'params': net.features.parameters()}, # 学习率为1e-5
{'params': net.classifier.parameters(), 'lr': 1e-2}
], lr=1e-5)如果某个参数显示执行学习率,则使用最外层的默认学习率
指定自网络中的不同层有不同的学习率的例子:
- ```python
只为两个全连接层设置较大的学习率,其余层的学习率较小
special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
special_layers_params = list(map(id, special_layers.parameters()))
base_params = filter(lambda p: id(p) not in special_layers_params,
optimizer = t.optim.SGD([net.parameters())
{'params': base_params}, {'params': special_layers.parameters(), 'lr': 0.01} ], lr=0.001 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
##### 调整学习率
###### 新建优化器
- 优点:简单
- 缺点:如果使用动量的优化器,会丢失动量等状态信息
###### 修改学习率
- 手动decay保存动量
- ```python
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 学习率为之前的0.1倍
- ```python
nn.functional
- 大部分layer在
functional
有与之对应的函数
nn.functional和nn.Module的区别
- module: 自动提取可学习参数
- function: 类似纯函数,不需要可学习参数时可使用. e.g. 激活函数,池化层
- 不具备可学习参数的层不放在构造函数
__init__
中。
初始化策略
- 随机初始化可能导致后期训练中梯度爆炸或梯度消失
nn.init
模块
nn.Module深入分析
属性
变量名 | 类型 | 说明 | 备注 |
---|---|---|---|
_parameters |
字典 | 用户设置 | 子模块的参数不会存放于此 |
_modules |
字典 | 子模块 | |
_buffers |
字段 | 缓存 | |
_backward_hooks 与_forward_hooks |
钩子,提取中间变量 | ||
training |
区分训练阶段于测试阶段,据此确定前向传播策略 |
_parameters
, _modules
, _buffers
使用
named_paramaters()
的时候将parameters和modules的parameter全部返回module层层嵌套,常用方法:
named_children
- 查看直接子modulenamed_modules
- 查看所有子module
training
- 一些layer在训练和测试阶段差距较大,为了使得每个
training
都要被设置好 => 定义了模型的的train和eval模式model.train()
- 将当前Module及其子module所有training
属性设置为Truemodel.eval()
- 将training属性都设为False
_backward_hooks
与_forward_hooks
- 在module前向传播或反向传播时注册,传播执行结束执行钩子,使用完及时删除
- 钩子用于获取某些中间结果
使用方法
1 | model = VGG() |
Python的原始实现:
- result = obj.name会调用buildin函数
getattr(obj, 'name')
,如果该属性找不到,会调用obj.__getattr__('name')
- obj.name = value会调用buildin函数
setattr(obj, 'name', value)
,如果obj对象实现了__setattr__
方法,setattr
会直接调用obj.__setattr__('name', value')
在nn.Module
中的实现:
- 在
nn.Module
中实现了__setattr__
函数,执行module.name = value
时,在__setattr__
中判断value是否为Parameter
或nn.Module
对象。如果是则加入_parameters
和_modules
字典。如果是其他的对象,则保存在__dict__
_modules
和_parameters
的item未保存在__dict__
中,默认getattr
无法获取,nn.Module
实现__getattr__
:如果getattr
无法处理,则调用自定义的__getattr__
从_modules
,_parameters
和_buffers
三个字典中获取
保存模型
使用Module的
state_dict()
函数,返回当前Module所有状态数据。下次使用,module.load_state_dict()
优化器的实现:
# 保存模型 t.save(net.state_dict(), 'net.pth') # 加载已保存的模型 net2 = Net() net2.load_state_dict(t.load('net.pth'))
将Module放在GPU上运行
model = model.cuda()
将所有参数转存到GPUinput.cuda()
将输入放入GPU
在多个GPU计算
nn.parallel.data_parallel(module, inputs, device_ids=None, output_device=None, dim=0, module_kwargs=None)
class torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
- 将一个输入batch分成多分,送到对应GPU计算,各个GPU得到的梯度累加
通过
device_ids
参数指定在哪些GPU上优化,output_devices
指定输出到哪个GPU