|
|
@@ -0,0 +1,335 @@ |
|
|
|
# pytorch学习笔记 |
|
|
|
|
|
|
|
来自datawhale的深入浅出pytorch教程的笔记。主要对第二章和第三章的常用函数和方法进行梳理,方便要用的时候查找和复习,同时巩固对pytorch实现深度学习基本流程的认识。 |
|
|
|
**教程链接:** [深入浅出pytorch](https://datawhalechina.github.io/thorough-pytorch/index.html) |
|
|
|
|
|
|
|
### 第二章:pytorch基础知识 |
|
|
|
|
|
|
|
#### 2.1张量 |
|
|
|
|
|
|
|
1. 生成tensor |
|
|
|
|
|
|
|
| 函数 | 功能 | |
|
|
|
| ------------------- | ---------------------------------- | |
|
|
|
| Tensor(sizes) | 基础构造函数 | |
|
|
|
| tensor(data) | 类似于np.array | |
|
|
|
| ones(sizes) | 全1 | |
|
|
|
| zeros(sizes) | 全0 | |
|
|
|
| eye(sizes) | 对角为1,其余为0 | |
|
|
|
| arange(s,e,step) | 从s到e,步长为step | |
|
|
|
| linspace(s,e,steps) | 从s到e,均匀分成step份 | |
|
|
|
| rand/randn(sizes) | rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布 | |
|
|
|
| normal(mean,std) | 正态分布(均值为mean,标准差是std) | |
|
|
|
| randperm(m) | 随机排列 | |
|
|
|
| 2. 加法 | | |
|
|
|
|
|
|
|
1. x+y |
|
|
|
2. x.add_(y) |
|
|
|
3. torch.add(x,y) |
|
|
|
|
|
|
|
3. 索引:类似numpy,如x[3:5,:] |
|
|
|
|
|
|
|
4. x.view(-1,3):改变形状,填-1表示由其他维度决定,view只改变观察角度不改tensor本身 |
|
|
|
|
|
|
|
5. y=x.clone():复制x,二者内存独立 |
|
|
|
|
|
|
|
6. x.item():x 的数值,不涉及其他性质 |
|
|
|
|
|
|
|
7. 广播机制:两个形状不同的tensor按元素计算(如加法)时会先适当复制元素使这两个 Tensor 形状相同后再按元素运算。 |
|
|
|
|
|
|
|
#### 2.2自动求导 |
|
|
|
|
|
|
|
- requires_grad:布尔值,在创建张量时设置,表示是否计算梯度,若训练需要用到梯度为true,某些情况例如评估时为false。 |
|
|
|
|
|
|
|
- grad_fn:布尔值。将y看作x的函数。这样创建的张量的grad_fn为true,如果是赋值给自身则为false。 |
|
|
|
|
|
|
|
- out.backward():对out反向传播,对于标量out.backward()和 out.backward(torch.tensor(1.)) 等价,对于张量必须在括号内传入一个张量。每次传播梯度都会叠加 |
|
|
|
|
|
|
|
- x.grad:x的梯度,返回的也是一个tensor对象 |
|
|
|
|
|
|
|
- x.data:x的值,对x.data的操作会影响x的值但不影响梯度计算 |
|
|
|
|
|
|
|
- x.grad.data.zero_():令梯度为0,防止梯度的累加 |
|
|
|
|
|
|
|
- with torch.no_grad()::其后面的代码不会跟踪梯度的变化 |
|
|
|
|
|
|
|
一个例子 |
|
|
|
|
|
|
|
```python |
|
|
|
import torch |
|
|
|
x = torch.ones(2, 2, requires_grad=True) |
|
|
|
print(x) |
|
|
|
y = x**2 |
|
|
|
print(y) |
|
|
|
print(y.grad_fn) |
|
|
|
z = y * y * 3 |
|
|
|
out = z.mean() |
|
|
|
print(z, out) |
|
|
|
out.backward() |
|
|
|
print(x.grad) |
|
|
|
``` |
|
|
|
|
|
|
|
最后一项输出为tensor([[3., 3.], [3., 3.]]) |
|
|
|
|
|
|
|
---------- |
|
|
|
|
|
|
|
### 第三章:pytorch主要组成模块 |
|
|
|
|
|
|
|
#### 3.2基本配置 |
|
|
|
|
|
|
|
必须导入的包 |
|
|
|
|
|
|
|
```python |
|
|
|
import os |
|
|
|
import numpy as np |
|
|
|
import torch |
|
|
|
import torch.nn as nn |
|
|
|
from torch.utils.data import Dataset, DataLoader |
|
|
|
import torch.optim as optimizer |
|
|
|
``` |
|
|
|
|
|
|
|
一些基础配置 |
|
|
|
|
|
|
|
- batch size |
|
|
|
|
|
|
|
- 初始学习率(初始) |
|
|
|
|
|
|
|
- 训练次数(max_epochs) |
|
|
|
|
|
|
|
- GPU配置 |
|
|
|
|
|
|
|
```python |
|
|
|
batch_size = 16 |
|
|
|
# 批次的大小 |
|
|
|
lr = 1e-4 |
|
|
|
# 优化器的学习率 |
|
|
|
max_epochs = 100 |
|
|
|
``` |
|
|
|
|
|
|
|
GPU的设置有两种常见的方式: |
|
|
|
|
|
|
|
```python |
|
|
|
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置 |
|
|
|
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' |
|
|
|
方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可 |
|
|
|
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") |
|
|
|
``` |
|
|
|
|
|
|
|
我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数: |
|
|
|
|
|
|
|
- __init__: 用于向类中传入外部参数,同时定义样本集 |
|
|
|
|
|
|
|
- __getitem__: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据 |
|
|
|
|
|
|
|
- __len__: 用于返回数据集的样本数 |
|
|
|
|
|
|
|
```python |
|
|
|
class MyDataset(Dataset): |
|
|
|
def __init__(self, data_dir, info_csv, image_list, transform=None): |
|
|
|
""" |
|
|
|
Args: |
|
|
|
data_dir: path to image directory. |
|
|
|
info_csv: path to the csv file containing image indexes |
|
|
|
with corresponding labels. |
|
|
|
image_list: path to the txt file contains image names to training/validation set |
|
|
|
transform: optional transform to be applied on a sample. |
|
|
|
""" |
|
|
|
label_info = pd.read_csv(info_csv) |
|
|
|
image_file = open(image_list).readlines() |
|
|
|
self.data_dir = data_dir |
|
|
|
self.image_file = image_file |
|
|
|
self.label_info = label_info |
|
|
|
self.transform = transform |
|
|
|
|
|
|
|
def __getitem__(self, index): |
|
|
|
""" |
|
|
|
Args: |
|
|
|
index: the index of item |
|
|
|
Returns: |
|
|
|
image and its labels |
|
|
|
""" |
|
|
|
image_name = self.image_file[index].strip('\n') |
|
|
|
raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name] |
|
|
|
label = raw_label.iloc[:,0] |
|
|
|
image_name = os.path.join(self.data_dir, image_name) |
|
|
|
image = Image.open(image_name).convert('RGB') |
|
|
|
if self.transform is not None: |
|
|
|
image = self.transform(image) |
|
|
|
return image, label |
|
|
|
|
|
|
|
def __len__(self): |
|
|
|
return len(self.image_file) |
|
|
|
``` |
|
|
|
|
|
|
|
#### 3.3数据读入 |
|
|
|
|
|
|
|
构建好Dataset后,就可以使用DataLoader来按批次读入数据了,实现代码如下: |
|
|
|
|
|
|
|
```python |
|
|
|
from torch.utils.data import DataLoader |
|
|
|
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True) |
|
|
|
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False) |
|
|
|
``` |
|
|
|
|
|
|
|
其中: |
|
|
|
|
|
|
|
- batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数 |
|
|
|
|
|
|
|
- num_workers:有多少个进程用于读取数据 |
|
|
|
|
|
|
|
- shuffle:是否将读入的数据打乱 |
|
|
|
|
|
|
|
- drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练 |
|
|
|
|
|
|
|
#### 3.4模型构建 |
|
|
|
|
|
|
|
定义MLP类:_init_:用于初始化和定义前向计算的基础函数(如全连接层,relu);forward:调用_init_中的函数形成前向计算函数,无需定义backward,系统会自动计算。类的输入可以是模型的任意一个组件。 |
|
|
|
神经网络的层可分为有参数与无参数两种,无参数例如标准化,有参数的有多种,包括: |
|
|
|
|
|
|
|
- 二维卷积层:Conv2D,参数有输入通道数in_channels,输出通道数out_channels,卷积核宽度kernel_size,卷积核长宽不一致的填充数字padding |
|
|
|
|
|
|
|
- 池化层:pool2d,参数有池化层边长pool_size,和池化方式mode |
|
|
|
一个神经网络的典型训练过程如下: |
|
|
|
1. 定义包含一些可学习参数(或者叫权重)的神经网络 |
|
|
|
2. 在输入数据集上迭代 |
|
|
|
3. 通过网络处理输入 |
|
|
|
4. 计算 loss (输出和正确答案的距离) |
|
|
|
5. 将梯度反向传播给网络的参数 |
|
|
|
6. 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient(梯度下降) |
|
|
|
|
|
|
|
使用torch.nn包来构建神经网络,nn包则依赖于autograd包来定义模型并对它们求导。一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。 |
|
|
|
|
|
|
|
- torch.Tensor - 一个多维数组,支持诸如backward()等的自动求导操作,同时也保存了张量的梯度。 |
|
|
|
|
|
|
|
- nn.Module - 神经网络模块。是一种方便封装参数的方式,具有将参数移动到GPU、导出、加载等功能。 |
|
|
|
|
|
|
|
- nn.Parameter - 张量的一种,当它作为一个属性分配给一个Module时,它会被自动注册为一个参数。 |
|
|
|
|
|
|
|
- autograd.Function - 实现了自动求导前向和反向传播的定义,每个Tensor至少创建一个Function节点,该节点连接到创建Tensor的函数并对其历史进行编码。 |
|
|
|
|
|
|
|
- net.parameters() -网络的可学习参数 |
|
|
|
|
|
|
|
#### 3.5模型初始化 |
|
|
|
|
|
|
|
使用torch初始化conv和linear |
|
|
|
|
|
|
|
```python |
|
|
|
torch.nn.init.kaiming_normal_(conv.weight.data) |
|
|
|
torch.nn.init.constant_(linear.weight.data,0.3) |
|
|
|
``` |
|
|
|
|
|
|
|
#### 3.6损失函数 |
|
|
|
|
|
|
|
常用损失函数: |
|
|
|
|
|
|
|
1. 二分类交叉熵损失函数 |
|
|
|
torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean') |
|
|
|
- weight:每个类别的loss设置权值 |
|
|
|
|
|
|
|
- size_average:数据为bool,为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。 |
|
|
|
|
|
|
|
- reduce:数据类型为bool,为True时,loss的返回是标量。 |
|
|
|
2. 交叉熵损失函数 |
|
|
|
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean') |
|
|
|
ignore_index:忽略某个类的损失函数。 |
|
|
|
|
|
|
|
3. L1损失函数 |
|
|
|
torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean') |
|
|
|
reduction参数决定了计算模式。有三种计算模式可选:none:逐个元素计算。 sum:所有元素求和,返回标量。 mean:加权平均,返回标量。 如果选择none,那么返回的结果是和输入元素相同尺寸的。默认计算方式是求平均。 |
|
|
|
|
|
|
|
4. MSE损失函数 |
|
|
|
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean') |
|
|
|
|
|
|
|
5. 平滑L1 (Smooth L1)损失函数 |
|
|
|
torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean', beta=1.0) |
|
|
|
|
|
|
|
6. 二分类logistic损失函数 |
|
|
|
torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean') |
|
|
|
|
|
|
|
#### 3.7 训练与评估 |
|
|
|
|
|
|
|
切换模型的状态: |
|
|
|
|
|
|
|
```python |
|
|
|
model.train() # 训练状态 |
|
|
|
model.eval() # 验证/测试状态 |
|
|
|
``` |
|
|
|
|
|
|
|
一个完整的训练过程: |
|
|
|
用for循环读取DataLoader中的全部数据。 |
|
|
|
for data, label in train_loader: |
|
|
|
|
|
|
|
后将数据放到GPU上用于后续计算,此处以.cuda()为例 |
|
|
|
data, label = data.cuda(), label.cuda() |
|
|
|
|
|
|
|
开始用当前批次数据做训练时,应当先将优化器的梯度置零: |
|
|
|
optimizer.zero_grad() |
|
|
|
|
|
|
|
之后将data送入模型中训练: |
|
|
|
output = model(data) |
|
|
|
|
|
|
|
根据预先定义的criterion计算损失函数: |
|
|
|
loss = criterion(output, label) |
|
|
|
|
|
|
|
将loss反向传播回网络: |
|
|
|
loss.backward() |
|
|
|
|
|
|
|
使用优化器更新模型参数: |
|
|
|
optimizer.step() |
|
|
|
|
|
|
|
#### 3.9 pytorch优化器 |
|
|
|
|
|
|
|
共十种,均来自pytorch.optim。先实例化再step一下即可更新参数。 |
|
|
|
|
|
|
|
```python |
|
|
|
optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9) |
|
|
|
optimizer.step() |
|
|
|
``` |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
### 其他 |
|
|
|
|
|
|
|
#### 模型定义与修改 |
|
|
|
|
|
|
|
有三种方式定义模型:Sequential,ModuleList和ModuleDict,他们的方法大致相同。以Sequential为例: |
|
|
|
|
|
|
|
```python |
|
|
|
import torch.nn as nn |
|
|
|
net = nn.Sequential( |
|
|
|
nn.Linear(784, 256), |
|
|
|
nn.ReLU(), |
|
|
|
nn.Linear(256, 10), |
|
|
|
) |
|
|
|
print(net) |
|
|
|
``` |
|
|
|
|
|
|
|
通过net.fc(某一模型层)=classifier(替换层)可以修改模型的某一层。 |
|
|
|
|
|
|
|
在forward函数里可以给模型增加输入输出 |
|
|
|
|
|
|
|
```python |
|
|
|
def forward(self, x, add_variable): |
|
|
|
x1000 = self.net(x) |
|
|
|
x10 = self.dropout(self.relu(x1000)) |
|
|
|
x10 = self.fc1(x10) |
|
|
|
x10 = self.output(x10) |
|
|
|
return x10, x1000 |
|
|
|
``` |
|
|
|
|
|
|
|
#### 模型保存与读取 |
|
|
|
|
|
|
|
```python |
|
|
|
# 保存+读取整个模型 |
|
|
|
torch.save(model, save_dir) |
|
|
|
loaded_model = torch.load(save_dir) |
|
|
|
loaded_model.cuda() |
|
|
|
``` |
|
|
|
|
|
|
|
#### torchvision |
|
|
|
|
|
|
|
torchvision包含了在计算机视觉中常常用到的数据集,模型和图像处理的方式。常用模块: |
|
|
|
|
|
|
|
- torchvision.datasets 常见数据集 |
|
|
|
- torchvision.models 预训练模型 |
|
|
|
- torchvision.tramsforms 图像处理方法 |