本次练习,用迁移学习思想,结合paddle框架,来实现图像的分类。

相关理论:

1. 原有模型作为一个特征提取器:

使用一个用ImageNet数据集提前训练(pre-trained)好的CNN,再除去最后一层全连接层(fully-connected layer),即除去原有的分类器部分。然后再用剩下的神经网络作为特征提取器应用在新的数据集上。我们只需要用新的训练集训练一个嫁接到这个特征提取器上的分类器即可。

2.fine-turning原有模型

这种方法要求不仅仅除去原有模型的分类器,再用新的数据集训练新的分类器,还要求微调(fine-tune)本身神经网络的参数(weights)。和方式1的区别是:方式1要求固定原有神经网络的参数,而fine-tune允许训练新的数据集时对原有神经网络的参数进行更改。为了防止过拟合,有时可以要求固定某些层的参数,只微调剩下的一些层。

本次实验总共分两个steps

  • step-1: 在step-1中,加载在imagenet数据集上训练好的Resnet50模型,作为预训练模型,并且冻结除fc层之外的参数,只训练fc层。得到step_1_model;

拿到新数据集,想要用预训练模型处理的时候,通常都会先用step-1的方法看看预训练模型在新数据上的表现怎么样,摸个底。如果表现不错,还想看看能不能进一步提升,就可以试试Fine-tune(即解锁比较少的卷积层继续训练),但是不要期待会有什么质的飞跃。如果由于新数据集与原数据集(例如ImageNet数据集)的差别太大导致表现很糟,那么一方面可以考虑从头训练模型,另一方面也可以考虑解锁比较多层的训练,亦或干脆只用预训练模型的参数作为初始值,对模型进行完整训练。

  • step-2: 在step-2中,把step_1_model作为预训练模型,并在此基础上重新训练,得到最终的模型step_2_model。

但是要注意:
事实上,step-2必须在已经进行过‘冻结特征提取器参数的训练之后再尝试训练模型,这时分类器的参数已经经过一次训练。如果从随机生成的分类器参数开始直接训练,在做参数更新迭代过程中梯度将很可能过大,而导致模型的崩溃,使模型忘记学到的所有东西。

在finetune时,batch_size设置最好不要太大,以便于加速模型收敛。学习率也适当小一些。


目录

step-1:

1. 导入库

2. 定义ResNet网络

3.训练前准备

4.开始训练

step-2:

5. 导入库

6.定义ResNet网络

7.训练前准备


step-1:

在step-1中,加载在imagenet数据集上训练好的Resnet50模型,作为预训练模型,并且冻结除fc层之外的参数,只训练fc层。得到step_1_model。

1. 导入库

实验第一步,导入需要的库,本实验中专门定义了一个 reader.py文件,用来对数据集进行读取和预处理,所以也需要把reader.py文件import进来。

import os
import shutil
import paddle as paddle
import paddle.fluid as fluid
from paddle.fluid.param_attr import ParamAttr
import reader
# 获取flowers数据
train_reader = paddle.batch(reader.train(), batch_size=16)
test_reader = paddle.batch(reader.val(), batch_size=16)

2. 定义ResNet网络

  • 本次实验使用ResNet50这个残差神经网络,所以,接下来需要定义一个残差神经网络。
  • PaddlePaddle官方已经提供了ResNet以及其他经典的网络模型。链接:https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/models/resnet.py
  • 网络定义时,每一个层都由指定参数名字。
# 定义残差神经网络(ResNet)
def resnet50(input):def conv_bn_layer(input, num_filters, filter_size, stride=1, groups=1, act=None, name=None):conv = fluid.layers.conv2d(input=input,num_filters=num_filters,filter_size=filter_size,stride=stride,padding=(filter_size - 1) // 2,groups=groups,act=None,param_attr=ParamAttr(name=name + "_weights"),bias_attr=False,name=name + '.conv2d.output.1')if name == "conv1":bn_name = "bn_" + nameelse:bn_name = "bn" + name[3:]return fluid.layers.batch_norm(input=conv,act=act,name=bn_name + '.output.1',param_attr=ParamAttr(name=bn_name + '_scale'),bias_attr=ParamAttr(bn_name + '_offset'),moving_mean_name=bn_name + '_mean',moving_variance_name=bn_name + '_variance', )def shortcut(input, ch_out, stride, name):ch_in = input.shape[1]if ch_in != ch_out or stride != 1:return conv_bn_layer(input, ch_out, 1, stride, name=name)else:return inputdef bottleneck_block(input, num_filters, stride, name):conv0 = conv_bn_layer(input=input,num_filters=num_filters,filter_size=1,act='relu',name=name + "_branch2a")conv1 = conv_bn_layer(input=conv0,num_filters=num_filters,filter_size=3,stride=stride,act='relu',name=name + "_branch2b")conv2 = conv_bn_layer(input=conv1,num_filters=num_filters * 4,filter_size=1,act=None,name=name + "_branch2c")short = shortcut(input, num_filters * 4, stride, name=name + "_branch1")return fluid.layers.elementwise_add(x=short, y=conv2, act='relu', name=name + ".add.output.5")depth = [3, 4, 6, 3]num_filters = [64, 128, 256, 512]conv = conv_bn_layer(input=input, num_filters=64, filter_size=7, stride=2, act='relu', name="conv1")conv = fluid.layers.pool2d(input=conv, pool_size=3, pool_stride=2, pool_padding=1, pool_type='max')for block in range(len(depth)):for i in range(depth[block]):conv_name = "res" + str(block + 2) + chr(97 + i)conv = bottleneck_block(input=conv,num_filters=num_filters[block],stride=2 if i == 0 and block != 0 else 1,name=conv_name)pool = fluid.layers.pool2d(input=conv, pool_size=7, pool_type='avg', global_pooling=True)return pool

本次实验,使用的图片数据集是flowers。图片是3通道宽高都是224的彩色图,总类别是5种,每一个种类大约有六百多张。

3.训练前准备

接下来,开始做训练前的准备工作。首先,定义图片数据和标签数据的输入层:

# 定义输入层
image = fluid.layers.data(name='image', shape=[3, 224, 224], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')

利用stop_gradient,使得pool以上的层停止梯度传递,相当于keras中的freeze。这样就可以只训练最后的fc层,但是要注意:数据集是5分类,所以size要设为5.

# 获取分类器的上一层
pool = resnet50(image)
# 停止梯度下降
pool.stop_gradient = True
# 由这里创建一个基本的主程序
base_model_program = fluid.default_main_program().clone()# 这里再重新加载网络的分类器,大小为本项目的分类大小
model = fluid.layers.fc(input=pool, size=5, act='softmax')

接下来,要做的工作有:

  • 定义损失函数;
  • 求准确率;
  • 定义优化器;
  • 设定训练场所;
  • 定义执行器,并且完成参数初始化;
# 获取损失函数和准确率函数
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)# 定义优化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=1e-3)
opts = optimizer.minimize(avg_cost)# 定义训练场所
place = fluid.CUDAPlace(0)#用GPU训练
#place = fluid.CPUPlace() #用CPU训练
exe = fluid.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())
  • 接下来加载预训练模型,使用paddle官网上训练好的ResNet50模型,这个模型存储在“./ResNet50_pretrained/”,也可以去官网上下载,链接:http://paddle-imagenet-models-name.bj.bcebos.com/ResNet50_pretrained.tar
  • 通过if_exist函数判断网络所需的模型文件是否存在,然后再通过调用fluid.io.load_vars加载存在的模型文件。
# 官方提供的原预训练模型
src_pretrain_model_path = './ResNet50_pretrained/'# 通过这个函数判断模型文件是否存在
def if_exist(var):path = os.path.join(src_pretrain_model_path, var.name)exist = os.path.exists(path)return exist# 加载模型文件,只加载存在模型的模型文件
fluid.io.load_vars(executor=exe, dirname=src_pretrain_model_path, predicate=if_exist, main_program=base_model_program)

4.开始训练

接下来就定义一个双层循环来开始训练模型,并且还可以把训练过程中的cost值和acc值打印出来,以此来直观的感受训练效果。

# 优化内存
# optimized = fluid.transpiler.memory_optimize(input_program=fluid.default_main_program(), print_log=False)# 定义输入数据维度
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])# 训练10次
for pass_id in range(10):# 进行训练for batch_id, data in enumerate(train_reader()):train_cost, train_acc = exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost, acc])# 每100个batch打印一次信息if batch_id % 100 == 0:print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %(pass_id, batch_id, train_cost[0], train_acc[0]))

训练结束之后,使用fluid.io.save_param保存训练好的参数。

到这里为止,把从imagenet数据集上训练好的的原预训练模型,结合数据集,把最后一层fc进行了训练。接下来就是使用这个已经处理过的模型正式训练了。

# 保存参数模型
save_pretrain_model_path = 'models/step-1_model/'# 删除旧的模型文件
shutil.rmtree(save_pretrain_model_path, ignore_errors=True)
# 创建保持模型文件目录
os.makedirs(save_pretrain_model_path)
# 保存参数模型
fluid.io.save_params(executor=exe, dirname=save_pretrain_model_path)

step-2:

5. 导入库

import os
import shutil
import paddle as paddle
import paddle.dataset.flowers as flowers
import paddle.fluid as fluid
from paddle.fluid.param_attr import ParamAttr
import reader# 获取flowers数据
train_reader = paddle.batch(reader.train(), batch_size=16)
test_reader = paddle.batch(reader.val(), batch_size=16)

6.定义ResNet网络

仍然需要定义一个残差神经网络,这个残差神经网络跟第一步时的基本一样的,只是把分类器(也就是fc层)也加进去了,这是一个完整的神经网络。

# 定义残差神经网络(ResNet)
def resnet50(input, class_dim):def conv_bn_layer(input, num_filters, filter_size, stride=1, groups=1, act=None, name=None):conv = fluid.layers.conv2d(input=input,num_filters=num_filters,filter_size=filter_size,stride=stride,padding=(filter_size - 1) // 2,groups=groups,act=None,param_attr=ParamAttr(name=name + "_weights"),bias_attr=False,name=name + '.conv2d.output.1')if name == "conv1":bn_name = "bn_" + nameelse:bn_name = "bn" + name[3:]return fluid.layers.batch_norm(input=conv,act=act,name=bn_name + '.output.1',param_attr=ParamAttr(name=bn_name + '_scale'),bias_attr=ParamAttr(bn_name + '_offset'),moving_mean_name=bn_name + '_mean',moving_variance_name=bn_name + '_variance', )def shortcut(input, ch_out, stride, name):ch_in = input.shape[1]if ch_in != ch_out or stride != 1:return conv_bn_layer(input, ch_out, 1, stride, name=name)else:return inputdef bottleneck_block(input, num_filters, stride, name):conv0 = conv_bn_layer(input=input,num_filters=num_filters,filter_size=1,act='relu',name=name + "_branch2a")conv1 = conv_bn_layer(input=conv0,num_filters=num_filters,filter_size=3,stride=stride,act='relu',name=name + "_branch2b")conv2 = conv_bn_layer(input=conv1,num_filters=num_filters * 4,filter_size=1,act=None,name=name + "_branch2c")short = shortcut(input, num_filters * 4, stride, name=name + "_branch1")return fluid.layers.elementwise_add(x=short, y=conv2, act='relu', name=name + ".add.output.5")depth = [3, 4, 6, 3]num_filters = [64, 128, 256, 512]conv = conv_bn_layer(input=input, num_filters=64, filter_size=7, stride=2, act='relu', name="conv1")conv = fluid.layers.pool2d(input=conv, pool_size=3, pool_stride=2, pool_padding=1, pool_type='max')for block in range(len(depth)):for i in range(depth[block]):conv_name = "res" + str(block + 2) + chr(97 + i)conv = bottleneck_block(input=conv,num_filters=num_filters[block],stride=2 if i == 0 and block != 0 else 1,name=conv_name)pool = fluid.layers.pool2d(input=conv, pool_size=7, pool_type='avg', global_pooling=True)output = fluid.layers.fc(input=pool, size=class_dim, act='softmax')return output

7.训练前准备

接下来,开始做训练前的准备工作:

  • 定义图片数据和标签数据的输入层;
  • 定义损失函数;
  • 求准确率;
  • 定义优化器;
  • 设定训练场所;
  • 定义执行器,并且完成参数初始化;
# 定义输入层
image = fluid.layers.data(name='image', shape=[3, 224, 224], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')# 获取分类器
model = resnet50(image, 5)# 获取损失函数和准确率函数
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)# 获取训练和测试程序
test_program = fluid.default_main_program().clone(for_test=True)# 定义优化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=1e-3)
opts = optimizer.minimize(avg_cost)# 定义一个使用GPU的执行器
place = fluid.CUDAPlace(0)
#place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())

加载经过step-1训练好的模型,作为新的预训练模型。并在此基础上进行重新训练:

# 经过step-1处理后的的预训练模型
pretrained_model_path = 'models/step-1_model/'# 加载经过处理的模型
fluid.io.load_params(executor=exe, dirname=pretrained_model_path)

接下来就定义一个双层循环来开始训练模型,并且还可以把训练过程中的cost值和acc值打印出来,以此来直观的感受训练效果。

# 定义输入数据维度
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])# 训练10次
for pass_id in range(10):# 进行训练for batch_id, data in enumerate(train_reader()):train_cost, train_acc = exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost, acc])# 每100个batch打印一次信息if batch_id % 100 == 0:print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %(pass_id, batch_id, train_cost[0], train_acc[0]))# 进行测试test_accs = []test_costs = []for batch_id, data in enumerate(test_reader()):test_cost, test_acc = exe.run(program=test_program,feed=feeder.feed(data),fetch_list=[avg_cost, acc])test_accs.append(test_acc[0])test_costs.append(test_cost[0])# 求测试结果的平均值test_cost = (sum(test_costs) / len(test_costs))test_acc = (sum(test_accs) / len(test_accs))print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))

训练结束之后,可以保存预测模型用于之后的预测使用。

# 保存预测模型
save_path = 'models/step_2_model/'# 删除旧的模型文件
shutil.rmtree(save_path, ignore_errors=True)
# 创建保持模型文件目录
os.makedirs(save_path)
# 保存预测模型
fluid.io.save_inference_model(save_path, feeded_var_names=[image.name], target_vars=[model], executor=exe)

动手学PaddlePaddle(5):迁移学习相关推荐

  1. 动手学ocr·十讲--学习笔记一

    动手学ocr·十讲 技术导论 课程链接请点击这里 1.1 OCR技术背景: OCR是什么 OCR(Optical Character Recognition,光学字符识别)是计算机视觉重要方向之一.传 ...

  2. paddlepaddle 13 迁移学习中的卷积基加强训练方法-RIFLE

    预训练模型作为当下迁移学习的一种主流方法,让"从零开始训练一个新模型"的时代一去不复返.这类在大型数据集上训练好的模型进行微调后,便能够较好地适配类似的新目标任务,可以极大地节约时 ...

  3. 动手学PaddlePaddle(4):MNIST(手写数字识别)

    本次练习将使用 PaddlePaddle 来实现三种不同的分类器,用于识别手写数字.三种分类器所实现的模型分别为 Softmax 回归.多层感知器.卷积神经网络. 您将学会 实现一个基于Softmax ...

  4. 动手学PaddlePaddle(1):线性回归

    你将学会: 机器学习的基本概念:假设函数.损失函数.优化算法 数据怎么进行归一化处理 paddlepaddle深度学习框架的一些基本知识 如何用paddlepaddle深度学习框架搭建全连接神经网络 ...

  5. 动手学PaddlePaddle(2):房价预测

    通过这个练习可以了解到: 机器学习的典型过程: 获取数据 数据预处理 -训练模型 -应用模型 fluid训练模型的基本步骤: 配置网络结构: 定义成本函数avg_cost 定义优化器optimizer ...

  6. 动手学PaddlePaddle(3):猫脸识别

    你将学会: 预处理图片数据 利用PaddlePaddle框架实现Logistic回归模型: 在开始练习之前,简单介绍一下图片处理的相关知识: 图片处理 由于识别猫问题涉及到图片处理知识,这里对计算机如 ...

  7. 动手学PaddlePaddle(0):新版本PaddlePaddle安装

    目录 0.引言 1.环境 2.Windows下安装 安装Python 安装PaddlePaddle 0.引言 今天介绍如何安装新版本的PaddlePaddle,现在最新版的PaddlePaddle是指 ...

  8. CV好文推荐+迁移学习经验分享

    前几日,52CV分享了最近一本新出的书<深度学习500问>中迁移学习相关的内容:有故事.有实践,谈一谈深度迁移学习方法的基本思路,并举办了以"论文推荐+经验分享"赠送这 ...

  9. 李沐d2l《动手学深度学习》第二版——风格迁移源码详解

    本文是对李沐Dive to DL<动手学深度学习>第二版13.12节风格迁移的源码详解,整体由Jupyter+VSCode完成,几乎所有重要代码均给出了注释,一看就懂.需要的同学可以在文末 ...

最新文章

  1. Windows7 WIN 7 64位 环境编译6sv2.1版本的大气传输模型
  2. latex 1图加标题_学习|Latex排版
  3. 1 python基础
  4. debian9 配置ssh 允许 root直接远程登录
  5. 输出最大值MXNet实现
  6. js小例子(标签页)
  7. 微博android4.1.2,Fuubo微博(新浪微博第三方客户端)app
  8. median filter
  9. python函数之任意数量的实参
  10. 贴片晶振的脚位方向如何区分
  11. win10 系统不显示机械硬盘(装在光驱位置)
  12. 8天掌握EF的Code First开发系列之动手写第一个Code First应用
  13. iscsi initiator 安装配置
  14. openstack-mitaka(二) 基于vmware的搭建
  15. 成都笔试——PL/SQL准备
  16. 为什么不用kotlin_为什么Kotlin和斯威夫特是兄弟姐妹?
  17. 学习笔记(08):区块链应用案例-区块链商业应用-供应链管理
  18. Springboot 设置上传文件大小
  19. tp5下delete()方法
  20. java代码---------陈勇老师的

热门文章

  1. [Leedcode][JAVA][第102题][二叉树的层序遍历][递归][迭代][BFS]
  2. python collections模块_Python 的collections模块
  3. 微星刀锋 无法进入bios_夏天来了 微星主板风扇转速调节攻略
  4. 1285B. Just Eat It
  5. linux搭建虚拟化平台报告,部署KVM虚拟化平台------搭建(示例代码)
  6. 基于python的个人博客系统的设计开题报告_个人博客设计实现开题报告
  7. java 高飞_高飞(土木与水利工程学院)老师 - 合肥工业大学
  8. “无法解析外部符号 __security_cookie”问题解决
  9. Platform Builder实践之配置文件
  10. linux arp 文件,LINUX 下ARP 的查找