前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >神经网络算法

神经网络算法

作者头像
foochane
发布2019-05-23 15:42:03
8020
发布2019-05-23 15:42:03
举报
文章被收录于专栏:foochanefoochane

1 感知机

20 世纪五、六⼗年代,科学家 Frank Rosenblatt其受到 Warren McCulloch 和 Walter Pitts早期的⼯作的影响,发明了感知机(Perceptrons)。

⼀个感知器接受⼏个⼆进制输⼊,

x_1,x_2,x_3.....
x_1,x_2,x_3.....

,并产⽣⼀个⼆进制输出:

如上图所示的感知机有三个输⼊:

x_1,x_2,x_3
x_1,x_2,x_3

。通常可以有更多或更少输⼊。 我们再引⼊权重:

w_1,w_2,w_3
w_1,w_2,w_3

,衡量输入对输出的重要性。感知机的输出为0 或者 1,则由分配权重后的总和

∑_j w_jx_j
∑_j w_jx_j

⼩于等于或者⼤于阈值决定。和权重⼀样,阈值(threshold)是⼀个实数,⼀个神经元的参数。⽤更精确的代数形式如下:

举例:今天下午去不去看电影?

给三个因素设置权重来作出决定:

  1. 今天天⽓好吗?
  2. 你的朋友会不会陪你去?
  3. 电影好不好看?

可以把这三个因素对应地⽤⼆进制变量

x_1,x_2,x_3
x_1,x_2,x_3

来表⽰。例如,如果天⽓好,我们把

x_1= 1
x_1= 1

,如果不好,

x_1 = 0
x_1 = 0

。类似地,如果你的朋友陪你去,

x_2 = 1
x_2 = 1

,否则

x_2 = 0
x_2 = 0

x_3
x_3

也类似。

这三个对于可能对你来说,“电影好不好看”对你来说最重要,而天气显得不是那么的重要。所以你会这样分配权值:

w_2=2,w_2=3,w_3=5
w_2=2,w_2=3,w_3=5

,然后定义阈值threshold=5。

现在,你可以使⽤感知器来给这种决策建⽴数学模型。

例如:

x_1=0,x_2=0,x_3=1
x_1=0,x_2=0,x_3=1

,即今天天气不好,而且你的朋友不陪你去看电影,但是今天的电影很好看,最终可以看到

0\times 2 + 0 \times 2 +1 \times 5 \leqslant 5
0\times 2 + 0 \times 2 +1 \times 5 \leqslant 5

,所以你的决定是不去看电影。

x_1=1,x_2=0,x_3=1
x_1=1,x_2=0,x_3=1

,即今天天气很好,你的朋友不陪你去看电影,但是今天的电影很好看,最终可以看到

1\times 2 + 0 \times 2 +1 \times 5 > 5
1\times 2 + 0 \times 2 +1 \times 5 > 5

,所以即使你的朋友不去,但是今天天气很好而且电影也很好看,你决定去看电影。

随着权重和阈值的变化,你可以得到不同的决策模型。很明显,感知机不是⼈做出决策使⽤的全部模型。但是这个例⼦说明了⼀个感知机如何能权衡不同的依据来决策。这看上去也可以⼤致解释⼀个感知机⽹络有时确实能够做出一些不错的决定。

现在我们队上面的结构做一点变化,令b=-threshold,即把阈值移到不等号左边,变成偏置, 那么感知器的规则可以重写为:

引⼊偏置只是我们描述感知器的⼀个很⼩的变动,但是我们后⾯会看到它引导更进⼀步的符号简化。因此,我们不再⽤阈值,⽽总是使⽤偏置。

感知机是首个可以学习的人工神经网络,它的出现引起的神经网络的第一层高潮。需要指出的是,感知机只能做简单的线性分类任务,而且Minsky在1969年出版的《Perceptron》书中,证明了感知机对XOR(异或)这样的问题都无法解决。但是感知机的提出,对神经网络的发展是具有重要意义的。

2 S型神经元

通过上面的感知机的观察我们发现一个问题,每个感知机的输出只有0和1,这就意味着有时我们只是在单个感知机上稍微修改了一点点权值w或者偏置b,就可能造成最终输出完全的反转。也就是说,感知机的输出是一个阶跃函数。如下图所示,在0附近的时候,输出的变化是非常明显的,而在远离0的地方,我们可能调整好久参数也不会发生输出的变化。

这样阶跃的跳变并不是我们想要的,我们需要的是当我们队权值w或者偏置b做出微小的调整后,输出也相应的发生微小的改变。这同时也意味值我们的输出不再只是0和1,还可以输出小数。由此我们引入了S型神经元。

S型神经元使用 S 型函数,也叫Sigmoid function函数,我们用它作为激活函数。其表达式如下:

图像如下图所示:

利⽤实际的 σ 函数,我们得到⼀个,就像上⾯说明的,平滑的感知器。 σ 函数的平滑特性,正是关键因素,⽽不是其细部形式。 σ 的平滑意味着权重和偏置的微⼩变化,即 ∆w 和 ∆b,会从神经元产⽣⼀个微⼩的输出变化 ∆output。实际上,微积分告诉我们

∆output 可以很好地近似表⽰为:

上面的式子是⼀个反映权重、偏置变化和输出变化的线性函数。这⼀线性使得我们可以通过选择权重和偏置的微⼩变化来达到输出的微⼩变化。所以当 S 型神经元和感知器本质上是相同的,但S型神经元在计算处理如何变化权重和偏置来使输出变化的时候会更加容易。

3 神经网络的结构

有了对S型神经元的了解,我们就可以介绍神经网络的基本结构了。具体如下:

在⽹络中最左边的称为输⼊层,其中的神经元称为输⼊神经元。最右边的,即输出层包含有输出神经元,在图中,输出层只有⼀个神经元。中间层,既然这层中的神经元既不是输⼊也不是输出,则被称为隐藏层。

这就是神经网络的基本结构,随着后面的发展神经网络的层数也随之不断增加和复杂。

我们回顾一下神经网络发展的历程。神经网络的发展历史曲折荡漾,既有被人捧上天的时刻,也有摔落在街头无人问津的时段,中间经历了数次大起大落。

从单层神经网络(感知机)开始,到包含一个隐藏层的两层神经网络,再到多层的深度神经网络,一共有三次兴起过程。详见下图。

4 梯度下降算法

我们希望有⼀个算法,能让我们找到权重和偏置,以⾄于⽹络的输出 y(x) 能够拟合所有的 训练输⼊ x。为了量化我们如何实现这个⽬标,我们定义⼀个代价函数:

这⾥ w 表⽰所有的⽹络中权重的集合, b 是所有的偏置, n 是训练输⼊数据的个数, a 是表⽰当输⼊为 x 时输出的向量,求和则是在总的训练输⼊ x 上进⾏的。当然,输出 a 取决于 x, w和 b,但是为了保持符号的简洁性,我没有明确地指出这种依赖关系。符号 ∥v∥ 是指向量 v 的模。我们把 C 称为⼆次代价函数;有时也称被称为均⽅误差或者 MSE。观察⼆次代价函数的形式我们可以看到 C(w, b) 是⾮负的,因为求和公式中的每⼀项都是⾮负的。此外,代价函数 C(w,b)的值相当⼩,即 C(w; b) ≈ 0,精确地说,是当对于所有的训练输⼊ x, y(x) 接近于输出 a 时。因

此如果我们的学习算法能找到合适的权重和偏置,使得 C(w; b) ≈ 0,它就能很好地⼯作。相反,当 C(w; b) 很⼤时就不怎么好了,那意味着对于⼤量地输⼊, y(x) 与输出 a 相差很⼤。因此我们的训练算法的⽬的,是最⼩化权重和偏置的代价函数 C(w; b)。换句话说,我们想要找到⼀系列能让代价尽可能⼩的权重和偏置。我们将采⽤称为梯度下降的算法来达到这个⽬的。

下面我们将代价函数简化为C(v)。它可以是任意的多元实值函数,

v=v_1,v_2,.....v_n
v=v_1,v_2,.....v_n

。 注意我们⽤ v 代替了 w 和 b 以强调它可能是任意的函数,我们现在先不局限于神经⽹络的环境。

为了使问题更加简单我们先考虑两个变量的情况,想象 C 是⼀个只有两个变量

v_1
v_1

v_2
v_2

的函数,我们的目的是找到

v_1
v_1

v_2
v_2

使得C最小。

如上图所示,我们的目的就是找到局部最小值。对于这样的一个问题,一种方法就是通过微积分的方法来解决,我们可以通过计算导数来求解C的极值点。但是对于神经网络来说,我们往往面对的是非常道的权值和偏置,也就是说v的维数不只是两维,有可能是亿万维的。对于一个高维的函数C(v)求导数几乎是不可能的。

在这种情况下,有人提出了一个有趣的算法。想象一下一个小球从山顶滚下山谷的过程, 我们的⽇常经验告诉我们这个球最终会滚到⾕底。我们先暂时忽略相关的物理定理, 对球体的⾁眼观察是为了激发我们的想象⽽不是束缚我们的思维。因此与其陷进物理学⾥凌乱的细节,不如我们就这样问⾃⼰:如果我们扮演⼀天的上帝,能够构造⾃⼰的物理定律,能够⽀配球体可以如何滚动,那么我们将会采取什么样的运动学定律来让球体能够总是滚落到⾕底呢?

为了更精确地描述这个问题,让我们思考⼀下,当我们在

v_1
v_1

v_2
v_2

⽅向分别将球体移动⼀个很⼩的量,即 ∆

v_1
v_1

和 ∆

v_2
v_2

时,球体将会发⽣什么情况。微积分告诉我们 C 将会有如下变化:

也可以用向量表示为

现在我们的问题就转换为不断寻找一个小于0的∆C,使得C+∆C不断变小。

假设我们选取:

这⾥的 η 是个很⼩的正数(称为学习速率),于是

由于 ∥∇C∥2 ≥ 0,这保证了 ∆C ≤ 0,即,如果我们按照上述⽅程的规则去改变 v,那么 C 会⼀直减⼩,不会增加。

所以我们可以通过不断改变v来C的值不断下降,是小球滚到最低点。

总结⼀下,梯度下降算法⼯作的⽅式就是重复计算梯度 ∇C,然后沿着相反的⽅向移动,沿着⼭⾕“滚落”。我们可以想象它像这样:

为了使梯度下降能够正确地运⾏,我们需要选择合适的学习速率η,确保C不断减少,直到找到最小值。

知道了两个变量的函数 C 的梯度下降方法,我们可以很容易的把它推广到多维。我们假设 C 是⼀个有 m 个变量

v_1,v_2,....v_m
v_1,v_2,....v_m

的多元函数。 ∆C 将会变为:

其中, ∇C为

∆v为:

更新规则为:

在回到神经网络中,w和b的更新规则为:

5 反向传播算法

前面提到神经⽹络如何使⽤梯度下降算法来学习他们⾃⾝的权重和偏置。但是,这⾥还留下了⼀个问题:我们并没有讨论如何计算代价函数的梯度。这里就需要用到一个非常重要的算法:反向传播算法(backpropagation)。

反向传播算法的启示是数学中的链式法则。

四个方程:

输出层误差方程:

当前层误差方程:

误差方程关于偏置的关系:

误差方程关于权值的关系

算法描述:

检视这个算法,你可以看到为何它被称作反向传播。我们从最后⼀层开始向后计算误差向量δ。这看起来有点奇怪,为何要从后⾯开始。但是如果你认真思考反向传播的证明,这种反向移动其实是代价函数是⽹络输出的函数的结果。为了理解代价随前⾯层的权重和偏置变化的规律,我们需要重复作⽤链式法则,反向地获得需要的表达式。

代码语言:javascript
复制
#coding=utf-8
"""
#coding=utf-8

import random
import numpy as np

class Network(object):

    def __init__(self, sizes):
       

        """列表siezes包含神经网络各层的数量,比如列表为[2,3,1],那么就是3层神经网络,
        其中第一层包含2个神经元,第二层包含3个神经元,第三层包含1个神经元。
        biases和weights是网络随机初试化的,使用均值为0,方差为1的高斯分布。
        注意:这里的第1层假设的是输入层,通常这一层的神经元我们是不设置biases和weights的,
        因为biases是用来计算后面几层的输出的"""
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]  #输入层不设biases
        self.weights = [np.random.randn(y, x)
                        for x, y in zip(sizes[:-1], sizes[1:])]
        """
        >>> sizes = [2,3,1]
        >>> sizes[:-1]
        [2, 3]
        >>> sizes[1:]
        [3, 1]
        >>> zip(sizes[:-1], sizes[1:])
        [(2, 3), (3, 1)]
        >>> [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
        [array([[-0.16282195, -0.31576039],
            [ 0.04294583,  1.54797799],
            [ 0.56115799, -0.61884929]]), array([[-0.51401074,  0.1096134 ,  1.01849354]])]
        >>>

        """
    # 前向传播
    def feedforward(self, a):
        """Return the output of the network if ``a`` is input."""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a)+b)
        return a

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):


        """训练神经网络使用小批量的随机梯度下降算法(SGD),training_data是一个元组(x,y)
        ,代表训练的输入,和期望的输出。如果提供了training_data,在每一轮的训练中,将会打印输出
        的结果,这对训练是有用的,但是也会使训练变慢。

        epochs: 迭代期数量,每一次迭代都会喂完所有的数据,不断更新w和b
        mini_batch_size:采样时的⼩批量数据的⼤⼩,将training_data按大小为mini_batch_size分为若干份,依次喂入神经网络。
        eta:学习率
        """
        if test_data: n_test = len(test_data)
        n = len(training_data)
        for j in xrange(epochs):
            random.shuffle(training_data) # 随机打乱
            mini_batches = [
                training_data[k:k+mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]  # xrange 从0到n,每次间隔mini_batch_size
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print "Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

    def update_mini_batch(self, mini_batch, eta):

        """通过反向传播的梯度下降算法更新biases和weights"""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]

    def backprop(self, x, y):

        """
        返回偏导数
        """

        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # feedforward
        activation = x
        activations = [x] # list to store all the activations, layer by layer
        zs = [] # list to store all the z vectors, layer by layer
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # backward pass
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())

        for l in xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())

        return (nabla_b, nabla_w)

    def evaluate(self, test_data):
        """Return the number of test inputs for which the neural
        network outputs the correct result. Note that the neural
        network's output is assumed to be the index of whichever
        neuron in the final layer has the highest activation."""
        test_results = [(np.argmax(self.feedforward(x)), y)
                        for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    def cost_derivative(self, output_activations, y):
        """Return the vector of partial derivatives \partial C_x /
        \partial a for the output activations."""
        return (output_activations-y)

#### Miscellaneous functions
def sigmoid(z):
    """The sigmoid function."""
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z)*(1-sigmoid(z))

参考链接:http://neuralnetworksanddeeplearning.com/

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.01.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 感知机
    • 举例:今天下午去不去看电影?
    • 2 S型神经元
    • 3 神经网络的结构
    • 4 梯度下降算法
    • 5 反向传播算法
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档