继续跟着paddle的官方教程,这一章讲的是优化算法--手写数字识别之优化算法
这一章没有太多的代码部分,主要是原理的讲解,跳过
paddle官方教程下一章讲的是资源配置,也就是使用GPU训练和多卡训练。
使用GPU单卡训练很简单,加一句use_gpu = True就完事了
torch中使用gpu稍微复杂一点,model,data和loss都要在后面加cuda()放入显存
打印的时候还要加cpu()回到内存
from torch.nn import Conv2d,MaxPool2d,Linear
#数据处理部分之后的代码,数据读取的部分调用load_data函数
# 定义网络结构,同上一节所使用的网络结构
class Mnist(nn.Module):
def __init__(self):
super(Mnist,self).__init__()
self.conv1 = Conv2d(in_channels=1,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool1 = MaxPool2d(kernel_size=2,stride=2)
self.conv2 = Conv2d(in_channels=20,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool2 = MaxPool2d(kernel_size=2,stride=2)
self.fc = Linear(in_features=980,out_features=10)
def forward(self,x):
x = self.conv1(x)
x = torch.relu(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = torch.relu(x)
x = self.max_pool2(x)
x = torch.reshape(x,[x.shape[0],-1])
x = self.fc(x)
x = F.softmax(x,dim=1)
return x
# 训练配置,并启动训练过程
model = Mnist()
model=model.cuda()
model.train(mode=True)
#调用加载数据的函数
train_loader = load_data('train')
optimizer = optim.SGD(model.parameters(),lr= 0.01)
EPOCH_NUM = 10
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
image_data, label_data = data
image = torch.tensor(image_data).cuda()
label = torch.tensor(label_data).cuda()
image = torch.reshape(image,[image.shape[0],1,28,28])
#前向计算的过程
predict = model(image)
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predict, label.squeeze(dim=1)).cuda()
avg_loss = torch.mean(loss)
#每训练了200批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.cpu().detach().numpy()))
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.step()
model.zero_grad()
多卡训练没这个条件,看看到了所里不知道服务器有没有多卡,到时候研究研究
这章内容也很少,直接下一章【手写数字识别】之训练调试与优化
训练过程优化思路主要有如下五个关键环节:
1. 计算分类准确率,观测模型训练效果。
交叉熵损失函数只能作为优化目标,无法直接准确衡量模型的训练效果。准确率可以直接衡量训练效果,但由于其离散性质,不适合做为损失函数优化神经网络。
2. 检查模型训练过程,识别潜在问题。
如果模型的损失或者评估指标表现异常,通常需要打印模型每一层的输入和输出来定位问题,分析每一层的内容来获取错误的原因。
3. 加入校验或测试,更好评价模型效果。
理想的模型训练结果是在训练集和验证集上均有较高的准确率,如果训练集的准确率低于验证集,说明网络训练程度不够;如果训练集的准确率高于验证集,可能是发生了过拟合现象。通过在优化目标中加入正则化项的办法,解决过拟合的问题。
4. 加入正则化项,避免模型过拟合。
飞桨框架支持为整体参数加入正则化项,这是通常的做法。此外,飞桨框架也支持为某一层或某一部分的网络单独加入正则化项,以达到精细调整参数训练的效果。
5. 可视化分析。
用户不仅可以通过打印或使用matplotlib库作图,飞桨还提供了更专业的可视化分析工具VisualDL,提供便捷的可视化分析方法。
计算准确率(Accuracy)
paddle本身提供的准确率api可直接计算准确率
# 加载相关库
import os
import random
import paddle
import numpy as np
from PIL import Image
import gzip
import json
# 定义数据集读取器
def load_data(mode='train'):
# 读取数据文件
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
data = json.load(gzip.open(datafile))
# 读取数据集中的训练集,验证集和测试集
train_set, val_set, eval_set = data
# 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
IMG_ROWS = 28
IMG_COLS = 28
# 根据输入mode参数决定使用训练集,验证集还是测试
if mode == 'train':
imgs = train_set[0]
labels = train_set[1]
elif mode == 'valid':
imgs = val_set[0]
labels = val_set[1]
elif mode == 'eval':
imgs = eval_set[0]
labels = eval_set[1]
# 获得所有图像的数量
imgs_length = len(imgs)
# 验证图像数量和标签数量是否一致
assert len(imgs) == len(labels), \
"length of train_imgs({}) should be the same as train_labels({})".format(
len(imgs), len(labels))
index_list = list(range(imgs_length))
# 读入数据时用到的batchsize
BATCHSIZE = 100
# 定义数据生成器
def data_generator():
# 训练模式下,打乱训练数据
if mode == 'train':
random.shuffle(index_list)
imgs_list = []
labels_list = []
# 按照索引读取数据
for i in index_list:
# 读取图像和标签,转换其尺寸和类型
img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
label = np.reshape(labels[i], [1]).astype('int64')
imgs_list.append(img)
labels_list.append(label)
# 如果当前数据缓存达到了batch size,就返回一个批次数据
if len(imgs_list) == BATCHSIZE:
yield np.array(imgs_list), np.array(labels_list)
# 清空数据缓存列表
imgs_list = []
labels_list = []
# 如果剩余数据的数目小于BATCHSIZE,
# 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
if len(imgs_list) > 0:
yield np.array(imgs_list), np.array(labels_list)
return data_generator
# 定义模型结构
import paddle.nn.functional as F
from paddle.nn import Conv2D, MaxPool2D, Linear
# 多层卷积神经网络实现
class MNIST(paddle.nn.Layer):
def __init__(self):
super(MNIST, self).__init__()
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
# 定义一层全连接层,输出维度是10
self.fc = Linear(in_features=980, out_features=10)
# 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
# 卷积层激活函数使用Relu,全连接层激活函数使用softmax
def forward(self, inputs, label):
x = self.conv1(inputs)
x = F.sigmoid(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = F.sigmoid(x)
x = self.max_pool2(x)
x = paddle.reshape(x, [x.shape[0], 980])
x = self.fc(x)
x = F.softmax(x)
if label is not None:
acc = paddle.metric.accuracy(input=x, label=label)
return x, acc
else:
return x
#调用加载数据的函数
train_loader = load_data('train')
#在使用GPU机器时,可以将use_gpu变量设置成True
use_gpu = True
paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
#仅优化算法的设置有所差别
def train(model):
model = MNIST()
model.train()
#四种优化算法的设置方案,可以逐一尝试效果
# opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())
# opt = paddle.optimizer.Momentum(learning_rate=0.01, momentum=0.9, parameters=model.parameters())
# opt = paddle.optimizer.Adagrad(learning_rate=0.01, parameters=model.parameters())
opt = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
EPOCH_NUM = 5
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
#准备数据
images, labels = data
images = paddle.to_tensor(images)
labels = paddle.to_tensor(labels)
#前向计算的过程
predicts, acc = model(images, labels)
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predicts, labels)
avg_loss = paddle.mean(loss)
#每训练了100批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}, acc is {}".format(epoch_id, batch_id, avg_loss.numpy(), acc.numpy()))
#后向传播,更新参数,消除梯度的过程
avg_loss.backward()
opt.step()
opt.clear_grad()
#保存模型参数
paddle.save(model.state_dict(), 'mnist.pdparams')
#创建模型
model = MNIST()
#启动训练过程
train(model)
torch中好似没有这样包装好的api,不过原理不复杂我们可以自己写。
返回的predict的size是【batchsize,10】,也就是一次返回一个batchsize的结果,每个结果是十个数字的list,是softmax后每个label对应的可能性,我们只要找出数值最大的那个记录下他的label就是预测的结果,然后去和真实label对比一下就行,计算一下一个batchsize中预测对了多少个,然后除以batchsize,非常简单的小学数学。
torch中提供了max函数可以直接返回最大值的值和标签,这就很方便。这个函数还有一个参数是维数,就是从哪个维数去计算,于是我们只用torch.max(predict,1)[1]就可以得到一维的预测结果,与真实标签比对一下就是当前batch的准确率。paddle这个api返回的应该就是当前批次的acc。我们设置的是200个batch返回一次loss和acc,实际上我们也可以把acc做一个平均,返回这200个batch的平均acc,这样更科学一点。
这里思考了一下,其实paddle这个api放在了model里的forward过程,意味着每个batch都计算了acc,但我们只在每隔200个batch才输出一次acc,loss用于梯度下降没办法每个batch都要算,但是acc如果只是要输出每200batch时当前batch的那个acc那完全没必要每个batch都算acc,虽然acc比loss计算简单很多,但能写在if判断后在计算还是能省下一点性能的。
from torch.nn import Conv2d,MaxPool2d,Linear
#数据处理部分之后的代码,数据读取的部分调用load_data函数
# 定义网络结构,同上一节所使用的网络结构
class Mnist(nn.Module):
def __init__(self):
super(Mnist,self).__init__()
self.conv1 = Conv2d(in_channels=1,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool1 = MaxPool2d(kernel_size=2,stride=2)
self.conv2 = Conv2d(in_channels=20,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool2 = MaxPool2d(kernel_size=2,stride=2)
self.fc = Linear(in_features=980,out_features=10)
def forward(self,x):
x = self.conv1(x)
x = torch.relu(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = torch.relu(x)
x = self.max_pool2(x)
x = torch.reshape(x,[x.shape[0],-1])
x = self.fc(x)
x = F.softmax(x,dim=1)
return x
# 训练配置,并启动训练过程
model = Mnist()
model=model.cuda()
model.train(mode=True)
#调用加载数据的函数
train_loader = load_data('train')
optimizer = optim.Adam(model.parameters(),lr= 0.001)
BATCHSIZE=100
EPOCH_NUM = 10
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
image_data, label_data = data
image = torch.tensor(image_data).cuda()
label = torch.tensor(label_data).cuda()
image = torch.reshape(image,[image.shape[0],1,28,28])
#前向计算的过程
predict = model(image)
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predict, label.squeeze(dim=1)).cuda()
avg_loss = torch.mean(loss)
#每训练了200批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
correct=0
predict_label = torch.max(predict,1)[1]
correct += (predict_label == label.squeeze(dim=1)).sum()
print("epoch: {}, batch: {}, loss is: {}, Accuracy:{:.3f}".format(epoch_id, batch_id, avg_loss.cpu().detach().numpy(),correct/BATCHSIZE))
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.step()
model.zero_grad()
一开始对比了一下,发现torch的acc最后也只有0.80左右,而paddle能到0.96,检查了一下发现是梯度下降的方法不一样,把torch的也改成adam之后最后一个batch的acc能到1,也就是这一个batch里100个数字全部识别正确了。
接着把代码改成计算每200个batch的平均acc
from torch.nn import Conv2d,MaxPool2d,Linear
#数据处理部分之后的代码,数据读取的部分调用load_data函数
# 定义网络结构,同上一节所使用的网络结构
class Mnist(nn.Module):
def __init__(self):
super(Mnist,self).__init__()
self.conv1 = Conv2d(in_channels=1,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool1 = MaxPool2d(kernel_size=2,stride=2)
self.conv2 = Conv2d(in_channels=20,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool2 = MaxPool2d(kernel_size=2,stride=2)
self.fc = Linear(in_features=980,out_features=10)
def forward(self,x):
x = self.conv1(x)
x = torch.relu(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = torch.relu(x)
x = self.max_pool2(x)
x = torch.reshape(x,[x.shape[0],-1])
x = self.fc(x)
x = F.softmax(x,dim=1)
return x
# 训练配置,并启动训练过程
model = Mnist()
model=model.cuda()
model.train(mode=True)
#调用加载数据的函数
train_loader = load_data('train')
optimizer = optim.Adam(model.parameters(),lr= 0.001)
BATCHSIZE=100
EPOCH_NUM = 10
for epoch_id in range(EPOCH_NUM):
correct=0
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
image_data, label_data = data
image = torch.tensor(image_data).cuda()
label = torch.tensor(label_data).cuda()
image = torch.reshape(image,[image.shape[0],1,28,28])
#前向计算的过程
predict = model(image)
predict_label = torch.max(predict,1)[1]
correct += (predict_label == label.squeeze(dim=1)).sum()
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predict, label.squeeze(dim=1)).cuda()
avg_loss = torch.mean(loss)
#每训练了200批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}, Accuracy:{:.3f}".format(epoch_id, batch_id, avg_loss.cpu().detach().numpy(),\ correct/(BATCHSIZE*200)))
correct=0
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.step()
model.zero_grad()
这样发现每个epoch开始batchid=0输出的时候只累积了第一个batch的correct,但又当成了200个batch的correct总数除了200,出了bug,事实上我们不该输出batch_id=0时候的数据,而应该在最后一组batch不足200时的参数。最好的解决办法是把头尾都不输出,这样最简单。
if batch_id!=0 and batch_id % 200 == 0:这样就行。要是嫌一个epoch内输出两个中间结果太少,就把200改成150或者100就好。
检查模型训练过程,识别潜在训练问题
这一部分讲的打印网络结构和内容值来检查结构是否正确,设计是否有问题,有没有无效部分
示例代码在训练开始时打印出网络结构,并在每个epoch的第401个batch打印每一层的内容值。
import paddle.nn.functional as F
# 定义模型结构
class MNIST(paddle.nn.Layer):
def __init__(self):
super(MNIST, self).__init__()
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
# 定义一层全连接层,输出维度是10
self.fc = Linear(in_features=980, out_features=10)
#加入对每一层输入和输出的尺寸和数据内容的打印,根据check参数决策是否打印每层的参数和输出尺寸
# 卷积层激活函数使用Relu,全连接层激活函数使用softmax
def forward(self, inputs, label=None, check_shape=False, check_content=False):
# 给不同层的输出不同命名,方便调试
outputs1 = self.conv1(inputs)
outputs2 = F.relu(outputs1)
outputs3 = self.max_pool1(outputs2)
outputs4 = self.conv2(outputs3)
outputs5 = F.relu(outputs4)
outputs6 = self.max_pool2(outputs5)
outputs6 = paddle.reshape(outputs6, [outputs6.shape[0], -1])
outputs7 = self.fc(outputs6)
outputs8 = F.softmax(outputs7)
# 选择是否打印神经网络每层的参数尺寸和输出尺寸,验证网络结构是否设置正确
if check_shape:
# 打印每层网络设置的超参数-卷积核尺寸,卷积步长,卷积padding,池化核尺寸
print("\n########## print network layer's superparams ##############")
print("conv1-- kernel_size:{}, padding:{}, stride:{}".format(self.conv1.weight.shape, self.conv1._padding, self.conv1._stride))
print("conv2-- kernel_size:{}, padding:{}, stride:{}".format(self.conv2.weight.shape, self.conv2._padding, self.conv2._stride))
#print("max_pool1-- kernel_size:{}, padding:{}, stride:{}".format(self.max_pool1.pool_size, self.max_pool1.pool_stride, self.max_pool1._stride))
#print("max_pool2-- kernel_size:{}, padding:{}, stride:{}".format(self.max_pool2.weight.shape, self.max_pool2._padding, self.max_pool2._stride))
print("fc-- weight_size:{}, bias_size_{}".format(self.fc.weight.shape, self.fc.bias.shape))
# 打印每层的输出尺寸
print("\n########## print shape of features of every layer ###############")
print("inputs_shape: {}".format(inputs.shape))
print("outputs1_shape: {}".format(outputs1.shape))
print("outputs2_shape: {}".format(outputs2.shape))
print("outputs3_shape: {}".format(outputs3.shape))
print("outputs4_shape: {}".format(outputs4.shape))
print("outputs5_shape: {}".format(outputs5.shape))
print("outputs6_shape: {}".format(outputs6.shape))
print("outputs7_shape: {}".format(outputs7.shape))
print("outputs8_shape: {}".format(outputs8.shape))
# 选择是否打印训练过程中的参数和输出内容,可用于训练过程中的调试
if check_content:
# 打印卷积层的参数-卷积核权重,权重参数较多,此处只打印部分参数
print("\n########## print convolution layer's kernel ###############")
print("conv1 params -- kernel weights:", self.conv1.weight[0][0])
print("conv2 params -- kernel weights:", self.conv2.weight[0][0])
# 创建随机数,随机打印某一个通道的输出值
idx1 = np.random.randint(0, outputs1.shape[1])
idx2 = np.random.randint(0, outputs4.shape[1])
# 打印卷积-池化后的结果,仅打印batch中第一个图像对应的特征
print("\nThe {}th channel of conv1 layer: ".format(idx1), outputs1[0][idx1])
print("The {}th channel of conv2 layer: ".format(idx2), outputs4[0][idx2])
print("The output of last layer:", outputs8[0], '\n')
# 如果label不是None,则计算分类精度并返回
if label is not None:
acc = paddle.metric.accuracy(input=outputs8, label=label)
return outputs8, acc
else:
return outputs8
#在使用GPU机器时,可以将use_gpu变量设置成True
use_gpu = True
paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
def train(model):
model = MNIST()
model.train()
#四种优化算法的设置方案,可以逐一尝试效果
opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())
# opt = paddle.optimizer.Momentum(learning_rate=0.01, momentum=0.9, parameters=model.parameters())
# opt = paddle.optimizer.Adagrad(learning_rate=0.01, parameters=model.parameters())
# opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters())
EPOCH_NUM = 1
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
images, labels = data
images = paddle.to_tensor(images)
labels = paddle.to_tensor(labels)
#前向计算的过程,同时拿到模型输出值和分类准确率
if batch_id == 0 and epoch_id==0:
# 打印模型参数和每层输出的尺寸
predicts, acc = model(images, labels, check_shape=True, check_content=False)
elif batch_id==401:
# 打印模型参数和每层输出的值
predicts, acc = model(images, labels, check_shape=False, check_content=True)
else:
predicts, acc = model(images, labels)
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predicts, labels)
avg_loss = paddle.mean(loss)
#每训练了100批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}, acc is {}".format(epoch_id, batch_id, avg_loss.numpy(), acc.numpy()))
#后向传播,更新参数的过程
avg_loss.backward()
opt.step()
opt.clear_grad()
#保存模型参数
paddle.save(model.state_dict(), 'mnist_test.pdparams')
#创建模型
model = MNIST()
#启动训练过程
train(model)
print("Model has been saved.")
torch版本也同样加入输出结构和内容值的代码,注意torch的conv的变量没有下划线
paddle是self.conv1._padding而torch的是self.conv1.padding
from torch.nn import Conv2d,MaxPool2d,Linear
#数据处理部分之后的代码,数据读取的部分调用load_data函数
# 定义网络结构,同上一节所使用的网络结构
class Mnist(nn.Module):
def __init__(self):
super(Mnist,self).__init__()
self.conv1 = Conv2d(in_channels=1,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool1 = MaxPool2d(kernel_size=2,stride=2)
self.conv2 = Conv2d(in_channels=20,out_channels=20,kernel_size=5,stride=1,padding=2)
self.max_pool2 = MaxPool2d(kernel_size=2,stride=2)
self.fc = Linear(in_features=980,out_features=10)
def forward(self,inputs,check_shape=False,check_content=False):
outputs1 = self.conv1(inputs)
outputs2 = torch.relu(outputs1)
outputs3 = self.max_pool1(outputs2)
outputs4 = self.conv2(outputs3)
outputs5 = torch.relu(outputs4)
outputs6 = self.max_pool2(outputs5)
outputs6 = torch.reshape(outputs6,[outputs6.shape[0],-1])
outputs7 = self.fc(outputs6)
outputs8 = F.softmax(outputs7,dim=1)
if check_shape:
# 打印每层网络设置的超参数-卷积核尺寸,卷积步长,卷积padding,池化核尺寸
print("\n########## print network layer's superparams ##############")
print("conv1-- kernel_size:{}, padding:{}, stride:{}".format(self.conv1.weight.shape, self.conv1.padding, self.conv1.stride))
print("conv2-- kernel_size:{}, padding:{}, stride:{}".format(self.conv2.weight.shape, self.conv2.padding, self.conv2.stride))
#print("max_pool1-- kernel_size:{}, padding:{}, stride:{}".format(self.max_pool1.pool_size, self.max_pool1.pool_stride, self.max_pool1._stride))
#print("max_pool2-- kernel_size:{}, padding:{}, stride:{}".format(self.max_pool2.weight.shape, self.max_pool2._padding, self.max_pool2._stride))
print("fc-- weight_size:{}, bias_size_{}".format(self.fc.weight.shape, self.fc.bias.shape))
# 打印每层的输出尺寸
print("\n########## print shape of features of every layer ###############")
print("inputs_shape: {}".format(inputs.shape))
print("outputs1_shape: {}".format(outputs1.shape))
print("outputs2_shape: {}".format(outputs2.shape))
print("outputs3_shape: {}".format(outputs3.shape))
print("outputs4_shape: {}".format(outputs4.shape))
print("outputs5_shape: {}".format(outputs5.shape))
print("outputs6_shape: {}".format(outputs6.shape))
print("outputs7_shape: {}".format(outputs7.shape))
print("outputs8_shape: {}".format(outputs8.shape))
# 选择是否打印训练过程中的参数和输出内容,可用于训练过程中的调试
if check_content:
# 打印卷积层的参数-卷积核权重,权重参数较多,此处只打印部分参数
print("\n########## print convolution layer's kernel ###############")
print("conv1 params -- kernel weights:", self.conv1.weight[0][0])
print("conv2 params -- kernel weights:", self.conv2.weight[0][0])
# 创建随机数,随机打印某一个通道的输出值
idx1 = np.random.randint(0, outputs1.shape[1])
idx2 = np.random.randint(0, outputs4.shape[1])
# 打印卷积-池化后的结果,仅打印batch中第一个图像对应的特征
print("\nThe {}th channel of conv1 layer: ".format(idx1), outputs1[0][idx1])
print("The {}th channel of conv2 layer: ".format(idx2), outputs4[0][idx2])
print("The output of last layer:", outputs8[0], '\n')
return outputs8
# 训练配置,并启动训练过程
model = Mnist()
model=model.cuda()
model.train(mode=True)
#调用加载数据的函数
train_loader = load_data('train')
optimizer = optim.Adam(model.parameters(),lr= 0.001)
BATCHSIZE=100
EPOCH_NUM = 10
for epoch_id in range(EPOCH_NUM):
correct=0
for batch_id, data in enumerate(train_loader()):
#准备数据,变得更加简洁
image_data, label_data = data
image = torch.tensor(image_data).cuda()
label = torch.tensor(label_data).cuda()
image = torch.reshape(image,[image.shape[0],1,28,28])
#前向计算的过程
if batch_id==0 and epoch_id==0:
predict=model(image,check_shape=True)
elif batch_id==401:
predict=model(image,check_content=True)
else:
predict = model(image)
predict_label = torch.max(predict,1)[1]
correct += (predict_label == label.squeeze(dim=1)).sum()
#计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predict, label.squeeze(dim=1)).cuda()
avg_loss = torch.mean(loss)
#每训练了150批次的数据,打印下当前Loss的情况
if batch_id!=0 and batch_id % 150 == 0:
print("epoch: {}, batch: {}, loss is: {}, Accuracy:{:.3f}".format(epoch_id, batch_id, avg_loss.cpu().detach().numpy(),\
correct/(BATCHSIZE*150)))
correct=0
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.step()
model.zero_grad()
torch.save(model.state_dict, 'mnist_test.pdparams')
加入校验或测试,更好评价模型效果
如下程序读取上一步训练保存的模型参数,读取校验数据集,并测试模型在校验数据集上的效果。
def evaluation(model):
print('start evaluation .......')
# 定义预测过程
params_file_path = 'mnist.pdparams'
# 加载模型参数
param_dict = paddle.load(params_file_path)
model.load_dict(param_dict)
model.eval()
eval_loader = load_data('eval')
acc_set = []
avg_loss_set = []
for batch_id, data in enumerate(eval_loader()):
images, labels = data
images = paddle.to_tensor(images)
labels = paddle.to_tensor(labels)
predicts, acc = model(images, labels)
loss = F.cross_entropy(input=predicts, label=labels)
avg_loss = paddle.mean(loss)
acc_set.append(float(acc.numpy()))
avg_loss_set.append(float(avg_loss.numpy()))
#计算多个batch的平均损失和准确率
acc_val_mean = np.array(acc_set).mean()
avg_loss_val_mean = np.array(avg_loss_set).mean()
print('loss={}, acc={}'.format(avg_loss_val_mean, acc_val_mean))
model = MNIST()
evaluation(model)
torch的保存模型和加载和paddle又是关键词有区别,加载参数是load_state_dict
注意这里torch.save(model.state_dict(),'mnist_test.pdparams'),state_dict必须要跟个括号,要不然之后load时蜜汁报错。
def evaluation(model):
print('start evaluation .......')
model.load_state_dict(torch.load(r'./mnist_test.pdparams'))
model.eval()
eval_loader = load_data('eval')
acc_set = []
avg_loss_set = []
for batch_id, data in enumerate(eval_loader()):
images, labels = data
images = torch.tensor(images)
labels = torch.tensor(labels)
predicts = model(images)
predict_labels = torch.max(predicts, 1)[1]
correct=0
correct += (predict_labels == labels.squeeze(dim=1)).sum()
acc=correct/100
print(acc)
loss = F.cross_entropy(input=predicts, target=labels.squeeze(dim=1))
avg_loss = torch.mean(loss)
acc_set.append(float(acc.numpy()))
avg_loss_set.append(float(avg_loss.detach().numpy()))
#计算多个batch的平均损失和准确率
acc_val_mean = np.array(acc_set).mean()
avg_loss_val_mean = np.array(avg_loss_set).mean()
print('loss={}, acc={}'.format(avg_loss_val_mean, acc_val_mean))
model = Mnist()
evaluation(model)
加入正则化项,避免模型过拟合
为了防止模型过拟合,在没有扩充样本量的可能下,只能降低模型的复杂度,可以通过限制参数的数量或可能取值(参数值尽量小)实现。
具体来说,在模型的优化目标(损失)中人为加入对参数规模的惩罚项。当参数越多或取值越大时,该惩罚项就越大。通过调整惩罚项的权重系数,可以使模型在“尽量减少训练损失”和“保持模型的泛化能力”之间取得平衡。泛化能力表示模型在没有见过的样本上依然有效。正则化项的存在,增加了模型在训练集上的损失。
飞桨支持为所有参数加上统一的正则化项,也支持为特定的参数添加正则化项。前者的实现如下代码所示,仅在优化器中设置weight_decay
参数即可实现。使用参数coeff
调节正则化项的权重,权重越大时,对模型复杂度的惩罚越高。
#各种优化算法均可以加入正则化项,避免过拟合,参数regularization_coeff调节正则化项的权重
# opt = paddle.optimizer.SGD(learning_rate=0.01, weight_decay=paddle.regularizer.L2Decay(coeff=0.1), parameters=model.parameters())
opt = paddle.optimizer.Adam(learning_rate=0.001, weight_decay=paddle.regularizer.L2Decay(coeff=1e-5), parameters=model.parameters())
torch的优化器中也有weight_decay参数
optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)
Comments NOTHING