本篇主要是讲整个的流程,如果想了解本篇涉及所函数详细的用法可自行查阅.
另外这只是学习笔记和一些心得体会, 里面会有不对之处,请告诉我吧
代码下载地址:https://github.com/hanlinshi/pytorch-for-classfication
图像分类六个步骤:
第一步骤和第二步骤流程图:
- 流程图链接线上的数字表示实现这一流程的章节序号.
可以用自己准备的图像数据, 或者用公共数据集(本文以CIFAR10为例).
如果用公共数据集有两种方式:
步骤: 图像归类 —> 划分数据集
1) 图像归类
2) 划分数据集
脚本: image2train_val_test.py
将图像数据分成训练集,验证集,测试集.(train, validation, test)
我习惯按照7:1:2比例对数据集进行划分, 也有很多按照8:1:1进行划分.看自己的考虑吧.
步骤: 下载数据集 —> 解压数据集 —> 提取图片 —> 划分数据集
1)下载数据
2) 解压数据
解压出来8个文件:
data_batch_1 ~ data_batch_5, 这5个文件是训练数据, 各包含1万个样本
test_batch 这个是测试数据 包含1万个样本
batches.meta 是十个标签名称
readme.html 是官网链接地址
3) 提取图片
脚本: cifar10_to_png.py
步骤: 构建存放图片的文件目录 —> 解析(读取)数据文件 —> 按照图片标签保存图片
构建存放图片的文件目录
解析(读取)数据文件
label_names[0] == "airplane"
label_names[1] == "automobile"
我这里将训练数据和测试数据都解压保存一起了.
4) 划分数据集
脚本(同1.1的2): image2train_val_test.py
使用torchvision提供的datasets工具包datasets.CIFAR10可以直接下载该公共数据集.并返回返回Dataset对象 这种方式下载需要网好.
# 训练集
trainset = torchvision.datasets.CIFAR10(
root='./data',
train=True,
download=True,
transform=transform)
# 测试集
testset = torchvision.datasets.CIFAR10(
root='./data',
train=False,
download=True,
transform=transform)
步骤: 从图片数据到pytorch的Dataset对象 --> 从pytorch的Dataset对象到pytorch的数据迭代器
数据加载是指,将图片数据转换成模型输入数据.
第一步,从图片数据到pytorch的Dataset对象有两种方式,
第二步,使用pytorch的工具函数DataLoader.实现从pytorch的Dataset对象到pytorch的数据迭代器
可以看到不管哪种方式都少不了图片预处理.图片预处理可以使得图片更加多样性,训练出来的模型泛化能力也会更强.
torchvision提供的transforms有22钟预处理图片方式,可满足平常所需.(transforms详细请先自行查阅)或者我的笔记TORCHVISION.TRANSFORMS的图像预处理
transform = transforms.Compose(
[transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor()])
注意这里是下载好图片以后,在训练调用数据时,会按transform已经指定好的顺序依次预处理操作, 然后送给网络进行训练,并不是下载图片时进行预处理,然后保存预处理后的图片,也不会训练时保存预处理后的图片,总之不会保存预处理后的图片,除非你自己写代码保存,
脚本: loaddata.py
torchvision提供的ImageFolder函数能够以目录名为标签(就是第一节准备的图片目录结构)来对数据集做划分. (ImageFolder详细请先自行查阅)
trainset = torchvision.datasets.ImageFolder(
root="root folder path",
transform=transform,
target_transform=None,
loader=default_loader)
脚本:mydataset.py
自己写的方法是通过继承Dataset这个基类实现的.
可以参考ImageFolde的方式来实现.
具体实现参见脚本代码.
torch.utils.data.DataLoader 构建可迭代的数据装载器. 加载数据的时候使用mini-batch可以进行多线程并行处理,这样可以加快数据加载速度. (DataLoader详细请先自行查阅)
data_loader = torch.utils.data.DataLoader(
data,
batch_size=batch_size,
shuffle=True,
drop_last=False,
num_workers=4)
脚本:mysimplenet.py
搭建网络的思路相对简单一些:
可以先看简单的网络脚本:mysimplenet.py,
import torch.nn as nn
""" 定义网络 """
class Batch_Net(nn.Module):
"""
定义了一个简单的三层全连接神经网络,每一层都是线性的 nn.Linear
加快收敛速度的方法--批标准化 nn.BatchNorm1d
前两层,在每层的输出部分添加了激活函数 nn.ReLU(True)
"""
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Batch_Net, self).__init__()
self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1), nn.ReLU(True))
self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.BatchNorm1d(n_hidden_2), nn.ReLU(True))
self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
return x
PS: 想深入理解神经网络参考【深度学习】神经网络入门(最通俗的理解神经网络)
神经网络的训练过程中的参数学习是基于梯度下降法进行优化的。梯度下降法需要在开始训练时给各个节点的权重和偏置赋一个初始值。这个初始值的选取十分关键。参数的初始化关系到网络能否训练出好的结果或者是以多快的速度收敛。
初始化方法总共有三种:
在创建网络实例过程中,就会进行默认初始化。一般是均值分布采样初始化。
可以使用pytorch提供的初始化函数进行初始化(初始化函数在 torch.nn.init 中给出,自行查阅或者参见我的笔记TORCH.NN.INIT)。
具体初始化流程:遍历模型的每一层,判断各层属于什么类型, 例如, 是否是 nn.Conv2d、nn.BatchNorm2d、nn.Linear 等,然后根据不同类型的层,设定不同的权值初始化方法,例如,Xavier,kaiming,normal_,uniform_等。
kaiming 也称之为 MSRA 初始化,当年何恺明还在微软亚洲研究院,因而得名
初始化例子
from torch.nn import init
# 不同类型的层不同初始化
def weigth_init(net):
'''初始化网络'''
for m in net.modules():
if isinstance(m, nn.Conv2d):
init.xavier_uniform_(m.weight.data)
init.constant_(m.bias.data, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight.data, 1)
init.constant_(m.bias.data, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight.data, std=1e-3)
init.constant_(m.bias.data, 0)
# 初始化调用一
model = Net() # 实例化一个网络
model.apply(weigth_init) # 初始化
# 初始化调用二
net = Net() # 实例化一个网络
weigth_init(net)
预训练模型在线下载,下模型后的地址是:~/.cache/torch/hub/checkpoints
预训练模型下载地址:
model_urls = {
‘vgg11’: ‘https://download.pytorch.org/models/vgg11-bbd30ac9.pth’,
‘vgg13’: ‘https://download.pytorch.org/models/vgg13-c768596a.pth’,
‘vgg16’: ‘https://download.pytorch.org/models/vgg16-397923af.pth’,
‘vgg19’: ‘https://download.pytorch.org/models/vgg19-dcbb9e9d.pth’,
‘vgg11_bn’: ‘https://download.pytorch.org/models/vgg11_bn-6002323d.pth’,
‘vgg13_bn’: ‘https://download.pytorch.org/models/vgg13_bn-abd245e5.pth’,
‘vgg16_bn’: ‘https://download.pytorch.org/models/vgg16_bn-6c64b313.pth’,
‘vgg19_bn’: ‘https://download.pytorch.org/models/vgg19_bn-c79401a0.pth’,
}
模型微调(Fine Tune)
给一个预训练模型(pre-trained model),基于这个模型进行微调,相对从头开始训练(Training a model from scratch)节省了大量的计算资源和计算时间,提高了计算效率,甚至提高了准确率。Fine Tune是迁移学习的一种手段。
详细讲解可以参见CNN入门讲解:什么是微调(Fine Tune)?
网络模型可以分为三段:第一段为浅层卷积神经网络,提取基础特征,如边缘,轮廓等;第二段为深层卷积神经网络,提取抽象特征,如整个脸型;第三段为最后几层,一般为全连接层,根据特征组合进行评分分类。所以总的来说Fine Tune有三种调整方式:
还有一种情况,就是数据量大,但是相似度低。这个时候预训练模型没有什么特别意义。可以选择上面说的默认初始化或者自定义初始化网络。然后从头训练(Training a model from scatch)
这里面就涉及到三个具体的代码实现问题:
参考博客:pytorch 使用预训练模型如resnet、vgg等并修改部分结构
对于第一个问题:
下载好预训练模型,将预训练模型放入默认的模型下载目录:~/.cache/torch/hub/checkpoints中
下列代码就可以获得已经是预训练模型的网络了.
net = torchvision.models.vgg19_bn(pretrained=True)
如果没有预先下载好预训练模型,直接运行这个代码是可以直接下载预训练模型的。但是因为国内的网络原因,还是先手动下载预训练模型,放入默认下载目录下。这样会更快速一些。
使用这行代码我们都不需要自己搭建网络模型了。
我认为这里初始化有两个思路:
一个是自己搭建网络,然后将预训练模型的权重某些层赋值过来。但是这种操作应该比较复杂,我还没有看到相关的代码。
另外一个思路就是获得预训练模型网络以后,改部分层,通常是后面分类层,这种操作的代码比较多。
如何修改预训练模型网络的最后几层?根据层的名字拿到层,然后在重新赋值即可。如果对网络层不熟悉,可以先通过print()函数来查看层的名称。
for name, parameters in net.named_parameters():
print(name, ':', parameters.size())
# 以下是后面几层print的结果
...
features.49.weight : torch.Size([512, 512, 3, 3])
features.49.bias : torch.Size([512])
features.50.weight : torch.Size([512])
features.50.bias : torch.Size([512])
classifier.0.weight : torch.Size([4096, 25088])
classifier.0.bias : torch.Size([4096])
classifier.3.weight : torch.Size([4096, 4096])
classifier.3.bias : torch.Size([4096])
classifier.6.weight : torch.Size([1000, 4096])
classifier.6.bias : torch.Size([1000])
修改方式为,这里10是将默认的1000个分类改成了10个分类
net.classifier._modules['6'] = nn.Sequential(nn.Linear(4096, 10), nn.Softmax(dim=1))
对于第二个问题,如何冻结指定层的神经网络,训练指定层的神经网络
比如可以对不是分类层的其他层冻结起来。指定层的requires_grad属性为False就可以了。没有为False的层在训练代码中将得到训练更新。如下
# 冻结特征层前面30层,特征层后20层以及分类层进行训练
# 这里冻结层数可以根据自己需求更改
param_group = []
learning_rate = 1e-3
for name, parameters in net.named_parameters():
# 获取层的数量
layersid = int(name.split(".")[1])
if not name.__contains__('classifier') and layersid <= 30:
parameters.requires_grad = False
param_group += [{
'params': parameters, 'lr': learning_rate}]
对于第三个问题,如何为不同层设置不同的学习率
也同第二个问题一样处理,如下:
param_group = []
learning_rate = 1e-6
for name, parameters in net.named_parameters():
if not name.__contains__('classifier'):
param_group += [{
'params': parameters, 'lr': learning_rate}]
else:
param_group += [{
'params': parameters, 'lr': learning_rate*1000}]
这三个问题清楚以后,只要组合应用这三种方式,就可以写出前面说的三种fine-tune的调整方式。
为了不混淆,将五种不同的初始化,单独写出训练脚本:
1 train_default_init.py
: 默认初始化训练脚本。
2 train_custom_init.py
: 自定义初始化训练脚本。
3 train_finetune_classifier.py
: fine tune更改分类层训练脚本。
4 train_finetune_deep.py
: fine tune冻结浅层卷积神经网络训练脚本。
5 train_finetune_whole.py
: fine tune训练整个神经网络,但是设置不同的学习率训练脚本。
脚本: train.py
现在已经准备好数据集,网络也搭建好了. 现在要做的事情就是确定权重,也就是训练模型,
用 a @ x = y a@x=y a@x=y进行类比:
- x x x表示图像, y y y表示该图像的分类. ( x , y ) (x,y) (x,y)就是数据集;
- a a a表示权重.一个计算通常是两个数的某种操作,图像表示是一个数,而权重则是另外一个数;
- @ @ @表示网络,也就是权重和图像之前的操作,规定好了计算方法,加减乘除,以及计算步骤,先做哪一步,再做哪一步;
- 如果有了一张图像, 并且知道了权重,也知道了网络. 我们就可以对图像进行一系列的计算,然后得到 y y y这个分类,这就是推理预测;
- 而现在是根据(x,y)数据集来反推 a a a. 就是训练模型.做法先给 a a a赋个初始值 a ′ a' a′, 然后计算 a ′ @ x a'@x a′@x 得到 y ′ y' y′, 再根据预测值 y ′ y' y′和真实值 y y y之间的差距,最后根据差距来调整 a ′ a' a′. 反复这个过程就是训练模型;
- 计算预测值 y ′ y' y′和真实值 y y y之间的差距的函数就是损失函数(Loss Function);
- 根据损失来调整 a ′ a' a′.就是优化器所做的工作.
训练流程图:
从流程图上看,训练之前我们需求准备六样东西:
数据和网络已经准备好了.
pytorch提供了17种损失函数.可先自行查阅.
步骤:在迭代循环外定义损失函数 --> 循环计算损失 -->循环根据损失计算反向梯度
import torch.nn as nn
# 交叉熵损失函数
celoss = nn.CrossEntropyLoss()
# 计算损失值,outputs预测值,labels真值
loss = celoss(outputs, labels)
# loss反向传播 计算反向梯度
loss.backward()
合理的学习率可以使优化器快速收敛。一般在训练初期给予较大的学习率,随着训练的进行,学习率逐渐减小。学习率什么时候减小,减小多少,这就涉及到学习率调整方法。
PyTorch 中提供了六种方法供大家使用,.可先自行查阅.
PyTorch 提供的十种优化器,有常见的 SGD、ASGD、Rprop、RMSprop、Adam 等等
步骤:在迭代循环外定义优化器 -->循环梯度置零 --> 循环用优化器调整权重
import torch.optim as optim
# 优化器 lr学习率为0.001 momentum动量为0.9
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# 梯度置零,因为反向传播过程中梯度会累加上一次循环的梯度
optimizer.zero_grad()
# 利用反向梯度 参数更新
optimizer.step()
简单的保存与加载方法有两种方式:
import torch
# 保存
torch.save(net, PATH)
# 加载
model_dict=torch.load(PATH)
import torch
# 保存
torch.save(net.state_dict(),PATH)
# 加载 model是初始化的网络
model_dict=model.load_state_dict(torch.load(PATH))
使用 TensorBoardX可对对神经网络进行统计可视化.
测试与训练的流程大体一致,有区别的地方:
测试流程图:
预测部分比较简单,也是最终的目的。
预测流程图如下:
本篇只是大体梳理了一下整个流程。具体细节会慢慢补齐。