在之前PaddleFluid的实例分析中,一直是训练过后,进行预测就结束了,没有进行模型的保存。这篇文章就讲解了如何在训练过程中保存模型,用于之后进行预测或者继续训练模型。本章会介绍三种保存模型的方法。一种方法是保存预测模型,是以后用来进行预测的模型;另外两种方法用来保存初始化模型,用来初始化模型继续训练。
接下就在代码中看看他们怎么用吧!我们将使用残差神经网络对经典的cifar数据集做一个10分类的工作(cifar数据集有100分类、10分类两个选择,我们这个网络使用的是cifar的10分类)。
导入相关的模块
#coding:utf-8
'''
created on February 19 15:06 2019
@author:lhy
'''
import os
import shutil
import paddle
import paddle.dataset.cifar as cifar
import paddle.fluid as fluid
定义一个残差神经网络,这是目前比较常用的网络,它可以通过增加网络深度达到提高识别率。不像传统的神经网络,对于传统的神经网络,当网络深度加深时,会出现损失精度(比如梯度消失等问题)。
#定义一个残差神经网络(ResNet)
def resnet_cifar(ipt,class_dim):
#定义一个卷积+批标准化层
def conv_bn_layer(input,ch_out,filter_size,stride,padding,act='relu',bias_attr=False):
tmp=fluid.layers.conv2d(input=input,filter_size=filter_size,num_filters=ch_out,stride=stride,padding=padding,bias_attr=bias_attr)
return fluid.layers.batch_norm(input=tmp,act=act)
def shortcut(input,ch_in,ch_out,stride):
if ch_in!=ch_out:
return conv_bn_layer(input,ch_out,1,stride,0,None)
else:
return input
#残差块
def basicblock(input,ch_in,ch_out,stride):
temp=conv_bn_layer(input,ch_out,3,stride,1)
temp=conv_bn_layer(temp,ch_out,3,1,1,act=None,bias_attr=True)
#如果输入层和输出层的大小不一样,就经过一个conv_bn,将输入层的大小变得和输出层一样
short=shortcut(input,ch_in,ch_out,stride)
#将两次卷积批标准化的结果和输入层相加,达到学习残差的目的
return fluid.layers.elementwise_add(x=temp,y=short,act='relu')
def layer_warp(block_func,input,ch_in,ch_out,count,stride):
temp=block_func(input,ch_in,ch_out,stride)
for i in range(1,count):
temp=block_func(temp,ch_out,ch_out,1)
return temp
conv1=conv_bn_layer(ipt,ch_out=16,filter_size=3,stride=1,padding=1)
res1=layer_warp(basicblock,conv1,16,16,5,1)
res2=layer_warp(basicblock,res1,16,32,5,2)
res3=layer_warp(basicblock,res2,32,64,5,2)
pool=fluid.layers.pool2d(input=res3,pool_size=8,pool_type='avg',pool_stride=1)
predict=fluid.layers.fc(input=pool,size=class_dim,act='softmax')
return predict
定义数据层,使用的是cifar数据集,这个数据集的图片是宽高都是32的3通道彩色图片,所以定义的输入层的shape是[3,32,32]。
#使用的数据集是cifar数据集,这个数据集是32*32的图片,3通道,则输入层shape应该是[3,32,32]
image=fluid.layers.data(name='image',shape=[3,32,32],dtype='float32')
#label是每张图片对应的0-9的分类数字,是int类型的,所以shape=[1]
label=fluid.layers.data(name='label',shape=[1],dtype='int64')
获取残差神经网络的分类器,制定分类的大小为10分类,因为这个数据集有10个类别。
#获取残差神经网络的分类器
model=resnet_cifar(image,10)
定义交叉熵损失函数和平均准确率。其中交叉熵损失函数和计算准确率会选取输出model的10分类中最大的那个值的下标和label做对比。详情可以看PaddlePaddle官方的1.1版本的api:PaddlePaddle Fluid-1.1-api
#损失函数和准确率
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)
定义优化方法(又是我最喜欢的Adam优化器,速度比梯度下降快)
#定义优化方法
optimizer=fluid.optimizer.AdamOptimizer(learning_rate=1e-3)
opts=optimizer.minimize(avg_cost)
获取训练和测试数据,在这里使用的是cifar数据集,cifar数据集有两种,一种是100类别的,一种是10类别的,在这里我们使用10类别的。因为这个数据集很大,所以下载cifar数据集所需要的时间会比较长。
#获取cifar数据集
train_reader=paddle.batch(cifar.train10(),batch_size=32)
test_reader=paddle.batch(cifar.test10(),batch_size=32)
创建执行器,头铁,还用CPU进行训练(实际上就是我的虚拟机没有CUDA)。并进行参数初始化。
#创建执行器,虚拟机不配用CUDA(实际上是我没得CUDA用
place=fluid.CPUPlace()
exe=fluid.Executor(place)
#参数初始化
exe.run(fluid.default_startup_program())
创建执行器之后就可以用我们之前提到过的两种加载模型的方式,他们两个对应着两种方式保存模型,这两种方法随便用一种即可。而且这两种方式是用来初始化模型继续训练的的,而不是使用进行预测的那个模型。
fluid.io.load_params()
是加载之前训练保存的参数模型,对应的保存接口是fluid.io.save_params()
。当使用这个模型参数恢复网络参数时,可以这样写:
#加载之前的params_model模型,没有模型就不加载
save_path='models/params_model'
if os.path.exists(save_path):
print("使用参数模型作为预训练模型")
fluid.io.load_params(executor=exe,dirname=save_path)
fluid.io.load_persistables()
施加在之前训练保存的持久化变量模型,对应的保存接口是fluid.io.save_persistables()
。当使用这个模型参数恢复网络时,可以这样写:
#加载之前的persistables_model模型,没有模型就不加载
save_path='models/persistables_model/'
if os.path.exists(save_path):
print("使用持久化变量模型作为预训练模型")
fluid.io.load_persistables(executor=exe,dirname=save_path)
以上二者都是保存的是初始化模型,所以在选择的时候,这两者可以随意选择,我比较喜欢前者,所以在最终的全部代码中,我会使用参数模型来保存模型参数。
而对于预测模型的存储和使用,文章后面就会提到。
开始训练模型:
#开始训练
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])
#100batch打印一次
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))
在训练过程中就可以保存模型,这里介绍全部三种模型的保存,一般使用fluid.io.save_inference_model()
函数保存的模型之后用于预测,是预测模型。使用fluid.io.save_params()
或者fluid.io.save_persistables()
函数保存的模型用于初始化模型,进行训练。
fluid.io.save_inference_model()
函数一般用来保存预测模型,用于以后预测图像。通过这个方式保存的模型,之后看来使用是非常方便的,具体怎样使用可以阅读下面的预测部分。使用方法如下:
#保存预测模型,方便以后预测
save_path='models/infer_model/'
#删除旧的模型文件
shutil.rmtree(save_path,ignore_errors=True)
#创建预测模型文件目录
os.makedirs(save_path)
#保存预测模型,值得注意的是,这个需要把输入层的name记录下来以便以后使用输入层的name进行feed
#想要预测的内容也要存入模型,以便以后使用fetch_list进行预测
fluid.io.save_inference_model(save_path,feeded_var_names=[image.name],target_vars=[model],executor=exe)
保存参数模型,以便以后初始化模型,继续训练。
#保存参数模型
save_path='model/params_model/'
#删除旧的模型文件
shutil.rmtree(save_path,ignore_errors=True)
#创建保持模型文件目录
os.makedirs(save_path)
#保存模型参数
fluid.io.save_params(executor=exe,dirname=save_path)
保存持久化变量模型,便于以后初始化模型,继续训练。
# 保存持久化变量模型
save_path = 'models/persistables_model/'
# 删除旧的模型文件
shutil.rmtree(save_path, ignore_errors=True)
# 创建保持模型文件目录
os.makedirs(save_path)
# 保存持久化变量模型
fluid.io.save_persistables(executor=exe, dirname=save_path)
#coding:utf-8
'''
created on February 19 15:06 2019
@author:lhy
'''
#使用fluid.io.save_params保存模型
import os
import shutil
import paddle
import paddle.dataset.cifar as cifar
import paddle.fluid as fluid
#定义一个残差神经网络(ResNet)
def resnet_cifar(ipt,class_dim):
#定义一个卷积+批标准化层
def conv_bn_layer(input,ch_out,filter_size,stride,padding,act='relu',bias_attr=False):
tmp=fluid.layers.conv2d(input=input,filter_size=filter_size,num_filters=ch_out,stride=stride,padding=padding,bias_attr=bias_attr)
return fluid.layers.batch_norm(input=tmp,act=act)
def shortcut(input,ch_in,ch_out,stride):
if ch_in!=ch_out:
return conv_bn_layer(input,ch_out,1,stride,0,None)
else:
return input
#残差块
def basicblock(input,ch_in,ch_out,stride):
temp=conv_bn_layer(input,ch_out,3,stride,1)
temp=conv_bn_layer(temp,ch_out,3,1,1,act=None,bias_attr=True)
short=shortcut(input,ch_in,ch_out,stride)
return fluid.layers.elementwise_add(x=temp,y=short,act='relu')
def layer_warp(block_func,input,ch_in,ch_out,count,stride):
temp=block_func(input,ch_in,ch_out,stride)
for i in range(1,count):
temp=block_func(temp,ch_out,ch_out,1)
return temp
conv1=conv_bn_layer(ipt,ch_out=16,filter_size=3,stride=1,padding=1)
res1=layer_warp(basicblock,conv1,16,16,5,1)
res2=layer_warp(basicblock,res1,16,32,5,2)
res3=layer_warp(basicblock,res2,32,64,5,2)
pool=fluid.layers.pool2d(input=res3,pool_size=8,pool_type='avg',pool_stride=1)
predict=fluid.layers.fc(input=pool,size=class_dim,act='softmax')
return predict
#使用的数据集是cifar数据集,这个数据集是32*32的图片,3通道,则输入层shape应该是[3,32,32]
image=fluid.layers.data(name='image',shape=[3,32,32],dtype='float32')
label=fluid.layers.data(name='label',shape=[1],dtype='int64')
print(label.shape)
#获取残差神经网络的分类器,指定是10分类,这个数据集有10个类别
model=resnet_cifar(image,10)
print(model.shape)
#损失函数和准确率
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)
#获取cifar数据集,cifar数据集有两种,一种是100类别,一种是10类别,这里使用10类别,一组取出32组
train_reader=paddle.batch(cifar.train10(),batch_size=32)
test_reader=paddle.batch(cifar.test10(),batch_size=32)
#创建执行器,虚拟机不配用CUDA(实际上是我没得CUDA用
place=fluid.CPUPlace()
exe=fluid.Executor(place)
#参数初始化
exe.run(fluid.default_startup_program())
#加载之前的params_model模型,没有模型就不加载
save_path='models/params_model'
if os.path.exists(save_path):
print("使用参数模型作为预训练模型")
fluid.io.load_params(executor=exe,dirname=save_path)
#定义输入数据维度
feeder=fluid.DataFeeder(place=place,feed_list=[image,label])
#开始训练
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])
#100batch打印一次
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))
#保存模型,一般使用fluid.io.save_inference_model()函数保存的模型之后用于预测,是预测模型。
#使用fluid.io.save_params()或者fluid.io.save_persistables()函数保存的模型用于初始化模型,进行训练。
#保存预测模型,方便以后预测
save_path='models/infer_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)
#保存参数模型
save_path='models/params_model/'
#删除旧的模型文件
shutil.rmtree(save_path,ignore_errors=True)
#创建保持模型文件目录
os.makedirs(save_path)
#保存模型参数
fluid.io.save_params(executor=exe,dirname=save_path)
由于一共有10个pass,每个pass又有1500+的batch,输出信息太多,所以这里只展示最后一个Pass的输出:
Pass:9,Batch:0,Cost:0.23369,Accuracy:0.87500
Pass:9,Batch:100,Cost:0.13673,Accuracy:0.93750
Pass:9,Batch:200,Cost:0.14569,Accuracy:0.96875
Pass:9,Batch:300,Cost:0.11853,Accuracy:1.00000
Pass:9,Batch:400,Cost:0.10782,Accuracy:1.00000
Pass:9,Batch:500,Cost:0.10297,Accuracy:0.93750
Pass:9,Batch:600,Cost:0.09028,Accuracy:0.96875
Pass:9,Batch:700,Cost:0.13088,Accuracy:0.93750
Pass:9,Batch:800,Cost:0.46306,Accuracy:0.90625
Pass:9,Batch:900,Cost:0.15785,Accuracy:0.93750
Pass:9,Batch:1000,Cost:0.11927,Accuracy:0.93750
Pass:9,Batch:1100,Cost:0.12313,Accuracy:0.93750
Pass:9,Batch:1200,Cost:0.32262,Accuracy:0.84375
Pass:9,Batch:1300,Cost:0.20035,Accuracy:0.93750
Pass:9,Batch:1400,Cost:0.06530,Accuracy:1.00000
Pass:9,Batch:1500,Cost:0.23624,Accuracy:0.93750
Test:9,Cost:0.83426,Accuracy:0.78235
从测试集测试结果来看。。。有点过拟合了。。。
模型保存在models文件夹下,如下图所示:
模型中是这样存储参数的:
网络越复杂,参数越多,生成的模型文件越多。
在训练的时候,我们使用fluid.io.save_inference_model
接口保存了预测模型,通过以下程序,我们知道用这个接口保存的模型使用起来有多简单,其中很大一个改变就是不需要前向传播获得分类器了。
导入相关模块:
import paddle.fluid as fluid
from PIL import Image
import numpy as np
创建执行器:
#创建执行器
place=fluid.CPUPlace()
exe=fluid.Executor(place)
exe.run(fluid.default_startup_program())
重点:加载模型,通过fluid.io.load_inference_model()
函数可以轻松获得这个模型存储的预测程序,输入参数的名称,以及分类器的输出。
save_path='models/infer_model/'
#从模型中得到预测程序,输入数据名称列表和分类器
[infer_program,feeded_var_names,target_var]=fluid.io.load_inference_model(dirname=save_path,executor=exe)
预处理图像,将图片统一大小,修改图像的存储顺序和通道顺序,并转化成一个numpy数组:
#定义一个图像预处理的函数
def load_image(file):
im=Image.open(file)
im=im.resize((32,32),Image.ANTIALIAS)
im=np.array(im).astype(np.float32)
#PIL打开图片存储顺序为H(高度),W(宽度),C(通道数)
#但是PaddlePaddle要求数据的顺序是CHW,需要调整顺序
im=im.transpose((2,0,1))
#cifar训练的图片通道顺序为B(蓝),G(绿),R(红)
#但是PIL打开的图片是默认的RGB,所以要交换通道
im=im[(2,1,0),:,:]
im=im/255.0#归一化
im=np.expand_dims(im,axis=0)
return im
获取数据并进行预测,不需要再输入一个模拟的标签了,因为在保存模型的时候,已经对标签进行了修剪,去掉了这部分不必要的输入。
输入一张猫的图片:
#获取图片数据
img=load_image('cat.jpeg')
#执行预测
result=exe.run(program=infer_program,feed={feed_var_names[0]:img},fetch_list=target_var)
执行预测程序之后,获得一个10分类的数组,表示每种分类的概率,我们获得最大值的下标来确定该类的名称:
#获得值最大的元素对应的下标
lab=np.argmax(result[0][0])
names=['飞机','汽车','鸟','猫','鹿','狗','青蛙','马','船','卡车']
print("预测的标签是:%d,名称是:%s,概率是:%f"%(lab,names[lab],result[0][0][lab]))
输出结果:
预测的标签是:3,名称是:猫,概率是:0.951282
可以看到模型很成功的识别出了猫。
代码参考:夜雨飘零1—PaddlePaddle系列课程
非常感谢这位大佬!!!