资讯详情

百度飞桨架构师手把手带你零基础实践深度学习——【手写数字识别】之损失函数

百度飞桨架构师手把手带给你零基础实践深度学习-打卡计划

  • 总目录
  • 【手写数字识别】损失函数
    • 概述
    • 分类任务的损失函数
    • Softmax函数
    • 交叉熵
    • 实现交叉熵代码

以下是课程链接。欢迎报考!这篇文章将继续更新。我只是飞桨的搬运工

在这里插入图片描述

话不多说,这么良心的课程赶紧扫码上车!https://aistudio.baidu.com/aistudio/education/group/info/1297?activityId=5&directly=1&shared=1

总目录

【手写数字识别】损失函数

概述

在上一节中,我们试图通过更复杂的模型(经典的全连接神经网络和卷积神经网络)来提高手写数字识别模型训练的准确性。在本节中,我们继续从水平开始水平和垂直教学方法,如 探讨损失函数优化对模型训练效果的影响。

图1:横纵式教学方法 — 优化损失函数

损失函数是模型优化的目标,用于识别许多参数值中最理想的值。在训练过程的代码中计算损失函数,每轮模型训练过程相同,分为以下三个步骤:

  1. 预测输出应根据输入数据进行正向计算。
  2. 损失按预测值和真实值计算。
  3. 最后,根据损失反向传输梯度更新参数。

分类任务的损失函数

在之前的计划中,我们重复使用了房价预测模型的损失函数均方误差。从预测效果来看,虽然损失在下降,模型的预测值逐渐接近真实值,但模型的最终效果并不理想。根本上,不同的深度学习任务需要有自己合适的损失函数。以房价预测和手写数字识别为例,详细分析了原因如下:

  1. 房价预测是一项回归任务,而手写数字识别是一项分类任务。使用均方误差作为分类任务的损失函数缺乏逻辑和效果。
  2. 房价可以大于0,而手写数字识别的输出只能是0-9之间的10个整数,相当于一个标签
  3. 在房价预测的情况下,由于房价本身是一个连续的实际值,模型输出值与实际房价差距作为损失函数(loss)这是合理的。然而,对于分类问题,真正的结果是分类标签,而模型输出是一个真实的值,导致两者的相减没有物理意义。

那么,分类任务的合理输出是什么呢?分类任务本质上是特征组合下的分类概率。以下是一个简单的案例,如 所示。

图2:观测数据与背后规律的关系

在这种情况下,医生根据肿瘤的大小 x x x作为肿瘤性质 y y y参考判断(判断因素很多,肿瘤大小只是其中之一),所以我们观察到模型判断的结果是 x x x和 y y y标签(1是恶性的,0是良性的)。该数据背后的规则是不同大小的肿瘤,属于恶性肿瘤的可能性。观测数据是真实规则抽样的结果,分类模型应拟合真实规则,输出属于分类标签的概率。

Softmax函数

如果模型能够输出10个标签的概率,则真实标签的概率输出应尽可能接近100%,而其他标签的概率输出应尽可能接近0%,所有输出概率之和应为1。这是一个更合理的假设!相应地,真实的标签值可以转换为10维one-hot在对应数字的位置上,向量为1,其余位置为0,如标签6可转换为[0、0、0、0、0、0、1、0、0、0]。

为了实现上述思路,需要引入Softmax函数可以将原始输出转换为相应标签的概率,公式如下 C C C是标签类别的数量。

s o f t m a x ( x i ) = e x i ∑ j = 0 N e j x , i = 0 , . . . , C ? 1 softmax(x_i) = \frac {e^{x_i}}{\sum_{j=0}^N{e^x_j}}, i=0, ..., C-1 softmax(xi​)=∑j=0N​ejx​exi​​,i=0,...,C−1

从公式的形式可见,每个输出的范围均在0~1之间,且所有输出之和等于1,这是变换后可被解释成概率的基本前提。对应到代码上,我们需要在网络定义部分修改输出层:self.fc = Linear(input_dim=10, output_dim=1, act='softmax'),即是对全连接层的输出加一个softmax运算。

是一个三个标签的分类模型(三分类)使用的softmax输出层,从中可见原始输出的三个数字3、1、-3,经过softmax层后转变成加和为1的三个概率值0.88、0.12、0。

图3:网络输出层为softmax函数
上文解释了为何让分类模型的输出拟合概率的原因,但为何偏偏用softmax函数完成这个职能? 下面以二分类问题(只输出两个标签)进行探讨。

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

图4:对于二分类问题,等价于单一输出接入Sigmoid函数

在这种情况下,只有一层的模型为 S ( w T x i ) S(w^{T}x_i) S(wTxi​), S S S为Sigmoid函数。模型预测为1的概率为 S ( w T x i ) S(w^{T}x_i) S(wTxi​),模型预测为0的概率为 1 − S ( w T x i ) 1-S(w^{T}x_i) 1−S(wTxi​)。

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

图5:使用sigmoid拟合输出可提高分类模型对边界的分辨率

这就类似于公共区域使用的带有恒温装置的热水器温度阀门,如 所示。由于人体适应的水温在34度-42度之间,我们更期望阀门的水温条件集中在这个区域,而不是在0-100度之间线性分布。

图6:热水器水温控制

交叉熵

在模型输出为分类标签的概率时,直接以标签和概率做比较也不够合理,人们更习惯使用交叉熵误差作为分类问题的损失衡量。

交叉熵损失函数的设计是基于最大似然思想:最大概率得到观察结果的假设是真的。如何理解呢?举个例子来说,如 所示。有两个外形相同的盒子,甲盒中有99个白球,1个蓝球;乙盒中有99个蓝球,1个白球。一次试验取出了一个蓝球,请问这个球应该是从哪个盒子中取出的?

图7:体会最大似然的思想

相信大家简单思考后均会得出更可能是从乙盒中取出的,因为从乙盒中取出一个蓝球的概率更高 ( P ( D ∣ h ) ) (P(D|h)) (P(D∣h)),所以观察到一个蓝球更可能是从乙盒中取出的 ( P ( h ∣ D ) ) (P(h|D)) (P(h∣D))。 D D D是观测的数据,即蓝球白球; h h h是模型,即甲盒乙盒。这就是贝叶斯公式所表达的思想:

P ( h ∣ D ) ∝ P ( h ) ⋅ P ( D ∣ h ) P(h|D) ∝ P(h) \cdot P(D|h) P(h∣D)∝P(h)⋅P(D∣h)

依据贝叶斯公式,某二分类模型“生成” n n n个训练样本的概率:

P ( x 1 ) ⋅ S ( w T x 1 ) ⋅ P ( x 2 ) ⋅ ( 1 − S ( w T x 2 ) ) ⋅ … ⋅ P ( x n ) ⋅ S ( w T x n ) P(x_1)\cdot S(w^{T}x_1)\cdot P(x_2)\cdot(1-S(w^{T}x_2))\cdot … \cdot P(x_n)\cdot S(w^{T}x_n) P(x1​)⋅S(wTx1​)⋅P(x2​)⋅(1−S(wTx2​))⋅…⋅P(xn​)⋅S(wTxn​)


对于二分类问题,模型为 S ( w T x i ) S(w^{T}x_i) S(wTxi​), S S S为Sigmoid函数。当 y i y_i yi​=1,概率为 S ( w T x i ) S(w^{T}x_i) S(wTxi​);当 y i y_i yi​=0,概率为 1 − S ( w T x i ) 1-S(w^{T}x_i) 1−S(wTxi​)。


经过公式推导,使得上述概率最大等价于最小化交叉熵,得到交叉熵的损失函数。交叉熵的公式如下:

L = − [ ∑ k = 1 n t k log ⁡ y k + ( 1 − t k ) log ⁡ ( 1 − y k ) ] L = -[\sum_{k=1}^{n} t_k\log y_k +(1- t_k)\log(1-y_k)] L=−[k=1∑n​tk​logyk​+(1−tk​)log(1−yk​)]

其中, log ⁡ \log log表示以 e e e为底数的自然对数。 y k y_k yk​代表模型输出, t k t_k tk​代表各个标签。 t k t_k tk​中只有正确解的标签为1,其余均为0(one-hot表示)。

因此,交叉熵只计算对应着“正确解”标签的输出的自然对数。比如,假设正确标签的索引是“2”,与之对应的神经网络的输出是0.6,则交叉熵误差是 − log ⁡ 0.6 = 0.51 −\log 0.6 = 0.51 −log0.6=0.51;若“2”对应的输出是0.1,则交叉熵误差为 − log ⁡ 0.1 = 2.30 −\log 0.1 = 2.30 −log0.1=2.30。由此可见,交叉熵误差的值是由正确标签所对应的输出结果决定的。

自然对数的函数曲线可由如下代码实现。

import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0.01,1,0.01)
y = np.log(x)
plt.title("y=log(x)") 
plt.xlabel("x") 
plt.ylabel("y") 
plt.plot(x,y)
plt.show()
plt.figure()

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

交叉熵的代码实现

在手写数字识别任务中,仅改动三行代码,就可以将在现有模型的损失函数替换成交叉熵(cross_entropy)。

  • 在读取数据部分,将标签的类型设置成int,体现它是一个标签而不是实数值(飞桨默认将标签处理成“int64”)。
  • 在网络定义部分,将输出层改成“输出十个标签的概率”的模式。
  • 在训练过程部分,将损失函数从均方误差换成交叉熵。
#修改标签数据的格式,从float32到int64
import os
import random
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
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

    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)
            if len(imgs_list) == BATCHSIZE:
                yield np.array(imgs_list), np.array(labels_list)
                imgs_list = []
                labels_list = [ 

标签: kyk连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台