使用python实现深度神经网络|使用python实现深度神经网络 1
“`
深度学习基本概念
一、实验介绍
1.1 实验内容
深度学习
并没有你想象的那么难,本课程将会一边讲解深度学习中的基本理论,一边通过动手使用python实现一个简单的深度神经网络去验证这些理论,让你从原理上真正入门深度学习。
本次实验将会带大家学习深度学习
中的一些最基本的概念,本次实验很重要,理解这些概念是继续深入学习的基础。
1.2 实验知识点
- 如何让机器“学习”
- 神经网络的概念
- 有监督与无监督学习的区别
- 回归与分类的区别
- 损失函数的概念
- 梯度下降算法介绍
- 超参数的概念
本实验要求你在实验前具备以下基本能力:
- 熟练使用python
- 具备高中毕业生的数学水平
- 对深度学习的兴趣
- 函数
导数
的概念及求导链式法则
学习资料 矩阵
的概念及其基本运算规则 学习资料- python
numpy
科学计算库的使用 学习资料
- python 2.7
- numpy 1.12.1
如果你只是听说过
机器学习
或者深度学习
,你可能会被他们的名字吓到,机器真的已经能像人一样“学习”了吗?其实,这里的“学习”和人的学习的确有一些相似的地方,但又有很大不同。为了理解这里的“学习”究竟是什么含义,让我们先来回想一下以前我们是如何让机器工作的,我们会以编程计算圆的面积为例。2.1.1 计算圆的面积–手工编程的方法 计算圆的面积显然是一个很简单的问题,任何程序员应该都能立马写出类似以下的函数来进行计算:
- # 代码片段 1
- # 计算圆面积的旧办法
- def calArea(radius):
- area= 3.14*radius*radius
- return area
在编写这段代码的过程中,其实我们做的事情很简单。因为我们事先知道输入和输出之间的关系(输出等于3.14乘以输入的平方,高中毕业生应该都能轻易得出这个结论吧),所以我们直接将这种关系通过
area=3.14*radius*radisu
这个式子表达出来就行了。注意我们设置了一个参数:圆周率3.14。2.1.2 计算圆的面积–让机器“学习” 现在让你看看如何让机器通过“学习”来计算圆的面积:
- # 代码片段 2
- def learn(model,data):
- ... # 通过数据data训练模型model的代码
- return model # 已经训练好参数的模型
- def test(model,x):
- ... # 将x输入模型model中得出结果y的代码
- return y # 返回计算结果--圆的面积
- trained_model=learn(init_model,data) # 通过训练得到模型trained_model
- y=test(trained_model,x= 100) # 得到半径为100的圆的面积
其实让机器“学习”很简单不是吗(至少上面的代码看上去是如此^^),我们提供给机器足够的训练数据data,比如下面这样:
x | y |
---|---|
1 | 3.14 |
1.4 | 6.15 |
2 | 12.56 |
10 | 314 |
20 | 1256 |
… | … |
learn
函数(我们暂时不知道如何实现这个函数,之后的实验我们会逐步实现这个函数)使用这些数据去训练模型model
(我们接下来会介绍如何构建这个model),当我们需要计算一个新的圆的面积时,就调用test
函数,根据训练好的模型去计算其面积。注意这里我们不需要事先知道输入和输出之间的关系,也不需要手动设置任何参数,只要把数据“喂给”学习算法
learn
,它就会自动得出一个能够解决问题的模型。也就是说,在训练过程中,模型model其实是“学习”到了输入和输出之前的关系以及相关的参数。就像是一个数学家通过对圆的观察,得出了圆的面积和半径的关系,并且求出了圆周率。
2.1.3 机器学习–激动人心的未来 这太令人激动了不是吗,如果我们能找到一个完美的学习算法learn和合适的模型model,并且提供给它足够的学习数据data,那这个世界上几乎所有问题都可以用这个学习算法来解决。遗憾的是,目前我们的学习算法、学习模型还不够完美,我们的学习数据仍然不够,能够通过
机器学习
(一般认为深度学习
包含在机器学习
当中)解决的问题还很有限。不过目前取得的很多成果已经非常激动人心了,比如让人类“重新认识围棋”的AlphaGO
、准确率已经接近甚至达到人类水准的语音识别和机器翻译、已经能够上路的自动驾驶汽车。总之,
机器学习
将会彻底改变机器为我们服务的方式,我们将越来越少的通过编程明确地告诉计算机如何去解决一个问题,而是让计算机自己学习如何解决问题。2.1.4 让机器“学习”的必要性 & 实验项目的引入 上面的例子太过简单,无法体现出让机器学习的必要性,要计算圆的面积,更快的方法显然是直接手工编写代码。
但是现实世界中的很多问题是手工编写代码几乎无法解决的。比如将会贯穿本课程的一个小项目:机器实现图片英文字母识别,你几乎无法找到一个能手工编写出的算法去让程序能够从下面这样的图片中识别出英文字母:
为什么这个问题难以手工编程解决?一种思考的角度是,对于上图这样22*55尺寸的图片,如果将它看成是一个向量,向量中的每一个数值对应图片中的一个像素灰度值,那这个向量的维度(长度)就达到了22*55=1210,相对于计算圆面积问题中长度为1的输入向量,这个向量维度非常高,存在于1210维的向量空间当中。高维度带来的高复杂度使得实际上无法使用手工编程解决这个问题。只有通过让机器自己“学习”,才有可能找到解决办法。
2.2 几个基本概念
为了接下来的讲解方便,这里先告诉大家几个基本概念。
2.2.1回归问题与分类问题
回归(regression)
与分类(classification)
是机器学习中的两大类问题。上面我们举的计算圆形面积的例子就属于回归
问题,即我们的目的是对于一个输入x,预测其输出值y,且这个y值是根据x连续变化的值。分类
问题则是事先给定若干个类别,对于一个输入x,判断其属于哪个类别,即输出一般是离散的,比如本课程将会带大家实践的图片英文字母识别就属于分类问题(判断一个图片中包含的字符属于26个类别中的哪一个)。本课程主要介绍分类问题,回归问题可能会在后续的课程中向大家介绍。
2.2.2 有监督学习和无监督学习
有监督
与无监督
是机器学习方法的两大分类。上面我们举的计算圆形面积的例子就属于有监督学习,因为我们的输入data
既包含输入x
,又包含x
对应的y
,即学习数据已经事先给出了正确答案
。无监督学习则是只有输入
x
。你可能会感到不可思议,正确答案都不告诉我,我要怎么学习呢?确实,无监督学习要更难。无监督学习目前一般用于聚类(cluster)问题
,即给定一批数据,根据这批数据的特点,将其分为多个类别,虽然我并不知道这每个类别所代表的具体含义。比如网络商城的商品推荐算法可能会根据用户的使用习惯,以往的浏览历史等,将用户分为多个类别,同一类别的用户在行为模式上可能比较相似。而事先并不知道最终会划分出多少个类别,每个类别有哪些共同特点。本课程主要介绍有监督学习,无监督学习可能会在后续课程中向大家介绍。
2.3 模型的构建–神经网络
上面我们提到过要让机器“学习”,一般需要:
- 用来解决问题的模型
model
- 学习数据(或者说训练数据)
data
- 让模型
model
通过数据data
学会解决特定问题的学习算法learn
深度学习
中一种非常重要的模型–神经网络(neural network)
。2.3.1 与生物大脑的类比 一些人喜欢将生物大脑神经元网络的结构看做是
神经网络
的灵感来源。大脑神经元的结构如下:我们主要关注细胞体、树突、轴突。我们看到,树突作为接收端,接收从其他神经元传导过来的神经冲动,而轴突再将经过细胞体“处理”的神经冲动传递给其他神经元。
而
神经网络
的结构如下:你会发现这张图片和上图大脑神经元的图片有很多相似之处,只不过神经冲动在这里变成了具体的数值。
图中的圆圈代表一个个
神经元(neuron)
,其中网络层1
(输入层)中的每个神经元都与网络层2
中的每个神经元相连。同时我们注意到,神经元间相连的线上有该条连接线的权重w
,神经网络工作时,将前一层网络中的每个神经元的值,与连接线上的权重w
相乘,传递给下一层网络中的神经元。同时,每个神经元还会接收一个偏移量(bias)
。即在该图中,有:
- w11 *a1+w12*a2+w13*a3 + bias1=b1
- w21 *a1+w22*a2+w23*a3 + bias2=b2
如果写成矩阵的形式(如果你不知道矩阵运算法则,可以先跳过这里,之后的实验会有讲解),就是:
如果你对线性代数比较熟悉,你会发现这就是一个线性方程组而已,没错,线性代数是
神经网络
的重要理论基础之一,线性代数与神经网络的联系更密切直接。但是神经网络中不止有
线性运算
。对于网络层2
中的神经元b
,它的值在传给输出
之前,会先经过一次非线性运算
g(图中没有画出来)。即在该图中,有:
- g(b1)= Y1
- g ( b2 )=Y2
注:如果你不知道2.3.2 非线性激活函数 神经网络中,将上面提到的非线性运算g称为线性运算
和非线性运算
的区别,请自行查阅资料了解。
激活函数
。实际运用当中,有多种激活函数可以选择,这里我们介绍最经典的一种激活函数:
sigmoid
激活函数,它的数学形式为:sigmoid
函数的图像是这样的:即输入值
x
越大,函数值越接近1,输入值越小,函数值越接近0,当输入为0的时候,函数值为0.5。也许你会觉得奇怪,为什么我们需要这样一个复杂(但以后你会发现其实它的形式很简单优美)的函数来对网络层2的输出值再进行一遍运算呢?
有多种理由要求我们必须向神经网络中引入非线性运算部分,最简单的原因之一就是,对于如下图所示的多层神经网络(即
深度神经网络
),如果没有非线性运算部分,那么由于矩阵乘法的结合性
(这里又一次涉及到了矩阵的运算性质,如果你不熟悉又想搞清楚这里,请先看看第二次实验),多个线性运算层直接相连的效果实际上相当于只进行了一次线性运算,多层次的网络结构失去了意义。* 深度神经网络示例,图中非关键部分被略去。
* 如果没有非线性激活函数,则其中网路层1 与 网络层2 之间的尺寸为2x3的矩阵和网络层2 与 网络层3 之间的尺寸为3x2的变换矩阵由于矩阵乘法的结合性实际上会合并成为单独的一个3x3的矩阵(请思考这里为什么是3x3而不是2x2)。
第二个原因解释起来可能复杂一点,在解释第二个原因之前,让我们先看一看如果只有线性矩阵运算部分,我们的神经网络实际上在做些什么。
以神经网络示例图中的b1为例,假如我们将其用于分类问题,同时只考虑有两个输入变量的情况,即:
w11*a1+w12*a2 + bias1=b1
对于输入
a
,当最终得到的b1
大于0时,我们认为a
属于b1
所代表的的类别,否则不属于b1
所代表的的类别。对于式子
w11*a1+w12*a2 + bias1=b1
,其实你对他非常熟悉,如果在坐标轴上画出他的函数图形,就是这样的一条直线:对于图中红色的点,其对应的b1值要大于0,可以认为其属于b1所代表的类别,而绿色的点对应的b1值小于0,故它们不属于b1所代表的类别。
当输入数据的维度为3时,单纯的线性运算就相当于是使用一个平面对空间进行划分,当数据维度高于3维时类似,只是我们已经无法直观的观察大于3维的空间了。
也许你马上就会发现一个问题,如果我们要分类的数据分布是下面这样的该怎么办:
不同类别之间的分界线不是直线而是曲线,此时无论如何也无法用线性分类器去准确的进行分类。
而非线性部分的引入,在一定程度上可以使原本的
直线
变成曲线
,如下图:这样原本不可分的问题就变得可分了。
非线性激活函数还有其他作用,比如
sigmoid
将输出值的范围限制在0到1之间,使其刚好可以被作为概率来看待。2.4 评价神经网络的效果
2.4.1 使用随机值初始化神经网络参数 上一节中,我们只向大家介绍了神经网络的结构,我们现在还不知道如何设定神经网络中的网络参数值(包括连接线上的权重
w
和偏置值bias
)。一种最简单的方法是随机设定网络参数。如果神经网络中的网络参数是随机设置的,那么我们得到的“网络性能”应该就是随机情况下的平均值。比如我们要判断一些图片中的字母属于26个字母中的哪一个,如果网络参数值是随机设定的话,那我们得到的识别准确率理论上应该在1/26左右。随机设定参数值当然不可能是我们解决问题的办法,但要学习如何设置“好”的网络参数值来使神经网络正确的工作,我们要先研究一下上面提到的“网络性能”的问题。
我们如何判定一个神经网络是“好”的还是“不好”的呢,即我们用什么来评价一个神经网络的效果呢?
2.4.2 损失函数的概念 对于
有监督学习
,由于我们的学习数据data
中包含输入数据的预期正确输出值,一个简单的办法就是比较神经网络的输出h
与预期的正确输出y
之间的差异。比如计算(h-y)^2
,当得到的值比较大时,就说明我们的神经网络的输出与预期的正确输出偏差较大,反之,如果得到的值很小甚至等于0,就说明我们的模型工作的不错,能够正确的预测输出值。这就引出了
损失函数(cost function)
的概念,实际当中同样有多种损失函数可供选择,这里我们来介绍最经典的一种损失函数:二次损失函数(quadratic loss function)
。2.4.3 二次损失函数 quadratic loss 就像它的名字所暗示的那样,
quadratic loss function
通过计算h
和y
之间差值的二次方,来表达一个神经网络的效果。具体形式如下:这个公式几乎就是我们在上面提到的对模型的误差求平方,只是稍微再复杂一点。其中的希腊字母
theta
代表网络中的所有参数,X
代表向神经网络输入的数据,Y
代表输入数据对应的预期正确输出值。不知你是否注意到,这个公式有些奇怪,因为一般我们都是把变量名写入函数名后面的括号里,代表这个函数的自变量有哪些,可这里的网络参数
theta
怎么跑进去了,而训练数据X
和Y
却不在里面。这个地方有些微妙,而且非常重要。实际上,在这里
theta
才是自变量,因为我们一开始不知道让网络能够正确的工作的参数值是多少。我们的学习算法learn
按照某种策略,通过不断的更新参数值来使损失函数J(theta,X,Y)
的值减小。在这里,theta
是不断变化的量。那
X
和Y
就不是自变量了吗?对,它们确实不是自变量!因为对于我们要解决的一个具体的问题(比如图片字母识别),其训练数据中包含的信息其实代表的是这个问题本身的性质,我们的学习算法learn
其实最终就是要从训练数据data
中学习到这些性质,并通过网络模型model
的结构和其中的参数把这些性质表达出来。对于一个特定的问题来说,可以认为它们是常量!这里比较容易搞混,请仔细思考一下
theta
和X
Y
在这里所代表的意义。2.5 梯度下降算法
上一节我们介绍了衡量神经网络性能的办法:
损失函数
,那为了让我们的神经网络能够准确的识别图片中的字母,就必须想办法让损失函数的值尽量小,最好是让损失函数值为0。那么如何实现这个目标呢?这里就要佩服数学家的智慧(但是放心,这里不会涉及很高深的数学知识)。很久以前就有数学家发明了
梯度下降算法
来帮助我们减小损失函数的值。假设我们的损失函数图形是这样的:
我们随机初始化的网络参数在
A
这个位置。显然,我们的目标是使A
尽量靠近最低点B
,最好是与B
重合,这样才能最小化损失函数。这时,就必须介绍一下梯度
的概念,如果你还记得高等数学中梯度的概念,可以直接跳过下一段。上图这种,函数图形向下突出的函数被称为“凸函数”(注意这里的叫法,“凸”是指向下凸)。2.5.1 什么是“梯度” 梯度本身是一个向量,由损失函数对每个自变量(图中的theta0和theta1)分别求偏导(如果你不知道什么是偏导,可以先看看第二次试验中的相关讲解)得到。
形象的理解,梯度指向损失函数变化最快的那个方向(且该方向让函数值变大),对于上图中三维的情形,就是曲面上在A点最“陡峭”的方向。对于二维的情形,其实就是斜率方向。
2.5.2 梯度下降算法 那为了使A点向B点移动,就可以对A的值减去(请思考这里为什么是减去)该点的梯度。通过公式来表达,就是:
注意这里多了一个希腊字母
alpha
,因为梯度只为我们指明了更新参数theta
的方向,而我们具体朝着这个方向“走多远”则由alpha
控制。当走到下一个位置之后,再求该点的梯度,用梯度更新参数位置,如此重复,直到逼近B点。这就是梯度下降算法的原理
这里你可能会有疑问,如果我们的损失函数不是凸函数怎么办,那样我们的梯度下降算法就可能无法达到最低点。或者我们可能被“局部最低点”(梯度为0)欺骗而无法达到“全局最低点”。的确,这看起来是一个严重的问题,但实际当中,这个问题并没有那么突出,我们有一些办法来防止被“局部最低点”欺骗,后面的课程可能会讲到。2.6 参数(parameter)与超参数(hyper parameter)
注:alpha 在这里是一个 超参数(hyperparameter)
2.6.1 区别 如果你足够仔细,你会发现在上面的描述中,我们将模型
model
中的参数theta
称为参数
,而学习算法(即梯度下降算法)里的参数alhpa
被称为超参数
。这两个地方的“参数”为什么叫法不一样呢?叫法不同,是因为它们的作用及我们设置它们值的方式不一样。
theta
被称为“参数”,是因为theta
决定了我们的模型model
的性质,并且theta
的最终值是由我们的学习算法learn
学习得到的,不需要我们手工设定。而
alpha
则不同,在这里alpha
并不是模型model
中的参数,而是在学习算法learn
中决定我们的梯度下降算法
每一步走多远的参数,它需要我们手工设定,且决定了得到最优theta
的过程。即超参数
决定如何得到最优参数
。看到这里你也许会说,这不还是要手动设置参数吗?虽然这里的超参数仍然需要我们手工设定,但模型中的数量巨大(对于复杂问题,模型中可能有上亿个参数!)的参数已经不需要我们来设定。这已经大大的减轻了手动设置参数的负担。所以说机器学习还是非常非常强大的。
2.6.1 超参数alpha–学习速率
机器学习
中,我们将上面提到的超参数alpha
称为学习速率(learning rate)
。这很好理解,因为alpha
越大时,我们的参数theta
更新的幅度越大,我们可能会更快的到达最低点。但是alpha
不能设置的太大,否则有可能一次变化的太大导致“步子太长”,会直接越过最低点,甚至导致损失函数值不降反升。如下图:所以,设置合理的学习速率
alpha
值非常重要。实际当中的做法一般是先设置alpha
为一个较小的值,比如0.001,观察每一次参数theta
更新后损失函数J
的值如何变化,如果J
变大了,就将alpha
的值除以10,变成0.0001,直到损失函数值开始变小。如果J
变小了,则可以将alpha
再乘以10,使得学习的速率更快。最终使alpha
停留在一个能够使J
变小,又不会因为值过小导致学得太慢的“临界值”。深度学习中还可能有其他几个超参数,我们会在后面的课程继续介绍2.7 遗留的问题
我们已经介绍了深度学习中的网络模型
model
、学习算法learn
,还顺便提了一下有监督学习下的训练(学习)数据data
。可以说,到这里,深度学习中几个最基本最重要的概念你已经清楚了。但是这里还留有一个严重的问题,上面我们提到梯度下降算法根据损失函数的梯度来更新模型参数,那么我们该如何求梯度呢?这个问题将留到我们第三次实验课程来解决,我门同时会开始编写代码(讲了这么多理论,终于可以写代码了)来实现梯度的求解。
第二次实验我们会讲解深度学习必须要用到的一些基本数学知识以及三、实验总结 作为第一次实验,我们讲到的概念和基础理论比较多,你可能需要较多的时间才能消化。如果你暂时无法理解其中的一些概念,不要气馁,请回去重新看一下,思考一下。当后面再用到这些东西而你觉得映像模糊时,也请重新回过头来看一下。只要你能理解这些基本的东西,可以说,后面的学习都会势如破竹。你将从此走进深度学习的世界!numpy
科学计算库的用法。
本次实验,我们学到的知识有:
- 机器学习(包括深度学习)利用训练数据
data
,使用学习算法learn
对模型model
中包含的参数进行更新。使得评价模型效果的损失函数
不断减小并达到最优值。 - 深度学习中的模型一般是指
(深度)神经网络
,神经网络
由多个网络层构成,层与层之间通过连接来传递信息。 - 神经网络中一般包含线性运算和非线性运算(激活函数)部分,两个线性运算部分之间必须存在非线性部分,否则这两个线性部分会合并成一个线性部分。
- 损失函数用来描述一个神经网络模型的性能,损失函数值越小模型性能越好。
- 梯度下降算法可以通过多次迭代更新模型参数值来使损失函数值减小。梯度下降算法的关键是求损失函数对于网络参数的梯度。
- 机器学习(包括深度学习)分为有监督学习和无监督学习,前者的训练数据既包括输入,也包括输入对应的正确输出(标签
label
)。后者的训练数据不包含正确输出。 - 机器学习解决的问题一般可以分为回归问题、分类问题、聚类问题这三种(实际上还有别的种类),本课程主要研究分类问题。
- 超参数是在训练模型的过程中,为了得到最优模型参数而手工设置的一类参数。设置合理的超参数值,对模型参数的优化十分重要。
- “手工编程计算圆的面积”中,3.14是“参数”还是“超参数”?
- 一个如本次实验所描述的神经网络有两层,第一层有10个神经元,第二层有20个神经元,请问这两层神经元之间有多少条连接?第一层到第二层之间由这些连接所表示的线性变换矩阵尺寸是多少?
- “使用深度学习预测股票走势曲线”是分类问题还是回归问题?
- “根据人脸图片识别人的性别”是分类问题还是回归问题?
- 如果说,有监督学习的训练数据
data
由输入X
和正确答案Y
组成,那无监督学习的训练数据应该是什么样的? - [选做题]sigmoid函数对x求导结果是什么?
- [选做题]2.6.1小节中的图片里,为什么“学习速率适中”图中每次更新的“步子”越来越短,而“学习速率过大”图中每次更新的“步子”越来越长?
- [选做题]你能自己想出一种求损失函数梯度的方法吗?
【使用python实现深度神经网络|使用python实现深度神经网络 1】“
推荐阅读
- 动手学深度学习(pytorch|动手学深度学习(四)——用numpy和torch实现单层神经网络对比)
- 计算机视觉|OpenCV的简单使用教程与基本函数(C++版本)
- Python从零到壹|[Python从零到壹] 四十三.图像增强及运算篇之图像点运算和图像灰度化处理
- Material|Material Design 之 Behavior的使用和自定义Behavior
- java|java 调度_几种任务调度的Java实现方法与比较
- 程序员|Meterial Design常见控件的使用(四),android开发实战项目
- Android|安卓 实现网易云音乐底部播放栏效果之使用自定义BaseActivity实现 下
- Unity3D|【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)
- Unity3D|《学Unity的猫》——第十三章(Unity使用Animator控制动画播放,皮皮猫打字机游戏)
- Unity3D|《学Unity的猫》——第十六集(Unity动画使用混合树BlendTree实现动画过渡控制)