现在的数据集越来越大,都是大模型的训练,参数都早已超过亿级,面对如此大的训练集,绝大部分用户的硬件配置达不到,那有没有一种方法让这些训练好的大型数据集的参数,迁移到自己的一个目标训练数据集当中呢?比如使用最广泛的图像数据集ImageNet,超过1000万张的图像和1000个分类,这些耗费大量时间人力物力而训练出来的参数,为我所用?
答案是肯定的,就是接下来说的微调(fine tuning),顾名思义就是细微的调节将这个已训练好的模型参数迁移过来,或者说复制(不是完全拷贝,故有点区别,所以叫微调)过来,最后再对自己的模型进行训练。
比如说我们的一个数据集是想找出图片中的热狗,但ImageNet数据集的图像大多于此无关,那迁移过来的参数有用吗?有用,因为在训练ImageNet中抽取的特征,如:边缘、纹理、形状等,对于识别物体都有同样的效果。
如何从源数据集迁移到目标数据集呢?方法就是将除了输出层的其余层的参数复制(做了微调)到目标数据集的除了输出层的其余层。而对于目标数据集的输出层,我们随机初始化该层的模型参数,然后将整个目标数据集重新训练一遍即可。
我们下载热狗数据集,来识别图像中的热狗的一个示例:
-
import d2lzh
as d2l
-
from mxnet
import gluon,init,nd
-
from mxnet.gluon
import data
as gdata,loss
as gloss,model_zoo
-
from mxnet.gluon
import utils
as gutils
-
import os
-
import zipfile
-
#下载热狗数据集(如果超时等下载不了,就直接手动下载再解压)
-
#解压之后是hotdog目录,里面是train和test目录,分别都有hotdog和not-hotdog目录,存放热狗和非热狗(和热狗长的像的,比如香蕉之类)的图像
-
data_dir=
'../data'
-
fname=gutils.download(
'https://apache-mxnet.s3-accelerate.amazonaws.com/gluon/dataset/hotdog.zip')
-
with zipfile.ZipFile(fname,
'r')
as z:
-
z.extractall(data_dir)
当然如果遇到权限错误,比如我的是C盘存放,使用管理员权限的命令行执行即可。
PermissionError: [WinError 5] 拒绝访问。: '..\\data\\hotdog'
数据集下载下来了,我们先来显示正类图像(热狗)和负类图像(非热狗),熟悉下这个数据集,代码如下:
-
train_imgs=gdata.vision.ImageFolderDataset(os.path.join(data_dir,
'hotdog/train'))
-
test_imgs=gdata.vision.ImageFolderDataset(os.path.join(data_dir,
'hotdog/test'))
-
#print(len(train_imgs),len(test_imgs))#2000,800
-
-
#print(train_imgs[0])#...<NDArray 144x122x3 @cpu(0)>, 0)(高,宽,通道)和标签值,0是热狗,1是非热狗
-
#print(train_imgs.items[0])#('../data\\hotdog/train\\hotdog\\0.png', 0)
-
hotdogs=[train_imgs[i][
0]
for i
in
range(
8)]
-
not_hotdogs=[test_imgs[-i][
0]
for i
in
range(
8)]
-
d2l.show_images(hotdogs+not_hotdogs,
2,
8,scale=
1.5)
-
d2l.plt.show()
从画布中显示的图像可以看出,都是些大小和宽高比都不一样的热狗与非热狗图。
训练模型前的图像增广
在训练时,先从图像中裁剪出随机大小和随机高宽比的一块区域,然后将该区域缩放到高宽为224像素的输入。测试时,我们将图像的高宽缩放到256像素,然后从中裁剪出高宽为224的中心区域作为输入。此外,我们对颜色通道做标准化,就是每个数值减去所有数值的均值,再除以标准差。
-
# 对颜色通道做标准化处理
-
normalize = gdata.vision.transforms.Normalize(
-
[
0.485,
0.456,
0.406], [
0.229,
0.224,
0.225])
# 均值,方差
-
train_augs = gdata.vision.transforms.Compose([gdata.vision.transforms.RandomResizedCrop(
224), gdata.vision.transforms.RandomFlipLeftRight(),
-
gdata.vision.transforms.ToTensor(), normalize])
-
test_augs = gdata.vision.transforms.Compose([gdata.vision.transforms.Resize(
256), gdata.vision.transforms.CenterCrop(
224),
-
gdata.vision.transforms.ToTensor(), normalize])
定义和初始化模型
使用ImageNet数据集上预训练的ResNet-18作为源模型。预训练模型的参数,将下载到:C:\Users\Tony\.mxnet\models里面的resnet18_v2-8aacf80f.params参数文件
-
pretrained_net = model_zoo.vision.resnet18_v2(pretrained=
True)
-
#包含两个成员变量:features和output
-
#打印output输出层看下,输出1000类的全连接层
-
print(pretrained_net.features)
#Dense(512 -> 1000, linear)
接下来新建目标模型,其定义和预训练的源模型一样,只不过将最后的输出数修改为目标数据集的类别数,因为features中的模型参数是已经在ImageNet数据集上预训练得到的,已经足够好,所以只需要使用较小的学习率来微调这些参数。对于output中的模型参数我们采用随机初始化,一般需要更大的学习率(10倍学习率)从头训练。
-
finetune_net=model_zoo.vision.resnet18_v2(classes=
2)
-
finetune_net.features=pretrained_net.features
-
finetune_net.output.initialize(init.Xavier())
-
finetune_net.output.collect_params().
setattr(
'lr_mult',
10)
微调模型
定义一个使用微调的训练函数,便于多次调用:
-
def
train_fine_tuning(
net,learning_rate,batch_size=128,num_epochs=5):
-
train_iter=gdata.DataLoader(train_imgs.transform_first(train_augs),batch_size,shuffle=
True)
-
test_iter=gdata.DataLoader(test_imgs.transform_first(test_augs),batch_size)
-
ctx=d2l.try_all_gpus()
-
net.collect_params().reset_ctx(ctx)
-
net.hybridize()
-
loss=gloss.SoftmaxCrossEntropyLoss()
-
trainer=gluon.Trainer(net.collect_params(),
'sgd',{
'learning_rate':learning_rate,
'wd':
0.001})
-
d2l.train(train_iter,test_iter,net,loss,trainer,ctx,num_epochs)
我们以小的学习率0.01微调获得预训练模型参数,然后以10倍学习率从头训练目标模型的输出层参数,当然本人配置不是很好,批处理大小就弄小点,不然内存溢出。
-
train_fine_tuning(finetune_net,
0.01,
32)
-
'''
-
(pygpu) C:\Users\Tony>python p.py
-
training on [gpu(0)]
-
epoch 1, loss 1.5428, train acc 0.815, test acc 0.594, time 28.9 sec
-
epoch 2, loss 0.7645, train acc 0.864, test acc 0.921, time 24.9 sec
-
epoch 3, loss 0.5352, train acc 0.882, test acc 0.921, time 24.8 sec
-
epoch 4, loss 0.4242, train acc 0.882, test acc 0.774, time 24.8 sec
-
epoch 5, loss 0.3898, train acc 0.893, test acc 0.917, time 24.9 sec
-
'''
作为对比,定义一个相同模型,但是将它所有模型参数都是初始化为随机值,由于需要从头训练,学习率大点0.1。
-
scratch_net=model_zoo.vision.resnet18_v2(classes=
2)
-
scratch_net.initialize(init=init.Xavier())
-
train_fine_tuning(scratch_net,
0.1,
32)
-
'''
-
(pygpu) C:\Users\Tony>python p.py
-
training on [gpu(0)]
-
epoch 1, loss 0.5667, train acc 0.750, test acc 0.818, time 27.9 sec
-
epoch 2, loss 0.4731, train acc 0.795, test acc 0.824, time 25.0 sec
-
epoch 3, loss 0.4109, train acc 0.818, test acc 0.854, time 24.9 sec
-
epoch 4, loss 0.3790, train acc 0.828, test acc 0.812, time 24.9 sec
-
epoch 5, loss 0.4080, train acc 0.826, test acc 0.853, time 24.9 sec
-
'''
如果不微调,直接使用源模型参数,将会怎么样呢?
-
finetune_net = model_zoo.vision.resnet18_v2(classes=
2)
-
finetune_net.features=pretrained_net.features
-
finetune_net.features.collect_params().
setattr(
'grad_req',
'null')
-
finetune_net.output.initialize(init.Xavier())
-
finetune_net.output.collect_params().
setattr(
'lr_mult',
10)
测试几次的效果,loss效果比较差,精度还好,不稳定性要大点,可能是小数据集容易过拟合的原因,不知道伙伴们测试的效果如何?
模型前缀不一样
我们也可以将微调得到的最终参数保存起来
finetune_net.collect_params().save('hotdog.params')
然后我们加载保存的参数看下:
-
mynet=model_zoo.vision.resnet18_v2(classes=
2)
-
mynet.collect_params().load(
'hotdog.params')
-
train_fine_tuning(mynet,
0.01,
32)
出现如下错误:
AssertionError: Parameter 'resnetv22_batchnorm0_gamma' is missing in file 'hotdog.params', which contains parameters: 'resnetv20_batchnorm0_gamma', 'resnetv20_batchnorm0_beta', 'resnetv20_batchnorm0_running_mean', ..., 'resnetv20_batchnorm2_running_mean', 'resnetv20_batchnorm2_running_var', 'resnetv21_dense0_weight', 'resnetv21_dense0_bias'. Please make sure source and target networks have the same prefix.
很明显里面的前缀名不一致,这个就是没有固定前缀,在训练模型的时候都是动态递增的前缀,所以我们需要固定前缀,这样在加载的时候就指定前缀就好了
训练的时候,指定前缀:
-
pretrained_net = model_zoo.vision.resnet18_v2(pretrained=
True,prefix=
'res_')
-
finetune_net = model_zoo.vision.resnet18_v2(classes=
2,prefix=
'res_')
-
finetune_net.features=pretrained_net.features
-
finetune_net.output.initialize(init.Xavier())
-
finetune_net.output.collect_params().
setattr(
'lr_mult',
10)
加载的时候,同样指定前缀即可:
-
mynet=model_zoo.vision.resnet18_v2(classes=
2,prefix=
'res_')
-
mynet.collect_params().load(
'hotdog.params')
-
#print(mynet)
然后看下效果:
-
train_fine_tuning(mynet,
0.01,
32)
-
'''
-
(pygpu) C:\Users\Tony>python p.py
-
training on [gpu(0)]
-
epoch 1, loss 0.1788, train acc 0.933, test acc 0.939, time 28.0 sec
-
epoch 2, loss 0.1303, train acc 0.947, test acc 0.926, time 25.7 sec
-
epoch 3, loss 0.1398, train acc 0.948, test acc 0.943, time 25.7 sec
-
epoch 4, loss 0.1236, train acc 0.953, test acc 0.939, time 25.1 sec
-
epoch 5, loss 0.1157, train acc 0.955, test acc 0.941, time 25.1 sec
-
'''
ImageNet中热狗
目前的输出层我们使用的是随机初始化,现在我们将ImageNet数据集中的热狗这个类的权重参数应用到我们的初始化里面来,看下有什么效果:
-
weight=pretrained_net.output.weight
-
hotdog_w=nd.split(weight.data(),
1000,axis=
0)[
713]
-
output_init=nd.concat(hotdog_w,-hotdog_w,dim=
0)
#将热狗的权重分一个负类的代表非热狗
-
print(output_init)
-
'''
-
[[-0.07650785 0.02459255 0.00455526 ... -0.06427797 -0.01825024
-
-0.02214353]
-
[ 0.07650785 -0.02459255 -0.00455526 ... 0.06427797 0.01825024
-
0.02214353]]
-
<NDArray 2x512 @cpu(0)>
-
'''
-
finetune_net.output.initialize(init.Constant(output_init))
#加载ImageNet里的热狗权重参数(增加一个负类)
-
finetune_net.output.collect_params().
setattr(
'lr_mult',
10)
-
train_fine_tuning(finetune_net,
0.01,
32)
-
'''
-
training on [gpu(0)]
-
[12:41:37] c:\jenkins\workspace\mxnet-tag\mxnet\src\operator\nn\cudnn\./cudnn_algoreg-inl.h:97: Running performance tests to find the best convolution algorithm, this can take a while... (set the environment variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable)
-
epoch 1, loss 1.3655, train acc 0.804, test acc 0.818, time 27.9 sec
-
epoch 2, loss 0.6536, train acc 0.875, test acc 0.902, time 25.0 sec
-
epoch 3, loss 0.5011, train acc 0.885, test acc 0.934, time 25.1 sec
-
epoch 4, loss 0.3031, train acc 0.913, test acc 0.922, time 25.0 sec
-
epoch 5, loss 0.2167, train acc 0.917, test acc 0.924, time 24.9 sec
-
'''
转载:https://blog.csdn.net/weixin_41896770/article/details/127947706