如何用30行JavaScript代码编写神经网络异或运算器

配置环境、安装合适的库、下载数据集……有时候学习深度学习的前期工作很让人沮丧,如果只是为了试试现在人人都谈的深度学习,做这些麻烦事似乎很不值当。但好在我们也有一些更简单的方法可以体验深度学习。近日,编程学习平台 Scrimba 联合创始人 Per Harald Borgen 在 Medium 上发文介绍了一种仅用 30 行 JavaScript 代码就创建出了一个神经网络的教程,而且使用的工具也只有 Node.js、Synaptic.js 和浏览器而已。另外,作者还做了一个交互式 Scrimba 教程,也许能帮你理解其中的复杂概念。

Synaptic.js:https://synaptic.juancazala.com

Node.js:https://nodejs.org

Scrimba 教程:https://scrimba.com/casts/cast-1980

Synaptic.js 让你可以使用 Node.js 和浏览器做深度学习。在这篇文章中,我将介绍如何使用 Synaptic.js 创建和训练神经网络。

// 创建网络
const { Layer, Network } = window.synaptic;

var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);

var myNetwork = new Network({
    input: inputLayer,
    hidden: [hiddenLayer],
    output: outputLayer
});

// 训练网络——学习异或运算
var learningRate = .3;
for (var i = 0; i < 20000; i++)
{
    // 0,0 => 0
    myNetwork.activate([0,0]);
    myNetwork.propagate(learningRate, [0]);

    // 0,1 => 1
    myNetwork.activate([0,1]);
    myNetwork.propagate(learningRate, [1]);

    // 1,0 => 1
    myNetwork.activate([1,0]);
    myNetwork.propagate(learningRate, [1]);

    // 1,1 => 0
    myNetwork.activate([1,1]);
    myNetwork.propagate(learningRate, [0]);
}

// 测试网络
console.log(myNetwork.activate([0,0])); // [0.015020775950893527]
console.log(myNetwork.activate([0,1])); // [0.9815816381088985]
console.log(myNetwork.activate([1,0])); // [0.9871822457132193]
console.log(myNetwork.activate([1,1])); // [0.012950087641929467]

我们将创建一个最简单的神经网络:一个可以执行异或运算的网络。上面就是这个网络的全部代码,但在我们深入解读这些代码之前,首先我们先了解一下神经网络的基础知识。


神经元和突触

神经网络的基本构造模块是神经元。神经元就像是一个函数,有几个输入,然后可以得到一个输出。神经元的种类有很多。我们的网络将使用 sigmoid 神经元,它可以输入任何数字并将其压缩到 0 到 1 之间。下图就是一个 sigmoid 神经元。它的输入是 5,输出是 1。箭头被称为突触,可以将该神经元与网络中的其它层连接到一起。



所以,红色的数字 5 是哪里来的?它是左边的三个突触的和,让我们来剖析一下。

在最左边我们可以看到两个值和一个所谓偏置(bias)值。这两个值是 1 和 0,用绿色表示。偏置值是 -2,用棕色表示。

首先,这两个输入与它们的权重(weight)相乘,即蓝色的数字 7 和 3。最后我们将这两个值与偏置加到一起就得到了红色的 5。这就是这个人工神经元的输入。



因为这是一个 sigmoid 神经元,会将任何值压缩到 0 到 1 之间,那么这个输出可以被压缩成 1。

如果你将这些神经元连接成一个网络,你就得到了一个神经网络。通过突触彼此相连的神经元可以向前传播输入,从而得到输出,如下图所示:



训练神经网络的目的是让它能够进行泛化,比如识别手写的数字或垃圾邮件。实现很好的泛化涉及为整个网络找到合适的权重和偏置值,就像我们上面案例中的蓝色和棕色数字。

当训练一个神经网络时,你只需要向其展示大量样本(比如手写数字),然后让其预测正确的答案即可。

在每次预测之后,你要计算这个预测的错误程度,并调整其权重和偏置值让该网络在下一轮预测时能更正确一点。这个学习过程被称为反向传播(backpropagation)。如此反复几千次,你的网络很快就擅长泛化了。

本教程不会解释反向传播的具体技术细节,但如果你有兴趣了解,可以参阅下面的文章:

  • 反向传播的一步步示例:http://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
  • 神经网络黑客指南:http://karpathy.github.io/neuralnets/
  • 神经网络和深度学习:http://neuralnetworksanddeeplearning.com/chap1.html

代码

现在你已经了解了基本的知识,就开始写代码吧!首先我们需要创建层。我们可以使用 synaptic 中的 new Layer() 函数。传递给该函数的数字表示每层应该有多少个神经元。

如果你不知道层是什么,可以看看上面提到的交互式教程。

const { Layer, Network } = window.synaptic;
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);

接下来,我们将这些层连接到一起,并实例化一个新网络,如下:

inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
var myNetwork = new Network({
 input: inputLayer,
 hidden: [hiddenLayer],
 output: outputLayer
});

所以,这就是一个「2 层-3 层-1 层」的网络,可以可视化为下图的形式:


现在训练这个网络:

// train the network - learn XOR
var learningRate = .3;
for (var i = 0; i < 20000; i++) {
  // 0,0 => 0
  myNetwork.activate([0,0]);
  myNetwork.propagate(learningRate, [0]);
  // 0,1 => 1
  myNetwork.activate([0,1]);
  myNetwork.propagate(learningRate, [1]);
  // 1,0 => 1
  myNetwork.activate([1,0]);
  myNetwork.propagate(learningRate, [1]);
  // 1,1 => 0
  myNetwork.activate([1,1]);
  myNetwork.propagate(learningRate, [0]);
}

这里我们运行该网络 20000 次。每一次我们都前向和反向传播 4 次,为该网络输入 4 组可能的输入:[0,0] [0,1] [1,0] [1,1]。

首先我们执行 myNetwork.activate([0,0]),其中 [0,0] 是我们发送给该网络的数据点。这是前向传播,也称为激活这个网络。在每次前向传播之后,我们需要执行反向传播,这时候网络会更新自己的权重和偏置。

反向传播是通过这行代码完成的:myNetwork.propagate(learningRate, [0]),其中 learningRate 是一个常数,给出了网络每次应该调整的权重的量。第二个参数 0 是给定输入 [0,0] 对应的正确输出。

然后,该网络将自己的预测与正确的标签进行比较,从而了解自己的正确程度有多少。

然后网络使用这个比较为基础来校正自己的权重和偏置值,这样让自己的下一次猜测更加正确一点。

这个过程如此反复 20000 次之后,我们可以使用所有四种可能的输入来检查网络的学习情况:

console.log(myNetwork.activate([0,0])); 
-> [0.015020775950893527]
console.log(myNetwork.activate([0,1]));
->[0.9815816381088985]
console.log(myNetwork.activate([1,0]));
-> [0.9871822457132193]
console.log(myNetwork.activate([1,1]));
-> [0.012950087641929467]

如果我们将这些值四舍五入到最近的整数,我们就得到了正确的异或运算结果。欢呼吧!

这样就完成了。尽管这仅仅只碰到了神经网络的表皮,但也足以帮助你进一步探索 Synaptic 和继续学习了。https://github.com/cazala/synaptic/wiki 这里还包含了更多好教程。

工程工程javascript神经网络实现