前言:第三天,老师结合ppt文图详细讲解了线性和卷积网络的构建,由简单到复杂的讲解卷积网络的发展。最后结合几个项目加深理解。愈发感觉老师讲的好了。第二天的课听完后还感觉自己什么都懂了,结果轮到自己动手搭建模型时发现自己的无知,所以今天要把各个知识点详尽列举。
———————————————
课程文档PPT
https://aistudio.baidu.com/aistudio/education/preview/1106964
权重w
,偏置b
,激活函数σ
,还有输入x
,输出y
组成。注意:paddle1版本,relu激活函数是合并在线性层里面一起写的,不需要独立出来加。但在paddle2.0版本中,线性层和激活函数层是分开的,你用完线性层,激活函数必须加后面。
将“线性函数”转变为“非线性函数”
,个人的理解/换个说话,意思就是:如果没有激活函数纯神经元组成的线性网络,无论多少层都可以拍扁为一层。意思就是达不到多层的目的。 这一点还是蛮好理解的,因为一个神经元就是线性函数,如果全都用这种线性函数组成,那整个模型就能通过化简运算变为单层,达不到多层的效果。题外话:一览经典模型的结构图,看多后养成概念:模型其实就是就是一堆层的操作的叠加。背后的功能也都是抽象性的。而且现在的层数也是越来越多了。有了概念后,看入门不会那么懵逼。
SoftMax激活函数
常被用到,经过运算后输出值范围为(0,1)
,相当于概率值。用于分类很合适,在第二课中也有项目例子,可以查看。SoftMax激活函数
的方法是paddle.nn.functional.softmax
,而还有一个paddle.nn.CrossEntropyLoss()
方法是交叉熵损失函数
和SoftMax激活函数
组合而成的SoftMax分类器
。SoftMax激活函数
……这就有点奇怪了,因为用分类器,我开预测值范围并不是(0,1)
。这是一个疑惑的问题,日后如果明白了回来补上。SoftMax激活函数
,才能使预测值像概率。但另一个问题是我现在哈不知道训练时不加,预测时加这种分开的操作……在老师的定义归类中,模型只有前面那堆神经元组成正向传播过程。而后续的反向传播过程是另外单独的。这样归类也很合理,因为模型在用的时候也只有前向传播的过程,而反向传播的过程是在优化模型和设计模型中起作用。
输入x
经过神经网络得到输出y'
,然后与真实值y
经过损失函数,得到损失值l
,全部加和得到总损失L
。输出y'
接近真实值y
,即损失值l
尽可能小。神经网络参数
。通过改变网络参数,损失值会不断改变。而我们每次改变的步长
与学习率n
有关,而移动的方向,就与由损失值计算的梯度
有关。初始值
和步长
就很重要,决定了训练的速度。局部最小值
,而不是全局最小值
。而且,因为步长
而错过最小值也有可能。调参数,又是另一门学问了,第四课笔记再详解。涉及:
超参数
,等概念。还有调参参考的指标。
【学习目录】基于PaddlePaddle2.0-构建机器学习、深度学习模型
https://aistudio.baidu.com/aistudio/projectdetail/1354419
这一部分的4个模型是昨天讲的,这里再快速过一遍。其中模型构建如代码所示,除了线性回归,其余代码都使用了
paddle.nn.CrossEntropyLoss()
。即在模型的最后输出层都有一个SoftMax激活函数
。
基于PaddlePaddle2.0-构建线性回归模型
https://aistudio.baidu.com/aistudio/projectdetail/1322247
# 构建模型。
model=paddle.nn.Linear(in_features=2, out_features=1)
基于PaddlePaddle2.0-构建SoftMax分类器
https://aistudio.baidu.com/aistudio/projectdetail/1323298
# 构建模型。
linear=paddle.nn.Sequential(
paddle.nn.Flatten(),#将[1,28,28]形状的图片数据改变形状为[1,784]
paddle.nn.Linear(784,10)
)
基于PaddlePaddle2.0-构建多层感知机模型
https://aistudio.baidu.com/aistudio/projectdetail/1323886
# 构建模型。
def forward(self, x):
x=self.flatten(x)
x=self.hidden(x) #经过隐藏层
x=F.relu(x) #经过激活层
x=self.output(x)
return x
基于PaddlePaddle2.0-构建卷积网络模型LeNet-5
https://aistudio.baidu.com/aistudio/projectdetail/1329509
# 构建模型,正向传播过程
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.pool1(x)
x = F.relu(x)
x = self.conv2(x)
x = self.pool2(x)
x = paddle.flatten(x, start_axis=1,stop_axis=-1)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.relu(x)
out = self.fc3(x)
return out
深度全连接神经网络模型的缺点
- ①,模型结构不够灵活:只能增加神经元个数和网络层数,不多样。
- ②,模型参数太多:全连接网络中,一个神经元都对前一层每个神经元数有单独的参数。如果层数一多且神经元个数一多,参数就成次方增长。
池化层
。卷积conv
+池化pooling
叠加,然后输出层为全连接
+Soft-max
输出预测概率。神经元
+激活函数
,结合记忆。矩阵加法运算
https://aistudio.baidu.com/aistudio/projectdetail/1616566
卷积层 - 扩展理解
- 如果把输入输出图像拍扁,然后把卷积核用神经元“全连接”的形式展现,会得到下图。
- 神经元是局部连接的,且权重共享。可以结合理解卷积和神经元。
特征feature
,所以一幅图像会采用多个卷积核kernel
提取特征。同时会输出多个特征图feature maps
,一个卷积核对应一个。特征图feature maps
,那这一层的一个卷积核kernel
就会自动广播(此时卷积核通常表示为一个立方体)然后和多个特征图feature maps
运算得到一个特征图feature map
。(下图就是这个操作!!!)卷积核kernels
,就会又生成多个特征图feature maps
到下一层,继续套娃。(下图就是这个操作!!!)Pooling层
理解为下采样也是可以的。(言外之意是不是指并不完全相等?先留个坑,自己知道并警惕一下。)Pooling层
运算也有很多种,常用的有取最大值max pooling
和取平均值average pooling
。其作用就是增大感受野。插值
方法)Softamx分类器
是全连接层
+Softmax激活函数
+交叉熵损失函数
的组合,所以飞桨2.0把后两者组合在一起使用。通过方法paddle.nn.CrossEntropyLoss()
调用。小知识:交叉熵瞬损失函数的公式,的后半段
ln(y)
是指信息量,在第二课中有详细介绍,不记得的可返回查看。
【学习目录】基于PaddlePaddle2.0-构建机器学习、深度学习模型
https://aistudio.baidu.com/aistudio/projectdetail/1354419
基于PaddlePaddle2.0-构建卷积网络模型AlexNet
https://aistudio.baidu.com/aistudio/projectdetail/1332790
#构建模型
class AlexNetModel(paddle.nn.Layer):
def __init__(self):
super(AlexNetModel, self).__init__()
self.conv_pool1 = paddle.nn.Sequential( #输入大小m*3*227*227
paddle.nn.Conv2D(3,96,11,4,0), #L1, 输出大小m*96*55*55
paddle.nn.ReLU(), #L2, 输出大小m*55*55*96
paddle.nn.MaxPool2D(kernel_size=3, stride=2)) #L3, 输出大小m*96*27*27
self.conv_pool2 = paddle.nn.Sequential(
paddle.nn.Conv2D(96, 256, 5, 1, 2), #L4, 输出大小m*256*27*27
paddle.nn.ReLU(), #L5, 输出大小m*256*27*27
paddle.nn.MaxPool2D(3, 2)) #L6, 输出大小m*256*13*13
self.conv_pool3 = paddle.nn.Sequential(
paddle.nn.Conv2D(256, 384, 3, 1, 1),#L7, 输出大小m*384*13*13
paddle.nn.ReLU()) #L8, 输出m*384*13*13
self.conv_pool4 = paddle.nn.Sequential(
paddle.nn.Conv2D(384, 384, 3, 1, 1),#L9, 输出大小m*384*13*13
paddle.nn.ReLU()) #L10, 输出大小m*384*13*13
self.conv_pool5 = paddle.nn.Sequential(
paddle.nn.Conv2D(384, 256, 3, 1, 1),#L11, 输出大小m*256*13*13
paddle.nn.ReLU(), #L12, 输出大小m*256*13*13
paddle.nn.MaxPool2D(3, 2)) #L13, 输出大小m*256*6*6
self.full_conn = paddle.nn.Sequential(
paddle.nn.Linear(256*6*6, 4096), #L14, 输出大小m*4096
paddle.nn.ReLU(), #L15, 输出大小m*4096
paddle.nn.Dropout(0.5), #L16, 输出大小m*4096
paddle.nn.Linear(4096, 4096), #L17, 输出大小m*4096
paddle.nn.ReLU(), #L18, 输出大小m*4096
paddle.nn.Dropout(0.5), #L19, 输出大小m*4096
paddle.nn.Linear(4096, 10)) #L20, 输出大小m*10
self.flatten=paddle.nn.Flatten()
def forward(self, x): #前向传播
x = self.conv_pool1(x)
x = self.conv_pool2(x)
x = self.conv_pool3(x)
x = self.conv_pool4(x)
x = self.conv_pool5(x)
x = self.flatten(x)
x = self.full_conn(x)
return x
paddle.nn.Sequential
打包,也使用forward
排布。这也算一种技巧,很好利用,当层数太多时,这样确实不错。import paddle
import paddle.nn.functional as F
import numpy as np
from paddle.vision.transforms import Compose, Resize, Transpose, Normalize
#准备数据
t = Compose([Resize(size=227),Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], data_format='HWC'),Transpose()]) #数据转换
cifar10_train = paddle.vision.datasets.cifar.Cifar10(mode='train', transform=t, backend='cv2')
cifar10_test = paddle.vision.datasets.cifar.Cifar10(mode="test", transform=t, backend='cv2')
epoch_num = 20
batch_size = 256
learning_rate = 0.0001
val_acc_history = []
val_loss_history = []
def train(model):
#启动训练模式
model.train()
opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=model.parameters())
train_loader = paddle.io.DataLoader(cifar10_train, shuffle=True, batch_size=batch_size)
valid_loader = paddle.io.DataLoader(cifar10_test, batch_size=batch_size)
for epoch in range(epoch_num):
for batch_id, data in enumerate(train_loader()):
x_data = paddle.cast(data[0], 'float32')
y_data = paddle.cast(data[1], 'int64')
y_data = paddle.reshape(y_data, (-1, 1))
y_predict = model(x_data)
loss = F.cross_entropy(y_predict, y_data)
loss.backward()
opt.step()
opt.clear_grad()
print("训练轮次: {}; 损失: {}".format(epoch, loss.numpy()))
#每训练完1个epoch, 用测试数据集来验证一下模型
#切换到训练模式
model.eval()
accuracies = []
losses = []
for batch_id, data in enumerate(valid_loader()):
x_data = paddle.cast(data[0], 'float32')
y_data = paddle.cast(data[1], 'int64')
y_data = paddle.reshape(y_data, (-1, 1))
y_predict = model(x_data)
loss = F.cross_entropy(y_predict, y_data)
acc = paddle.metric.accuracy(y_predict, y_data)
accuracies.append(np.mean(acc.numpy()))
losses.append(np.mean(loss.numpy()))
avg_acc, avg_loss = np.mean(accuracies), np.mean(losses)
print("评估准确度为:{};损失为:{}".format(avg_acc, avg_loss))
val_acc_history.append(avg_acc)
val_loss_history.append(avg_loss)
#重新又启动训练模式
model.train()
model = AlexNetModel()
train(model)
基于PaddlePaddle2.0-构建卷积网络GoogLeNet
https://aistudio.baidu.com/aistudio/projectdetail/1340883
(1)GoogLeNet模型串联结构:
GoogLeNet模型
结构由多个模块串联而成。其中Inception层
内部为并联结构。
(2)GoogLeNet模型Inception内部结构:GoogLeNet模型
的每个Inception模块
包括4条并联路径。
forward
中还有套娃使用的方法,学习一下。paddle.concat
返回。这个做法是因为等一会这个模型还要再套娃一次……#构建模型
class Inception(paddle.nn.Layer):
def __init__(self, in_channels, c1, c2, c3, c4):
super(Inception, self).__init__()
#路线1,卷积核1x1
self.route1x1_1 = paddle.nn.Conv2D(in_channels, c1, kernel_size=1)
#路线2,卷积层1x1、卷积层3x3
self.route1x1_2 = paddle.nn.Conv2D(in_channels, c2[0], kernel_size=1)
self.route3x3_2 = paddle.nn.Conv2D(c2[0], c2[1], kernel_size=3, padding=1)
#路线3,卷积层1x1、卷积层5x5
self.route1x1_3 = paddle.nn.Conv2D(in_channels, c3[0], kernel_size=1)
self.route5x5_3 = paddle.nn.Conv2D(c3[0], c3[1], kernel_size=5, padding=2)
#路线4,池化层3x3、卷积层1x1
self.route3x3_4 = paddle.nn.MaxPool2D(kernel_size=3, stride=1, padding=1)
self.route1x1_4 = paddle.nn.Conv2D(in_channels, c4, kernel_size=1)
def forward(self, x):
route1 = F.relu(self.route1x1_1(x))
route2 = F.relu(self.route3x3_2(F.relu(self.route1x1_2(x))))
route3 = F.relu(self.route5x5_3(F.relu(self.route1x1_3(x))))
route4 = F.relu(self.route1x1_4(self.route3x3_4(x)))
out = [route1, route2, route3, route4]
return paddle.concat(out, axis=1) #在通道维度(axis=1)上进行连接
class GoogLeNet(paddle.nn.Layer):
def __init__(self, in_channel, num_classes):
super(GoogLeNet, self).__init__()
self.b1 = paddle.nn.Sequential(
BasicConv2d(in_channel, out_channels=64, kernel=7, stride=2, padding=3),
paddle.nn.MaxPool2D(3, 2))
self.b2 = paddle.nn.Sequential(
BasicConv2d(64, 64, kernel=1),
BasicConv2d(64, 192, kernel=3, padding=1),
paddle.nn.MaxPool2D(3, 2))
self.b3 = paddle.nn.Sequential(
Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
paddle.nn.MaxPool2D(3, 2))
self.b4 = paddle.nn.Sequential(
Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
paddle.nn.MaxPool2D(3, 2))
self.b5 = paddle.nn.Sequential(
Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (182, 384), (48, 128), 128),
paddle.nn.AvgPool2D(2))
self.flatten=paddle.nn.Flatten()
self.b6 = paddle.nn.Linear(1024, num_classes)
def forward(self, x):
x = self.b1(x)
x = self.b2(x)
x = self.b3(x)
x = self.b4(x)
x = self.b5(x)
x = self.flatten(x)
x = self.b6(x)
return x
基于PaddlePaddle2.0-构建残差神经网络模型
https://aistudio.baidu.com/aistudio/projectdetail/1342659
残差网络(ResNet)模型是由何凯明开发,它是2015年ImageNet ILSVRC-2015分类挑战赛的冠军模型。ResNet模型引入残差模块,它能够有效地消除由于模型层数增加而导致的梯度弥散或梯度爆炸问题。下面详细解析ResNet模型原理。
#构建模型
class Residual(paddle.nn.Layer):
def __init__(self, in_channel, out_channel, use_conv1x1=False, stride=1):
super(Residual, self).__init__()
self.conv1 = paddle.nn.Conv2D(in_channel, out_channel, kernel_size=3, padding=1, stride=stride)
self.conv2 = paddle.nn.Conv2D(out_channel, out_channel, kernel_size=3, padding=1)
if use_conv1x1: #使用1x1卷积核
self.conv3 = paddle.nn.Conv2D(in_channel, out_channel, kernel_size=1, stride=stride)
else:
self.conv3 = None
self.batchNorm1 = paddle.nn.BatchNorm2D(out_channel)
self.batchNorm2 = paddle.nn.BatchNorm2D(out_channel)
def forward(self, x):
y = F.relu(self.batchNorm1(self.conv1(x)))
y = self.batchNorm2(self.conv2(y))
if self.conv3:
x = self.conv3(x)
out = F.relu(y+x) #核心代码
return out
def ResNetBlock(in_channel, out_channel, num_layers, is_first=False):
if is_first:
assert in_channel == out_channel
block_list = []
for i in range(num_layers):
if i == 0 and not is_first:
block_list.append(Residual(in_channel, out_channel, use_conv1x1=True, stride=2))
else:
block_list.append(Residual(out_channel, out_channel))
resNetBlock = paddle.nn.Sequential(*block_list) #用*号可以把list列表展开为元素
return resNetBlock
class ResNetModel(paddle.nn.Layer):
def __init__(self):
super(ResNetModel, self).__init__()
self.b1 = paddle.nn.Sequential(
paddle.nn.Conv2D(3, 64, kernel_size=7, stride=2, padding=3),
paddle.nn.BatchNorm2D(64),
paddle.nn.ReLU(),
paddle.nn.MaxPool2D(kernel_size=3, stride=2, padding=1))
self.b2 = ResNetBlock(64, 64, 2, is_first=True)
self.b3 = ResNetBlock(64, 128, 2)
self.b4 = ResNetBlock(128, 256, 2)
self.b5 = ResNetBlock(256, 512, 2)
self.AvgPool = paddle.nn.AvgPool2D(2)
self.flatten = paddle.nn.Flatten()
self.Linear = paddle.nn.Linear(512, 10)
def forward(self, x):
x = self.b1(x)
x = self.b2(x)
x = self.b3(x)
x = self.b4(x)
x = self.b5(x)
x = self.AvgPool(x)
x = self.flatten(x)
x = self.Linear(x)
return x
ResNet
中的1x1卷积
类似。逐通道卷积不改变通道数,所以要通过逐点卷积修改通道数。(好像通道数变得更加多了)paddle.nn.Conv2d
方法实现,具体看飞桨文档吧。改一下参数就可以了。基于PaddlePaddle2.0的蝴蝶图像识别分类——利用预训练残差网络ResNet101模型
https://aistudio.baidu.com/aistudio/projectdetail/1417071
AIStudio 各类操作详解
https://blog.csdn.net/weixin_41450123/category_10707833.html
notebook
上和本地(因操作系统不同)操作不太一样。各自相应百度即可。数据准备过程包括以下两个重点步骤:
- 一、是建立样本数据读取路径与样本标签之间的关系。
- 二、是构造读取器与数据预处理。可以写个自定义数据读取器,它继承于PaddlePaddle2.0的
dataset类
,在__getitem__方法
中把自定义的预处理方法加载进去。
.txt
文件读取字符串,然后拼接。# 代码略,有点乱,具体去项目里看吧。
#自定义的数据预处理函数,输入原始图像,输出处理后的图像,可以借用paddle.vision.transforms的数据处理功能
def preprocess(img):
# 一个可调用的Compose对象,它将依次调用每个给定的 transforms。
transform = Compose([
# 数据增强
# tf.RandomHorizontalFlip(), # 基于概率来执行图片的水平翻转。
tf.RandomVerticalFlip(), # 基于概率来执行图片的垂直翻转。
#tf.ColorJitter(0.1,0.1,0.1,0.1),#随机调整图像的亮度,对比度,饱和度和色调。
# 将输入数据调整为指定大小。
Resize(size=(224, 224)), #把数据长宽像素调成224*224
# 图像归一化处理,支持两种方式: 1. 用统一的均值和标准差值对图像的每个通道进行归一化处理;
# 2. 对每个通道指定不同的均值和标准差值进行归一化处理。 255/2 = 127.5
Normalize(mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], data_format='HWC'), #标准化
# 将输入的图像数据更改为目标格式。例如,大多数数据预处理是使用HWC格式的图片,而神经网络可能使用CHW模式输入张量。 输出的图片是numpy.ndarray的实例。
Transpose(), #原始数据形状维度是HWC格式,经过Transpose,转换为CHW格式
])
# 把图像传进入然后经过上面的处理顺序再返回。
img = transform(img).astype("float32")
return img
Dataset类
,使用方法有点像搭建模式时继承paddle.nn.Layer
一样。类比记忆。#自定义数据读取器
# 重点,Dataset类是paddle用力打包数据的
# 这个类的继承和建立模型的paddle.nn.Layer 的使用方法大同小异,也是有几个必须要写的函数,一个是__getitem__,一个是__len__
class Reader(Dataset):
def __init__(self, data, is_val=False): # is_val 参数是用来划分训练集和验证集的。前80%还是后20%。
super().__init__()
#在初始化阶段,把数据集划分训练集和测试集。由于在读取前样本已经被打乱顺序,取20%的样本作为测试集,80%的样本作为训练集。
self.samples = data[-int(len(data)*0.2):] if is_val else data[:-int(len(data)*0.2)]
print(data[0])
# is_val 作用在于是从前面取还是从后面取,都已经打乱了,应该都没关系了吧。
def __getitem__(self, idx):
#处理图像
img_path = self.samples[idx][0] #得到某样本的路径
# 使用PIL模块读取图片,而不是cv2
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
# 之后自己写 数据增强。
img = preprocess(img) #数据预处理--这里仅包括简单数据预处理,没有用到数据增强
#处理标签
label = self.samples[idx][1] #得到某样本的标签
label = np.array([label], dtype="int64") #把标签数据类型转成int64
return img, label
def __len__(self):
#返回每个Epoch中图片数量
return len(self.samples)
# 读取数据
class InferDataset(Dataset):
def __init__(self, img_path=None):
"""
数据读取Reader(推理)
:param img_path: 推理单张图片
"""
super().__init__()
if img_path:
self.img_paths = [img_path]
else:
raise Exception("请指定需要预测对应图片路径")
def __getitem__(self, index):
# 获取图像路径
img_path = self.img_paths[index]
# 使用Pillow来读取图像数据并转成Numpy格式
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img = preprocess(img) #数据预处理--这里仅包括简单数据预处理,没有用到数据增强
return img
def __len__(self):
return len(self.img_paths)
1.为了提升探索速度,建议首先选用比较成熟的基础模型,看看基础模型所能够达到的准确度。之后再试试模型融合,准确度是否有提升。最后可以试试自己独创模型。
2.为简便,这里直接采用101层的残差网络ResNet,并且采用预训练模式。为什么要采用预训练模型呢?因为通常模型参数采用随机初始化,而预训练模型参数初始值是一个比较确定的值。这个参数初始值是经历了大量任务训练而得来的,比如用CIFAR图像识别任务来训练模型,得到的参数。虽然蝴蝶识别任务和CIFAR图像识别任务是不同的,但可能存在某些机器视觉上的共性。用预训练模型可能能够较快地得到比较好的准确度。
3.在PaddlePaddle2.0中,使用预训练模型只需要设定模型参数pretained=True。值得注意的是,预训练模型得出的结果类别是1000维度,要用个线性变换,把类别转化为20维度。
#定义模型
class MyNet(paddle.nn.Layer):
def __init__(self):
super(MyNet,self).__init__()
self.resnet=paddle.vision.models.resnet50(pretrained=True)
# Dropout是一种正则化手段,该算子根据给定的丢弃概率 p ,在训练过程中随机将一些神经元输出设置为0,通过阻止神经元节点间的相关性来减少过拟合
self.dropout=paddle.nn.Dropout(p=0.4)
self.fc = paddle.nn.Linear(1000, 20)
#网络的前向计算过程
def forward(self,x):
x=self.resnet(x)
x=self.dropout(x)
x=self.fc(x)
return x
一、定义输入数据形状大小和数据类型。
二、实例化模型。如果要用高阶API,需要用Paddle.Model()
对模型进行封装,如model = paddle.Model(model,inputs=input_define,labels=label_define)
。
三、定义优化器。这个使用Adam
优化器,学习率设置为0.0001,优化器中的学习率(learning_rate
)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低。
四、准备模型。这里用到高阶API,model.prepare()
。
五、训练模型。这里用到高阶API,model.fit()
。参数意义详见下述代码注释。
Adam
优化器的学习率影响确实很重要,不能高,不然会震荡,也不能低,不然就停滞不前,很容易卡在小坑里。#定义输入
input_define = paddle.static.InputSpec(shape=[-1,3,224,224], dtype="float32", name="img")
label_define = paddle.static.InputSpec(shape=[-1,1], dtype="int64", name="label")
#实例化网络对象并定义优化器等训练逻辑
model = MyNet()
model = paddle.Model(model,inputs=input_define,labels=label_define) #用Paddle.Model()对模型进行封装
optimizer = paddle.optimizer.Adam(learning_rate=0.00002, parameters=model.parameters())
#上述优化器中的学习率(learning_rate)参数很重要。要是训练过程中得到的准确率呈震荡状态,忽大忽小,可以试试进一步把学习率调低。
model.prepare(optimizer=optimizer, #指定优化器
loss=paddle.nn.CrossEntropyLoss(), #指定损失函数
metrics=paddle.metric.Accuracy()) #指定评估方法
model.fit(train_data=train_dataset, #训练数据集
eval_data=eval_dataset, #测试数据集
batch_size=64, #一个批次的样本数量
epochs=50, #迭代轮次
save_dir="/home/aistudio/lup", #把模型参数、优化器参数保存至自定义的文件夹
save_freq=10, #设定每隔多少个epoch保存模型参数及优化器参数
log_freq=100 #打印日志的频率
)
如果是要参加建模比赛,通常赛事组织方会提供待预测的数据集,我们需要利用自己构建的模型,来对待预测数据集合中的数据标签进行预测。也就是说,我们其实并不知道到其真实标签是什么,只有比赛的组织方知道真实标签,我们的模型预测结果越接近真实结果,那么分数也就越高。
预测流程分为以下几个步骤:
一、构建数据读取器。因为预测数据集没有标签,该读取器写法和训练数据读取器不一样,建议重新写一个类,继承于Dataset基类。(这一步我归类到处理训练集验证集时一起做了)
二、实例化模型。如果要用高阶API,需要用Paddle.Model()
对模型进行封装,如paddle.Model(MyNet(),inputs=input_define)
,由于是预测模型,所以仅设定输入数据格式就好了。
三、读取刚刚训练好的参数。这个保存在/home/aistudio/work目录之下,如果指定的是final
则是最后一轮训练后的结果。可以指定其他轮次的结果,比如model.load(’/home/aistudio/work/30’),这里用到了高阶API,model.load()
四、准备模型。这里用到高阶API,model.prepare()
。
五、读取待预测集合中的数据,利用已经训练好的模型进行预测。
六、结果保存。
#实例化推理模型
model = paddle.Model(MyNet(),inputs=input_define)
#读取刚刚训练好的参数
model.load('/home/aistudio/lup/final')
#准备模型
model.prepare()
#利用训练好的模型进行预测
results=[]
# 这是一张一张图片丢进去预测。
for infer_path in infer_list: # infer_list 是每个图像的读取路径
infer_data = InferDataset(infer_path)
result = model.predict(test_data=infer_data)[0] #关键代码,实现预测功能
result = paddle.to_tensor(result)
result = np.argmax(result.numpy()) #获得最大值所在的序号
results.append("{}".format(label_dict2[result])) #查找该序号所对应的标签名字
# 最后把结果保存起来,然后就可以提交了。
with open("work/result.txt", "w") as f:
for r in results:
f.write("{}\n".format(r))