Python神经网络项目实战
上QQ阅读APP看书,第一时间看更新

神经网络是一类机器学习算法,该算法受到了人类大脑中神经元的启发。不过,我们没必要将其完全类比于人类大脑,我发现把神经网络用数学方程描述为将给定输入映射到期望输出,理解起来会更简单。为了理解上述问题,让我们看看单层神经网络是什么样的(单层神经网络也被称为感知器)。

感知器(perceptron)如图1-4所示。

图1-4

它的核心就是一个数学函数,接收一组输入,然后进行某种数学运算,然后将计算结果输出。

指的是感知器的权重。我们会在后面的章节中介绍神经网络中的权重。目前我们只需要知道神经网络就是一些简单的数学函数,它们将给定的输入映射为期望的输出。

在开始编写神经网络之前,有必要了解一下神经网络算法能够在机器学习和人工智能领域具有举足轻重的地位的原因。

第一个原因,神经网络是一种通用函数逼近器(universal function approximator)。这句话的意思是,给定任意我们希望建模的函数,不论该函数多么复杂,神经网络总是能够表示该函数。这一特性对神经网络和人工智能具有深远的影响。假设现实中的任何问题都可以被数学函数所表示(不论其多么复杂),那么我们都可以用神经网络来表示该函数,从而有效地对现实世界的问题进行建模。需要补充一点的是,尽管科学家们已经证明了神经网络的通用性,但是一个超大且复杂的神经网络可能永远都无法完成训练及泛化。

第二个原因,神经网络结构的可扩展性非常好而且很灵活。在后续章节中会看到,我们可以将神经网络堆叠起来,以此来增加神经网络的复杂性。更有趣的可能是,神经网络的能力仅仅局限于我们的想象力。通过富有创造性的神经网络结构设计,机器学习工程师已经学会了如何利用神经网络预测时间序列数据(这个模型被称为RNN),它被应用于语音识别等领域。最近几年,科学家还展示了通过让两个神经网络在竞赛中互相对抗[称为生成对抗网络(generative adversarial network,GAN)],来生成人眼无法辨别的写实图像。

在本节中,我们会研究神经网络的基础结构,所有复杂的神经网络都是基于此构建的。同时,我们也会使用Python开始构建最基础的神经网络(不使用任何机器学习函数库)。这一练习会帮助我们理解神经网络的内部工作原理。

神经网络包含如下组成部分:

一个输入层x

一定数量的隐藏层;

一个输出层ŷ

每一层之间包含权重W和偏差b

为每个隐藏层所选择的激活函数σ

图1-5所示的为一个两层神经网络的结构(注意,在统计神经网络层数的时候,输入层通常不被计算在内)。

图1-5

现在我们已经了解了神经网络的基本结构,让我们使用Python从头创建一个神经网络吧!

首先,在Python中创建一个神经网络的类:

import numpy as np
class NeuralNetwork:
    def__ init__(self, x, y):
       self.input    = x
       self.weights1 = np.random.rand(self.input.shape[1],4) 
       self.weights2 = np.random.rand(4,1)
       self.y        = y
self.output = np.zeros(self.y.shape)

注意前述代码,权重(self.weights1和self.weights2)被初始化为一个包含随机数的NumPy数组。NumPy数组被用来表示Python中的多维数组。上述代码中权重的维度是通过np.random.rand函数的参数来设定的。基于输入的维度,使用变量(self.input.shape[1])创建了对应维度的数组。

一个简单的两层神经网络的输出:ŷ,表述为如下形式:

你也许注意到了,在上述公式中,权重W以及偏差b是影响输出ŷ的唯一变量。

因此,正确的权重和偏差决定了预测的强度。对权重和偏差进行调优的过程被称为神经网络的训练。

迭代训练神经网络的每一个循环都包括以下步骤:

1.计算预测输出ŷ,被称为前馈(feedforward);

2.更新权重和偏差,被称为反向传播(backpropagation)。

图1-6对该步骤做出了解释。

图1-6

1.前馈

在图1-6中我们可以看到,前馈就是简单的计算。而对于一个基础的两层神经网络来说,网络的输出可以用下列公式表示:

下面,在Python代码中增加一个feedforward函数来完成上述功能。注意,为了降低难度,我们假设偏差为0:

import numpy as np
def sigmoid(x):
    return 1.0/(1 + np.exp(-x))
class NeuralNetwork:
    def__init__(self, x, y): 
       self.input    = x
       self.weights1 = np.random.rand(self.input.shape[1],4) 
       self.weights2 = np.random.rand(4,1)
       self.y        = y
       self.output   = np.zeros(self.y.shape)
    def feedforward(self):
       self.layer1 = sigmoid(np.dot(self.input, self.weights1))
self.output = sigmoid(np.dot(self.layer1, self.weights2))

然而,我们还需要找到一种方法来评估预测的准确率(预测偏差有多大)。损失函数(loss function)可以帮助我们完成这个工作。

2.损失函数

损失函数有很多种,它的选择需要根据待解决问题的本质来决定。就目前来讲,我们选择一个平方和误差(Sum-of-Squares Error)作为损失函数:

平方和误差就是对实际值和预测值之间的差值求和,不过我们对其进行了平方运算,因此计算结果是其绝对差值。

我们的目标是训练神经网络并找到能使得损失函数最小化的最优权重和偏差。

3.反向传播

现在已经计算出了预测结果的误差(损失),我们需要找到一种方法将误差在网络中反向传导以便更新权重和偏差。

为了找到合适的权重及偏差矫正量,我们需要知道损失函数关于权重及偏差的导数。

回忆一下微积分知识,一个函数的导数就是该函数的斜率,如图1-7所示。

图1-7

如果得到了导数,我们就可以根据导数,通过增加导数值或减少导数值的方式来调节权重和偏差(如图1-7所示)。这种方法称为梯度下降法(gradient descent)。

然而,我们不能直接求损失函数关于权重和偏差的导数,因为损失函数中并不包含它们。我们需要利用链式法则(chain rule)进行计算。就目前阶段来讲,我们不会深究链式法则,因为其背后的数学原理相当复杂。而且,像Keras等机器学习库会帮我们完成梯度下降计算而不需要从头编写链式法则。

我们需要理解的关键点是,一旦我们得到了损失函数关于权重的导数(斜率),我们便可以依此相应地调整权重。

现在,向代码添加backprop函数 :

import numpy as np
def sigmoid(x):
    return 1.0/(1 + np.exp(-x))
def sigmoid_derivative(x): 
    return x * (1.0 - x)
class NeuralNetwork:
    def __init__(self, x, y):
        self.input    = x
        self.weights1 = np.random.rand(self.input.shape[1],4)
        self.weights2 = np.random.rand(4,1)
        self.y        = y
        self.output = np.zeros(self.y.shape)
    def feedforward(self):
        self.layer1 = sigmoid(np.dot(self.input, self.weights1))
        self.output = sigmoid(np.dot(self.layer1, self.weights2))
    def backprop(self):
        # 使用链式法则来找到损失函数关于weights2和weights1的导数
        d_weights2 = np.dot(self.layer1.T, (2*(self.y - self.output) *
         sigmoid_derivative(self.output)))
         d_weights1 = np.dot(self.input.T, (np.dot(2*(self.y - self.output)
         * sigmoid_derivative(self.output), self.weights2.T) * sigmoid_
         derivative(self.layer1)))
         self.weights1 += d_weights1 
         self.weights2 += d_weights2
if __name__ == "__main__": 
    X = np.array([[0,0,1],
                  [0,1,1],
                  [1,0,1],
                  [1,1,1]])
    y = np.array([[0],[1],[1],[0]])
    nn = NeuralNetwork(X,y)
    for i in range(1500):
        nn.feedforward()
        nn.backprop()
print(nn.output)

注意上述代码,我们在feedforward函数中使用了一个sigmoid函数。sigmoid函数是一种激活函数,它将函数值压缩到0~1。这一特性很重要,因为对于二元预测问题,我们需要预测结果位于0~1。我们将在第2章中详细探讨sigmoid激活函数。

现在我们已经完成了具有前馈和反向传播功能的Python代码,让我们在以下案例中应用该神经网络,看看它效果如何。

表1-1包括了4个数据点,每个点包括3个输入变量(x1, x2x3)和一个目标变量(Y)。

表1-1

我们的神经网络需要学习到能够表示该函数的最理想权重。注意,如果我们想要通过观察的方式来确定这一组权重,可不是什么容易的事。

迭代训练神经网络1500次,看看发生了什么。如图1-8所示,从损失-迭代次数图可以清晰地看出,损失是单调递减到最小值的。这和我们之前讨论的梯度下降算法的描述是一致的。

让我们看一下神经网络经过1500次迭代训练后最终的预测(输出)结果,如表1-2所示。

表1-2

图1-8

成功了!我们的前馈和反向传播算法成功地训练了神经网络且预测值向真实值收敛。

注意预测值和真实值之间存在的微小差异。这是我们期望发生的,它可以防止模型的过拟合(overfitting)并使其能够更好地泛化以便处理新的数据。

现在已经知道了神经网络的内部原理,接下来会介绍Python的机器学习函数库,这些函数库在后续的章节中都会用到。如果你感到从头创建一个神经网络非常困难,请不必担心。在本书的后续部分,我们会使用机器学习库来极大地简化神经网络的构建和训练过程。

深度学习是什么?它和神经网络有什么区别?简单来讲,深度学习是一种机器学习算法,它使用多层神经网络进行学习(也被称为深网)。如果我们将一个单层感知器看作最简单的神经网络,那么深度神经网络则走向了复杂性的一个极端。

在深度神经网络(DNN)中,每一层学习到的信息的复杂度是不断增加的。例如,当训练一个用于进行面部识别的深度神经网络时,第一层用于检测脸部的轮廓,然后是识别轮廓(例如眼睛)的层,直到最后完成全部的脸部特征识别。

尽管感知器在20世纪50年代就产生了,但是深度学习一直到近几年才开始蓬勃发展。深度学习在过去一个世纪发展相对比较缓慢,很大程度上是由于缺少数据以及相应的计算能力。然而,在过去的几年中,深度学习成为了驱动机器学习的关键技术。今天,深度学习已经成为图像识别、自动驾驶、语音识别和游戏领域的首选算法。那么,过去几年究竟发生了什么呢?

近些年来,用于存储深度学习所需的海量数据的计算机存储设备变得经济实惠。如果你将数据存放在云端,存储数据的费用还可以变得更便宜,而且可以被世界各地的计算机集群访问。除了拥有能够消费得起的数据存储服务之外,数据也变得更加平民化。例如像ImageNet这样的网站,它们向机器学习研究人员提供了1400万张图像。数据已经不再是少数人才能拥有的商品了。

深度学习所需的计算能力同样变得更便宜也更强大。大多数的深度学习项目受益于图形处理单元(GPU),它非常擅长满足深度神经网络的计算需求。继续刚才关于平民化的话题,现在很多网站给深度学习爱好者提供免费的GPU处理资源。举例来说,Google Colab提供免费的Tesla K80 GPU云服务用于深度学习,每个人都可以使用。

基于这些近期的技术发展,深度学习已经成为了人人都能使用的技术。在后面的章节中,我们会介绍一些你将会用到的Python深度学习函数库。