面向初学者: 神经网络概述(一)

508 阅读4分钟

最近网上看到一篇质量很不错的翻译文《小白都能看懂的神经网络教程:从原理到优化如此简单》,如获至宝。细读后发现里面有些翻译不到位。然后翻到原文读了一遍,又感觉原文有些虎头蛇尾,前半部分讲解深入浅出,后半部分讲解不够细致,匆匆收尾,作者似乎着急忙慌要睡觉似的。于是决定对原文再翻译一遍,以飨读者。

译文如下:

一篇简单介绍神经网络如何工作,以及如何用Python从头实现的文章

如果说“神经网络”其实不复杂,这可能会让你感到惊讶。事实上“神经网络”往往比你想象的要简单。

这篇文章是面向初学者的,并假定你机器学习基础为0。 我们将从头了解如何用 Python 实现神经网络。

1. 基本构建模块:神经元(Neurons)

首先,我们必须讨论下神经元,这是神经网络的基本单位。一个神经元接受多个输入,并对它们进行一些数学运算,然后产生一个输出。下图展示的是一个接收2个输入的神经元: perceptron.png

这里发生了三件事: 首先,每个 输入 (input) 都乘以一个 权重 w (weight):

x1x1w1x2x2w2\begin{array}{l} x_{1} \rightarrow x_{1} * w_{1} \\ x_{2} \rightarrow x_{2} * w_{2} \end{array}

然后,所有的加权输入求和后又加上一个 偏移 b (bias):

(x1w1)+(x2w2)+b\left(x_{1} * w_{1}\right)+\left(x_{2} * w_{2}\right)+b

最后,加他们的 传递给一个 激活函数 (activation function):

y=f(x1w1+x2w2+b)y=f\left(x_{1} * w_{1}+x_{2} * w_{2}+b\right)

这里的激活函数作用是将无限制的输入转换为可预测的输出。一种常见的激活函数是 sigmoid函数

11+ex\begin{aligned} \frac{1}{1+e^{-x}} \end{aligned}

image.png

激活函数的输出数值只会落在 (0,1) 这个区间。你可以把它想象成将 (,+)(−∞,+∞) 压缩成 (0,1)(0,1)

  • 把无限小的负数转化为无限接近于0
  • 把无限大的正数转化为无限接近于1

一个简单的例子

假设我们有一个使用sigmoidsigmoid激活函数的神经元,它可以接收两个输入,下面是他的权重偏移

w=[0,1]b=4w = [0,1] \\ b = 4

w = [0 ,1] 是 w1=0w2=1w_{1} = 0,w_{2} = 1

的矢量表示法。

现在我们输入 x=[2,3]x = [2, 3]。我们将用 点积 来简明的阐述:

(wx)+b=((w1x1)+(w2x2))+b=02+13+4=7\begin{aligned} (w \cdot x)+b &=\left(\left(w_{1} * x_{1}\right)+\left(w_{2} * x_{2}\right)\right)+b \\ &=0 * 2+1 * 3+4 \\ &=7 \end{aligned}
y=f(wx+b)=f(7)=0.999y=f(w \cdot x+b)=f(7)=\bm{0.999}

这个神经元给定输入 [2,3][2,3],给定输出 0.9990.999。就是这样!这种向前传递输入以获得输出的过程成为前馈(feedforward)

编写一个神经元

是时候实现一个神经元了!我们将利用 Numpy (一个流行而强大的科学计数库) 来帮我们实现数学公式:

import numpy as np

def sigmoid(x):
  # Our activation function: f(x) = 1 / (1 + e^(-x))
  return 1 / (1 + np.exp(-x))

class Neuron:
  def __init__(self, weights, bias):
    self.weights = weights
    self.bias = bias

  def feedforward(self, inputs):
    # Weight inputs, add bias, then use the activation function
    total = np.dot(self.weights, inputs) + self.bias
    return sigmoid(total)

weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4                   # b = 4
n = Neuron(weights, bias)

x = np.array([2, 3])       # x1 = 2, x2 = 3
print(n.feedforward(x))    # 0.9990889488055994

认识这个数字吧?我们得到了相同的结果 0.9990.999

2. 将神经元组合成神经网络

神经网络是什么?其实无非就是一束连接在一起的神经元。这是一个简单的神经网络的样子: image.png

该网络有2个输入,一个包含2个神经元的隐藏层(h1h_1h2h_2​),以及一个带有1个神经元的输出层(o1o_1​)。请注意,o1o_1​ 的输入来自 h1h_1h2h_2的输出 -这就是神经网络。

举个栗子:前馈

以上面图中的神经网络为例,并假定所有神经元都有相同的权重 w=[0,1]w=[0,1], 相同的偏移 b=0b=0,以及相同的sigmoidsigmoid激活函数。用h1,h2,o1h_1,h_2,o_1表示他们代表的神经元输出。

如果我们输入 x=[2,3]x = [2,3],会发生什么?

h1=h2=f(wx+b)=f((02)+(13)+0)=f(3)=0.9526\begin{aligned} h_{1}=h_{2} &=f(w \cdot x+b) \\ &=f((0 * 2)+(1 * 3)+0) \\ &=f(3) \\ &=0.9526 \end{aligned}
o1=f(w[h1,h2]+b)=f((0h1)+(1h2)+0)=f(0.9526)=0.7216\begin{aligned} o_{1} &=f\left(w \cdot\left[h_{1}, h_{2}\right]+b\right) \\ &=f\left(\left(0 * h_{1}\right)+\left(1 * h_{2}\right)+0\right) \\ &=f(0.9526) \\ &=\bm{0.7216} \end{aligned}

当输入为 x=[2,3]x = [2,3],这个神经网络输出为 0.72160.7216, 是不是很简单?

一个神经网络可以有任意数量的层,每层可以有任意数据的神经元。基本思想一致:向前给神经元传递输入,并在后面得到结果。

方便起见,本文的其余部分将继续使用上图所示的神经网络

编写一个神经网络:前馈

让我们实现一个前馈神经网络。这是该网络的图片,仅供参考:

image.png

import numpy as np

# ... code from previous section here

class OurNeuralNetwork:
  '''
  A neural network with:
    - 2 inputs
    - a hidden layer with 2 neurons (h1, h2)
    - an output layer with 1 neuron (o1)
  Each neuron has the same weights and bias:
    - w = [0, 1]
    - b = 0
  '''
  def __init__(self):
    weights = np.array([0, 1])
    bias = 0

    # The Neuron class here is from the previous section
    self.h1 = Neuron(weights, bias)
    self.h2 = Neuron(weights, bias)
    self.o1 = Neuron(weights, bias)

  def feedforward(self, x):
    out_h1 = self.h1.feedforward(x)
    out_h2 = self.h2.feedforward(x)

    # The inputs for o1 are the outputs from h1 and h2
    out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))

    return out_o1

network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x)) # 0.7216325609518421

我们又得出了 0.72160.7216,完美!