PaddlePaddle第二周学习笔记

  • 项目一:使用飞浆完成手写数字识别模型
    • MNIST数据集
      • MNIST数据集是从NIST的Special Database 3(SD-3)和Special Database 1(SD-1)构建而来。

        PaddlePaddle第二周学习笔记_第1张图片

    • 构建手写数字识别的神经网络模型
      • PaddlePaddle第二周学习笔记_第2张图片

    • 飞桨各模型代码结构一致,大大降低了用户的编码难度
      • PaddlePaddle第二周学习笔记_第3张图片

    • 教程采用"横纵式"教学法,适用于深度学习初学者
      • PaddlePaddle第二周学习笔记_第4张图片

  • 项目二:通过极简方案快速构建手写数字识别模型
    • 加载飞桨与手写数字识别模型相关的类库
    • 数据处理
      • 飞桨提供了多个封装好的数据集API,涵盖计算机视觉、自然语言处理、推荐系统等多个领域,帮助读者快速完成深度学习任务。
        • mnist
        • cifar
        • Conll05
        • imdb
        • imikolov
        • movielens
        • sentiment
        • uci_housing
        • wmt14
        • wmt16
      • 手写数字识别任务中,通过paddle.dataset.mnist可以直接获取处理好的MNIST训练集、测试集
      • 通过paddle.dataset.mnist.train()函数设置数据读取器,batch_size设置为8,即一个批次有8张图片和8个标签
    • 飞桨API的使用方法
      • 飞桨API文档获取方式
        • 登录“飞桨官网->文档->API Reference
        • 通过搜索和分类浏览两种方式查阅API文档
      • API文档使用方法
        • 飞桨每个API的文档结构一致,包含接口形式、功能说明和计算公式、参数和返回值、代码示例
    • 模型设计
      • 输入像素的位置排布信息对理解图像内容非常重要(如将原始尺寸为28*28图像的像素按照7*112的尺寸排布,那么其中的数字将不可识别),因此网络的输入设计为28*28的尺寸,而不是1*784,以便于模型能够正确处理像素之间的空间信息。
      • 事实上,采用只有一层的简单网络(对输入求加权和)时并没有处理位置关系信息,因此可以猜测出此模型的预测效果有限。在后续优化环节中,介绍的卷积神经网络则更好的考虑了这种位置关系信息,模型的预测效果也会显著提升
    • 训练配置
      • 训练配置需要先生成模型实例(设为“训练”状态),再设置优化算法和学习率(使用随机梯度下降SGD,学习率设置为0.001)
    • 训练过程
      • 训练过程采用二层循环嵌套方式,训练完成后需要保存模型参数,以便后续使用。
        • 内层循环:负责整个数据集的一次遍历,遍历数据集采用分批次(batch)方式。
        • 外层循环:定义遍历数据集的次数,本次训练中外层循环10次,通过参数EPOCH_NUM设置。
    • 模型测试
      • 声明实例
      • 加载模型:加载训练过程中保存的模型参数。
      • 灌入数据:将测试样本传入模型,模型的状态设置为校验状态(eval),显式告诉框架我们接下来只会使用前向计算的流程,不会计算梯度和梯度反向传播。
      • 获取预测结果,取整后作为预测标签输出。
      • 在模型测试之前,需要先从'./work/example_0.jpg'文件中读取样例图片,并进行归一化处理。
  • 项目三:【手写数字识别】之数据处理
    • 前提条件
      • 在数据读取与处理前,首先要加载飞桨和数据处理库
    • 读入数据并划分数据集
      • MNIST数据集以json格式存储在本地,其数据存储结构

        PaddlePaddle第二周学习笔记_第5张图片

      • data包含三个元素的列表:train_set、val_set、 test_set,包括50000条训练样本,10000条测试样本,共60000条数据。每个样本包含手写数字图片和对应的标签
        • train_set(训练集):用于确定模型参数。
          • train_images:[50000, 784]的二维列表,包含50000张图片。每张图片用一个长度为784的向量表示,内容是28*28尺寸的像素灰度值(黑白图片)。
          • train_labels:[50000, ]的列表,表示这些图片对应的分类标签,即0-9之间的一个数字。
        • val_set(验证集):用于调节模型超参数(如多个网络结构、正则化权重的最优选择)。
        • test_set(测试集):用于估计应用效果(没有在模型中应用过的数据,更贴近模型在真实场景应用的效果)。
    • 为什么学术界的模型总在不断精进呢?
      • 假设所有论文共产生1000个模型,这些模型使用的是测试数据集来评判模型效果,并最终选出效果最优的模型。这相当于把原始的测试集当作了验证集,使得测试集失去了真实评判模型效果的能力
      • 当几个模型的准确率在测试集上差距不大时,尽量选择网络结构相对简单的模型。往往越精巧设计的模型和方法,越不容易在不同的数据集之间迁移。
    • 训练样本乱序、生成批次数据
      • 训练样本乱序: 先将样本按顺序进行编号,建立ID集合index_list。然后将index_list乱序,最后按乱序后的顺序读取数据。
        • 通过大量实验发现,模型对最后出现的数据印象更加深刻。训练数据导入后,越接近模型训练结束,最后几个批次数据对模型参数的影响越大。为了避免模型记忆影响训练效果,需要进行样本乱序操作。
      • 生成批次数据: 先设置合理的batch_size,再将数据转变成符合模型输入要求的np.array格式返回。同时,在返回数据时将Python生成器设置为yield模式,以减少内存占用。
    • 校验数据有效性
      • 在实际应用中,原始数据可能存在标注不准确、数据杂乱或格式不统一等情况。因此在完成数据处理流程后,还需要进行数据校验
        • 机器校验:加入一些校验和清理数据的操作。
        • 人工校验:先打印数据输出结果,观察是否是设置的格式;再从训练的结果验证数据处理和读取的有效性
    • 封装数据读取与处理函数
      • 从读取数据、划分数据集、到打乱训练数据、构建数据读取器以及数据校验,完成了一整套一般性的数据处理流程,下面将这些步骤放在一个函数中实现,方便在神经网络训练时直接调用。
      • 下面定义一层神经网络,利用定义好的数据处理函数,完成神经网络的训练。
    • 异步数据读取
      • PaddlePaddle第二周学习笔记_第6张图片

      • 同步数据读取:数据读取与模型训练串行。当模型需要数据时,才运行数据读取函数获得当前批次的数据。在读取数据期间,模型一直等待数据读取结束才进行训练,数据读取速度相对较慢。
      • 异步数据读取:数据读取和模型训练并行。读取到的数据不断的放入缓存区,无需等待模型训练就可以启动下一轮数据读取。当模型训练完一个批次后,不用等待数据读取过程,直接从缓存区获得下一批次数据进行训练,从而加快了数据读取速度。
      • 异步队列:数据读取和模型训练交互的仓库,二者均可以从仓库中读取数据,它的存在使得两者的工作节奏可以解耦。
      • fluid.io.DataLoader.from_generator参数名称和含义如下:
        • feed_list:仅在PaddlePaddle静态图中使用,动态图中设置为“None”,本教程默认使用动态图的建模方式;
        • capacity:表示在DataLoader中维护的队列容量,如果读取数据的速度很快,建议设置为更大的值;
        • use_double_buffer:是一个布尔型的参数,设置为“True”时,Dataloader会预先异步读取下一个batch的数据并放到缓存区;
        • iterable:表示创建的Dataloader对象是否是可迭代的,一般设置为“True”;
        • return_list:在动态图模式下需要设置为“True”。
      • 异步读取数据只在数据量规模巨大时会带来显著的性能提升,对于多数场景采用同步数据读取的方式已经足够。
  • 项目四:【手写数字识别】之网络结构
    • 经典的全连接神经网络
      • 输入层:将数据输入给神经网络。在该任务中,输入层的尺度为28×28的像素值。
      • 隐含层:增加网络深度和复杂度,隐含层的节点数是可以调整的,节点数越多,神经网络表示能力越强,参数量也会增加。在该任务中,中间的两个隐含层为10×10的结构,通常隐含层会比输入层的尺寸小,以便对关键信息做抽象,激活函数使用常见的sigmoid函数。
        • 隐含层引入非线性激活函数sigmoid是为了增加神经网络的非线性能力。
        • 举例来说,如果一个神经网络采用线性变换,有四个输入x1~x4​,一个输出y。假设第一层的变换是z1=x1−x2​和z2=x3+x4​,第二层的变换是y=z1+z22​,则将两层的变换展开后得到y=x1−x2+x3+x4​。也就是说,无论中间累积了多少层线性变换,原始输入和最终输出之间依然是线性关系。
      • 输出层:输出网络计算结果,输出层的节点数是固定的。如果是回归问题,节点数量为需要回归的数字数量;如果是分类问题,则是分类标签的数量。在该任务中,模型的输出是回归一个数字,输出层的尺寸为1。
    • 卷积神经网络
      • 卷积神经网络针对视觉问题的特点进行了网络结构优化,更适合处理视觉问题
      • 卷积神经网络由多个卷积层和池化层组成,卷积层负责对输入进行扫描以生成更抽象的特征表示,池化层对这些特征表示进行过滤,保留最关键的特征信息。
  • 项目五:【手写数字识别】之损失函数
    • 概述:
      • 损失函数是模型优化的目标,用于在众多的参数取值中,识别最理想的取值。
        • 先根据输入数据正向计算预测输出。
        • 再根据预测值和真实值计算损失。
        • 最后根据损失反向传播梯度并更新参数。
    • 分类任务的损失函数
      • 房价预测是回归任务,而手写数字识别是分类任务,使用均方误差作为分类任务的损失函数存在逻辑和效果上的缺欠。
      • 房价可以是大于0的任何浮点数,而手写数字识别的输出只可能是0-9之间的10个整数,相当于一种标签。
      • 在房价预测的案例中,由于房价本身是一个连续的实数值,因此以模型输出的数值和真实房价差距作为损失函数(loss)是符合道理的。但对于分类问题,真实结果是分类标签,而模型输出是实数值,导致以两者相减作为损失不具备物理含义。
      • 观测数据和背后规律之间的关系

        PaddlePaddle第二周学习笔记_第7张图片

    • Softmax函数
      • 如果模型能输出10个标签的概率,对应真实标签的概率输出尽可能接近100%,而其他标签的概率输出尽可能接近0%,且所有输出概率之和为1。这是一种更合理的假设!与此对应,真实的标签值可以转变成一个10维度的one-hot向量,在对应数字的位置上为1,其余位置为0,比如标签“6”可以转变成[0,0,0,0,0,0,1,0,0,0]
      • 实现上述思路,需要引入Softmax函数,它可以将原始输出转变成对应标签的概率,公式如下,其中C是标签类别个数

        PaddlePaddle第二周学习笔记_第8张图片

      • 从公式的形式可见,每个输出的范围均在0~1之间,且所有输出之和等于1,这是变换后可被解释成概率的基本前提。
      • 网络输出层为softmax函数,是一个三个标签的分类模型(三分类)使用的softmax输出层,从中可见原始输出的三个数字3、1、-3,经过softmax层后转变成加和为1的三个概率值0.88、0.12、0。

        PaddlePaddle第二周学习笔记_第9张图片

      • 对于二分类问题,使用两个输出接入softmax作为输出层,等价于使用单一输出接入Sigmoid函数。如图所示,利用两个标签的输出概率之和为1的条件,softmax输出0.6和0.4两个标签概率,从数学上等价于输出一个标签的概率0.6。

        PaddlePaddle第二周学习笔记_第10张图片

      • 在这种情况下,只有一层的模型为S(wTxi)S(w^{T}x_i)S(wTxi​),SSS为Sigmoid函数。模型预测为1的概率为S(wTxi),模型预测为0的概率为1−S(wTxi)
      • 从肿瘤大小和肿瘤性质的数据图中可发现,往往尺寸越大的肿瘤几乎全部是恶性,尺寸极小的肿瘤几乎全部是良性。只有在中间区域,肿瘤的恶性概率会从0逐渐到1(绿色区域),这种数据的分布是符合多数现实问题的规律。如果我们直接线性拟合,相当于红色的直线,会发现直线的纵轴0-1的区域会拉的很长,而我们期望拟合曲线0-1的区域与真实的分类边界区域重合。那么,观察下Sigmoid的曲线趋势可以满足我们对这个问题的一切期望,它的概率变化会集中在一个边界区域,有助于模型提升边界区域的分辨率。

        PaddlePaddle第二周学习笔记_第11张图片

    • 交叉熵
      • 在模型输出为分类标签的概率时,直接以标签和概率做比较也不够合理,人们更习惯使用交叉熵误差作为分类问题的损失衡量。
      • 交叉熵损失函数的设计是基于最大似然思想:最大概率得到观察结果的假设是真的。

        PaddlePaddle第二周学习笔记_第12张图片

      • 从乙盒中取出一个蓝球的概率更高(P(D∣h))(P(D|h))(P(D∣h)),所以观察到一个蓝球更可能是从乙盒中取出的(P(h∣D))(P(h|D))(P(h∣D))。DDD是观测的数据,即蓝球白球;hhh是模型,即甲盒乙盒。这就是贝叶斯公式所表达的思想:

      • 经过公式推导,使得上述概率最大等价于最小化交叉熵,得到交叉熵的损失函数。交叉熵的公式如下:其中log表示以e为底数的自然对数。yk代表模型输出,tk​代表各个标签。tk​中只有正确解的标签为1,其余均为0(one-hot表示)。

        PaddlePaddle第二周学习笔记_第13张图片

      • 交叉熵误差的值是由正确标签所对应的输出结果决定的,如自然对数的图形所示,当x等于1时,y为0;随着x向0靠近,y逐渐变小。因此,“正确解”标签对应的输出越大,交叉熵的值越接近0;当输出为1时,交叉熵误差为0。反之,如果“正确解”标签对应的输出越小,则交叉熵的值越大。

        PaddlePaddle第二周学习笔记_第14张图片

    • 交叉熵的代码实现
      • 在手写数字识别任务中,仅改动三行代码,就可以将在现有模型的损失函数替换成交叉熵(cross_entropy)
        • 在读取数据部分,将标签的类型设置成int,体现它是一个标签而不是实数值(飞桨默认将标签处理成“int64”)。
          • 从:label = np.reshape(labels[i], [1]).astype('float32')
          • 到:label = np.reshape(labels[i], [1]).astype('int64')
        • 在网络定义部分,将输出层改成“输出十个标签的概率”的模式。
          • 从:self.fc = Linear(input_dim=980, output_dim=1, act=None)
          • 到:self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
        • 修改计算损失的函数,从均方误差(常用于回归问题)到交叉熵误差(常用于分类问题)。
          • 从:loss = fluid.layers.square_error_cost(predict, label)
          • 到:loss = fluid.layers.cross_entropy(predict, label)
      • 全连接神经网络、卷积神经网络,在模型的最后阶段,都是使用Softmax进行处理。

        PaddlePaddle第二周学习笔记_第15张图片

  • 项目六:【手写数字识别】之优化算法
    • 设置学习率
      • 在深度学习神经网络模型中,通常使用标准的随机梯度下降算法更新参数,学习率代表参数更新幅度的大小,即步长。当学习率最优时,模型的有效容量最大,最终能达到的效果最好。学习率和深度学习任务类型有关,合适的学习率往往需要大量的实验和调参经验。探索学习率最优值时需要注意如下两点:

        PaddlePaddle第二周学习笔记_第16张图片

        • 学习率不是越小越好。学习率越小,损失函数的变化速度越慢,意味着我们需要花费更长的时间进行收敛,如 图2 左图所示。
        • 学习率不是越大越好。只根据总样本集中的一个批次计算梯度,抽样误差会导致计算出的梯度不是全局最优的方向,且存在波动。在接近最优解时,过大的学习率会导致参数在最优解附近震荡,损失难以收敛,
      • 在训练前,我们往往不清楚一个特定问题设置成怎样的学习率是合理的,因此在训练时可以尝试调小或调大,通过观察Loss下降的情况判断合理的学习率
        • 设置不同初始学习率
          • optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
          • optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001, parameter_list=model.parameters())
          • optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.1, parameter_list=model.parameters())
    • 学习率的主流优化算法
      • 学习率是优化器的一个参数,调整学习率看似是一件非常麻烦的事情,需要不断的调整步长,观察训练时间和Loss的变化。经过研究员的不断的实验,当前已经形成了四种比较成熟的优化算法:SGD、Momentum、AdaGrad和Adam

        PaddlePaddle第二周学习笔记_第17张图片

        • SGD: 随机梯度下降算法,每次训练少量数据,抽样偏差导致参数收敛过程中震荡。
        • Momentum: 引入物理“动量”的概念,累积速度,减少震荡,使参数更新的方向更稳定。
          • 每个批次的数据含有抽样误差,导致梯度更新的方向波动较大。如果我们引入物理动量的概念,给梯度下降的过程加入一定的“惯性”累积,就可以减少更新路径上的震荡,即每次更新的梯度由“历史多次梯度的累积方向”和“当次梯度”加权相加得到。历史多次梯度的累积方向往往是从全局视角更正确的方向,这与“惯性”的物理概念很像,也是为何其起名为“Momentum”的原因。类似不同品牌和材质的篮球有一定的重量差别,街头篮球队中的投手(擅长中远距离投篮)喜欢稍重篮球的比例较高。一个很重要的原因是,重的篮球惯性大,更不容易受到手势的小幅变形或风吹的影响。
        • AdaGrad: 根据不同参数距离最优解的远近,动态调整学习率。学习率逐渐下降,依据各参数变化大小调整学习率。
          • 通过调整学习率的实验可以发现:当某个参数的现值距离最优解较远时(表现为梯度的绝对值较大),我们期望参数更新的步长大一些,以便更快收敛到最优解。当某个参数的现值距离最优解较近时(表现为梯度的绝对值较小),我们期望参数的更新步长小一些,以便更精细的逼近最优解。类似于打高尔夫球,专业运动员第一杆开球时,通常会大力打一个远球,让球尽量落在洞口附近。当第二杆面对离洞口较近的球时,他会更轻柔而细致的推杆,避免将球打飞。与此类似,参数更新的步长应该随着优化过程逐渐减少,减少的程度与当前梯度的大小有关。根据这个思想编写的优化算法称为“AdaGrad”,Ada是Adaptive的缩写,表示“适应环境而变化”的意思。RMSProp是在AdaGrad基础上的改进,AdaGrad会累加之前所有的梯度平方,而RMSprop仅仅是计算对应的梯度平均值,因而可以解决AdaGrad学习率急剧下降的问题。
        • Adam: 由于动量和自适应学习率两个优化思路是正交的,因此可以将两个思路结合起来,这就是当前广泛应用的算法。
      • 四种优化算法的设置方案,可以逐一尝试效果
        • optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
        • optimizer = fluid.optimizer.MomentumOptimizer(learning_rate=0.01, momentum=0.9, parameter_list=model.parameters())
        • optimizer = fluid.optimizer.AdagradOptimizer(learning_rate=0.01, parameter_list=model.parameters())
        • optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01, parameter_list=model.parameters())
  • 项目七:【手写数字识别】之资源配置
    • 单GPU训练
      • 飞浆动态图通过fluid.dygraph.guard(place=None)里的place参数,设置在GPU上训练还是CPU上训练。
        • with fluid.dygraph.guard(place=fluid.CPUPlace()) #设置使用CPU资源训神经网络。
        • with fluid.dygraph.guard(place=fluid.CUDAPlace(0)) #设置使用GPU资源训神经网络,默认使用服务器的第一个GPU卡。"0"是GPU卡的编号,比如一台服务器有的四个GPU卡,编号分别为0、1、2、3。
    • 分布式训练
      • 在工业实践中,很多较复杂的任务需要使用更强大的模型。强大模型加上海量的训练数据,经常导致模型训练耗时严重。比如在计算机视觉分类任务中,训练一个在ImageNet数据集上精度表现良好的模型,大概需要一周的时间,因为过程中我们需要不断尝试各种优化的思路和方案。如果每次训练均要耗时1周,这会大大降低模型迭代的速度。
      • 在机器资源充沛的情况下,建议采用分布式训练,大部分模型的训练时间可压缩到小时级别,分布式训练有两种实现模式:模型并行和数据并行。
        • 模型并行
          • 模型并行是将一个网络模型拆分为多份,拆分后的模型分到多个设备上(GPU)训练,每个设备的训练数据是相同的。模型并行的实现模式可以节省内存,但是应用较为受限。
            • 模型架构过大: 完整的模型无法放入单个GPU。如2012年ImageNet大赛的冠军模型AlexNet是模型并行的典型案例,由于当时GPU内存较小,单个GPU不足以承担AlexNet,因此研究者将AlexNet拆分为两部分放到两个GPU上并行训练。
            • 网络模型的结构设计相对独立: 当网络模型的设计结构可以并行化时,采用模型并行的方式。如在计算机视觉目标检测任务中,一些模型(如YOLO9000)的边界框回归和类别预测是独立的,可以将独立的部分放到不同的设备节点上完成分布式训练。
        • 数据并行
          • 数据并行与模型并行不同,数据并行每次读取多份数据,读取到的数据输入给多个设备(GPU)上的模型,每个设备上的模型是完全相同的,飞桨采用的就是这种方式。
          • 数据并行的方式与众人拾柴火焰高的道理类似,如果把训练数据比喻为砖头,把一个设备(GPU)比喻为一个人,那单GPU训练就是一个人在搬砖,多GPU训练就是多个人同时搬砖,每次搬砖的数量倍数增加,效率呈倍数提升。值得注意的是,每个设备的模型是完全相同的,但是输入数据不同,因此每个设备的模型计算出的梯度是不同的。如果每个设备的梯度只更新当前设备的模型,就会导致下次训练时,每个模型的参数都不相同。因此我们还需要一个梯度同步机制,保证每个设备的梯度是完全相同的。
          • 梯度同步有两种方式:PRC通信方式和NCCL2通信方式(Nvidia Collective multi-GPU Communication Library)。
            • PRC通信方式:通常用于CPU分布式训练,它有两个节点:参数服务器Parameter server和训练节点Trainer

              PaddlePaddle第二周学习笔记_第18张图片

              • parameter server收集来自每个设备的梯度更新信息,并计算出一个全局的梯度更新。Trainer用于训练,每个Trainer上的程序相同,但数据不同。当Parameter server收到来自Trainer的梯度更新请求时,统一更新模型的梯度。
            • NCCL2通信方式(Collective):当前飞桨的GPU分布式训练使用的是基于NCCL2的通信方式

              PaddlePaddle第二周学习笔记_第19张图片

              • 相比PRC通信方式,使用NCCL2(Collective通信方式)进行分布式训练,不需要启动Parameter server进程,每个Trainer进程保存一份完整的模型参数,在完成梯度计算之后通过Trainer之间的相互通信,Reduce梯度数据到所有节点的所有设备,然后每个节点再各自完成参数更新。
          • 单机程序通过简单的改造,变成多机多卡程序。
            • 在启动训练前,需要配置如下参数:
              • 从环境变量获取设备的ID,并指定给CUDAPlace。
              • 对定义的网络做预处理,设置为并行模式。
              • 定义多GPU训练的reader,不同ID的GPU加载不同的数据集。
              • 收集每批次训练数据的loss,并聚合参数的梯度。
            • 启动多GPU的训练,还需要在命令行中设置一些参数变量
              • $ python -m paddle.distributed.launch --selected_gpus=0,1,2,3 --log_dir ./mylog train_multi_gpu.py
                • paddle.distributed.launch:启动分布式运行。
                • selected_gpus:设置使用的GPU的序号(需要是多GPU卡的机器,通过命令watch nvidia-smi查看GPU的序号)。
                • log_dir:存放训练的log,若不设置,每个GPU上的训练信息都会打印到屏幕。
                • train_multi_gpu.py:多GPU训练的程序,包含修改过的train_multi_gpu()函数
  • 项目八:【手写数字识别】之训练调试与优化
    • 训练过程优化思路
      • 1. 计算分类准确率,观测模型训练效果。
        • 交叉熵损失函数只能作为优化目标,无法直接准确衡量模型的训练效果。准确率可以直接衡量训练效果,但由于其离散性质,不适合做为损失函数优化神经网络。
      • 2. 检查模型训练过程,识别潜在问题。
        • 如果模型的损失或者评估指标表现异常,通常需要打印模型每一层的输入和输出来定位问题,分析每一层的内容来获取错误的原因。
      • 3. 加入校验或测试,更好评价模型效果。
        • 理想的模型训练结果是在训练集和验证集上均有较高的准确率,如果训练集上的准确率高于验证集,说明网络训练程度不够;如果验证集的准确率高于训练集,可能是发生了过拟合现象。通过在优化目标中加入正则化项的办法,解决过拟合的问题。
      • 4. 加入正则化项,避免模型过拟合。
        • 飞桨框架支持为整体参数加入正则化项,这是通常的做法。此外,飞桨框架也支持为某一层或某一部分的网络单独加入正则化项,以达到精细调整参数训练的效果。
      • 5. 可视化分析。
        • 用户不仅可以通过打印或使用matplotlib库作图,飞桨还提供了更专业的可视化分析工具VisualDL,提供便捷的可视化分析方法。
    • 计算模型的分类准确率
      • 飞桨提供了计算分类准确率的API,使用fluid.layers.accuracy可以直接计算准确率,该API的输入参数input为预测的分类结果predict,输入参数label为数据真实的label。
    • 检查模型训练过程,识别潜在训练问题
      • 使用飞桨动态图可以方便的查看和调试训练的执行过程。在网络定义的Forward函数中,可以打印每一层输入输出的尺寸,以及每层网络的参数。通过查看这些信息,不仅可以更好地理解训练的执行过程,还可以发现潜在问题,或者启发继续优化的思路
      • 使用check_shape变量控制是否打印“尺寸”,验证网络结构是否正确。使用check_content变量控制是否打印“内容值”,验证数据分布是否合理。假如在训练中发现中间层的部分输出持续为0,说明该部分的网络结构设计存在问题,没有充分利用
    • 加入校验或测试,更好评价模型效果
      • 在训练过程中,我们会发现模型在训练样本集上的损失在不断减小。但这是否代表模型在未来的应用场景上依然有效?为了验证模型的有效性,通常将样本集合分成三份,训练集、校验集和测试集。
        • 训练集 :用于训练模型的参数,即训练过程中主要完成的工作。
        • 校验集 :用于对模型超参数的选择,比如网络结构的调整、正则化项权重的选择等。
        • 测试集 :用于模拟模型在应用后的真实效果。因为测试集没有参与任何模型优化或参数训练的工作,所以它对模型来说是完全未知的样本。在不以校验数据优化网络结构或模型超参数时,校验数据和测试数据的效果是类似的,均更真实的反映模型效果。
      • 读取上一步训练保存的模型参数,读取测试数据集,并测试模型在测试数据集上的效果。
    • 加入正则化项,避免模型过拟合
      • 对于样本量有限、但需要使用强大模型的复杂任务,模型很容易出现过拟合的表现,即在训练集上的损失小,在验证集或测试集上的损失较大.

        PaddlePaddle第二周学习笔记_第20张图片

      • 如果模型在训练集和测试集上均损失较大,则称为欠拟合。过拟合表示模型过于敏感,学习到了训练数据中的一些误差,而这些误差并不是真实的泛化规律(可推广到测试集上的规律)。欠拟合表示模型还不够强大,还没有很好的拟合已知的训练样本,更别提测试样本了。因为欠拟合情况容易观察和解决,只要训练loss不够好,就不断使用更强大的模型即可,因此实际中我们更需要处理好过拟合的问题。
      • 导致过拟合原因:模型过于敏感,而训练数据量太少或其中的噪音太多。
        • 理想的回归模型是一条坡度较缓的抛物线,欠拟合的模型只拟合出一条直线,显然没有捕捉到真实的规律,但过拟合的模型拟合出存在很多拐点的抛物线,显然是过于敏感,也没有正确表达真实规律。

          PaddlePaddle第二周学习笔记_第21张图片

        • 理想的分类模型是一条半圆形的曲线,欠拟合用直线作为分类边界,显然没有捕捉到真实的边界,但过拟合的模型拟合出很扭曲的分类边界,虽然对所有的训练数据正确分类,但对一些较为个例的样本所做出的妥协,高概率不是真实的规律。

          PaddlePaddle第二周学习笔记_第22张图片

      • 过拟合的成因与防控
        • 成因:
          • 情况1:训练数据存在噪音,导致模型学到了噪音,而不是真实规律。
          • 情况2:使用强大模型(表示空间大)的同时训练数据太少,导致在训练数据上表现良好的候选假设太多,锁定了一个“虚假正确”的假设。
        • 防控:
          • 对于情况1,我们使用数据清洗和修正来解决。 对于情况2,我们或者限制模型表示能力,或者收集更多的训练数据。
      • 正则化项
        • 为了防止模型过拟合,在没有扩充样本量的可能下,只能降低模型的复杂度,可以通过限制参数的数量或可能取值(参数值尽量小)实现。
        • 具体来说,在模型的优化目标(损失)中人为加入对参数规模的惩罚项。当参数越多或取值越大时,该惩罚项就越大。通过调整惩罚项的权重系数,可以使模型在“尽量减少训练损失”和“保持模型的泛化能力”之间取得平衡。泛化能力表示模型在没有见过的样本上依然有效。正则化项的存在,增加了模型在训练集上的损失。
        • 飞桨支持为所有参数加上统一的正则化项,也支持为特定的参数添加正则化项。前者的实现如下代码所示,仅在优化器中设置regularization参数即可实现。使用参数regularization_coeff调节正则化项的权重,权重越大时,对模型复杂度的惩罚越高。
    • 可视化分析
      • 训练模型时,经常需要观察模型的评价指标,分析模型的优化过程,以确保训练是有效的。可选用这两种工具:Matplotlib库和VisualDL。
        • Matplotlib库:Matplotlib库是Python中使用的最多的2D图形绘图库,它有一套完全仿照MATLAB的函数形式的绘图接口,使用轻量级的PLT库(Matplotlib)作图是非常简单的。
        • VisualDL:如果期望使用更加专业的作图工具,可以尝试VisualDL,飞桨可视化分析工具。VisualDL能够有效地展示飞桨在运行过程中的计算图、各种指标变化趋势和数据信息。
      • 使用Matplotlib库绘制损失随训练下降的曲线图
        • 将训练的批次编号作为X轴坐标,该批次的训练损失作为Y轴坐标。
        • 训练开始前,声明两个列表变量存储对应的批次编号(iters=[])和训练损失(losses=[])。
        • 随着训练的进行,将iter和losses两个列表填满。
        • 训练结束后,将两份数据以参数形式导入PLT的横纵坐标。
        • 最后,调用plt.plot()函数即可完成作图。
      • 使用VisualDL可视化分析
        • 飞桨可视化分析工具,以丰富的图表呈现训练参数变化趋势、模型结构、数据样本、高维数据分布等。帮助用户清晰直观地理解深度学习模型训练过程及模型结构,进而实现高效的模型调优
          • 步骤1:引入VisualDL库,定义作图数据存储位置(供第3步使用),本案例的路径是“log”。
            • from visualdl import LogWriter
            • log_writer = LogWriter("./log")
          • 步骤2:在训练过程中插入作图语句。当每100个batch训练完成后,将当前损失作为一个新增的数据点(iter和acc的映射对)存储到第一步设置的文件中。使用变量iter记录下已经训练的批次数,作为作图的X轴坐标。
            • log_writer.add_scalar(tag = 'acc', step = iter, value = avg_acc.numpy())
            • log_writer.add_scalar(tag = 'loss', step = iter, value = avg_loss.numpy())
            • iter = iter + 100
          • 步骤3:命令行启动VisualDL。
            • 使用“visualdl --logdir [数据文件所在文件夹路径] 的命令启动VisualDL。在VisualDL启动后,命令行会打印出可用浏览器查阅图形结果的网址。
            • $ visualdl --logdir ./log --port 8080

              PaddlePaddle第二周学习笔记_第23张图片

    • 模型加载及恢复训练
      • 应用程序可以随时加载模型,完成预测任务。但是在日常训练工作中,我们会遇到一些突发情况,导致训练过程主动或被动的中断。如果训练一个模型需要花费几天的时间,中断后从初始状态重新训练是不可接受的。
      • 飞桨支持从上一次保存状态开始继续训练,只要我们随时保存训练过程中的模型状态,就不用从初始状态重新训练。
      • 进行恢复训练的程序不仅要保存模型参数,还要保存优化器参数。这是因为某些优化器含有一些随着训练过程变换的参数,例如Adam, AdaGrad等优化器采用可变学习率的策略,随着训练进行会逐渐减少学习率。这些优化器的参数对于恢复训练至关重要。
      • 训练程序使用Adam优化器,学习率以多项式曲线从0.01衰减到0.001(polynomial decay)
        • lr = fluid.dygraph.PolynomialDecay(0.01, total_steps, 0.001)

          PaddlePaddle第二周学习笔记_第24张图片

          • learning_rate:初始学习率
          • decay_steps:衰减步数
          • end_learning_rate:最终学习率
          • power:多项式的幂,默认值为1.0
          • cycle:下降后是否重新上升,polynomial decay的变化曲线下图所示。

 

你可能感兴趣的:(飞浆)