神经网络
1.简介(什么是神经网络)
神经网络是一门重要的机器学习技术。它是目前最为火热的研究方向–深度学习的基础。学习神经网络不仅可以让你掌握一门强大的机器学习方法,同时也可以更好地帮助你理解深度学习技术。
人工神经网络(Artificial Neural Networks,简写为ANNs)也简称为神经网络(NNs)或称作连接模型(Connection Model),它是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。在工程与学术界也常直接简称为“神经网络”或类神经网络。
目前人工神经网络在模式识别、自动控制、预测估计、生物、医学等领域应用广泛。
工神经网络是受到人类大脑结构的启发而创造出来的,这也是它能拥有真智能的根本原因。在我们的大脑中,有数十亿个称为神经元的细胞,它们连接成了一个神经网络。
人工神经网络正是模仿了上面的网络结构。下面是一个人工神经网络的构造图。每一个圆代表着一个神经元,他们连接起来构成了一个网络。
2.单层神经网络与多层神经网络
我们现在可能对神经网络的作用很难理解,我们拿线性回归举例
这是关于线性回归的内容,我们可以简单了解一下线性回归是干什么的就ok了。
前面讲的是总结一串数字的规律,现实生活中我们往往要根据多个特征(多串数字)来分析一件事情,每个原始特征我们都看作是一个维度(Dimension)。例如一个学生的学习成绩好坏要根据语文、数学、英语等多门课程的分数来综合判断,这里每门课程都是一个维度。当使用二次曲线和多变量(多维)拟合的情况下,特征的数量会剧增,特征数=
维
度
2
/
2
维度^2/2
维度2/2 这个公式可以大概计算出特征增加的情况,例如一个100维的数据,二次多项式拟合后,特征会增加到100100/2=5000个。
下面是一张1003 x 1720像素的彩色图片,如果用二次多项式拟合的话,它有多少个特征呢?有14880亿。它的维度是1003 x 1720=1725160,特征数=17251601725160/2=1488088512800。如果是彩色图片,维度会增加到原来的3倍,那么特征数将…………
这么小的一张图片,就有这么巨大的特征量,可以想像一下我们的数码相机拍下来的照片会有多大的特征量!而我们要做的是从十万乃至亿万张这样的图片中找规律,这可能吗?
很显然,前面的那些回归方法已经不够用了,我们急需找到一种数学模型,能够在此基础上不断减少特征,降低维度。
于是,“人工神经网络(ANN, Artificial Neural Network)”就在这样苛刻的条件下粉墨登场了,神经科学的研究成果为机器学习领域开辟了广阔的道路。
我们知道我们做回归时,关键在于拟合函数的参数,神经网络就可以帮我们去完成这样庞大的任务,我们带着问题去学可能更好理解。下面就开一点一点介绍神经网络的基本结构。
2.1单层神经网络
下图是单个神经元(Neuron),或者说一个脑细胞的生理结构:
上面的x是神经元的输入,相当于树突接收的多个外部刺激。w是每个输入对应的权重,它影响着每个输入x的刺激强度,最后进行输出。
Z
=
g
(
a
1
∗
w
1
+
a
2
∗
w
2
+
a
3
∗
w
3
)
Z=g(a_{1}*w_{1}+a_{2}*w_{2}+a_{3}*w_{3})
Z=g(a1∗w1+a2∗w2+a3∗w3)
如上图,有两个层次。分别是输入层和输出层。输入层里的“输入单元”只负责传输数据,不做计算。输出层里的“输出单元”则需要对前面一层的输入进行计算。
我们把需要计算的层次称之为“计算层”,并把拥有一个计算层的网络称之为“单层神经网络”。
然而单层神经网络具有模型简单、结构清晰、计算量小等优点,但由于结构简单,它无法很好的处理一些非线性的问题,其实就是说他能力有限,因此我们需要多层神经网络来帮助我们解决问题。
2.2双层神经网络
大脑的结构越简单,那么智商就越低。单细胞生物是智商最低的了,而像上面的这种单个结构我们叫做单层神经网络。人工神经网络也是一样的,网络越复杂它就越强大,所以我们需要深度神经网络。这里的深度是指层数多,层数越多那么构造的神经网络就越复杂。
两层神经网络除了包含一个输入层,一个输出层以外,还增加了一个中间层。此时,中间层和输出层都是计算层。我们扩展上节的单层神经网络,在右边新加一个层次(只含有一个节点)。
现在,我们的权值矩阵增加到了两个,我们用上标来区分不同层次之间的变量。
两层神经网络(中间层计算)
计算最终输出z的方式是利用了中间层的
a
1
(
2
)
a_{1}^{(2)}
a1(2),
a
2
(
2
)
a_{2}^{(2)}
a2(2)和第二个权值矩阵计算得到的
两层神经网络(输出层计算)
需要说明的是,至今为止,我们对神经网络的结构图的讨论中都没有提到偏置节点(bias unit)。事实上,这些节点是默认存在的。它本质上是一个只含有存储功能,且存储值永远为1的单元。在神经网络的每个层次中,除了输出层以外,都会含有这样一个偏置单元。正如线性回归模型与逻辑回归模型中的一样。
偏置单元与后一层的所有节点都有连接,我们设这些参数值为向量b,称之为偏置。如下图。
在考虑了偏置以后的一个神经网络的矩阵运算如下:
从输入层到隐藏层
g
(
W
(
1
)
∗
a
(
1
)
+
b
(
1
)
)
=
a
(
2
)
;
g(W^{(1)} * a^{(1)} + b^{(1)}) = a^{(2)};
g(W(1)∗a(1)+b(1))=a(2);
从隐藏层到输出层
g
(
W
(
2
)
∗
a
(
2
)
+
b
(
2
)
)
=
z
;
g(W^{(2)} * a^{(2)} + b^{(2)}) = z;
g(W(2)∗a(2)+b(2))=z;
2.3多层神经网络
我们延续两层神经网络的方式来设计一个多层神经网络。
我们加入更多的中间层也就是隐含层,所以神经网络的计算层就在增加,我们就构成了多层神经网络
依照这样的方式不断添加,我们可以得到更多层的多层神经网络。公式推导的话其实跟两层神经网络类似,使用矩阵运算的话就仅仅是加一个公式而已。
在已知输入a(1),参数W(1),W(2),W(3)的情况下,输出z的推导公式如下:
g
(
W
(
1
)
∗
a
(
1
)
)
=
a
(
2
)
;
g(W^{(1)} * a^{(1)}) = a^{(2)};
g(W(1)∗a(1))=a(2);
g
(
W
(
2
)
∗
a
(
2
)
)
=
a
(
3
)
;
g(W^{(2)} * a^{(2)}) = a^{(3)};
g(W(2)∗a(2))=a(3);
g
(
W
(
3
)
∗
a
(
3
)
)
=
z
;
g(W^{(3)} * a^{(3)}) = z;
g(W(3)∗a(3))=z;
3.激活函数
激活函数又称非线性映射,激活函数的引入是为了增加整个网络的表达能力(即非线性),否则多层线性操作层堆叠只能起到线性线性映射的作用。
所以这里要对网络注入灵魂:激活层。
简而言之,激活层是为矩阵运算的结果添加非线性的。常用的激活函数有三种,分别是阶跃函数、Sigmoid和ReLU。
阶跃函数:当输入小于等于0时,输出0;当输入大于0时,输出1
f
(
x
)
=
{
0
(
x
<
0
)
1
(
x
>
0
)
f(x)=\left\{ \begin{aligned} 0(x<0)\\ \\ 1(x>0)\\ \end{aligned} \right.
f(x)=⎩⎪⎨⎪⎧0(x<0)1(x>0)
Sigmoid:当输入趋近于正无穷/负无穷时,输出无限接近于1/0。 f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+e−x1ReLU:当输入小于0时,输出0;当输入大于0时,输出等于输入。 f ( x ) = m a x ( 0 , x ) f(x)=max(0,x) f(x)=max(0,x)
- 其中,阶跃函数输出值是跳变的,且只有二值,较少使用;Sigmoid函数在当x的绝对值较大时,曲线的斜率变化很小(梯度消失),并且计算较复杂;ReLU是当前较为常用的激活函数。
激活函数具体是怎么计算的呢?假如经过公式H=X*W1+b1计算得到的H值为:(1,-2,3,-4,7…),那么经过阶跃函数激活层后就会变为(1,0,1,0,1…),经过ReLU激活层之后会变为(1,0,3,0,7…)。
- 需要注意的是,每个隐藏层计算(矩阵线性运算)之后,都需要加一层激活层,要不然该层线性计算是没有意义的。
4.前向传播
我们都知道,神经网络是分为“训练‘’和“使用”两个步骤的。如果是在“使用”的步骤,第2部分内容就已经完成整个过程了,所以我们现在来构建一个能够进行线性回归预测的神经网络,上面我们说到构建神经网络需要”参数“,在此我们学习构建神经网络。
我们先来复习一下前面学的双层神经网络,下面这张图十分直观,输入层、隐含层、Relu激活层、输出层。像这样一层 一层,层层递进我们称这个过程为前向传播
- 前向传播的过程如下
从输入层到隐藏层
g
(
W
(
1
)
∗
a
(
1
)
+
b
(
1
)
)
=
a
(
2
)
;
g(W^{(1)} * a^{(1)} + b^{(1)}) = a^{(2)};
g(W(1)∗a(1)+b(1))=a(2);
从隐藏层到输出层
g
(
W
(
2
)
∗
a
(
2
)
+
b
(
2
)
)
=
z
;
g(W^{(2)} * a^{(2)} + b^{(2)}) = z;
g(W(2)∗a(2)+b(2))=z;
我们现在谈谈参数的更新如何实现。我们需要引入新概念反向传播
5.*反向传播
我们首先明确目标以至于我们不会看的时候迷了路
反向传播是构建神经网络的难点,更是关键,调整不好参数,我们的神经网络也是没处理问题的能力。
因此,反向传播目的就是通过运用训练集的训练来更新参数以至于得到令我们满意的神经网络
这里我们要用到梯度下降的思想,因此我们少不了构造损失函数
5.1构造误差函数
机器学习模型训练的目的,就是使得参数尽可能的与真实的模型逼近。具体做法是这样的。首先给所有参数赋上随机值。我们使用这些随机生成的参数值,来预测训练数据中的样本。样本的预测目标为
y
p
y^{p}
yp,真实目标为
y
y
y。那么,定义一个值loss,计算公式如下。
l
o
s
s
=
(
y
p
−
y
)
2
loss = (y^{p} - y)2
loss=(yp−y)2
这个值称之为损失(loss),我们的目标就是使对所有训练数据的损失和尽可能的小。
此时这个问题就被转化为一个优化问题。一个常用方法就是高等数学中的求导,但是这里的问题由于参数不止一个,求导后计算导数等于0的运算量很大,所以一般来说解决这个优化问题使用的是梯度下降算法。梯度下降算法每次计算参数在当前的梯度,然后让参数向着梯度的反方向前进一段距离,不断重复,直到梯度接近零时截止。一般这个时候,所有的参数恰好达到使损失函数达到一个最低值的状态。
5.2参数更新
在神经网络模型中,由于结构复杂,每次计算梯度的代价很大。反向传播算法是利用了神经网络的结构进行的计算。不一次计算所有参数的梯度,而是从后往前。首先计算输出层的梯度,然后是第二个参数矩阵的梯度,接着是中间层的梯度,再然后是第一个参数矩阵的梯度,最后是输入层的梯度。计算结束以后,所要的两个参数矩阵的梯度就都有了。
反向传播算法可以直观的理解为下图。梯度的计算从后往前,一层层反向传播。前缀E代表着相对导数的意思。
反向传播算法的启示是数学中的链式法则。
更新参数需要神经网络不断的迭代,损失值越来越小,直到我们满意为止。这个过程就是利用梯度下降算法
优化问题只是训练中的一个部分。机器学习问题之所以称为学习问题,而不是优化问题,就是因为它不仅要求数据在训练集上求得一个较小的误差,在测试集上也要表现好。因为模型最终是要部署到没有见过训练数据的真实场景。提升模型在测试集上的预测效果的主题叫做泛化(generalization),相关方法被称作正则化(regularization)。神经网络中常用的泛化技术有权重衰减等。
在此我为了更好的说明参数更新过程附上一段链接https://www.cnblogs.com/charlotte77/p/5629865.html,这篇文章用了简单的例子将梯度下降更新参数的过程详细说明了
我们的神经网络构建完成后就可以去处理对应问题了,这也印证了前面说的不同情况要用到不同的神经网络。
6.代码简单实现
import numpy as np
#定义激活函数,这里使用到的是 Sigmoid 函数
def nonlin(x,deriv=False):
if(deriv==True): #定义 Sigmoid 的导数
return x*(1-x)
return 1/(1+np.exp(-x)) #Sigomid 函数
#定义输入数据
X = np.array([[0,0,1],
[0,1,1],
[1,0,1],
[1,1,1]])
#print (X.shape)
#目标比对模型
y = np.array([[0],
[1],
[1],
[0]])
#print (y.shape)
np.random.seed(1)
# randomly initialize our weights with mean 0
w0 = 2*np.random.random((3,4)) - 1
w1 = 2*np.random.random((4,1)) - 1
#print (w0)
#print (w1)
#print (w0.shape)
#print (w1.shape)
for j in range(60000):
#前向传播,l0 为输入层,l1 为隐层,l2 为输出层
l0 = X
l1 = nonlin(np.dot(l0,w0)) #矩阵运算
l2 = nonlin(np.dot(l1,w1))
l2_error = y - l2
#打印误差值
if (j% 10000) == 0:
print ("Error:" + str(np.mean(np.abs(l2_error))))
#反向传播
l2_delta = l2_error * nonlin(l2,deriv=True)
l1_error = l2_delta.dot(w1.T)
l1_delta = l1_error * nonlin(l1,deriv=True)
#更新权重
alpha = 0.5 #学习率
w1 += alpha * l1.T.dot(l2_delta)
w0 += alpha * l0.T.dot(l1_delta)