神经网络与深度学习
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.4 用代码实现一个感知机

了解感知机的运作原理之后,如果你是程序员或者你习惯用代码去表示,那么现在就可以动手试试了!我们挑一个受众面比较广的语言作为基础语言——Java,构建一个感知机。

2.4.1 Neuroph:一个基于Java的神经网络框架

在整个第2章中,我们都将用Neuroph作为基础框架,实现本书提及的神经网络和相关案例。

下面,我们简单认识一下Neuroph神经网络的框架。Neuroph是一个轻量级的开源Java神经网络框架,结构简单清晰,非常适合初学者入门,随着深入学习,可以慢慢了解Neuroph不只是初学者的入门工具,它可以简化神经网络的开发和实现,也能实现一些用在生产环境中的算法,图2-5所示为它的基本类库。

图2-5 Neuroph神经网络的框架

Neuroph由两部分组成。一部分是由基于Java开发的API组成,你可以方便地利用这部分API创建神经网络。另一部分是图形工具,能直接通过简单的图形化工具构造一个神经网络,帮助我们构造多层神经网络时更加快捷方便。

Java API部分分成三块,一块是neuroph.core,这是Neuroph的核心库,如表2-3所示。

表2-3 Neuroph类库说明

图2-6所示最简化地表示了Neuroph是如何工作的。

图2-6 Neuroph是如何工作的

(1)神经网络和学习规则对应,神经网络按照一定的学习规则训练相应的数据集。

(2)神经网络由基础的层(layer)组成,按照结构分为输入层、隐藏层和输出层,我们讲神经网络的时候会具体谈到各层的概念。

(3)神经网络的每层由最基础的神经元组成。

(4)训练规则包含一个训练集,允许有多个训练集,训练集由单个训练元素组成。

接下来,我们看看Neuroph中的类Neuron,它表示单个神经元的构造。如图2-7所示,这里没有罗列出Neuron所有的属性和方法,只展示了与本节相关的部分。

图2-7 类Neuron

其中inputConnections表示神经元的输入连接。比如,一个输入刺激到神经元,就构成一条输入。由于神经元是可以有多个输入的,所以神经元和输入连接是一对多的关系。netInput表示净输入,输入函数的输出。output表示神经元的输入。error为神经元的误差。inputFunction表示输入函数,通常选择加权求和。transferFunction表示传输函数。

另一个重要的类为Connection。它表示神经元的连接,可以是两个神经元之间的连接,也可以是输入信号与神经元之间的连接(实际上,输入信号可以看作一个输出永远等于输入的简单神经元), weight表示这个连接的权重。

这个结构已经与之前所述的感知机原理很接近了,下面我们就来构造一个感知机。

2.4.2 代码实现感知机

回到本节的主题,我们用代码实现一个感知机,以便更加深入地了解感知机的构造。你也可以选择跳过所有与代码相关的章节,不影响阅读。

首先,我们先想想要构建一个什么样的感知机:我们将神经元输入链接个数、神经元输出链接个数和传输函数类型作为输入的参数,如图2-8所示。

图2-8 感知机

    private void createNetwork(int inputNeuronsCount, int outputNeuronsCount,
    TransferFunctionType transferFunctionType) {
      //设置神经网络类型,这里我们将类型设置为感知器
      this.setNetworkType(NeuralNetworkType.PERCEPTRON);

      //初始化神经元输入刺激设置
      NeuronProperties inputNeuronProperties = new NeuronProperties();
      inputNeuronProperties.setProperty("transferFunction",
    TransferFunctionType.LINEAR);

      //创建输入刺激
      Layer inputLayer = LayerFactory.createLayer(inputNeuronsCount,
    inputNeuronProperties);
      this.addLayer(inputLayer);

      NeuronProperties outputNeuronProperties = new NeuronProperties();
      outputNeuronProperties.setProperty("neuronType", ThresholdNeuron.
    class);
      outputNeuronProperties.setProperty("thresh", new Double(Math.abs
    (Math.random())));
      outputNeuronProperties.setProperty("transferFunction", transferFunctionType);
      //为sigmoid和tanh传输函数设置斜率属性
      outputNeuronProperties.setProperty("transferFunction.slope", new Double(1));
    //create一个神经元的输出
    Layer outputLayer = LayerFactory.createLayer(outputNeuronsCount,
  outputNeuronProperties);
    this.addLayer(outputLayer);

    //在输入和输出层中建立全链接
    ConnectionFactory.fullConnect(inputLayer, outputLayer);

    //为神经网络设置默认输入输出
    NeuralNetworkFactory.setDefaultIO(this);
                this.setLearningRule(new BinaryDeltaRule());
   }

至此,一个简单的感知机已经建立,调用这个感知机可以处理诸如水果分类之类的简单问题。我们将训练这个神经网络,让它具有记忆和解决简单问题的能力。

2.4.3 感知机学习一个简单逻辑运算

我们已经建立了一个简单的感知机,本节中我们将训练这个感知机,并让学习逻辑运算AND。

首先,我们列出逻辑运算AND中的基本规则:

0 and 0=0

0 and 1=0

1 and 0=0

1 and 1=1

也就是说,在感知机接收0、0输入时,感知机应该响应1;当接收1、1输入时,应该响应1。当接收0、1或1、0时,应该输出0。AND主要用在条件判断中,也就是当两个子条件都成立时,才往下进行,两个子条件就用AND连接。

我们先给出完整代码,再顺着代码的脉络看看感知机是如何学会AND运算的。

    public static void main(String args[]) {

        //建立训练集,有两个输入一个输出
        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[]{0}));
        trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{0}));
        trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{1}));

        //建立一个感知机,定义输入刺激是2个,感知机输出是1个,这里我们调用Neuroph提供
    的Perceptron类。
        NeuralNetwork myPerceptron = new Perceptron(2, 1);
        LearningRule lr =myPerceptron.getLearningRule();

        lr.addListener(this);
        //开始学习训练集
        myPerceptron.learn(trainingSet);
        //测试感知机是否正确输出,打印
        System.out.println("Testing trained perceptron");
        testNeuralNetwork(myPerceptron, trainingSet);
    }

我们运行程序,结果如下:

    [main] INFO org.neuroph.core.learning.LearningRule - Learning Started
    1. iterate
    2. iterate
    3. iterate
    4. iterate
    5. iterate
    [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped
    5. iterate
    Testing trained perceptron
    Input: [0.0, 0.0] Output: [0.0]
    Input: [0.0, 1.0] Output: [0.0]
    Input: [1.0, 0.0] Output: [0.0]
    Input: [1.0, 1.0] Output: [1.0]

结果输出表明,网络经过5次迭代后,误差为0,初始的权值和偏置是随机数,接着网络根据输入的训练数据迭代学习,不断调整权值和偏置,并最终记忆AND逻辑运算。从测试数据中可以看到,4个输出已经完全正确,该网络已经对AND逻辑操作有正确的响应。

同理,只需要改变训练数据,就可以让感知机记忆其他内容,比如OR逻辑运算。

0 or 0=0

0 or 1=1

1 or 0=1

1 or 1=1

我们依据OR逻辑运算的规则,更改上例的训练数据:

    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[]{1}));

然后测试该网络,给出的输出可能为:

    [main] INFO org.neuroph.core.learning.LearningRule - Learning Started
    1. iterate
    2. iterate
    3. iterate
    4. iterate
    5. iterate
    6. iterate
    7. iterate
    8. iterate
    9. iterate
    10. iterate
    11. iterate
    12. iterate
    [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped
    12. iterate
    Testing trained perceptron
    Input: [0.0, 0.0] Output: [0.0]
    Input: [0.0, 1.0] Output: [1.0]
    Input: [1.0, 0.0] Output: [1.0]
    Input: [1.0, 1.0] Output: [1.0]

可以看到,经过12次迭代,网络已经正确记忆了OR逻辑运算。到这里,细心的读者可以发现,在学习AND逻辑运算的时候,网络进行了5次迭代,但在学习OR逻辑运算的时候迭代了12次。这种结果是完全随机的,实际上在网络建立的时候,初始权值是随机产生的(绝对值小于1),因此迭代几次才能使网络给出正确输出是不确定的,但不论初始值如何,经过有限次迭代,网络一定能完全记住训练数据。

2.4.4 XOR问题

大家还记得罗森布拉特吗?他是我们在第0章提到的那位神经网络中感知机奠基人之一,并且还是利用计算机模拟了感知机模型的天才,他是怎么被自己的中学同学明斯基找到感知机漏洞,郁闷至死的呢?对!就是因为XOR问题,我们来看看XOR能不能在我们的感知机模型中得到解决。

老规矩,先列出XOR的运算规则:

0 xor 0=0

0 xor 1=1

1 xor 0=1

1 xor 1=0

可以看到,当输入的两个值相等时,XOR返回0,否则得到1。

依然使用给出的感知机学习XOR会得到什么结果呢?感知机还可以正常工作吗?

我们将训练数据替换成如下代码:

    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}));

再来运行一下程序:

    …...
    573884. iterate
    573885. iterate
    573886. iterate
    573887. iterate
    573888. iterate
    573889. iterate
    573890. iterate
    573891. iterate
    573892. iterate
    573893. iterate
    573894. iterate
    ……

我暂停了,已经计算了60w次仍然没有得到结果。

如果不手动终止程序,它将永远运行下去。读者如果执行此程序,一定也会得到一样的结果。这是什么原因呢?为什么感知机可以轻松地记忆AND和OR运算,但是无法学习XOR呢?

先不解答此问题,我们先来构造一个神经网络,大家且往下看。