前言

本文教程均来自b站【小白也能听懂的人工智能原理】,感兴趣的可自行到b站观看。

代码及工具箱

本专栏的代码和工具函数已经上传到GitHub:1571859588/xiaobai_AI: 零基础入门人工智能 (github.com),可以找到对应课程的代码

正文

深度学习简介

深度学习的核心在于让神经网络通过增加隐藏层和神经元来深入学习数据。这样做可以帮助模型发现数据中复杂的规律,从而做出更准确的预测。

如果一个网络的隐藏层超过三层,我们就叫它深度神经网络。虽然听起来技术性很强,但其实深度学习就是让计算机通过分析很多数据来学习如何做出更好的预测。

我们可能不清楚网络内部每个部分具体是如何运作的,但通过给网络提供数据并进行训练,我们可以看到它的效果。虽然不能直接控制网络的所有部分,但我们可以通过改变一些重要的设置,比如学习速度、选择不同的激活函数或者调整隐藏层的数量,来影响网络的训练和表现。这个过程就是我们常说的“调参”。

Tensorflow游乐场体验

让我们来试试Tensorflow游乐场,这个在线工具让初学者可以轻松地搭建和训练自己的神经网络。

这个网站界面简单,包括文字说明、控制区、数据集选择和神经网络的可视化展示。

数据集选择区提供了四种数据集,都是我们在课程中见过的,涉及两个特征维度的输入。

我们先从简单的第三个数据集开始,这个数据集容易分开,就像我们之前讨论的豆豆问题,大小和颜色代表两个特征,颜色不同的点代表是否有毒。

在三维图像的俯视图上,我们的任务是用预测函数的0.5等高线来分开两种豆豆。这种简单问题只需要一个输出层神经元,不需要隐藏层。

设置时,我们可以选择Sigmoid作为激活函数,学习率用默认的0.03。

运行模型,我们可以看到预测曲面的0.5等高线很好地分开了两种豆豆。

接着,我们看第一个数据集,它是圆形分布的,中间蓝色有毒,周围黄色无毒。要分开这种圆形数据,需要至少三个神经元在隐藏层。因为一个神经元的等高线是直线,三个神经元可以形成一个闭合圆形,这样就可以区分内外。

通过这些分析,我们对搭建神经网络有了基本认识。虽然实际计算可能更复杂,但Tensorflow游乐场是一个很好的实践和学习平台。

让我们在Tensorflow游乐场上试试。首先,我们给网络加上一个隐藏层,里面放三个神经元,学习率保持原样。这样设置后,模型跑起来,能很好地把数据分成两类。

如果你把隐藏层里的神经元减少到两个,模型就分不开数据了。这说明多一个神经元,网络的分辨能力就强一些。

然后,我们再试试第二个数据集,它是一个特殊的问题,叫做异或(XOR)。异或的规则是,两个输入如果一样,结果是0;不一样,结果是1。很久以前,人们发现单个感知器解决不了这个问题,这让很多人对神经网络失去了信心。

但现在我们知道了,只要在隐藏层里放三个神经元,网络就能解决异或问题,很好地分类数据。这说明神经网络比单个感知器厉害,能处理更复杂的分类任务。

给大家留一个问题自己思考:为什么至少需要三个神经元的隐藏层才能解决某些数据分类问题?这个问题值得探究。

现在,让我们看看最后一种数据集,它看起来像一个螺旋形状,这种复杂的数据分布对于只有一层隐藏层的神经网络来说可能太困难了。

为了应对这种复杂性,我们可以尝试使用更深的网络,比如增加到三层隐藏层,每层有四个神经元。但是,如果运行后发现预测结果没有变化,那可能是因为我们一直在使用的Sigmoid激活函数不太适合这种情况。

这个问题提示我们,不同的数据集可能需要不同类型的神经网络架构和激活函数来达到最佳的分类效果。

我们之前选择了Sigmoid函数作为激活函数,因为它在任何地方都能求导,而且导数从不为零,这在训练神经网络时很有帮助。但在Sigmoid函数的两端,也就是当输入值非常大或非常小的时候,函数的导数会变得非常小。这意味着在这些区域,梯度下降算法的更新步伐会非常缓慢,导致训练过程非常困难,这就是所谓的梯度消失问题。这个问题在深层网络中尤为严重,因为层数越多,梯度消失的影响也越大。

为了解决这个问题,现在人们更倾向于使用另一种叫做ReLU(Rectified Linear Unit)的激活函数。ReLU函数在正区间内导数很大,这有助于缓解梯度消失问题,让深层网络的训练变得更加可行。

ReLU是一种流行的激活函数,它简单且有效。这个函数的规则是,如果计算出的线性结果是正数,ReLU就输出这个数;如果是负数,它的输出就是0。这样设计的好处是,在正数区域,ReLU的导数是1,这意味着梯度下降算法可以顺利地更新权重,不会出现梯度消失问题。

然而,当线性结果是0时,ReLU的导数是0,这可能导致一个称为“Dead ReLU”的问题,即神经元的权重停止更新。为了避免这个问题,人们发明了Leaky ReLU,它在负数区域也有一个小的非零斜率,确保即使输入是负数,神经元仍然可以有所学习。

尽管存在这些挑战,实践中发现,即使在深层网络中,直接使用ReLU也能取得很好的效果。虽然梯度消失问题可能由多种因素引起,但ReLU因其简单性和有效性,目前成为了最流行的激活函数之一。

好的,让我们把激活函数换成ReLU,再运行一次模型。你会发现,对于圆形分布的数据,使用ReLU的收敛速度比Sigmoid快多了。所以,如果没有特别的理由,对于隐藏层的神经元,ReLU通常是首选的激活函数。

不过,在输出层,我们还是用Sigmoid,因为它的输出范围是0到1,适合用来做分类。

从一开始,我们用Sigmoid来做分类,后来发现激活函数还能让神经网络处理更复杂的问题。现在,虽然有了更好的激活函数,但Sigmoid也完成了它的任务。

当我们用ReLU激活函数训练网络,对螺旋形的数据集进行分类时,初步效果可能并不完美。为了改进结果,我们可以尝试增加隐藏层中的神经元数量,比如将每层的神经元增加到8个,然后重新训练。

经过这样的调整,我们发现在螺旋形数据集上的分类效果有显著提升。此时,网络中的每个神经元都学习到了不同的参数值,它们对输入数据进行了各种中间处理和特征提取。

但到了这个阶段,我们会发现,没有简单的数学方法可以精确分析每个神经元到底在做什么。这就是深度学习中常说的“炼丹”过程,意味着模型的内部工作机制往往是黑箱的,我们更多地依赖于实验和观察来优化模型。

编程实验

好的,同学们,我们开始做本节课的编程实验,这次实验呢我们只做一件事情,用凯瑞斯搭建一个深度神经网络,拟合一下这个长得像蚊香一样的螺旋形数据记好。好的老规矩,我们还是先创建一个源码文件,这是我们最后一次接触小蓝了。

  • 需要三层隐藏层,激活函数为ReLU;一层输出层,激活为sigmoid

  • 代码

    # 创建模型
    model = Sequential() 
    model.add(Dense(units=8, activation='relu',input_dim=2 ))
    model.add(Dense(units=8, activation='relu' ))
    model.add(Dense(units=8, activation='relu'))
    model.add(Dense(units=1, activation='sigmoid'))
    # 告诉keras使用均方误差代价函数和随机梯度下降算法SGD,学习率为0.05,评估指标选择accuracy
    model.compile(loss='mse', optimizer=SGD(learning_rate=0.05),metrics=['accuracy'])
    # 开始训练
    model.fit(X, Y, epochs=5000, batch_size=10,verbose=0)
    
  • 拟合结果