使用python实现深度神经网络 5

深度学习

一、实验介绍

1.1 实验内容

上次实验我们使用浅层神经网络达到了0.9的验证集准确率,本次实验我们将增加神经网络的深度,进行真正的深度学习

1.2 实验知识点

  • 梯度消失问题
  • 交叉熵损失函数

1.3 实验环境

  • python 2.7
  • numpy 1.12.1
  • scipy 0.19.0

二、实验步骤

2.1 增加网络的深度

要进行深度学习很简单,向神经网络里多增加几个网络层就可以了,只需对上次实验中的shallow.py稍加修改,我们就可以得到一个深度神经网络,修改后的文件我们命令为deep.py

# encoding=utf-8
from layers import *


def main():
    datalayer1 = Data('train.npy', 1024)  # 用于训练,batch_size设置为1024
    datalayer2 = Data('validate.npy', 10000)  # 用于验证,所以设置batch_size为10000,一次性计算所有的样例
    inner_layers = []
    inner_layers.append(FullyConnect(17 * 17, 20))
    inner_layers.append(Sigmoid())
    inner_layers.append(FullyConnect(20, 26))  # 增加一个隐层
    inner_layers.append(Sigmoid())
    losslayer = QuadraticLoss()
    accuracy = Accuracy()

    for layer in inner_layers:
        layer.lr = 1000.0  # 为所有中间层设置学习速率

    epochs = 20
    for i in range(epochs):
        print 'epochs:', i
        losssum = 0
        iters = 0
        while True:
            data, pos = datalayer1.forward()  # 从数据层取出数据
            x, label = data
            for layer in inner_layers:  # 前向计算
                x = layer.forward(x)

            loss = losslayer.forward(x, label)  # 调用损失层forward函数计算损失函数值
            losssum += loss
            iters += 1
            d = losslayer.backward()  # 调用损失层backward函数曾计算将要反向传播的梯度

            for layer in inner_layers[::-1]:  # 反向传播
                d = layer.backward(d)

            if pos == 0:  # 一个epoch完成后进行准确率测试
                data, _ = datalayer2.forward()
                x, label = data
                for layer in inner_layers:
                    x = layer.forward(x)
                accu = accuracy.forward(x, label)  # 调用准确率层forward()函数求出准确率
                print 'loss:', losssum / iters
                print 'accuracy:', accu
                break


if __name__ == '__main__':
    main()

我们只做了非常简单的修改,将原本的一层FullyConnect层改变成了两层,同时第一个FullyConnect层的输出有20个节点,这与第二个FullyConnect层的输入20个节点相匹配。这里中间网络层的节点数20也是一个需要我们手工设置的超参数。

2.2 深度学习的困难--梯度消失问题

我们期待深度神经网络能带来更好的性能,可是使用python deep.py运行上面的代码,你会发现我们的模型性能似乎不升反降了!上次实验我们的模型第一个epoch结束后就能达到0.4左右的准确率,可这次训练了好几个epoch之后准确率都还不到0.1, 20个epoch结束后,大概只有0.8左右的准确率。  

不过其实如果你增大epochs(比如增大到100,但是这样比较耗时间),你会发现其实到最后,深度神经网络的准确率还是超过了浅层神经网络,大概能到0.97。最终准确率的提升验证了我们之前提到的深度神经网络具有更强的表达能力。  

可是为什么一开始我们在验证集上的准确率增长的那么慢呢?难道说,我们的模型,“学习速率”变慢了?  

对,确实是学习速率变慢了,你可以增大超参数lr试试(比如增大到5000),准确率增长缓慢的问题会得到缓解,但和之前比起来还是慢了很多,为什么会这样呢?
要解释这个问题,我们再看看第一次实验中,梯度下降算法里给出的参数更新公式:

此处输入图片的描述

我们说超参数alpha(对应代码里的lr)控制学习速率,但同时也要注意到此处输入图片的描述也会影响每次参数更新的“步子”大小。由于我们这里的lr没有变,那么一定是此处输入图片的描述变小了。  

为什么变小了呢?你可以检查我们反向传播梯度的代码,尝试找出是哪个环节使得梯度变小。实际上,这里的主要问题出在Sigmoid层。Sigmoid层是这样反向传递梯度的:

    def backward(self, d):
        sig = self.sigmoid(self.x)
        self.dx = d * sig * (1 - sig)
        return self.dx  # 反向传递梯度

对于从之前的网络层反向传递来的梯度,Sigmoid层会乘上其对于输入x的导数并再次反向传递。让我们看看sigmoid函数的导函数是什么样的:

此处输入图片的描述

我们看到,当输入为0时,sigmoid函数的导函数最大值为0.25,当输入数据的绝对值增大时,导函数值迅速减小,马上就会接近于0。  

这便是导致我们的深度神经网络学习的更慢的“元凶”,由于我们这里多了一个Sigmoid层,梯度值经过Sigmoid层时,会减小很多,我们的梯度就像是“消失了”(vanishing gradient),导致我们的模型学习的非常慢。

实际上,梯度消失问题(或者说比这个问题更广泛的“梯度不稳定问题”),是导致难以训练深度神经网络的重要原因之一。  

2.3 拯救消失的梯度--交叉熵损失函数

解决梯度消失问题(vanishing gradient problem)的方法有很多,可以选择使用别的激活层替换掉Sigmoid层,但我们不打算介绍其他种类的激活层。在这里我们打算使用一种间接的,曲线救国的方式去解决梯度消失问题--使用交叉熵损失函数(cross-entropy loss)替换掉平方损失函数。

2.3.1 什么是交叉熵

交叉熵这个概念来源于信息论,我们这里不打算深究交叉熵的深层含义,如果有兴趣你可以自己查阅相关资料。交叉熵损失函数公式如下:

此处输入图片的描述

公式的形式有些复杂,其中h(theta,x)代表我们的神经网络对输入数据x对应输出的预测,y代表输入数据的预期正确输出值,同时这里的log是以自然数e为底的对数。我们可以举几个例子去感受这个损失函数的性质,当y=1而h=0时,损失函数值为正无穷,这是合理的,因为我们的模型此时的预测是完全错误的。当y=1且h=1时,损失函数值为0, 即模型的预测是正确时,损失函数值为0。你可以多列举几组数据测试一下,感受一下交叉熵损失函数的特性。

2.3.2 对交叉熵损失函数求导

损失函数层计算损失函数对于该层输入的梯度,并反向传递回去,所以这里我们要对交叉熵损失函数求导。

交叉熵公式比较复杂,你可以自己尝试对它进行求导,求得的导函数为:

此处输入图片的描述

这个导函数似乎有些复杂,但其实有一个非常巧妙有趣的巧合在这里,观察导函数的分母,这里的h(theta,x)就是从上一个Sigmoid层前向计算传过来的输出,而我们说Sigmoid层之所以会导致梯度消失,是因为它反向传递梯度的过程中将梯度值乘以了一个非常小的数:sigmoid(x)*(1-sigmoid(x)),而sigmoid(x)*(1-sigmoid(x))不就是h(thera,x)*(1-h(theta,x))吗?所以损失函数层反向传递时先除以h(thera,x)*(1-h(theta,x)),而Sigmoid层再乘以sigmoid(x)*(1-sigmoid(x)),两者刚好相互抵消,于是我们的梯度就不会变小!!

这真是一个美妙的巧合,我在这里不得不感叹数学实在太美妙了。

2.3.3 编写交叉熵损失函数层

只要我们把之前的平方损失函数层替换成交叉熵损失函数层,应该就可以解决学习速率变低的问题。我们可以根据上面的公式编写出交叉熵损失函数层:

class CrossEntropyLoss:
    def __init__(self):
        pass

    def forward(self, x, label):
        self.x = x
        self.label = np.zeros_like(x)
        for a, b in zip(self.label, label):
            a[b] = 1.0
        self.loss = np.nan_to_num(-self.label * np.log(x) - ((1 - self.label) * np.log(1 - x)))  # np.nan_to_num()避免log(0)得到负无穷的情况
        self.loss = np.sum(self.loss) / x.shape[0]
        return self.loss

    def backward(self):
        self.dx = (self.x - self.label) / self.x / (1 - self.x)  # 分母会与Sigmoid层中的对应部分抵消
        return self.dx

其实整体与平方损失函数差不多,注意由于log(0)得到的负无穷属于nan,所以这里调用np.nan_to_num()避免这种情况。

CrossEntropyLoss已经包含在了上次实验的layers.py文件中。  

2.3.4 再次尝试深度神经网络

让我们把平方损失函数替换成交叉熵损失函数,再试一试,创建deep2.py文件,修改代码如下:

# encoding=utf-8
from layers import *


def main():
    datalayer1 = Data('train.npy', 1024)  # 用于训练,batch_size设置为1024
    datalayer2 = Data('validate.npy', 10000)  # 用于验证,所以设置batch_size为10000,一次性计算所有的样例
    inner_layers = []
    inner_layers.append(FullyConnect(17 * 17, 20))
    inner_layers.append(Sigmoid())
    inner_layers.append(FullyConnect(20, 26))
    inner_layers.append(Sigmoid())
    losslayer = CrossEntropyLoss()
    accuracy = Accuracy()

    for layer in inner_layers:
        layer.lr = 1.0  # 为所有中间层设置学习速率

    epochs = 20
    for i in range(epochs):
        print 'epochs:', i
        losssum = 0
        iters = 0
        while True:
            data, pos = datalayer1.forward()  # 从数据层取出数据
            x, label = data
            for layer in inner_layers:  # 前向计算
                x = layer.forward(x)

            loss = losslayer.forward(x, label)  # 调用损失层forward函数计算损失函数值
            losssum += loss
            iters += 1
            d = losslayer.backward()  # 调用损失层backward函数曾计算将要反向传播的梯度

            for layer in inner_layers[::-1]:  # 反向传播
                d = layer.backward(d)

            if pos == 0:  # 一个epoch完成后进行准确率测试
                data, _ = datalayer2.forward()
                x, label = data
                for layer in inner_layers:
                    x = layer.forward(x)
                accu = accuracy.forward(x, label)  # 调用准确率层forward()函数求出准确率
                print 'loss:', losssum / iters
                print 'accuracy:', accu
                break


if __name__ == '__main__':
    main()

在上次实验下载的代码包中已经包括了这段代码(在deep2.py中)。
这里只做了两处修改,一处是把QuadraticLoss替换成了CrossEntropyLoss,另一处是我们把学习速率调低到了1.0,因为这里我们的梯度不会像原来那样“消失”了,所以学习速率可以适当调低一点。

运行python deep2.py, 准确率在第一个epoch结束时就可以达到0.5左右,同时20个epoch结束后准确率可以达到0.98左右,实际上如果你多训练几个epoch,准确率可以超过0.99,就我自己测试而言,最高能到0.992,也就是说,10000张图片里面,只有80张图片的分类出现了错误。如果说,最开始0.9的准确率已经可以在一些实际场合运用的话,那0.992的准确率已经差不多达到人类的分类水准了,因为实际上验证图片中会有一些字母对于人来说都很难区分(比如一些"I"和“J”)。
此处输入图片的描述

当我第一次做出这个结果的时候,我非常非常的兴奋,深度学习的强大力量深深震撼了我。可以预见未来几年,深度学习的运用将会改变很多产业的格局,很多原本需要人来完成的事可以由机器替代我们去完成。甚至也许在有生之年,我们能够看到真正意义上的人工智能的出现(虽然到时候的智能算法不一定是深度学习)。所以朋友们,抓住这个机会吧!

2.4 尾声--更多的深度学习

本课程到这里就基本结束了。回想一下所有的实验,我们学习了深度学习领域的一些基本知识,编写出了一些神经网络层,用这些层组成了一个浅层神经网络并进一步将其改进为深度神经网络(我们的代码只包含模型训练和验证过程,你可以添加代码,将训练好的网络结构和参数保存下来,用到实际的项目中,比如识别图片验证码之类的)。

虽然我们最终达到了0.992的准确率,但实际上,我们的神经网络还是比较简陋(too simple),深度学习领域有很多其它类别的网络(比如卷积神经网络CNN, 循环神经网络RNN)在处理特定问题时有更强大的能力。我们的系列后续课程会进行介绍。

三、实验总结

作为最后一次实验,我们实现了一个真正意义上的深度神经网络,你以后可以自豪的说,你是做过“深度学习”的人了。  

本次实验,我们学习了:

  1. Sigmoid层的导数很小是梯度消失问题的重要原因
  2. 交叉熵损失函数配合sigmoid激活函数可以避免梯度消失问题
  3. 损失函数如何选择也可以被视为一个超参数
  4. 深度学习领域还有其它种类的网络结构

四、课后作业

  1. [选做]深度学习中有很多种类的激活函数,请你查找相关资料了解。
  2. [选做]深度学习中有很多种类的损失函数,请你查找相关资料了解。
  3. [选做]请你添加将训练好的网络参数保存下来并运用于实际的图片字母识别的代码。
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值