最近参加飞桨的公开课,快速上手深度学习https://aistudio.baidu.com/aistudio/course/introduce/6771
PaddlePaddle开发文档
深度学习万能公式
数据处理
paddle.io.Dataset
paddle.io.DataLoader
组网和网络配置
paddle.prepare
训练和评估
model.fit
model.evaluate
预测
model.predict
数据集
使用cifar100数据集,完成对100个类别的分类。
导入相关库
import paddle
import numpy as np
数据准备
直接使用高层API内置的数据集Cifar100
// 首先查看一下数据集中的数据内容和标签
train = paddle.vision.datasets.Cifar100(mode='train')
print(train[0])
plt.imshow(train[0][0])
print('标签:',train[0][1])
正式准备训练集和验证集,并且使用高级API内置的数据增强方法对数据进行预处理。
import paddle.vision.transforms as T
# 训练数据集
train_dataset = paddle.vision.datasets.Cifar100(mode='train', transform=T.ToTensor())
# 验证数据集
eval_dataset = paddle.vision.datasets.Cifar100(mode='test', transform=T.ToTensor())
# 输出数据集大小
print('训练数据集大小为: ', len(train_dataset))
print('验证数据集大小为:', len(eval_dataset))
sample = train_dataset[0]
print('图片尺寸:',sample[0].shape)
print('图片标签:',sample[1])
模型搭建
论文地址:https://arxiv.org/abs/1804.06882
"""
一种面向实时应用的精简CNN架构,轻量级 backbone
基于DenseNet的轻量化网络变体,主要面向移动端部署。
"""
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
import numpy as np
from paddle.fluid.dygraph import to_variable
import layers
class Conv_bn_relu(nn.Layer):
def __init__(self,in_channels,out_channels,kernel_size=3,stride=1,padding=1,use_relu=True):
super(Conv_bn_relu, self).__init__()
self.use_relu = use_relu
if self.use_relu:
self.convs = nn.Sequential(
nn.Conv2D(in_channels,out_channels,kernel_size,stride,padding),
nn.BatchNorm2D(out_channels),
nn.ReLU()
)
else:
self.convs = nn.Sequential(
nn.Conv2D(in_channels,out_channels,kernel_size,stride,padding),
nn.BatchNorm2D(out_channels)
)
def forward(self, x):
out = self.convs(x)
return out
""" Stem Block:实现输入图像空间维度的第一次降采样(stride=2)和通道数的增加 """
class StemBlock(nn.Layer):
""" 尺寸缩小1/4,通道为32 """
def __init__(self,in_channels=3,out_channels=32):
super(StemBlock, self).__init__()
self.stem_1 = Conv_bn_relu(in_channels,out_channels,kernel_size=3,stride=2,padding=1)
self.stem_2a = Conv_bn_relu(out_channels,int(out_channels/2),1,1,0)
self.stem_2b = Conv_bn_relu(int(out_channels/2),out_channels,3,2,1)
self.stem_2p = nn.MaxPool2D(kernel_size=2,stride=2)
self.stem_3 = Conv_bn_relu(out_channels*2,out_channels,1,1,0)
def forward(self, x):
stem_1_out = self.stem_1(x) # 通道增加,尺寸缩小
# 左分支,先1*1改变通道数,再3*3缩小尺寸
stem_2a_out = self.stem_2a(stem_1_out)
stem_2b_out = self.stem_2b(stem_2a_out)
# 右分支,使用最大池化缩小尺寸
stem_2p_out = self.stem_2p(stem_1_out)
# Concat两个分支
out = self.stem_3(paddle.concat([stem_2b_out,stem_2p_out],1))
return out # 尺寸缩小1/4,通道32
""" Two-Way Dense Layer:由两路分别捕捉不同尺度感受野信息的网络分支构成 """
class DenseBlock(nn.Layer):
""" 分为左右两路提取特征 最后进行concat 尺寸不变 最终通道加倍 两个分支输出通道为growth_rate"""
def __init__(self,inp,inter_channel,growth_rate):
super(DenseBlock, self).__init__()
self.cb1_a = Conv_bn_relu(inp,inter_channel,1,1,0)
self.cb1_b = Conv_bn_relu(inter_channel,growth_rate,3,1,1)
self.cb2_a = Conv_bn_relu(inp,inter_channel,1,1,0)
self.cb2_b = Conv_bn_relu(inter_channel,growth_rate,3,1,1)
self.cb2_c = Conv_bn_relu(growth_rate,growth_rate,3,1,1)
def forward(self, x):
cb1_a_out = self.cb1_a(x)
cb1_b_out = self.cb1_b(cb1_a_out)
cb2_a_out = self.cb2_a(x)
cb2_b_out = self.cb2_b(cb2_a_out)
cb2_c_out = self.cb2_c(cb2_b_out)
out = paddle.concat([x,cb1_b_out,cb2_c_out],1)
return out
class TransitionBlock(nn.Layer):
""" 过渡层:输入与输出通道数保持一致 """
def __init__(self,inp,outp,with_pooling=True):
super(TransitionBlock, self).__init__()
if with_pooling: # 池化缩小尺寸
self.tb = nn.Sequential(
Conv_bn_relu(inp,outp,1,1,0),
nn.AvgPool2D(kernel_size=2,stride=2)
)
else: # 没有池化则尺寸不变
self.tb = Conv_bn_relu(inp,outp,1,1,0)
def forward(self, x):
out = self.tb(x)
return out
class PeleeNet(nn.Layer):
""" 组网开始 分类网络Backbone 轻量级架构 """
def __init__(self,num_classes=1000,
num_init_features=32,
growthRate=32,
nDenseBlocks=[3,4,8,6], # 每一个阶段DenseBlock的数量
bottleneck_width=[1,2,4,4]):
super(PeleeNet, self).__init__()
self.stage = nn.Sequential()
self.num_classes = num_classes
self.num_init_features = num_init_features
inter_channel = list()
total_filter = list()
dense_inp = list()
self.half_growth_rate = int(growthRate / 2) # 16
# building stemblock
self.stage.add_sublayer(name='stage_0',sublayer=StemBlock(3,num_init_features))
for i,b_w in enumerate(bottleneck_width): # bottleneck_width=[1,2,4,4]
inter_channel.append(int(self.half_growth_rate * b_w / 4) * 4)
if i==0:
total_filter.append(num_init_features + growthRate * nDenseBlocks[i])
dense_inp.append(self.num_init_features)
else:
total_filter.append(total_filter[i-1] + growthRate * nDenseBlocks[i])
dense_inp.append(total_filter[i-1])
if i == len(nDenseBlocks)-1:
with_pooling = False
else:
with_pooling = True
# building middle stageblock
self.stage.add_sublayer('stage_{}'.format(i+1),self._make_dense_transition(dense_inp[i],
total_filter[i],
inter_channel[i],
nDenseBlocks[i],
with_pooling=with_pooling))
# building classifier
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(total_filter[len(nDenseBlocks)-1],self.num_classes)
)
def _make_dense_transition(self,dense_inp,total_filter,inter_channel,ndenseblocks,with_pooling=True):
"""
dense_inp:输入通道数
total_filter:总输出通道数
inter_channel:中间通道数
ndenseblocks:稠密连接块的个数
with_pooling:池化操作缩小尺寸
"""
layers = []
for i in range(ndenseblocks):
layers.append(DenseBlock(dense_inp,inter_channel,self.half_growth_rate))
dense_inp += self.half_growth_rate * 2 # 对了
# Transition layer without Conpression
layers.append(TransitionBlock(dense_inp,total_filter,with_pooling))
return nn.Sequential(*layers)
def forward(self, x):
x = self.stage(x)
# print(self.stage.children())
# s = list(self.stage.children())
# print(len(s))
# global ave pooling
x = F.avg_pool2d(x,kernel_size=7)
x = paddle.fluid.layers.reshape(x,[x.shape[0],-1])
x = self.classifier(x)
out = F.log_softmax(x,1)
return out
net = PeleeNet(num_classes=1000)
peleenet = list(net.stage.children())
if __name__ == '__main__':
data = np.random.rand(1,3,224,224).astype(np.float32)
data = to_variable(data)
# model = StemBlock()
# out = model(data)
# print(out.shape)
#
# model1 = DenseBlock(inp=32,inter_channel=64,growth_rate=16)
# out1 = model1(out)
# print(out1.shape)
#
# model2 = TransitionBlock(inp=64,outp=128)
# out2 = model2(out1)
# print(out2.shape)
model = PeleeNet(num_classes=1000)
model.eval()
out = model(data)
print(out.shape)
model_info = paddle.summary(model,(1,3,224,224))
print(model_info)
import peleenet
pelee_network = peleenet.PeleeNet(num_classes=100)
模型可视化,使用paddle.Model接口对模型进行封装。
model = paddle.Model(pelee_network)
model.summary((-1, 3, 32, 32))
模型训练与调优
model.prepare(paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()),
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy())
model.fit(train_dataset,
eval_dataset,
epochs=20,
batch_size=64,
verbose=1)
// 模型存储
checkpoints_path = './checkpoints/models'
model.save(checkpoints_path,train=False)
// 模型存储两个文件,分别为.pdmodel和.pdiparams文件,可用于模型部署。
在这里,笔者对模型训练了20个epoch,精度为:
result = model.evaluate(eval_dataset, verbose=1)
print(result)
可以看到,模型在训练集的精度很高,但是验证集精度不够,发生了训练集的过拟合,可以对数据集以及模型结构进行进一步优化,达到验证集的精度提高。