深度学习笔记(7)

发布于 2022-08-04  502 次阅读


继续跟着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)
届ける言葉を今は育ててる
最后更新于 2022-08-04