2.5 构造一个神经网络
我们谈生物学上的神经网络时曾提到过,单个生物神经元有不同的作用,当这些不同种类的神经元依据某种结构联系起来时,就成为神经网络。人类靠这样的神经网络分析、判断、思考并指导肢体进行反应,其中联系或连接非常重要,我们模仿人类大脑,把人工神经网络看成是一种运算模型,由大量的节点(或称神经元)相互连接构成。每个节点代表一种特定的输出函数。每两个节点间的连接都代表一个通过该连接信号的加权值,称之为权重,这相当于人工神经网络的记忆。网络的输出根据网络的连接方式,权重值和激励函数的不同而不同。
先看看单层神经网络,如图2-9所示。
图2-9 单层神经网络
在实际运用中,我们很少用单层神经网络直接解决问题,但它是学习多层神经元网络的基础。
2.5.1 线性不可分
我们已经学习了单个神经元、感知机和单层神经网络(又被称为单层前馈型神经网络)的知识。
我们已经可以让模型正确地学习AND逻辑运算和OR逻辑运算,如果你愿意,你甚至可以将之前说的香蕉和苹果分类,再回头看看表2-1,你会发现这就是个逻辑运算。
将香蕉的-1更换成0,得到表2-4。
表2-4 逻辑运算
利用AND运算规则:
0 and 0=0 香蕉
1 and 1=1 苹果
等等,如果输入是以下组合怎么办?
0 and 1=0 香蕉?
1 and 0=0 香蕉?
这个计算式表示了无论{颜色红色、形状弯形}或{形状圆形、颜色黄色}都输出香蕉了!这明显和我们的期望不符。
怎么办,这就要先引入一个概念:线性不可分。
还是以上面的苹果和香蕉为例,如图2-10所示。
图2-10 线性可分展示
我设计了一个能直观显示的图,大家一眼就可以看出方形的接近苹果的特征,菱形的接近香蕉的特征,这两种水果的特征如此鲜明,我们可以用一条直线分割这两类水果,如图2-11所示。
图2-11 线性可分展示
这条直线变成了分割苹果和香蕉的最优直线,先不去探讨如何最优化得到这条直线。我们能够用直线清晰地分类这两类水果,这就是线性可分。
我们知道现实中很多东西都是线性不可分的,比如你去分类两类以上的水果,大多数情况下,我们不能用一条直线把多种水果分开,如图2-12所示。
图2-12 线性不可分
完全傻眼,无法用一条直线分类。
再来看看之前我们用程序实现的感知机无法解决的XOR问题,XOR问题就是无法用一条直线区分两类事物,这就是线性不可分问题,如图2-13所示。最右边的图要想把浅色和深色分开需要两条线。
图2-13 XOR线性不可分
好的,在机器学习领域,有多种方法可以解决这类问题,本书讲的是神经网络和深度学习,所以我们需要用以下知识体系了解如何解决线性不可分问题。
1.多层神经网络
还记得是谁提出的XOR解决方法吗?请回顾一下:1974年,哈佛大学的一篇博士论文证明了在神经网络多加一层,并且利用“后向传播”(Back-propagation)学习方法,可以解决XOR问题。
我们先讲讲神经网络分层的概念。
(1)感知器:感知器是一种双层神经网络模型,一层为输入层(以上我们简化为输入刺激,也就是输入节点不参与计算),另一层具有计算单元,可以通过监督学习建立模式判别的能力。
(2)多层神经网络(也叫前馈神经网络)。
特点:前馈网络的各神经元接受前一级输入,并输出到下一级,无反馈。
节点:输入节点,输出节点。
(3)计算单元:可有任意一个输入,但只有一个输出,输出可耦合到任意多个其他节点的输入。
(4)层:可见层——输入和输出节点;隐层——中间层。
三层(一般我们只画出两层计算单元,输入层不参与计算不画出)前馈网络可以实现任何逻辑函数,如图2-14所示。
图2-14 前馈神经网络
我们把中间单层神经元扩展成两个神经元层,并且赋予了新的名字。第一层神经元叫第一隐层,第二层神经元叫第二隐层,我们把由输入的信号源、隐层及输出组成的层叫作多层神经网络,多层神经网络里一个重要的特征是上一层输出只能是下一层输入,不可跨层链接。
2.5.2 解决XOR问题(解决线性不可分)
利用多层感知机结构,连接多个神经元就可以处理异或问题。使用一个两层神经网络就可以记忆异或运算。一个可行的方案如图2-15所示,f1使用Step函数。
网络接收两个输入p1和p2,第一层上侧的神经元有净输入:
2p1+2p2-1
图2-15 多层感知机处理异或
它的净输入/输出与p1、p2的关系如表2-6所示。
表2-6 净输入/输出与p1和p2的关系
实际上,这个神经元对输出结果做了如图2-16所示的划分。
图2-16 上侧神经元的划分
图2-16所示为第一层下侧的神经元,有净输入:
-2p1-2p2+3
它的净输入/输出与p1、p2的关系如表2-7所示。
表2-7 净输入/输出与p1和p2的关系
因此,下侧神经元对数据进行了如图2-17所示的划分。
图2-17 下侧神经元的划分
最后,由输出神经元对两个神经元的数据进行整合,这里使用逻辑与操作,得到图2-18所示的正确的异或运算的划分。
图2-18 正确异或运算划分
具体的工作过程如下。
上侧神经元净输入:2p1+2p2-1
下侧神经元净输入:-2p1-2p2+3
查表2-6和表2-7可知:
p1=0,p2=0
输出:0 and 1=0
p1=0,p2=1
输出:1 and 1=1
p1=1,p2=0
输出:1 and 1=1
p1=1,p2=1
输出:1 and 0=0
使用多层感知机成功解决了异或问题。
2.5.3 XOR问题的代码实现
我们来看看如何用Neuroph实现多层感知机,也就是多层神经网络。在Neuroph中使用多层感知机非常简单,我们只要调用MultiLayerPerceptron类,并且设定好层数及每层的神经元数量就能实现一个多层神经网络。
下面我们看看具体代码:
DataSet trainingSet = new DataSet(2, 1); trainingSet.addRow(new DataSetRow(new double[]{0, 0}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{0, 1}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{0})); //创建多层感知机,输入层2个神经元,隐含层3个神经元,最后输出层为1个隐含神经元,我们 使用TANH传输函数用于最后格式化的输出。 MultiLayerPerceptron myMlPerceptron = new MultiLayerPerceptron(TransferFunctionType.TANH, 2, 3, 1); //开始训练 System.out.println("Training neural network..."); myMlPerceptron.learn(trainingSet);
看看输出结果:
…… 231. iteration : 0.010857327129718937 232. iteration : 0.010643221657202485 233. iteration : 0.010435288982776427 234. iteration : 0.010233311250628364 235. iteration : 0.01003707920734522 236. iteration : 0.009846391849799534 [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped Testing trained neural network Input: [0.0, 0.0] Output: [0.03794371028454834] Input: [0.0, 1.0] Output: [0.8280251075783817] Input: [1.0, 0.0] Output: [0.8061029856179274] Input: [1.0, 1.0] Output: [0.0931520677705696]
经过236次计算,我们的程序学习完毕并输出结果,Input:[0.0,0.0],输出0.037经过TANH函数处理,得到最终结果0。
Input:[0.0, 1.0],输出结果0.8,经过TANH得到1,依此类推,我们最后得到XOR异或学习的准确计算结果。
我们再来看看改变隐层的神经元个数会怎么样?
new MultiLayerPerceptron(TransferFunctionType.TANH, 2, 3, 1); 改成new MultiLayerPerceptron(TransferFunctionType.TANH, 2, 2, 1);
计算结果:
…… 224. iteration : 0.010137332719035904 225. iteration : 0.009894060755430178 [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped Testing trained neural network Input: [0.0, 0.0] Output: [0.04957962733734304] Input: [0.0, 1.0] Output: [0.8184942278091288] Input: [1.0, 0.0] Output: [0.8006225511852936] Input: [1.0, 1.0] Output: [0.04613571126703119]
没有太大变化,所有的变化都在正常的误差内。
如果我们将中间层变成30,会发生什么变化呢?
2409. iteration : 0.002927935122956385 [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped Testing trained neural network Input: [0.0, 0.0] Output: [-0.022022339547133944] Input: [0.0, 1.0] Output: [0.9341172653775799] Input: [1.0, 0.0] Output: [0.9069350056674689] Input: [1.0, 1.0] Output: [0.009379875622236349]
计算了2409次才完成,太耗时了,看看得到的数字,-0.022、0.93、0.90、0.009,更趋向于1和0,换句话说就是更精确了。
好的,我们仅仅靠调整隐层的神经元个数就已经能优化一个较为精确的数字了。