神经网络演算——单层感知器

内容纲要

神经网络简介

神经网络是指一种模拟人类神经系统所设计出来的程序思想,用来模拟人类视觉、听觉等等智慧行为的原理,让电脑可以具有人类智慧的一种方法。

下图是生物神经细胞的结构图,这个图看来颇为复杂,如果电脑程序真的要模拟这么复杂的结构,那程序应该也会非常复杂才对。

生物神经细胞结构图

还好、神经网络程序不需要去模拟「细胞膜、粒线体、核糖体」等等复杂的结构,因为开发人员可以透过「抽象化」将上述的神经细胞结构简化成下图(a) 的样子。

在下图中,$a_1 … a_n$ 是输入,$w_1 … w_n$ 是权重,这些输入乘上权重之后加总(SUM),就会得到神经元的刺激强度,接着经过函数f() 转换之后,就得到了输出的刺激强度。

抽象华的生物神经细胞结构图

上图(a)所对应的数学公式如下:


$t=f(\vec x·\vec a + b) = f\left(\sum_{i = 1}^n(w_{i}*a_{i}) + b\right)$

其中的b 值是用作阀值,举例来说,若b是-0.5,那么就代表要将总合减掉0.5,才得到输入刺激强度,这可以用来调节刺激强度,才不会一直增强上去。

而上图(b) 中的网络,是一种单层的神经网络,所谓单层是不计算输入节点的计算方式,因此只有图中的大圈圈才算是一层,其中每个大圈圈都是如图(a) 中的一个神经元。

最早的神经网络程式称为感知器(Perceptron),这是由Frank Rosenblatt 在1957 年于Cornell 航空实验室(Cornell Aeronautical Laboratory) 所发明的。

但是在1969 年,Marvin Minsky 和Seymour Papert在《Perceptrons》书中,仔细分析了知器为的功能及局限,证明感知器不能解决简单的XOR等问题,结果导致神经网络技术经历了长达20年的低潮期。

后来在1986 年,Rumelhart等人于下列论文中提出「反向传播」(back-propagation) 演算法,并成功的被运用在语音辨识等领域之后,神经网络才又开始成为热门的研究主题。

Rumelhart, David E.; Hinton, Geoffrey E., Williams, Ronald J. Learning representations by back-propagating errors. Nature. 8 October 1986, 323 (6088): 533–536.

事实上、反向传播的方法,并不是Rumelhart 等人第一个提出来的,Paul J. Werbos 1974 年在哈佛的博士论文中就提出了类似的方法,只是大家都不知道而已。

Paul J. Werbos. Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences. PhD thesis, Harvard University, 1974

当然,神经网络再度成为研究焦点之后,各式各样的方法又被发展出来了,大致上这些方法可以分为两类,一种称为「有指导者」的神经网络(Supervised Neural Network) ,像是「感知器与反传递演算法」等,另一种称为「没有指导者」的神经网络(Unsupervised Neural Network),像是「霍普菲尔德网络(Hopfield Network) 与自组织神经网络(Self Organization network)」等等。

当然、神经网络并不是「神奇银弹」,可以解决人工智慧上的所有问题,神经网络最强大的地方是容错性很强,而且不需要像专家系统那样撰写一堆规则,但是有得必有失,神经网络自动学习完成之后,我们根本不知道该如何再去改进这个学习成果,因为那些权重对人类来说根本就没有什么直观的意义,因此也就很难再去改进这个网络。

不过,随着科技的发展,这些问题将会慢慢的被解决!

实例:单层感知器(Perceptron)

简介

Rosenblatt 于1958 年提出了第一个神经网络模型,称为感知器,这个模型是基于1943 年McCulloch 与Pitts 所提出的神经元模型,该模型的数学公式如下:


$Y = sign\left[\sum_{i-1}^n(x_{i} w_{i}) – \theta\right]$

其中的sign 是正负号判断函数,若是正数则传回1,负数则传回0。

请注意,在此我们所说的「感知器」是指Rosenblatt 当时所使用的感知器,特指只有一层节点的「单层感知器」,而不是指称那种具有隐藏层的「多层感知器」 (Multilayer Perceptron),这点必须特别澄清一下!

而所谓感知器的学习,就是透过调整权重$w_i$ 的方式,让整个网络可以学到某个函数的方法,所以权重的调整方法是整个感知器学习行为的核心。

感知器的学习

以下是感知器学习的演算法:

  1. 初始化:设定权重$w_1,w_2,..w_n$和临界值$\theta$的初值之范围为[-0.5, 0.5]。

  2. 激励:用输入$\left(x_1,x_2,…x_n\right)$、权重$w_1,w_2,..w_n$与阀值$\theta$计算感知器的输出值Y。


$Y = sign\left[\sum_{i-1}^n(x_{i} w_{i}) – \theta\right]$

  1. 权重修改:根据函数输出$Y_d$ 与感知器输出Y 之间的差异,进行权重调整。

    3.1 计算误差: $e = Y_d – Y$

    3.2 计算调整量: $\Delta w_i = \alpha * x_i * e$

    3.3 调整权重: $w_i = w_i + \Delta w_i$

  2. 重复2-3 步骤,直到学会为止(如果一直学不会,只好宣告失败)。

感知器模型(两个输入的情况)

根据以上的方法,假如感知器的输入只有两个$\left(x_1,x_2\right)$那么权重也只会有两个$\left(w_1,w_2\right)$,于是我们可以得到下列的感知器模型:

感知器结构模型

假如我们的目标函数对于某组$\left(x_1,x_2\right)$的期望输出为$y_d$,那么就可以计算出误差为$e=y_d-y$,于是我们可以透过下列方法调整权重:


$w_1 = \alpha * x_1 * e$


$w_2 = \alpha * x_2 * e$

可惜的是、上述的调整方法中,并没有调整到$\theta$值,如果我们想要连$\theta$值也一并设计成可浮动的,那么就可以将$\theta$加入到w中,成为$w_0$,,并将$x_0$设为- 1,如下图所示:

感知器结构图

经过上述的调整简化之后,我们只要在调整权重时加入下列这条,就可以连也一并调整了。


$w_0 = \alpha * x_0 * e$

那么有几个问题,需要思考:

当我们对某布林函数「真值」中的每一个输入,都反覆进行上述调整,最后是否能学会该「布林函数」呢?

那么、我们是否能够用这么简单的方法让感知器学会AND、OR 与XOR 函数呢?

如果可以的话,那么我们能不能扩大到n 输入的感知器上,让感知器学会任何一个布林函数呢?

如果感知器可以学会任何一个布林函数,那就会具有强大的威力了!

但可惜的是,这个问题的答案是否定的,虽然感知器可以学会AND 与OR,但是却不可能学会XOR 函数。

在说明这个问题的理论之前,先让我们透过实作来体会一下感知器是如何学习AND 与OR 函数的,然后感受一下感知器在学XOR 函数时发生了什么问题?

需要了解了程序的运作原理之后,再来说明为何感知器无法学会XOR 函数。

感知器实例

以下是实例感知器代码,在node.js 环境下执行此一程式:

var log = console.log;

var Perceptron = function() { // 感知器
  this.step=function(x, w) { //  计算目前权重w的情况下,网络的输出值为0或1
    var result = w[0]*x[0]+w[1]*x[1]+w[2]*x[2]; // y=w0*x0+x1*w1+x2*w2=-theta+x1*w1+x2*w2
    if (result >= 0) // 如果结果大于零
      return 1;      //   就输出 1
    else             // 否则
      return 0;      //   就输出 0
  }

  this.training=function(truthTable) { // 训练函数training(truthTable),其中truthTable是目标的真值
    var rate = 0.01; // 学习速率调整,也就是alpha
    var w = [ 1, 0, 0 ]; 
    for (var loop=0; loop<1000; loop++) { // 最多训练一千轮
      var eSum = 0.0;
      for (var i=0; i<truthTable.length; i++) { // 每轮对于真值中的每个输入输出配对,都训练一次。
        var x = [ -1, truthTable[i][0], truthTable[i][1] ]; // 输入: x
        var yd = truthTable[i][2];       // 期望的输出 yd
        var y = this.step(x, w);  // 目前的输出 y
        var e = yd - y;                  // 差距 e = 期望的输出 yd - 目前的输出 y
        eSum += e*e;                     // 计算差距总和
        var dw = [ 0, 0, 0 ];            // 权重调整的幅度dw
        dw[0] = rate * x[0] * e; w[0] += dw[0]; // w[0] 的调整幅度为 dw[0]
        dw[1] = rate * x[1] * e; w[1] += dw[1]; // w[1] 的调整幅度为 dw[1]
        dw[2] = rate * x[2] * e; w[2] += dw[2]; // w[2] 的调整幅度为 dw[2]
        if (loop % 10 == 0)
          log("%d:x=(%s,%s,%s) w=(%s,%s,%s) y=%s yd=%s e=%s", loop, 
               x[0].toFixed(3), x[1].toFixed(3), x[2].toFixed(3), 
               w[0].toFixed(3), w[1].toFixed(3), w[2].toFixed(3), 
               y.toFixed(3), yd.toFixed(3), e.toFixed(3));
      }
      if (Math.abs(eSum) < 0.0001) return w; // 当训练结果误差够小时,就完成训练了。
    }
    return null; // 否则,就传会null代表训练失败。
  }
}

function learn(tableName, truthTable) { // 学习主程式:输入为目标的真值truthTable与其名称tableName。
  log("================== 学习 %s 函数 ====================", tableName);
  var p = new Perceptron();       // 建立感知器
  var w = p.training(truthTable); // 训练传感器
  if (w != null)                  // 显示训练结果
    log("学习成功 !");
  else
    log("学习失败 !");
  log("w=%j", w);
}

var andTable = [ [ 0, 0, 0 ], [ 0, 1, 0 ], [ 1, 0, 0 ], [ 1, 1, 1 ] ]; // AND函数的真值
var orTable  = [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 1 ] ]; // OR函数的真值
var xorTable = [ [ 0, 0, 0 ], [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ]; // XOR函数的真值

learn("and", andTable); // 学习AND函数
learn("or",  orTable);  // 学习OR函数
learn("xor", xorTable); // 学习XOR函数

执行结果:

================== 学习 and 函数 ====================
0:x=(-1.000,0.000,0.000) w=(1.000,0.000,0.000) y=0.000 yd=0.000 e=0.000
0:x=(-1.000,0.000,1.000) w=(1.000,0.000,0.000) y=0.000 yd=0.000 e=0.000
0:x=(-1.000,1.000,0.000) w=(1.000,0.000,0.000) y=0.000 yd=0.000 e=0.000
0:x=(-1.000,1.000,1.000) w=(0.990,0.010,0.010) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,0.000,0.000) w=(0.900,0.100,0.100) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,0.000,1.000) w=(0.900,0.100,0.100) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,1.000,0.000) w=(0.900,0.100,0.100) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,1.000,1.000) w=(0.890,0.110,0.110) y=0.000 yd=1.000 e=1.000
20:x=(-1.000,0.000,0.000) w=(0.800,0.200,0.200) y=0.000 yd=0.000 e=0.000
20:x=(-1.000,0.000,1.000) w=(0.800,0.200,0.200) y=0.000 yd=0.000 e=0.000
20:x=(-1.000,1.000,0.000) w=(0.800,0.200,0.200) y=0.000 yd=0.000 e=0.000
20:x=(-1.000,1.000,1.000) w=(0.790,0.210,0.210) y=0.000 yd=1.000 e=1.000
30:x=(-1.000,0.000,0.000) w=(0.700,0.300,0.300) y=0.000 yd=0.000 e=0.000
30:x=(-1.000,0.000,1.000) w=(0.700,0.300,0.300) y=0.000 yd=0.000 e=0.000
30:x=(-1.000,1.000,0.000) w=(0.700,0.300,0.300) y=0.000 yd=0.000 e=0.000
30:x=(-1.000,1.000,1.000) w=(0.690,0.310,0.310) y=0.000 yd=1.000 e=1.000
学习成功 !
w=[0.6599999999999997,0.34000000000000014,0.34000000000000014]
================== 学习 or 函数 ====================
0:x=(-1.000,0.000,0.000) w=(1.000,0.000,0.000) y=0.000 yd=0.000 e=0.000
0:x=(-1.000,0.000,1.000) w=(0.990,0.000,0.010) y=0.000 yd=1.000 e=1.000
0:x=(-1.000,1.000,0.000) w=(0.980,0.010,0.010) y=0.000 yd=1.000 e=1.000
0:x=(-1.000,1.000,1.000) w=(0.970,0.020,0.020) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,0.000,0.000) w=(0.700,0.200,0.200) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,0.000,1.000) w=(0.690,0.200,0.210) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,1.000,0.000) w=(0.680,0.210,0.210) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,1.000,1.000) w=(0.670,0.220,0.220) y=0.000 yd=1.000 e=1.000
20:x=(-1.000,0.000,0.000) w=(0.460,0.340,0.340) y=0.000 yd=0.000 e=0.000
20:x=(-1.000,0.000,1.000) w=(0.450,0.340,0.350) y=0.000 yd=1.000 e=1.000
20:x=(-1.000,1.000,0.000) w=(0.440,0.350,0.350) y=0.000 yd=1.000 e=1.000
20:x=(-1.000,1.000,1.000) w=(0.440,0.350,0.350) y=1.000 yd=1.000 e=0.000
学习成功 !
w=[0.37999999999999945,0.38000000000000017,0.38000000000000017]
================== 学习 xor 函数 ====================
0:x=(-1.000,0.000,0.000) w=(1.000,0.000,0.000) y=0.000 yd=0.000 e=0.000
0:x=(-1.000,0.000,1.000) w=(0.990,0.000,0.010) y=0.000 yd=1.000 e=1.000
0:x=(-1.000,1.000,0.000) w=(0.980,0.010,0.010) y=0.000 yd=1.000 e=1.000
0:x=(-1.000,1.000,1.000) w=(0.980,0.010,0.010) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,0.000,0.000) w=(0.800,0.100,0.100) y=0.000 yd=0.000 e=0.000
10:x=(-1.000,0.000,1.000) w=(0.790,0.100,0.110) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,1.000,0.000) w=(0.780,0.110,0.110) y=0.000 yd=1.000 e=1.000
10:x=(-1.000,1.000,1.000) w=(0.780,0.110,0.110) y=0.000 yd=0.000 e=0.000
...
900:x=(-1.000,0.000,0.000) w=(0.010,-0.010,-0.000) y=1.000 yd=0.000 e=-1.000
900:x=(-1.000,0.000,1.000) w=(-0.000,-0.010,0.010) y=0.000 yd=1.000 e=1.000
900:x=(-1.000,1.000,0.000) w=(-0.010,-0.000,0.010) y=0.000 yd=1.000 e=1.000
900:x=(-1.000,1.000,1.000) w=(-0.000,-0.010,-0.000) y=1.000 yd=0.000 e=-1.000
...
990:x=(-1.000,0.000,0.000) w=(0.010,-0.010,-0.000) y=1.000 yd=0.000 e=-1.000
990:x=(-1.000,0.000,1.000) w=(-0.000,-0.010,0.010) y=0.000 yd=1.000 e=1.000
990:x=(-1.000,1.000,0.000) w=(-0.010,-0.000,0.010) y=0.000 yd=1.000 e=1.000
990:x=(-1.000,1.000,1.000) w=(-0.000,-0.010,-0.000) y=1.000 yd=0.000 e=-1.000
学习失败 !
w=null

分析

您可以看到在上述执行结果中, AND 与OR 两个真值,输入到单层感知器进行训练之后,都可以正确的进行学习,也就是单层感知器的输出可以与该真值完全一致,这代表单层感知器学习成功了。

但是对于XOR 这个真值,单层感知器却无法让输出与真值完全一致,这也正是Minsky 所说的,单层感知器无法正确学习XOR 函数的原因。

会产生这个现象的原因,可以用线性代数的概念解释,下图显示了AND, OR, XOR 等这三个真值在二维线性空间的状况,其中的粉红色圆圈代表真值的目标输出值为1,而浅蓝色圆圈代表目标输出为0。

生物神经细胞结构图

您可以看到对于AND 与OR 都可以用一条线将「粉红色圆圈」与「浅蓝色圆圈」分割开来。但是对XOR 而言,由于粉红色与浅蓝色分别处于斜对角,我们没有办法画出单一条线将两者分开,这也是会何上述单层感知器在学习XOR 这个函数上会失败的原因了。

结语

可惜的是,单层感知器并没有办法学会任意的布林函数,这个结果虽然令人失望,但是期望这么简单的模型就能拥有强大的能力,其实是一种非常天真的想法。

不过、如果我们将这种单层的网络继续扩充,变成双层以上的网络的话,其能力就会大大的提升了,这也就是我们接下来要探讨的主题,反传递演算法(Back -Propagation Algorithm) 了。

code enjoy! 🙂

作者:indeex

链接:http://indeex.cc

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


发表评论

您的电子邮箱地址不会被公开。