什么是神经网络
你看一只苍蝇的大脑只有10万个神经元,能耗那么低,但是它能看、能飞、能寻找食物,还能繁殖。而一台的超级计算机,消耗极大的能量,有庞大的体积,可是它的功能为什么还不如一只苍蝇?
这是因为苍蝇的大脑是高度专业化的,进化使得苍蝇的大脑只具备这些特定的功能,而我们的计算机是通用的,你可以对它进行各种编程,它理论上可以干任何事情。
这个关键在于,大脑的识别能力,不是靠临时弄一些规则临时编程。大脑的每一个功能都是专门的神经网络长出来的,那计算机能不能效法大脑呢?
这就给计算机科学家们带来了一些暗示:
- 大脑是一个强大的模式识别器。人脑非常善于在一个混乱的场景之中识别出你想要的那个东西。比如你能从满大街的人中,一眼就认出你熟悉的人。
- 大脑的识别功能可以通过训练提高。
- 大脑不管是练习还是使用识别能力,都不是按照各种逻辑和规则进行的。我们识别一个人脸,并不是跟一些抽象的规则进行比对。我们不是通过测量这个人两眼之间的距离来识别这个人。我们一眼看过去,就知道他是谁了。
- 大脑是由神经元组成的。我们大脑里有数百亿个神经元,大脑计算不是基于明确规则的计算,而是基于神经元的计算。
这就是神经网络计算做的事情,而后计算机科学家们就模拟出了一个神经网络。
人类大脑神经元细胞的树突接收来自外部的多个强度不同的刺激,并在神经元细胞体内进行处理,将其转化为一个输出结果。如下图所示。
人工神经元也有相似的工作原理。如下图所示。
- 求和 Z( Z = w 1 x 1 + w 2 x 2 + w 3 x 3 + b Z=w_{1}x_{1}+w_{2}x_{2}+w_{3}x_{3}+b Z=w1x1+w2x2+w3x3+b)
- 激活 a( a = f ( Z ) = σ ( Z ) = 1 1 + e − Z a=f(Z)=\sigma(Z)=\frac{1}{1+e^{-Z}} a=f(Z)=σ(Z)=1+e−Z1)
上面的x是神经元的输入,相当于树突接收的多个外部刺激。w是每个输入对应的权重,它影响着每个输入x的刺激强度。
假设周末即将到来,你听说在你的城市将会有一个音乐节。我们要预测你是否会决定去参加。音乐节离地铁挺远,而且你女朋友想让你陪游。
首先确定影响结果的几个要素,像天气(天气好不好)、性格(内向或者外向)、兴趣(对音乐的喜欢),也就是说有 3 个因素会影响你的决定,这 3 个因素就可以看作是 3 个输入特征。那你到底会不会去呢?你的个人喜好——你对上面 3 个因素的重视程度——会影响你的决定,这3个重视程度就是 3 个权重。
如果你觉得地铁远近无所谓,而且你很喜欢蓝天白云,那么我们将预测你会去音乐节。这个预测过程可以用我们的公式来表示。
我们假设结果 z > 0 z>0 z>0 的话就表示会去,小于 0 0 0表示不去。又设偏移值 b = − 5 b = -5 b=−5。
又设3个特征 ( x 1 , x 2 , x 3 ) (x1,x2,x3) (x1,x2,x3)为 ( 0 , 0 , 1 ) (0,0,1) (0,0,1),最后一个是 1 1 1,它代表了好天气。
又设三个权重 ( w 1 , w 2 , w 3 ) (w1,w2,w3) (w1,w2,w3)是 ( 2 , 2 , 7 ) (2,2,7) (2,2,7),最后一个是 7 7 7表示你很喜欢好天气。
那么就有 z = ( x 1 ∗ w 1 + x 2 ∗ w 2 + x 3 ∗ w 3 ) + b = ( 0 ∗ 2 + 0 ∗ 2 + 1 ∗ 7 ) + ( − 5 ) = 2 z = (x1 * w1 + x2 * w2 + x3 * w3) + b = (0 * 2 + 0 * 2 + 1 * 7) + (-5) = 2 z=(x1∗w1+x2∗w2+x3∗w3)+b=(0∗2+0∗2+1∗7)+(−5)=2。
预测结果 z = 2 z=2 z=2, 2 > 0 2>0 2>0,所以预测你会去音乐节。
如果你本质就是宅,并且对其它两个因素并不在意,那么我们预测你将不会去音乐节。
这同样可以用我们的公式来表示。设三个权重 ( w 1 , w 2 , w 3 ) (w1,w2,w3) (w1,w2,w3)是 ( 2 , 7 , 2 ) (2,7,2) (2,7,2),w2是7表示你就是特别喜欢呆在家里。
那么就有 z = ( x 1 ∗ w 1 + x 2 ∗ w 2 + x 3 ∗ w 3 ) + b = ( 0 ∗ 2 + 0 ∗ 7 + 1 ∗ 2 ) + ( − 5 ) = − 3 z = (x1 * w1 + x2 * w2 + x3 * w3) + b = (0 * 2 + 0 * 7 + 1 * 2) + (-5) = -3 z=(x1∗w1+x2∗w2+x3∗w3)+b=(0∗2+0∗7+1∗2)+(−5)=−3。
预测结果 z = − 3 z=-3 z=−3, − 3 < 0 -3<0 −3<0,所以预测你不会去,会呆在家里。
神经元的内部参数,包括权重 w 和偏移值b,都是可调的(开始时我们会随机初始化)。用数据训练神经网络的过程,就是调整更新各个神经元的内部参数的过程。神经网络的结构在训练中不变,是其中神经元的参数决定了神经网络的功能。
反复学习是刺激神经元,相当于加大权重的确定程度(不是加大权重的大小)。一开始神经元给这个输入数据的权重是0.9,但这是一个随机的分配,有很大的不确定性。随着训练的加深,神经网络越来越相信这个权重应该是0.11,参数稳定在这里。数值,增大或者减小了不重要,关建是确定性大大增加了。
对比到人,这就好比篮球,训练的目的不是让投篮的用力越来越大,而是越来越准确。
本质上,神经元做的事情就是按照自己的权重参数把输入值相加,再加入偏移值,形成一个输出值。如果输出值大于某个阈值,我们就说这个神经元被“激发”了。当然,人脑的神经元不一定是这么工作的,但是这个模型在计算中很好用。
这就是神经元的基本原理。真实应用中的神经元会在计算过程中加入非线性函数的处理,并且确保输出值都在 0 和 1 之间,这个非线性函数被称为激活函数,记录在下文。
这是单个神经元的计算,而在我们的大脑中,有数十亿个称为神经元的细胞,它们连接成了一个神经网络。
人工神经网络正是模仿了神经元的网络结构。
从下面这张图是,人工神经网络。
从左到右分为三层,每一个圆点代表一个神经元。
- 第零层是 “输入层”,代表输入的数据。
- 第一层叫 “隐藏层”,所谓深度学习就是中间不只有一个隐藏层。
- 第二层是 “输出层”,得到一个输出值。
数据输入进来,经过隐藏层各个神经元的一番处理,再把信号传递给输出层,输出层神经元再处理一番,最后作出判断。
对于多神经元网络,拆分看就是一个个单独的神经元,神经元网络的计算就是重复单神经元的计算。
神经网络预测的准确与否,由权重w和偏移值b决定,所以神经网络学习的目的就是找到合适的 w 和 b。
任何一个机器学习的过程,其实都是不断地调整数学模型参数的过程,直到参数收敛到最佳点。
每一次调整被称为是一次迭代,调整的幅度被称为迭代的步长。一开始的时候,迭代的步长要比较大,这样能够很快地确定大致范围,效率比较高。
但是,如果总是把步长设计得很大,那么最后可能会找不到最佳的参数,因为要么走过头了,要么没有走到。因此,机器学习到最后需要缩小步长,进行精调,以保证最后收敛到最佳点。
世界上每年有很多机器学习方面的论文,都是围绕提高学习效率展开的,而其中的核心其实就是怎样用最少次迭代,完成模型的训练——当然,任何好的机器学习算法都不是事先人为设定步长,而是在学习的过程中,自动找到合适的步长。
我们可以通过一种叫梯度下降的搜索方法,TA会一步步的改变w和b的值,新的 w 和 b 会使损失函数的输出结果更小,即一步步让预测更加精确。
损失函数:怎么验证神经网络的预测结果
损失函数用来评价模型的预测值和真实值不一样的程度,损失函数越好,通常模型的性能越好。
啥意思呢?我们人类学习同理,如果一个人一直不停的学,但是不验证自己的学习成果,那么有可能学的方向或者学习方法是错误的,不停的学但结果都白学了。要验证学习成果,就要判断预测结果是否准确,损失函数就是做这个的。
不同的模型用的损失函数一般也不一样,所以损失函数有几种,
J
(
)
、
L
(
)
J()、L()
J()、L() 。
激活函数:神经网络的智商怎么蹭的一下就涨了呢
神经网络其实就是线性函数,函数就是一条直线,能处理的问题也只是线性函数可以处理的问题。
激活函数是非线性函数,不同的激活函数的样子不同,可能是曲线等等,而后就可以让神经网络处理各种问题了。
但这个激活函数用的较少,一般用改进的 tanh。
tanh 图像和 sigmoid 的形状是一样的,只不过整体往下移了一点。
sigmoid 输出值是在
[
0
,
1
]
[0,1]
[0,1],平均值是0.5,tanh 输出值是在
[
−
1
,
1
]
[-1,1]
[−1,1],平均值是0。
改进就在这里,将靠近0的输出值传给下层神经网元,就会更有效。
俩者都有一个不足,数据大时,神经网络学习速度就很慢。
学习速度和偏导数大小相关,偏导数就是斜率(变化比例),斜率(变化比例)越大偏导数越大,学习速度越快。
通过观察俩者的图像发现,当输入值越来越大时,曲线的斜率(变化比例)是越来越小的。
为了解决这个问题,后来创造了relu。
relu 图像:
- 在输入为正时,斜率(变化比例)很大。
- 在输入为负时,就会输出 0,神经元就不会被激活 — 说明同一时间里,只有部分神经元会被激活,从而使得网络很稀疏,进而计算更高效,但没有斜率(变化比例)。
为了解决没有斜率(变化比例)的问题,又创造了一种激活函数:leaky relu。
leaky relu 的优点将 0 的梯度去掉,换成一个非0的梯度,比如0.1等,这样把0梯度变成一个很小不为0的梯度。
sigmoid 类的激活函数,只可以处理二元分类问题,神经网络只能判断是或否。
softmax 可以让神经网络的预测更加丰富,从二元判断到多元判断。
比如,之前只能用来判断图中有木有猫,现在可以同时判断出猫、狗、鸡。
常用的激活函数:
- sigmoid:特别适用于,二元分类的输出层,其他方面不如tanh
- tanh:sigmoid改进
- relu:首选,用的最多,但只在隐藏层使用
- leaky relu:relu改进
- softmax:N元分类的输出层,输出层神经元要有N个
单神经元
反向传播又叫误差反向传播,关键思想在于,参数调整有个方向,叫做 “误差梯度(方向)”。
具体的调整算法都是技术细节,我们这里关键是要理解这个设计思想:每次新的训练数据进来,就根据正确答案对参数进行一次微调,使得神经网络输出数值更接近正确答案。
就像体育训练,反馈是即时的,你做的结果立即就能跟正确答案比较。
不要求你马上就做对,但是每次总要进步一点点。但你每一次都明确知道,身体上每一个关节的动作应该往哪个方向调整。
算法步骤:
- 会先按前向传播方式计算并缓存(空间换时间)每个节点的输出值。
- 再按反向传播遍历图的方式计算损失函数值相对于每个参数的偏导数。
反向计算是因为,隐藏层神经元的参数调整更麻烦。我们必须考虑到,调整一个隐藏层的神经元,会同时影响全部10个输出层神经元的输入值。这就涉及到误差反向传递了,也就是调整前面神经元的参数,必须考虑它对后面神经元总体误差的影响。
单神经元的前向传播
神经网络的计算,由前向传播、反向传播构成。
-
前向传播过程:计算权重参数把输入值相加,再加入偏移值,形成一个输出值。
-
反向传播过程:先计算第N层的偏导数(变化比例),再计算第N-1层的,反向往前推。
这样来回不停的进行前向传播、反向传播,来训练(更新)参数使损失函数越来越小(预测越来越准确)。
原理:每次新的训练数据进来,就根据正确答案对参数进行一次微调,使得神经网络输出数值更接近正确答案。
过程总览如下:
不需要看懂,意会一下前向传播、反向传播在神经网络大概是怎么计算即可。
我们用函数 J ( a , b , c ) = 3 ( a + b c ) J(a,~b,~c) = 3(a+bc) J(a, b, c)=3(a+bc) 来演示前向传播过程,函数 J J J的计算过程就是下图:
假设
a
、
b
、
c
a、b、c
a、b、c 分别为
5
、
3
、
2
5、3、2
5、3、2,前向传播过程如下图:
在上图中通过前向传播一步步算出损失函数
J
J
J的值,以及预测值和损失值,上图是最简单的情况(单神经元的前向传播计算)。
单神经元的反向传播
反向传播用于计算函数C关于各个参数的偏导数,而后对参数进行梯度下降。
我们先计算C关于B的偏导数,记为
d
C
d
B
\frac{dC}{dB}
dBdC。
偏导数、斜率就是变化比例,即 B 变化一点后 C 会相应的变化多少。
所以,为了计算B的偏导数,我们假设让B变化一点点,比如B加上0.001(11.001),而后看C改变了多少,C从33变成了33.003(C = 3B = 3 * 11.001)。
C的变化量除以B的变化量(0.003 / 0.001 = 3),即变化比例为3。
同理,B关于A的偏导数为 d B d A = 1 \frac{dB}{dA} = 1 dAdB=1。
同理,A关于b的偏导数为 d A d b = 2 \frac{dA}{db} = 2 dbdA=2。
那么,C关于A的偏导数是多少呢?我们让A改变一点点,从6变成6.001,这会导致B从11变成11.001,而B的改变会导致C从33变成33.003,C的改变量除以A的改变量等于3(0.003/0.001=3),即偏导数 d C d A \frac{dC}{dA} dAdC 为3。
其实,C关于A的偏导数 = C关于B的偏导数 * B关于A的偏导数,即 d C d A = d C d B ∗ d B d A = 3 ∗ 1 = 3 \frac{dC}{dA} = \frac{dC}{dB}*\frac{dB}{dA}=3*1=3 dAdC=dBdC∗dAdB=3∗1=3
这种传导性计算在微积分里面,称为链式法则。
同理,C关于参数b的偏导数 d C d b = d C d B ∗ d B d A ∗ d A d b = 3 ∗ 1 ∗ 2 = 6 \frac{dC}{db}=\frac{dC}{dB}*\frac{dB}{dA}*\frac{dA}{db}=3*1*2=6 dbdC=dBdC∗dAdB∗dbdA=3∗1∗2=6。
d C d b \frac{dC}{db} dbdC这个偏导数才是我们最终需要的,我们需要的是函数C关于参数a、b、c的偏导数。
为了得到这三个偏导数,我们需要先计算出关于B的偏导数,而后再计算出关于A的偏导数,最后计算出关于参数的偏导数。一步步反向推进,这个过程就是一个反向传播过程。
- d C d a = d C d B ∗ d B d a = 3 ∗ 1 = 3 \frac{dC}{da}=\frac{dC}{dB}*\frac{dB}{da}=3*1=3 dadC=dBdC∗dadB=3∗1=3
- d C d b = d C d B ∗ d B d A ∗ d A d b = 3 ∗ 1 ∗ 2 = 6 \frac{dC}{db}=\frac{dC}{dB}*\frac{dB}{dA}*\frac{dA}{db}=3*1*2=6 dbdC=dBdC∗dAdB∗dbdA=3∗1∗2=6
- d C d c = d C d B ∗ d B d A ∗ d A d c = 3 ∗ 1 ∗ 3 = 9 \frac{dC}{dc}=\frac{dC}{dB}*\frac{dB}{dA}*\frac{dA}{dc}=3*1*3=9 dcdC=dBdC∗dAdB∗dcdA=3∗1∗3=9
P.S. 偏导数可以简写,如
d
C
d
b
\frac{dC}{db}
dbdC 简写为
d
b
db
db。
向量化:人工智能编程和传统编程不一样的地方
假设您已经有了Python基础。
人工智能编程和传统编程不一样的地方,在于训练一个智能模型需要非常多的数据,计算量很大,需要很长的时间,所以我们会向量化,来提高计算速度。
“向量化”(简化)是重写一个循环的过程,这样它可以不处理数组N次的单个元素,而是同时处理(比方说)数组的4个元素N/4次。
传统编程:
for(int i=0; i<10000; i++)
a[i] = 1
向量化:
for(int i=0; i<10000; i++){
a[i] = 1; a[i+1] = 1; a[i+2] = 1; a[i+3]=1;
i = i + 4;
}
判断和赋值就减少了 3 4 \frac{3}{4} 43。
因为现在的CPU都有“向量”或“SIMD”指令集,它们同时对两个、四个或多个数据进行相同的操作。如果使用 for 循环,那一条指令的for循环里并没有使用并行计算,也就没有充分利用计算机的资源。
举个例子, ∑ i = 1 3 a i ∗ b i \sum\limits_{i=1}^3 a_{i} * b_{i} i=1∑3ai∗bi。
传统编程:
a = b = [1, 2, 3]
ans = 0
for x in range(0, 3):
ans = a[i] + b[i]
向量化:
import numpy as np
a = b = [1, 2, 3]
np.dot(a, b)
# dot函数是如何对矩阵进行运算,里面的代码是用C语言实现的,比Python自身实现的要快得多
# 如果是向量,返回的是两向量的点乘:A*B = A1*B1 + A2*B2 ··· +An*Bn(需要检查边界:俩个向量维度相等),结果是一个数
# 如果是矩阵,返回的是俩矩阵的乘积:矩阵乘法内容,(需要检查边界:乘号前的矩阵的列数要等于后面矩阵的行数),结果是一个矩阵
基本上,我们每个算法里都会想怎么向量化。
比如逻辑回归(逻辑回归就是在线性回归基础上,加了sigmod函数,把线性回归结果变成了概率)。
- 线性回归: w T x i + b w^{T}x^{i}+b wTxi+b
- 逻辑回归: σ ( w T x i + b ) \sigma(w^{T}x^{i}+b) σ(wTxi+b)
在逻辑回归的前向传播过程中:
- 第一步,计算出输出值 Z Z Z: z = w T x + b z=w^{T}x+b z=wTx+b
P.S. w上的T表示转置矩阵,如果w是mn的矩阵,转置后w是nm的矩阵,之前的行变成列,之前的列变成行。
- 第二步,计算出预测值 y ^ 、 a \hat{y}、a y^、a: y ^ = a = σ ( z ) \hat{y}=a=\sigma(z) y^=a=σ(z)
- 第三步,计算出损失函数 L L L: L ( a , y ) = − ( y l o g ( a ) + ( 1 − y ) l o g ( 1 − a ) ) L(a, y)=-(y~log(a)+(1-y)log(1-a)) L(a,y)=−(y log(a)+(1−y)log(1−a))
逻辑回归的前向传播:
逻辑回归的反向传播:
- 最终目的是要计算出 d w 1 dw_{1} dw1、 d w 2 dw_{2} dw2、 d b db db,而后更新 w 1 、 w 2 、 b w_{1}、w_{2}、b w1、w2、b 以使损失函数 L 越来越小(预测越精准)
而为了计算出 d w 1 dw_{1} dw1、 d w 2 dw_{2} dw2、 d b db db:
- 首先,要计算 d a ( d L d a ) da(\frac{dL}{da}) da(dadL), d a = − ( y a + 1 − y 1 − a ) da = -(\frac{y}{a}+\frac{1-y}{1-a}) da=−(ay+1−a1−y)
- 接着,要求出 d z dz dz, d z = d a ( d L d a ) ∗ d z ( d a d z ) dz = da(\frac{dL}{da})*dz(\frac{da}{dz}) dz=da(dadL)∗dz(dzda),而 d a d z = a ( 1 − a ) \frac{da}{dz}=a(1-a) dzda=a(1−a),经过计算后 d z = a − y dz=a-y dz=a−y
- 同理,计算出 d w 1 = x 1 d z , d w 2 = x 2 d z , d b = d z dw_{1}=x_{1}dz,dw_{2}=x_{2}dz,db=dz dw1=x1dz,dw2=x2dz,db=dz
偏导数计算过程:
1:
d
z
=
a
−
y
dz= a-y
dz=a−y
2:
d
w
1
=
x
1
d
z
dw_{1}=x_{1}dz
dw1=x1dz
3:
d
w
2
=
x
2
d
z
dw_{2}=x_{2}dz
dw2=x2dz
4:
d
w
n
=
x
n
d
z
dw_{n}=x_{n}dz
dwn=xndz
5:
d
b
=
d
z
db = dz
db=dz
- 得到 d w 1 、 d w 2 、 d b dw_{1}、dw_{2}、db dw1、dw2、db 后,就可以更新这些参数值进行梯度下降,例如 w 1 ′ = w 1 − r ∗ d w 1 ( r 是学习率) w_{1}'=w_{1}-r*dw_{1}(r是学习率) w1′=w1−r∗dw1(r是学习率)
- 而后,用新的参数值再次进行前向传播再反向传播,通过这样不停的前向、反向传播来训练参数。
P.S. 这其实是单个训练样本时计算偏导数的过程(单个训练样本:比如训练猫的神经网络中,一张猫的图片就是一个训练样本)。
我们训练神经网络时,肯定不止单个训练样本,那在多个训练样本中,我们如何计算偏导数呢?
- 多个样本时的偏导数就等于每个样本的偏导数的平均值。
逻辑过程的代码如下:
def sigma(z):
return 1 / ( 1 + pow(e, -z) )
z = a = y = dz = []
for i in range(1, m): # 遍历所有样本,如一张图片就是一个样本
temp = 0
for j in range(1, n): # 遍历样本特征,如一张图片每一个像素的每一个颜色强度值就是一个特征
temp += w[j] * x[i][j] # x是特征,w是对应权重
z[i] = temp + b # 输出值z = 权重 + 偏移值
a[i] = sigma (z[i]) # 每一个神经元得出的z都需要通过激活函数变成预测值a
J += -( y[i] * log(a[i]) + (1 - y[i]) * log(1 - a[i]) ) # 通过预测值和真实值构建一个损失函数J来计算损失
dz[i] = a[i] - y[i] # 输出值误差 = 预测值 - 真实值
for j in range(1, n): # 遍历所有特征
dw[j] += x[i][j] * dz[i] # 每一个特征对应一个权重
db += dz[i] # 每一个权重都有一个对应的偏导数
# 多样本量
J = J / m, db = db / m # m是样本数量,除以m是求平均值
for j in range(1, n): # 遍历所有特征
dw[j] = dw[j] / m # 训练样本的平均损失
我们来一步步的进行向量化去除for循环:
# 原代码
temp = 0
for j in range(1, n): # 遍历样本特征,如一张图片每一个像素的每一个颜色强度值就是一个特征
temp += w[j] * x[i][j] # x是特征,w是对应权重
z[i] = temp + b # 输出值z = 权重 + 偏移值
# 向量化
z[i] = np.dot(w.t, x[i]) + b # w.t 转置矩阵,x[i] 是一个列向量,np.dot 矩阵相乘
Python广播化技术:当遇到俩个不同维度的对象进行运算时,Python会自动的通过复制元素来使俩个操作对象的维度相同。
e.g. [ 1 4 7 ] ∗ 100 = [ 1 4 7 ] ∗ [ 100 100 100 ] = [ 100 400 700 ] \left[ \begin{matrix} 1 \\ 4 \\ 7 \end{matrix} \right]*100=\left[ \begin{matrix} 1 \\ 4 \\ 7 \end{matrix} \right]*\left[ \begin{matrix} 100 \\ 100 \\ 100 \end{matrix} \right]=\left[ \begin{matrix} 100 \\ 400 \\ 700 \end{matrix} \right] 147 ∗100= 147 ∗ 100100100 = 100400700
# 原代码
for j in range(1, n): # 遍历所有特征
dw[j] += x[i][j] * dz[i] # 每一个特征对应一个权重
# 向量化
dw += x[i] * dz[i] # x[i]是一个向量,dz[i]是一个数值
# 原代码
for j in range(1, n): # 遍历所有特征
dw[j] = dw[j] / m # 每一个特征对应一个权重,每一个权重都有一个对应的偏导数
# 向量化
dw = dw / m
目前已经去除了俩个for循环(第二、第三):
for i in range(1, m): # 遍历所有样本,如一张图片就是一个样本
z[i] = np.dot(w.t, x[i]) + b # 遍历样本特征,x是特征,w是权重,输出值z = 权重 + 偏移值【向量化】
a[i] = sigma (z[i]) # 每一个神经元得出的z都需要通过激活函数变成预测值a
J += -( y[i] * log(a[i]) + (1 - y[i]) * log(1 - a[i]) ) # 通过预测值和真实值构建一个损失函数J来计算损失
dz[i] = a[i] - y[i] # 输出值误差 = 预测值 - 真实值
dw += x[i] * dz[i] # 偏导数dw
db += dz[i] # 每一个权重都有一个对应的偏导数
J = J / m, db = db / m # m是样本数量,除以m是求平均值
dw = dw / m
现在我们去除第一个for循环:
Z = np.dot(w.t, X) + b # 原:z[i] = np.dot(w.t, x[i]) + b
def sigma(Z):
return 1 / ( 1 + np.exp(-Z) ) # 原:return 1 / ( 1 + pow(e, -z) )
# np.exp会对向量中每个元素进行 pow(e, -z[i]) 运算
A = sigma(Z) # a[i] = sigma(z[i])
J = np.sum( -(Y * np.log(A) + (1 - Y) * np.log(1 - A) ) ) / m
# 原:J += -( y[i] * log(a[i]) + (1 - y[i]) * log(1 - a[i]) )
# np.log会对向量里每个元素进行log运算,np.sum里面就是一个向量,向量里的一个元素就是单独一个样本对应的损失
# 而np.sum会将向量里每一个元素加起来,除以m得到平均损失
dZ = A - Y # 原:dz[i] = a[i] - y[i]
dw = np.dot(X, dZ.t) / m # 原:dw += x[i] * dz[i]
db = np.sum(dZ) / m # 原:db += dz[i]
[牛刀小试:识别猫的项目]
神经网络识别猫的大致过程:
左边的
x
0
x_{0}
x0 到
x
12287
x_{12287}
x12287 是输入(input),我们称之为特征(feather)。列向量
x
(
i
)
x^{(i)}
x(i) 的
i
i
i 来表示第
i
i
i 个训练样本(图片)。
在图片识别中,特征通常是图片的像素值,把所有的像素值排成一个序列就是输入特征,每一个特征都有自己的一个权重(weight),就是图中连线上的 w 0 w_{0} w0 到 w 1 2287 w_12287 w12287,通常我们也把左右的权重组合成一个列向量W。
中间的圆圈,就是神经元,TA接收来自左边的输入并乘以相应的权重,再加上一个偏置项b(一个实数),所以最终接收的总输入为:
- x 0 w 0 ∗ x 1 w 1 ∗ ⋅ ⋅ ⋅ ∗ x 12287 w 12287 + b = W T x + b x_{0}w_{0}*x_{1}w_{1} *···* x_{12287}w_{12287} + b=W^{T}x+b x0w0∗x1w1∗⋅⋅⋅∗x12287w12287+b=WTx+b
P.S. 其实应该是 x i x^{i} xi(多样本时),这里用 x x x 是因为只讨论一个样本(一张图片)的情况。
但是这个并不是最后的输出,就跟神经元一样,会有一个激活函数(activation function)来对输入进行处理,来决定是否输出或者输出多少。
逻辑的激活函数是sigmoid函数,介于0和1之间,中间的斜率比较大,两边的斜率很小并在远处趋于零。长这样(记住函数表达式):
我们用
y
^
\hat{y}
y^ 来表示该神经元的输出,
σ
σ
σ函数代表sigmoid,则可知:
- y ^ = σ ( w T x + b ) \hat{y} = σ(w^{T}x+b) y^=σ(wTx+b)
y ^ \hat{y} y^ 是模型根据输入做出的一个预测,与 y ^ \hat{y} y^ 对应的,每一个样本x都有自己的一个真实标签y, y = 1 y=1 y=1代表图片是猫, y = 0 y=0 y=0代表不是猫。
我们希望模型输出的 y ^ \hat{y} y^ 可以尽可能的接近真实标签y,这样,这个模型就可以用来预测一个新图片是不是猫了。
所以,我们的任务就是要找出一组 W 、 b W、b W、b,使得我们的模型 y ^ = σ ( w T x + b ) \hat{y}= σ(w^{T}x+b) y^=σ(wTx+b) 可以根据给定的 x x x,正确地预测 y y y。
在此处,我们可以认为,只要算出的 y ^ \hat{y} y^ 大于0.5,那 y ^ \hat{y} y^ 就更接近 1 1 1,于是可以预测为【是猫】,反之则【不是猫】。
那怎么学习 ( W 、 b ) (W、b) (W、b) 呢?
我们需要学习到的 W W W 和 b b b 可以让模型的预测值 y ^ \hat{y} y^ 与真实标签 y y y 尽可能地接近,也就是 y ^ \hat{y} y^ 和 y y y 的差距尽量地缩小。因此,我们可以定义一个损失函数(Loss function),来衡量 y ^ \hat{y} y^ 和 y y y 的差距:
- L ( y ^ , y ) = − [ y ∗ l o g ( y ^ ) + ( 1 − y ) ∗ l o g ( 1 − y ^ ) ] L(\hat{y},~y)= -[ y * log(\hat{y}) + (1 - y) * log(1 - \hat{y}) ] L(y^, y)=−[y∗log(y^)+(1−y)∗log(1−y^)]
P.S. 实际编写代码时, y ^ \hat{y} y^ 会换成 a a a 的。
如何说明这个式子适合当损失函数呢?且看:
- 当 y = 1 y=1 y=1 时, L ( y ^ , y ) = − l o g ( y ^ ) L(\hat{y},~y)=-log(\hat{y}) L(y^, y)=−log(y^),要使 L L L 最小,则 y ^ \hat{y} y^ 要最大,则 y ^ = 1 \hat{y}=1 y^=1
- 当 y = 0 y=0 y=0 时, L ( y ^ , y ) = − l o g ( 1 − y ^ ) L(\hat{y},~y)=-log(1-\hat{y}) L(y^, y)=−log(1−y^),要使 L L L 最小,则 y ^ \hat{y} y^ 要最小,则 y ^ = 0 \hat{y}=0 y^=0
如此,便知 L ( y ^ , y ) L(\hat{y},~y) L(y^, y) 符合我们对损失函数的期望,因此适合作为损失函数。
我们知道, x x x 代表一组输入,相当于是一个样本的特征。但是我们训练一个模型会有很多很多的训练样本,也就是有很多很多的 x x x,就是会有 x ( 1 ) , x ( 2 ) , . . . , x ( m ) x^{(1)},x^{(2)},...,x^{(m)} x(1),x(2),...,x(m) 共 m m m 个样本,它们可以写成一个大 X 行向量:
- X = ( x ( 1 ) , x ( 2 ) , . . . , x ( m ) ) X = (x^{(1)},x^{(2)},...,x^{(m)} ) X=(x(1),x(2),...,x(m))
对应的样本的真实标签 Y Y Y(也是行向量):
- Y = ( y ( 1 ) , y ( 2 ) , . . . , y ( m ) ) Y = (y^{(1)},y^{(2)},...,y^{(m)} ) Y=(y(1),y(2),...,y(m))
通过我们的模型计算出的 y ^ \hat{y} y^ 们也可以组成一个行向量:
- Y ^ = ( y ^ ( 1 ) , y ^ ( 2 ) , . . . , y ^ ( m ) ) \hat{Y} = (\hat{y}^{(1)},\hat{y}^{(2)},...,\hat{y}^{(m)} ) Y^=(y^(1),y^(2),...,y^(m))
损失函数 L L L,对每个 x x x 都有,因此在学习模型的时候,我们需要看所有 x x x 的平均损失,因此定义一个代价函数(Cost function):
- J ( W , b ) = ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) m J(W,b) = \frac{\sum\limits_{i=1}^m L(\hat{y}^{(i)},~y(i))}{m} J(W,b)=mi=1∑mL(y^(i), y(i)) 代表所有训练样本的平均损失。
因此,我们的学习任务就可以用一句话来表述:找到 ( W , b ) (W, b) (W,b) 使得 J ( W , b ) J(W,b) J(W,b) 最小。
其实这是一个搜索问题,一般我们用梯度下降法。
这个方法通俗一点就是,先随机在曲线上找一个点,然后求出该点的斜率,也称为梯度,然后顺着这个梯度的方向往下走一步,到达一个新的点之后,重复以上步骤,直到到达最低点(或达到我们满足的某个条件)。
如,对 w w w 进行梯度下降,则就是重复一下步骤(重复一次称为一个迭代):
- w : = w − r ( d J d w ) w := w - r(\frac{dJ}{dw}) w:=w−r(dwdJ)
其中 : = := := 代表“用后面的值更新”, r r r代表学习率(learning rate), d J d w \frac{dJ}{dw} dwdJ 就是 J J J 对 w w w 求偏导。
回到我们的逻辑回归上,就是要初始化(initializing)一组 W W W 和 b b b,并给定一个学习率,指定要迭代的次数(就是你想让点往下面走多少步),而后每次迭代中求出 w w w 和 b b b 的梯度,并更新 w w w 和 b b b。
最终的 W W W 和 b b b 就是我们学习到的 W W W 和 b b b,把 W W W 和 b b b 放进我们的模型 y ^ = σ ( w T x + b ) \hat{y} = σ(w^{T}x+b) y^=σ(wTx+b) 中,就是我们学习到的模型,就可以用来进行预测了!
小结:
- 逻辑回归模型: y ^ = σ ( w T x + b ) \hat{y} = σ(w^{T}x+b) y^=σ(wTx+b)
- 损失函数: L ( y ^ , y ) = − [ y ∗ l o g ( y ^ ) + ( 1 − y ) ∗ l o g ( 1 − y ^ ) ] L(\hat{y},~y)= -[ y * log(\hat{y}) + (1 - y) * log(1 - \hat{y}) ] L(y^, y)=−[y∗log(y^)+(1−y)∗log(1−y^)],衡量预测值 y ^ \hat{y} y^ 与真实值 y y y 的差距,越小越好
- 代价函数:损失均值, J ( W , b ) = ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) m J(W,b) = \frac{\sum\limits_{i=1}^m L(\hat{y}^{(i)},~y(i))}{m} J(W,b)=mi=1∑mL(y^(i), y(i)) ,是 W W W 和 b b b 的函数,学习的过程就是寻找 W W W 和 b b b 使得 J ( W , b ) J(W,b) J(W,b) 最小化的过程。
- 求最小值的方法是用梯度下降法。
- 训练模型步骤:初始化 W W W 和 b b b,指定 l e a r n i n g r a t e learning~rate learning rate 和迭代次数,每次迭代,根据当前 W W W 和 b b b 计算对应的梯度( J J J 对 W 、 b W、b W、b 的偏导数),而后更新 W 、 b W、b W、b,迭代结束,学得 W 、 b W、b W、b,带入模型进行预测,分别测试在训练集合测试集上的准确率,从而评价模型。
动手试一下吧,源码请猛击《深度学习项目拆解:识别猫的项目》。
测试图片:
预测结果为 1,是猫。
测试图片:
预测结果为 0,不是猫。
由于我们的神经网络太简单了(最简单的神经网络:单神经元神经网络),所以识别率不高。
浅层神经网络
我们上面写的是单神经网络:
这个唯一的神经元,其实进行了如下运算。
前向传播:这个神经元通过对
w
、
x
、
b
w、x、b
w、x、b 进行运算,得出
z
z
z,而后再由
z
z
z 得出
a
a
a,通过预测值
a
a
a 和 真实值
y
y
y 可以构建出损失函数
L
L
L。
反向传播:从损失函数开始,回到这个神经元,计算出 w w w 和 b b b 相对于损失函数的偏导数(梯度),以便进行梯度下降。
反复迭代:而后再次进行前向传播,这样不停的反复的优化 w w w 和 b b b。
对于浅层神经网络也是如此(多神经元),就是重复单神经元的流程。
- 第一层,通过 w 、 x 、 b w、x、b w、x、b 计算出 3 个神经元的预测值 a a a
- 第二层,通过 a 1 、 a 2 、 a 3 a_{1}、a_{2}、a_{3} a1、a2、a3 当作 x x x 输入到神经元中进行计算
- 反向传播同,先算出第二层的偏导数(梯度)
d
w
、
d
b
dw、db
dw、db,而后再向第一层传播,算出第一层
3
3
3 个神经元的
d
w
、
d
b
dw、db
dw、db
浅层神经网络的前向传播
而后,我们还要把这个单神经元计算原理应用到浅层神经网络中。
先解图:
- x i :输入信号的第 i 个特征 x_{i}:输入信号的第 i 个特征 xi:输入信号的第i个特征
- w 1 i − j :第 1 层、第 i 个神经元、第 j 个参数 w_{1}^{i-j}:第 1 层、第 i 个神经元、第 j 个参数 w1i−j:第1层、第i个神经元、第j个参数
- Z 1 i :第 1 层、第 i 个神经元的求和项 Z_{1}^{i}:第 1 层、第 i 个神经元的求和项 Z1i:第1层、第i个神经元的求和项
- a 1 i :第 1 层、第 i 个神经元的激活值 a_{1}^{i}:第 1 层、第 i 个神经元的激活值 a1i:第1层、第i个神经元的激活值
第一层算例如下:
Z 1 1 = w 1 1 − 1 x 1 + w 1 1 − 2 x 2 + w 1 1 − 3 x 3 + b 1 1 Z_{1}^{1}=w_{1}^{1-1}x_{1}+w_{1}^{1-2}x_{2}+w_{1}^{1-3}x_{3}+b_{1}^{1} Z11=w11−1x1+w11−2x2+w11−3x3+b11
= [ w 1 1 − 1 w 1 1 − 2 w 1 1 − 3 ] ∗ [ x 1 x 2 x 3 ] + b 1 1 ~~~~~~= \begin{bmatrix} w_{1}^{1-1}w_{1}^{1-2}w_{1}^{1-3} \end{bmatrix}*\begin{bmatrix} x_{1} \\ x_{2} \\ x_{3} \end{bmatrix}+b_{1}^{1} =[w11−1w11−2w11−3]∗ x1x2x3 +b11
= W 1 1 X + b 1 1 ~~~~~~=W_{1}^{1}X+b_{1}^{1} =W11X+b11
a 1 1 = f ( Z 1 1 ) a_{1}^{1}=f(Z_{1}^{1}) a11=f(Z11)
第一层其他神经元,同理可得:
-
Z 1 2 = W 1 2 X + b 1 2 , a 1 2 = f ( Z 1 2 ) Z_{1}^{2}=W_{1}^{2}X+b_{1}^{2},~~~~ a_{1}^{2}=f(Z_{1}^{2}) Z12=W12X+b12, a12=f(Z12)
-
Z 1 3 = W 1 3 X + b 1 3 , a 1 3 = f ( Z 1 3 ) Z_{1}^{3}=W_{1}^{3}X+b_{1}^{3},~~~~ a_{1}^{3}=f(Z_{1}^{3}) Z13=W13X+b13, a13=f(Z13)
第一层的完整算例:
[ Z 1 1 Z 1 2 Z 1 3 ] = [ w 1 1 − 1 w 1 1 − 2 w 1 1 − 3 w 1 2 − 1 w 1 2 − 2 w 1 2 − 3 w 1 3 − 1 w 1 3 − 2 w 1 3 − 3 ] ∗ [ a 0 1 ( x 1 ) a 0 2 ( x 2 ) a 0 3 ( x 3 ) ] + [ b 1 1 b 1 2 b 1 3 ] , [ a 1 1 a 1 2 a 1 3 ] = f ( [ Z 1 1 Z 1 2 Z 1 3 ] ) \begin{bmatrix} Z_{1}^{1} \\ Z_{1}^{2} \\ Z_{1}^{3} \end{bmatrix}=\begin{bmatrix} w_{1}^{1-1}&w_{1}^{1-2}&w_{1}^{1-3}\\ w_{1}^{2-1}&w_{1}^{2-2}&w_{1}^{2-3}\\ w_{1}^{3-1}&w_{1}^{3-2}&w_{1}^{3-3} \end{bmatrix}*\begin{bmatrix} a_{0}^{1} ~~(x_{1})\\ a_{0}^{2} ~~(x_{2})\\ a_{0}^{3} ~~(x_{3}) \end{bmatrix}+\begin{bmatrix} b_{1}^{1} \\ b_{1}^{2} \\ b_{1}^{3} \end{bmatrix},~~~~~\begin{bmatrix} a_{1}^{1} \\ a_{1}^{2} \\ a_{1}^{3} \end{bmatrix}=f(\begin{bmatrix} Z_{1}^{1} \\ Z_{1}^{2} \\ Z_{1}^{3} \end{bmatrix}) Z11Z12Z13 = w11−1w12−1w13−1w11−2w12−2w13−2w11−3w12−3w13−3 ∗ a01 (x1)a02 (x2)a03 (x3) + b11b12b13 , a11a12a13 =f( Z11Z12Z13 )
第二层的完整算例:
[ Z 2 1 Z 2 2 ] = [ w 2 1 − 1 w 2 1 − 2 w 2 1 − 3 w 2 2 − 1 w 2 2 − 2 w 2 2 − 3 ] ∗ [ a 1 1 a 1 2 a 1 3 ] + [ b 2 1 b 2 2 ] , [ a 2 1 a 2 2 ] = f ( [ Z 2 1 Z 2 2 ] ) \begin{bmatrix} Z_{2}^{1} \\ Z_{2}^{2} \end{bmatrix}=\begin{bmatrix} w_{2}^{1-1}&w_{2}^{1-2}&w_{2}^{1-3} \\ w_{2}^{2-1}&w_{2}^{2-2}&w_{2}^{2-3} \end{bmatrix}*\begin{bmatrix} a_{1}^{1} \\ a_{1}^{2} \\ a_{1}^{3} \end{bmatrix}+\begin{bmatrix} b_{2}^{1} \\ b_{2}^{2} \end{bmatrix},~~~~~\begin{bmatrix} a_{2}^{1} \\ a_{2}^{2} \end{bmatrix}=f(\begin{bmatrix} Z_{2}^{1} \\ Z_{2}^{2} \end{bmatrix}) [Z21Z22]=[w21−1w22−1w21−2w22−2w21−3w22−3]∗ a11a12a13 +[b21b22], [a21a22]=f([Z21Z22])
第三层的完整算例:
[
Z
3
1
]
=
[
w
3
1
−
1
w
3
1
−
2
]
∗
[
a
2
1
a
2
2
]
+
[
b
3
1
]
,
a
3
1
=
f
(
[
Z
3
1
]
)
\begin{bmatrix} Z_{3}^{1} \end{bmatrix}=\begin{bmatrix} w_{3}^{1-1} w_{3}^{1-2} \end{bmatrix}*\begin{bmatrix} a_{2}^{1} \\ a_{2}^{2} \end{bmatrix}+\begin{bmatrix} b_{3}^{1} \end{bmatrix},~~~~~a_{3}^{1}=f(\begin{bmatrix} Z_{3}^{1} \end{bmatrix})
[Z31]=[w31−1w31−2]∗[a21a22]+[b31], a31=f([Z31])
浅层神经网络的反向传播
神经网络的偏导数,是相对于损失函数 L 来计算的。
第二层是直接与损失函数相邻的,所以我们就可以直接计算它的偏导数。
但第一层和损失函数没有直接联系(不相邻),所以不能直接计算。
神经网络的第一层、第二层就像是一个复合函数(函数中套了一个函数) y = f ( g ( x ) ) y = f(g(x)) y=f(g(x)) 。
微积分里,链式法则是求一个复合函数的导数的。
我们要求损失函数 L 关于 z [ 1 ] z^{[1]} z[1] 的偏导数,链式法则说:
-
损失函数 L 关于 z [ 1 ] z^{[1]} z[1] 的偏导数等于,L 关于 a [ 1 ] a^{[1]} a[1] 的偏导数 乘以 a [ 1 ] a^{[1]} a[1] 关于 z [ 1 ] z^{[1]} z[1] 的偏导数。
-
而 L 关于 a [ 1 ] a^{[1]} a[1] 的偏导数等于,L 关于 z [ 2 ] z^{[2]} z[2] 的偏导数 乘以 z [ 2 ] z^{[2]} z[2] 关于 a [ 1 ] a^{[1]} a[1] 的偏导数
计算得出:
- L 关于 a [ 1 ] a^{[1]} a[1] 的偏导数为 W [ 2 ] T d z [ 2 ] W^{[2]T}dz^{[2]} W[2]Tdz[2]
- 而 a [ 1 ] a^{[1]} a[1] 关于 z [ 1 ] z^{[1]} z[1] 的偏导数 结果不明确,因为 a [ 1 ] a^{[1]} a[1] 是一个激活函数的值,而激活函数有许多种,我们可以用 g [ 1 ] ′ ( z [ 1 ] ) g^{[1]_{'}}(z[1]) g[1]′(z[1]) 代替
综上所述,我们要求损失函数 L 关于 z [ 1 ] z^{[1]} z[1] 的偏导数:
-
d
z
[
1
]
=
W
[
2
]
T
d
z
[
2
]
∗
g
[
1
]
′
(
z
[
1
]
)
dz^{[1]}=W^{[2]T}dz^{[2]}*g^{[1]_{'}}(z[1])
dz[1]=W[2]Tdz[2]∗g[1]′(z[1]),
*
号表示矩阵元素间一一相乘,不是矩阵相乘
得到 d z 1 dz^{1} dz1 后, d W [ 1 ] dW^{[1]} dW[1] 及 d b [ 1 ] db^{[1]} db[1] 就可以通过 d z [ 1 ] dz^{[1]} dz[1] 求出来。
公式和单神经元网络,同:
- d W [ 1 ] = d z [ 1 ] x T dW^{[1]}=dz^{[1]}x^{T} dW[1]=dz[1]xT
- d b [ 1 ] = d z [ 1 ] db^{[1]}=dz^{[1]} db[1]=dz[1]
这些是针对单训练样本时的,我们再看看多训练样本:
d Z [ 2 ] = A [ 2 ] − Y dZ^{[2]}=A^{[2]}-Y dZ[2]=A[2]−Y
d W [ 2 ] = d Z [ 2 ] A [ 1 ] T m dW^{[2]}=\frac{dZ^{[2]} A^{[1]T} }{m} dW[2]=mdZ[2]A[1]T
d b [ 2 ] = n p . s u m ( d Z [ 2 ] m ) db^{[2]}=np.sum(\frac{dZ^{[2]}}{m}) db[2]=np.sum(mdZ[2])
d Z [ 1 ] = W [ 2 ] T d Z 2 ∗ g [ 1 ] ′ ( z [ 1 ] ) dZ^{[1]}=W^{[2]T} dZ^{2}*g^{[1]_{'}}(z^{[1]}) dZ[1]=W[2]TdZ2∗g[1]′(z[1])
d W [ 1 ] = d Z [ 1 ] X T m dW^{[1]}=\frac{dZ^{[1]}X^{T}}{m} dW[1]=mdZ[1]XT
d b [ 1 ] = n p . s u m ( d Z [ 1 ] , a x i s = 1 , k e e p d i m s = T r u e ) / m db^{[1]}=np.sum(dZ^{[1]}, axis=1,~keepdims = True)/m db[1]=np.sum(dZ[1],axis=1, keepdims=True)/m
很多小写字母变成了大写,这是因为向量化了,将小写的向量组合成了大写的矩阵。
还有不少式子都除以了 m,这是因为对于每个样本来说,训练得出的参数都是不同的,所以将所有样本得出的参数累加起来,而后除以一个样本总数 m 来得到一个平均值,让绝大部分样本都适用。
此外,sum 函数的参数不同了,因为第一层不再是一个神经元了,所以 d Z [ 1 ] dZ^{[1]} dZ[1] 的维度是 ( n [ 1 ] , m ) (n^{[1],m}) (n[1],m)
P.S. n 是有 n 个神经元,这里是 4,m 是 m 个训练样本。
每一个神经元都对应 m 个训练样本,所以有 4 个 m。
我们只需要将每个神经元本身对应的训练样本的结果参数累加起来(将每一行中的所有元素累加起来,不需要累积每一列中的元素)
-
asix = 1 是让 sum 只累积每一行中的所有元素。
-
keepdims 是防止 sum 输出结果变成 ( n [ 1 ] , ) (n^{[1]},) (n[1],) 这样的形式,我们需要的形式是 ( n [ 1 ] , 1 ) (n^{[1]},1) (n[1],1)
激活函数的偏导数
在上面一节中,我们计算 d Z [ 1 ] dZ^{[1]} dZ[1] 时, d Z [ 1 ] = W [ 2 ] T d Z 2 ∗ g [ 1 ] ′ ( z [ 1 ] ) dZ^{[1]}=W^{[2]T} dZ^{2}*g^{[1]_{'}}(z^{[1]}) dZ[1]=W[2]TdZ2∗g[1]′(z[1]),使用 g [ 1 ] ′ ( z [ 1 ] ) g^{[1]_{'}}(z^{[1]}) g[1]′(z[1]) 指代第一层的激活函数关于 z [ 1 ] z^{[1]} z[1] 的偏导数。
因为不同的激活函数的偏导数计算公式是不同的。
我们来看看常见激活函数的偏导数计算公式。
- S i g m o i d Sigmoid Sigmoid 函数的偏导数计算公式: g ′ ( z ) = a ( 1 − a ) g^{'}(z)= a(1-a) g′(z)=a(1−a),a 是激活函数的计算结果。
- T a n h Tanh Tanh 的偏导数计算公式: g ′ ( z ) = 1 − a 2 g^{'}(z)=1-a^{2} g′(z)=1−a2
- R e l u Relu Relu 的偏导数计算公式:当 z < 0, g ′ ( z ) = 0 g^{'}(z)=0 g′(z)=0;当 z >= 0, g ′ ( z ) = 1 g^{'}(z)=1 g′(z)=1
-
L
e
a
k
y
r
e
l
y
Leaky~rely
Leaky rely 的偏导数计算公式:当 z < 0,
g
′
(
z
)
=
0.01
g^{'}(z)=0.01
g′(z)=0.01;当
z
>
=
0
z >= 0
z>=0,
g
′
(
z
)
=
1
g^{'}(z)=1
g′(z)=1
深层神经网络:信息技术的核心思想是分层,用分层解决复杂性
一些复杂的问题只有深度神经网络才可以解决。
信息技术的核心思想是分层,用分层解决复杂性。
分层可以使简单的东西解决复杂的问题,正如搭积木。
深度神经网络的计算
深层神经网络的计算和浅层神经网络本质是一样的,浅层神经网络只有固定的俩层,而深度神经网络可以有任意层。
【前向传播】:
- z [ I ] = w [ I ] a [ I − 1 ] + b [ I ] z^{[I]}=w^{[I]}a^{[I-1]}+b^{[I]} z[I]=w[I]a[I−1]+b[I]
- a [ I ] = g [ I ] ( z [ I ] ) a^{[I]}=g^{[I]}(z^{[I]}) a[I]=g[I](z[I])
俩层计算,最开始输入值是 x x x,计算出新的输入值 a [ 1 ] a^{[1]} a[1] 后,带入公式 1 的 a [ I − 1 ] a^{[I-1]} a[I−1]。
【反向传播】:
- d z [ I ] = d a [ I ] ∗ g ′ [ I ] ( z [ I ] ) dz^{[I]}=da^{[I]}*g^{'[I]}(z^{[I]}) dz[I]=da[I]∗g′[I](z[I])
- d w [ I ] = d z I a [ I − 1 ] dw^{[I]}=dz^{I}a^{[I-1]} dw[I]=dzIa[I−1]
- d b [ I ] = d z [ I ] db^{[I]}=dz^{[I]} db[I]=dz[I]
- d a [ I − 1 ] = w [ I ] . T ⋅ d z [ I ] da^{[I-1]}=w^{[I]}.T ·dz^{[I]} da[I−1]=w[I].T⋅dz[I]
深度神经网络的流程:
- 整个过程,不停的更新 w 和 b,不停的优化 w 和 b。
神经元的内部参数,包括权重w和偏移值b,都是可调的(开始时我们会随机初始化)。用数据训练神经网络的过程,就是调整更新各个神经元的内部参数的过程。神经网络的结构在训练中不变,是其中神经元的参数决定了神经网络的功能。
反复学习是刺激神经元,相当于加大权重的确定程度(不是加大权重的大小)。一开始神经元给这个输入数据的权重是0.9,但这是一个随机的分配,有很大的不确定性。随着训练的加深,神经网络越来越相信这个权重应该是0.11,参数稳定在这里。数值,增大或者减小了不重要,关建是确定性大大增加了。
对比到人,这就好比篮球,训练的目的不是让投篮的用力越来越大,而是越来越准确。
这的确相当于大脑神经元之间的连接越来越稳固!有句话叫 fire together, wire together —— 经常在一起激发的两个神经元会“长”在一起,它们之间的电信号会更强。但是请注意,电信号强并不对应参数权重的数值大,而是对应参数更确定。这就好像发电报汇款,我收到一个很强很强的汇款信号,但这只是一笔很小的钱 —— 信号强烈只是确保钱数不会错。
参数和超参数:神经网络调音师
我们训练神经网络的目的就是要得到参数 w、b。
在构建神经网络时,还有选择学习率、神经网络的层数、每一层应该有多少个神经元、训练次数、每一层选用什么激活函数、正则化参数、代价函数的选择等······这些选择都会影响到 w、b的结果,我们称为超参数。
神经网络中的超参数主要包括:
- 学习率
- 正则化参数
- 神经网络的层数
- 每一个隐层中神经元的个数
- 学习的回合数 Epoch
- 小批量数据 minibatch 的大小
- 输出神经元的编码方式
- 代价函数的选择
- 权重初始化的方法
- 神经元激活函数的种类
- 参加训练模型数据的规模
-
1、8、10 影响学习速度,神经网络的学习速度就是代价函数的成本值的下降快慢。
-
2、3、4、7 影响预测准确率。
-
9 影响代价函数曲线下降速度,有时也会影响正确率。
-
5、6、11 影响模型预测准确率和训练用总体时间。
在实际应用中,寻找最适合的超参数是非常重要也非常大的工程,很多的时间和精力都花在了调配这些超参数上面。
一般都是先选一个常用的值,而后用这些值来慢慢的迭代调整超参数。
拟合
欠拟合与过拟合,是在实际的开发过程中必然遇到的问题。
合理拟合:如果用数学语言表达:损失函数最小。
- 损失函数的公式把这两个点相减平方求和以后得到的是,每个真实值与预测值差距的平方和,这个值衡量了我们的预测值和真实值之间的差距,越小越好。
- 损失函数用来评价模型的预测值和真实值不一样的程度,损失函数越好,通常模型的性能越好。
- 啥意思呢?我们人类学习同理,如果一个人一直不停的学,但是不验证自己的学习成果,那么有可能学的方向或者学习方法是错误的,不停的学但结果都白学了。要验证学习成果,就要判断预测结果是否准确,损失函数就是做这个的。不同的模型用的损失函数一般也不一样,所以损失函数有好多好多种。
欠拟合:划分很简单,准确率低。
解决欠拟合:
- 我们可以尝试更大的设计网络(增加神经网络的层数、增加神经元的个数)
- 增加训练次数
- 尝试其他的优化算法
- 尝试不同的神经网络架构
过拟合:
-
一丝不苟地反映已知的所有数据,那对未知数据的预测能力就会非常差。
-
这是因为所谓的 “已知” 数据,都是有误差的!精准的拟合会把数据的误差给放大 ——拟合得越精确,并不代表预测结果就越准确,拟合得过度精确后反而结果更加糟糕。
解决过拟合:
- 最佳方法就是获得更多的训练数据
- 使用正则化
- 尝试不同的神经网络架构
正则化
如果神经网络过拟合,我们就会先正则化处理。
正则化 = 丢弃,给需要训练的目标函数加上一些规则(限制),选择性的学习,可能学不到这份数据的细节,但学到的更有普适性。
有很多种正则化方法,实现主要分为俩步:
- 在成本函数后面加点东西
- 在计算偏导数的时候加点东西
如单神经元网络的成本函数:
- J ( W , b ) = 1 m ∑ i = 1 m L ( y ′ ( i ) , y ( i ) ) J(W,~b)=\frac{1}{m}\sum\limits_{i=1}^{m}L(y'^{(i)},~y^{(i)}) J(W, b)=m1i=1∑mL(y′(i), y(i))
L1正则化:
- J ( W , b ) = 1 m ∑ i = 1 m L ( y ′ ( i ) , y ( i ) ) + λ 2 m ∣ ∣ W ∣ ∣ J(W,~b)=\frac{1}{m}\sum\limits_{i=1}^{m}L(y'^{(i)},~y^{(i)})+\frac{\lambda}{2m}||W|| J(W, b)=m1i=1∑mL(y′(i), y(i))+2mλ∣∣W∣∣
L2正则化:
- J ( W , b ) = 1 m ∑ i = 1 m L ( y ′ ( i ) , y ( i ) ) + λ 2 m ∣ ∣ W ∣ ∣ 2 J(W,~b)=\frac{1}{m}\sum\limits_{i=1}^{m}L(y'^{(i)},~y^{(i)})+\frac{\lambda}{2m}||W||^{2} J(W, b)=m1i=1∑mL(y′(i), y(i))+2mλ∣∣W∣∣2
其中,
- λ \lambda λ 是一个超参数,被称为正则化参数。
- m m m 是样本数量
- W W W 是权重
一般我们用 L2,在某些情况下如高维度稀疏时,L1 更好。
这是单神经元网络的正则化,多神经元网络的正则化:
- J ( W [ 1 ] , b [ 1 ] , . . . , W [ L ] , b [ L ] ) = 1 m ∑ i = 1 m L ( y ′ ( i ) , y ( i ) ) + λ 2 m ∑ l = 1 L ∣ ∣ W ∣ ∣ 2 J(W^{[1]},~b^{[1]},...,W^{[L]},~b^{[L]})=\frac{1}{m}\sum\limits_{i=1}^{m}L(y'^{(i)},~y^{(i)})+\frac{\lambda}{2m}\sum\limits_{l=1}^{L}||W||^{2} J(W[1], b[1],...,W[L], b[L])=m1i=1∑mL(y′(i), y(i))+2mλl=1∑L∣∣W∣∣2
与单神经元网络不同的是多了一个累加操作,就是把每层的结果再累加起来。
成本函数加了东西后,接着在计算偏导数的时候加东西。
- d W [ I ] = 1 m d Z [ I ] A [ I − 1 ] T + λ m W [ I ] dW^{[I]}=\frac{1}{m}dZ^{[I]}A^{[I-1]T}+\frac{\lambda}{m}W^{[I]} dW[I]=m1dZ[I]A[I−1]T+mλW[I]
正则化加了东西后,那 dw
就变大了,所以进行梯度下降的时候,新的 w
就会更小
(
W
[
I
]
=
W
[
I
]
−
r
∗
d
W
[
I
]
)
(W^{[I]}=W^{[I]}-r*dW^{[I]})
(W[I]=W[I]−r∗dW[I])。
因为权重便携,L2 正则化也叫 “权重衰减”,可以解决过拟合的问题。
神经网络权重衰减了,那有些层数的神经元就失效了,就变成了简单的网络,有时候反而会导致欠拟合。
我们就需要调整超参数 λ \lambda λ。
在选择 λ \lambda λ 值时,目标是在简单化和训练数据拟合之间达到平衡:
- λ \lambda λ 过大,模型会非常简单,那将面临数据欠拟合的风险,无法做出有用的预测。
-
λ
\lambda
λ 过小,模型会较复杂,那将面临数据过拟合的风险,无法泛化到新数据。
dropout
除了正则化外, d r o p o u t dropout dropout 也是解决过拟合的手段。
d r o p o u t dropout dropout 原理是,随机的删除神经元,我们为每层设置一个概率数,如 0.8 意味着 80% 的神经元被保留,也能将复杂的神经网络变成简单的神经网络。
以第 2 层为例:
D2 = np.random.rand(a2.shape[0], a2.shape[1]) < keep.prob
# keep.prob 是概率数,我们要为每层设置一个概率数,如 0.8 意味着 80% 的神经元被保留
# D2 生成的元素 80% 是 1,20% 是 0
a2 = a2 * D2
# 之后用 D2 更改 a2
# 因为 D2 里面包含 20% 的 0,那 a2 * D2 后,20% 神经元就相当于被删除了
这就是实现了 Dropout ,但实现方式还有很多种,最常见的 inverted dropout(反向随机版本)。
inverted dropout 只需要在 dropout 的代码上再添一行。
a2 = a2 / keep.prob
# a2 / 0.8(概率数)
inverted dropout 就是多除了一个概率数。
因为训练时,我们删除了 20% 的神经元,预测值也相应小了,测试(实际应用)时,不能删除,否则这时用的就是简单网络。
- 训练时的预测值有缩小
- 测试时的预测值没缩小
为了保持这份平衡,我们就像求平均数那样,在训练时将 a 除以概率数即可。
Dropout 有一个问题,因为是随机删除,所以每次训练时神经网络架构都不同,成本不会随着训练次数而递减。
而在神经网络的训练过程中:
- 如果成本越来越小,神经网络是对的
- 如果成本不变、越来越大,神经网络是错的
为了查看这个过程,所以,我们先得把 Dropout 关掉,概率数设置为 1。
观察成本是否是下降的,如果是,说明神经网络是对的,再开启 Dropout 训练。
数据增强
除了正则化、Dropout外,也可以通过增加训练数据解决过拟合问题。
数据不足时,我们会使用数据增强技术来生成伪数据。
数据增强是一种生成合成数据的方法,通过调整原始样本来创建新样本。
正确编写程序
核对矩阵维度
在具体编程时,会有一部分问题是矩阵维度不对引起的,而且 numpy
有时还会改变矩阵的维度,所以我们需要经常核对矩阵维度。
比如每个函数为了安全起见,开头都会做一个边界判断,我们编程时,还要额外加一个核对矩阵维度的判断(使用 assert
即可)。
归一化
一些数据不能计算,我们想办法把数据转为可计算形式。
归一化把数据变成 ( 0 , 1 ) (0,1) (0,1) 或者 ( 1 , 1 ) (1,1) (1,1) 之间的小数,方法有很多种。
主要是为了数据处理方便提出来的,把数据映射到 0 ~ 1 0~1 0~1 范围之内处理,更加便捷快速。
因为归一后,使得输入值落在激活函数敏感的区域(激活函数在 0 附近的梯度都比较大),这样可以大幅度提升神经网络的学习速度。
配置数据集
小型数据集,我们会把数据分成训练集和测试集,训练集占70%,测试集占30%。
大规模数据时,通常是 98:2。
具体的分配比例,也是一个超参数,从一个常用的值开始,慢慢迭代调整超参数。
梯度消失与梯度爆炸
梯度消失、梯度爆炸是反向传播算法与生俱来的缺陷。
梯度消失就是越来越小,小到几乎没了,很多层学习速度都会放慢很多倍,深度神经网络变成浅层神经网络。
梯度消失经常出现在深层网络、采用了不合适的激活函数。
梯度爆炸就是越来越大,大到计算机都显示不出来了。
梯度爆炸经常出现在深层网络、权值初始化值太大。
常见的延缓梯度爆炸和消失的方法,如更加合理的初始化神经网络的权重 — 初始化靠近 0 的值,反而会避免梯度消失和梯度爆炸。
w[I] = np.random.randn() * np.sqrt(u / n[I-1])
# n[I-1] 是上一层的神经元个数,本层的特征输入个数
# u 是一个可调参数,不同激活函 u 不同,这个值主要是让上一层的神经元个数多,那 w 就越靠近 0
除此之外还有 L2 权重正则化、relu 激活函数,batchnorm、残差结构、LSTM、梯度剪切等。
如何判断网格中有漏洞
如果神经网络的算法或者代码本身就有漏洞,那调整超参数也不到好结果的。
所以,在调参前,我们会要判断神经网络中是否存在漏洞。
比如通过交叉验证的梯度检验就是其中一种判断方法。
在反向传播中得到梯度,这时我们再用另一种方法求得梯度,如果这个梯度与反向传播中得到的梯度差不多,那梯度就是正确的。
求导有俩种方法,一个是数值微分法,另一个是中心差分法。
# 数值微分法
def numerical_diff_1(f, x):
h = 1e-4
return (f(x+h) - f(x))/h
# 中心差分法
def numerical_diff_2(f, x):
h = 1e-4
return (f(x+h) - f(x-h))/2h
看用这些方法逼近求出的偏导数与反向传播求出的偏导数是否差不多即可。
H5文件
H5 文件(层次数据格式第 5 代),保存科学数据的一种文件格式和库文件,HDFView
软件查看里面的内容。
H5的文件结构包括俩个主要的对象类型:
- 数据集是多维数组(同一类型)
- 组是一种容器结构,若一个文件保存不同种类的数据集,就需要用组。
H5 文件的优点,除了数据本身之外,还有很多元信息(来解释数据)。
import h5py
import numpy as np
# HDF5的写入
imgData = np.zeros((30,3,128,256))
f = h5py.File('HDF5_FILE.h5','w') # 创建一个h5文件,文件指针是f
f['data'] = imgData # 将数据写入文件的主键data下面
f['labels'] = range(100) # 将数据写入文件的主键labels下面
f.close() # 关闭文件
import h5py
# HDF5的读取
f = h5py.File('HDF5_FILE.h5','r') # 打开h5文件
f.keys() # 可以查看所有的主键
a = f['data'][:] # 取出主键为data的所有的键值
f.close()
优化算法
小批量梯度下降法
我们知道训练神经网络是要很多数据的,动不动 200G 以上,这么大的数据量,电脑一次性都装不进。
从前有一个将军,要攻打一座城。攻城需要很多士兵同时发动进攻才好。可是这座城周围的道路都很窄小,并不适合大军通过。这怎么办呢?
将军知道通往这座城的道路有很多条,于是他把士兵分散开,以小队的形式从不同的道路出发,按照约定时间一起到达,结果就把城给攻下来了。
我们也是,可以将庞大的训练集拆分成一个个小的训练集,而后依次用小批量训练集来训练神经网络。
子训练集的大小,也会影响神经网络的训练效率,也是超参数。
一般选择 2 n 2^{n} 2n 次方,如 1024。
【扩展】:
指数平均加权
其实还有别的方法也可以,让神经网络学得更快并且方向更正确。
指数加权平均,是一种序列数据处理方式,通过计算局部的平均值,近似求平均,来描述数值的变化趋势。
- 指数加权平均公式: v t = β ∗ v t − 1 + ( 1 − β ) θ t v_{t}=\beta*v_{t-1}+(1-\beta)\theta_{t} vt=β∗vt−1+(1−β)θt
举个例子,假设我们现在有一年中每一天的温度数据,将其绘制为散点图如下:
我们可以通过指数加权平均,得到一条线来描述整个温度趋势。
在这个案例中, v t = β ∗ v t − 1 + ( 1 − β ) θ t v_{t}=\beta*v_{t-1}+(1-\beta)\theta_{t} vt=β∗vt−1+(1−β)θt:
- v t v_{t} vt:第 t 天的平均温度(预测值)
- θ t \theta_{t} θt:第 t 天的温度
- β \beta β:超参数
超参数 β = 0.9 \beta= 0.9 β=0.9,式子代入:
-
V 0 = 0 V_{0}=0 V0=0
-
V 1 = 0.9 V 0 + 0.1 θ 1 V_{1}=0.9V_{0}+0.1θ_{1} V1=0.9V0+0.1θ1
-
V 2 = 0.9 V 1 + 0.1 θ 2 V_{2}=0.9V_{1}+0.1θ_{2} V2=0.9V1+0.1θ2
-
V 3 = 0.9 V 2 + 0.1 θ 3 V_{3}=0.9V_{2}+0.1θ_{3} V3=0.9V2+0.1θ3
-
……
-
V t = 0.9 V t − 1 + 0.1 θ t V_{t}=0.9V_{t−1}+0.1θ_{t} Vt=0.9Vt−1+0.1θt
-
当天的趋势值 = 0.9 ∗ 上一天的趋势值 + 0.1 ∗ 当天的温度 当天的趋势值 = 0.9 * 上一天的趋势值 + 0.1 * 当天的温度 当天的趋势值=0.9∗上一天的趋势值+0.1∗当天的温度
超参数 β \beta β 决定了趋势值 v 受到前面多少天的影响。
- β = 0.9 \beta = 0.9 β=0.9,那 1 1 − 0.9 = 10 \frac{1}{1-0.9}=10 1−0.91=10,v 受前 10 天的影响
- β = 0.98 \beta = 0.98 β=0.98,那 1 1 − 0.98 = 50 \frac{1}{1-0.98}=50 1−0.981=50,v 受前 50 天的影响
- β \beta β 越大,v 受前 n 天的影响越多(受当天的影响越小),线条也会越平滑(0.98 的绿线比 0.9 的红线平滑)
假设 t = 100 、 b e t a = 0.9 t = 100、beta = 0.9 t=100、beta=0.9,得到指数平均公式:
化简开得到如下表达式(注意相同颜色):
v 100 = 0.1 θ 100 + 0.1 ∗ 0.9 θ 99 + 0.1 ∗ 0. 9 2 θ 98 + 0.1 ∗ 0. 9 3 θ 97 + ⋅ ⋅ ⋅ v_{100}=0.1\theta_{100}+0.1*0.9\theta_{99}+0.1*0.9^{2}\theta_{98}+0.1*0.9^{3}\theta_{97}+··· v100=0.1θ100+0.1∗0.9θ99+0.1∗0.92θ98+0.1∗0.93θ97+⋅⋅⋅
本质就是以指数式递减加权的移动平均,各数值的加权而随时间而指数式递减,越近期的数据加权越重,但较旧的数据也给予一定的加权。
v 100 v_{100} v100 就是由前 100 天温度的一小部分组合而成的,越前面权重越小,第 100 天的温度 v 越不受前面温度的影响。
b e t a = 0.9 beta = 0.9 beta=0.9,那每个温度的权重是 0.1,10 个 0.1 加起来等于 1,所以当天的 v 就相当于前 10 天温度的平均值( 1 1 − 0.9 = 10 \frac{1}{1-0.9}=10 1−0.91=10)。
其实前 10 天的平均值,我们也可以通过求平均值公式来计算呀:
- v = v 1 + v 2 + v 3 + v 4 + ⋅ ⋅ ⋅ + v 10 10 v = \frac{v_{1}+v_{2}+v_{3}+v_{4}+···+v_{10}}{10} v=10v1+v2+v3+v4+⋅⋅⋅+v10
因为编程实现指数加权平均的空间复杂度是常数:
v = 0
for t in 100:
v = b * v + (1 - b) * c[t]
而普通的求平均值,需要线性空间复杂度。
普通方法求平均值,从0到某一时刻(n)的平均值的时候,要保留所有的时刻值,求和,除以 n。
指数加权平均求平均值,只需要保留 0 - (n-1) 时刻的平均值和n时刻的温度值即可。也就是每次只需要保留常数值,再运算即可,这对于深度学习中的海量数据来说,是一个很好的减少内存和空间的做法。
动量梯度下降
训练神经网络就是要找着最小值,如下图的五角星⭐️:
梯度下降会如上图的红色折条,一步一步的不停的寻找最小值。
我们知道俩点之间直线距离最短,而梯度下降是来回的折线,做了很多无用功。
如何让曲线折叠的幅度更小一些呢?
这主要是因为,梯度下降有震荡(图中折线频繁更改方向)。
-
b e t a = 0.5 beta = 0.5 beta=0.5,黄线震荡的最厉害。
-
b e t a = 0.9 beta = 0.9 beta=0.9,红线震荡其次。
-
b e t a = 0.98 beta = 0.98 beta=0.98,绿线平滑。
回想指数加权平均,如果把之前下降的方向考量就来,那就会减少震荡。
使用指数加权平均优化梯度下降,使得搜索曲线更加平滑,震荡幅度更小。
这种优化的梯度下降,不止有常规的梯度外,额外增加了前几步中的动量,使得在运动方向上保持运动的趋势。
具体细节:和梯度下降一样是求出梯度 d w 、 d b dw、db dw、db,再用指数平均加权求出趋势值 V d w 、 V d b V_{dw}、V_{db} Vdw、Vdb。
- V d w = b ∗ V d w − 1 + ( 1 − b ) d w V_{dw}=b*V_{dw-1}+(1-b)dw Vdw=b∗Vdw−1+(1−b)dw
- V d b = b ∗ V d b − 1 + ( 1 − b ) d b V_{db}=b*V_{db-1}+(1-b)db Vdb=b∗Vdb−1+(1−b)db
用趋势值 V d w 、 V d b V_{dw}、V_{db} Vdw、Vdb 代替 d w 、 d b dw、db dw、db。
- w = w − r ∗ V d w w = w - r*V_{dw} w=w−r∗Vdw
-
b
=
b
−
r
∗
V
d
b
b=b-r*V_{db}
b=b−r∗Vdb
RMSprop
RMSprop 原理基本同动量梯度下降,也是一种优化方法。
我们想象梯度下降是在推一个球,把球推到最小值处。
而推这个球的,再假设有俩个方向的力。
- 一个是横向的力
- 一个是纵向的力,导致了锯齿,力越大锯齿也就越大。
我们再再假设参数 w 于横向的力相关,参数 b 与纵向的力相关。
P.S. 这里只是假设,实际了参数是多维的,梯度下降的方向是由很多个维度的力共同影响的。
按照上面对 w、b 的假设,通过 RMSprop 可以改变 w、b 方向上的力的相对大小,影响球的走向。
算法过程:
- 算出 d w 、 d b dw、db dw、db
- 算出指数平均值 S d w S_{dw} Sdw
S d w = k S d w + ( 1 − k ) d w 2 S_{dw}=kS_{dw}+(1-k){dw}^{2} Sdw=kSdw+(1−k)dw2
- 算出 S d b = k S d b + ( 1 − k ) d b 2 S_{db}=kS_{db}+(1-k)db^{2} Sdb=kSdb+(1−k)db2
- 更新 w 、 b w、b w、b
w = w − r ( d w S d w ) w = w-r(\frac{dw}{\sqrt{S_{dw}}}) w=w−r(Sdwdw)
b = b − r ( d b S d b ) b =b-r(\frac{db}{ \sqrt{S_{db} }} ) b=b−r(Sdbdb)
算法是如何消除锯齿的呢?
锯齿的产生是因为纵向的力大 ( d b 大、 d w 小 db 大、dw 小 db大、dw小)。
d w 小 dw 小 dw小,那 S d w S_{dw} Sdw 也会小,如此第四步中横向力 w w w 大。
d b 大 db 大 db大,那 S d b S_{db} Sdb 大,如此第四步中纵向力 b b b 小。
RMSprop 会尽量避免某个方向的力过大或过小,平衡横向力、纵向力,如果俩种力相同,那锯齿的角度就变成了 45 度,震荡会比之前小很多。
Adam优化算法
Adam 其实就是将动量梯度下降和 RMSprop 结合在一起。
从效率上看,动量梯度下降 < RMSprop < Adam。
Adam 主要步骤:
- 算出 d w 、 d b dw、db dw、db
- 求出动量指数平均值,算出 V d w 、 V d b V_{dw}、V_{db} Vdw、Vdb
V d w = k 1 V d w + ( 1 − k 1 d w ) V_{dw}=k_{1}V_{dw}+(1-k_{1}dw) Vdw=k1Vdw+(1−k1dw)
V d b = k 1 V d b + ( 1 − k 1 d b ) V_{db}=k_{1}V_{db}+(1-k_{1}db) Vdb=k1Vdb+(1−k1db)
动量梯度下降、RMSprop 中都有一个超参数,动量指数平均值中的超参数设为 k 1 k_{1} k1,RMSprop 中的超参数设为 k 2 k_{2} k2。
- 求出 RMSprop 指数平均值,算出 S d w 、 S d b S_{dw}、S_{db} Sdw、Sdb
S d w = k 2 S d w + ( 1 − k 2 d w 2 ) S_{dw}=k_{2}S_{dw}+(1-k_{2}dw^{2}) Sdw=k2Sdw+(1−k2dw2)
S d b = k 2 S d b + ( 1 − k 2 d b 2 ) S_{db}=k_{2}S_{db}+(1-k_{2}db^{2}) Sdb=k2Sdb+(1−k2db2)
动量梯度下降用的是梯度的指数加权平均,RMSprop 使用的是梯度平方的指数加权平均。
- 对指数平均值进行修正,算出 V d w c 、 V d b c V_{dw}^{c}、V_{db}^{c} Vdwc、Vdbc
V d w c = V d w ( 1 − k 1 t ) V_{dw}^{c}=\frac{V_{dw}}{(1-k_{1}^{t})} Vdwc=(1−k1t)Vdw
V d b c = V d b ( 1 − k 1 t ) V_{db}^{c}=\frac{V_{db}}{(1-k_{1}^{t})} Vdbc=(1−k1t)Vdb
- 对指数平均值进行修正,算出 S d w c 、 S d b c S_{dw}^{c}、S_{db}^{c} Sdwc、Sdbc
S d w c = S d w ( 1 − k 2 t ) S_{dw}^{c}=\frac{S_{dw}}{(1-k_{2}^{t})} Sdwc=(1−k2t)Sdw
S d b c = S d b 1 − k 2 t S_{db}^{c}=\frac{S_{db}}{1-k_{2}^{t}} Sdbc=1−k2tSdb
- 通过俩个指数平均值来更新参数,算出 w 、 b w、b w、b
w = w − r ( V d w c S d w c + u ) w=w-r(\frac{V_{dw}^{c}}{\sqrt{S_{dw}^{c}+u}}) w=w−r(Sdwc+uVdwc)
b = b − r ( V d b c S d b c + u ) b=b-r(\frac{V_{db}^{c}}{\sqrt{S_{db}^{c}+u}}) b=b−r(Sdbc+uVdbc)
P.S. 分母不能为 0,所以我们额外加一个 u。
在 Adam 中,有三个超参数( r 、 k 1 、 k 2 r、k_{1}、k_{2} r、k1、k2)。
一般不变, k 1 = 0.9 、 k 2 = 0.999 k_{1}=0.9、k_{2}=0.999 k1=0.9、k2=0.999,而学习率 r r r 尝试不同的值。
修正的原因:加修正是因为,用指数加权平均计算趋势值时,越是前面的几天越是不准确(太小了,和实际值相差很远)。
-
v 0 = 0 v_{0}=0 v0=0
-
v 1 = 0.9 v 0 + 0.1 θ 1 v_{1}=0.9v_{0}+0.1\theta_{1} v1=0.9v0+0.1θ1
超参数选 0.9 根据公式算,预测第一天温度4度,但已知(假设)的第一天温度是 40 度 θ 1 \theta_{1} θ1。
修正公式:
v
t
=
v
t
(
1
−
k
t
)
v_{t}=\frac{v_{t}}{(1-k^{t})}
vt=(1−kt)vt,如预测第一天温度
v
1
=
4
1
−
0.
9
1
=
40
v_{1}=\frac{4}{1-0.9^{1}}=40
v1=1−0.914=40
学习率衰减
之前超参数学习率 r 都是一个固定值,其实让学习率随着时间慢慢减小,有助于提升效率。
学习率衰减有多种方式。
迭代衰减: r = 1 ( 1 + d e c a y R a t e ∗ e p o c h N u m ) ∗ r 0 r=\frac{1}{(1+decayRate * epochNum)}*r_{0} r=(1+decayRate∗epochNum)1∗r0
- decayRate:超参数,用于控制学习衰减的速度
- epochNum:是整个训练集训练的次数
- r 0 r_{0} r0:超参数,初始的学习率
- r r r:学习率,随着 epochNum 次数越来越多,学习率会越来越小。
指数衰减: r = 0.9 5 e p o c h N u m ∗ r 0 r=0.95^{epochNum}*r_{0} r=0.95epochNum∗r0,学习率呈指数下降。
在模型优化中,常用到的几种学习率衰减方法有:
- 分段常数衰减
- 多项式衰减
- 指数衰减
- 自然指数衰减
- 余弦衰减
- 线性余弦衰减
- 噪声线性余弦衰减
请猛击:《常见学习率衰减方式》。
局部最优问题
我们接触到的超参数越来越多,而这些超参数都是需要调的,我们如何系统的调这些超参数呢?
如上图的小坑就是局部最优、中间最深的坑就是全局最优(最小值处)。
但实际情况是,上述现象只在参数很少的时候容易出现,比如只有两个参数w1,w2。
神经网络是多维的,在多维的图像中那些小坑是不会阻碍我们找到全局最优。
当我们有很多参数时,在梯度为0且每个方向都是凸函数的点(即局部最优点)是很难出现的。
局部最优的意思,一旦进入小坑就没有路走了,但多维图像中,那些小坑中依然有维度可以通向全局最优。
那些有路通往全局最优的小坑是凹函数(鞍点)。
从上图可以看出,梯度下降搜索过程中,鞍点的横向维度向上翘,没有路了,但纵向是可以往下走的。
在维度很多的神经网络里,其实局部最优问题是困不住搜索算法的。
其实我们需要关注的问题在于平稳段。平稳段指的是导数长时间接近于0的一段区域,这会减慢学习效率。如下图所示:
小坑底部的梯度是 0,没有斜率,而靠近小坑的那片区域的斜率也很小,这里是平稳段,斜率小意味着学习得会很慢。
调试神经网络
调参之所以复杂,是因为超参数太多。
在开始调试超参数之前,我们需要知道哪些超参数是需要优先被调整的,而哪些超参数是没有必要进行调整的。
不同超参数的调整对结果的影响程度是不同的。我们应该优先调整对模型影响较大的超参数。例如(按照超参数的重要性降序排列):
- 第一,学习率 r r r
- 第二,动量梯度下降 b e t a beta beta、每层的神经元个数 n n n、子训练集大小
- 第三、神经网络层数 L L L、学习率衰减控制超参数 d e c a y R a t e decayRate decayRate
- 第四,Adam 超参数 k 1 、 k 2 k_{1}、k_{2} k1、k2
网格搜索法:在网格中均匀的取点,常用于优化三个或者更少数量的超参数,本质是一种穷举法。
例如,我们有三个需要优化的超参数 A、B、C,候选的取值分别是 {1,2},{3,4},{5,6}。则所有可能的参数取值组合组成了一个 8 个点的 3 维空间网格如下:
- ( 1 , 3 , 5 ),( 1 , 3 , 6 ),( 1 , 4 , 5 ),( 1 , 4 , 6 ),( 2 , 3 , 5 ),( 2 , 3 , 6 ),( 2 , 4 , 5 ),( 2 , 4 , 6 ) {(1,3,5),(1,3,6),(1,4,5),(1,4,6),(2,3,5),(2,3,6),(2,4,5),(2,4,6)} (1,3,5),(1,3,6),(1,4,5),(1,4,6),(2,3,5),(2,3,6),(2,4,5),(2,4,6)
网格搜索就是通过遍历这 8 个可能的参数取值组合,进行训练和验证,最终得到最优解。
但其计算复杂度将随需要优化的超参数规模呈指数增长,因此该方法只适用于规模很小的超参数优化问题。
后来有人提出更好的随机搜索法,在搜索次数相同时,随机搜索相对于网格搜索会尝试更多的参数值。
关于调参技巧:
- 学习率:一般从 0.1、0.01 开始,学习率一般随着训练衰减
- 子训练集大小:32、64、128、256
- 学习的回合数 Epoch:如果随着Epoch次数增加,准确度在某些次数内下降了,就停止Epoch
- 隐含层神经元数 Hidden Units:模型越复杂隐含层数越多,但太大的模型会过拟合,当隐含层数直到 validation error 变差即可
- 激活函数选择:多分类任务 softmax、二分类 sigmoid、回归任务线性输出、中间隐层 relu
为调参选择采样标尺
随机搜索法也就是在合理的取值范围之内随机的选取一些点,这个选取的过程也可以叫做采样。
采样是指从总体中抽取个体或样品单过程,分随机抽样、非随机抽样俩种类型。
-
随机抽样:按照随机化原则从总体中抽取样本的抽样方法,客观。
-
非随机抽样:一种凭研究者的观点、经验或者有关知识来抽取样本的方法,主观。
随机搜索法,核心是随机均匀,每个区间段都会取,而不是偏向某个区间段。
只是应该取什么区间段呢?
-
线性: 1 、 10 、 20 、 30 1、10、20、30 1、10、20、30,适合神经网络的层数、神经元个数
-
指数: 2 、 4 、 8 、 16 2、4、8、16 2、4、8、16,适合学习率。
编程实现采样过程:
e = -4 * np.random.rand()
# 生成 [-4, 0] 之间的随机数
r = math.pow(10, e)
# 指数学习率,最小是 0.0001,最大是 1
调参经验
超参数集中初始化:神经网络的超参数多,我们就可以把超参数的初始化集中在一起。
记录:训练数据遍历一轮后,就把训练集和测试集准确率记录下来。训练一段时间后,
-
如果模型一直没有收敛(向某一值靠近),就停止训练,尝试其他参数。
-
如果(训练到最后)训练集、测试集预测准确率很低,说明模型都很低,说明模型可能欠拟合。
后续调参就是增强模型的拟合能力,如增加网络层数、节点数、减少 dropout 值、L2 正则值等。
- 反之较高的话,说明模型可能过拟合,就需要提高模型泛化。
最小算例:一般在小数据集上合适的参数,在数据集上效果也不会太差。我们可以对训练数据进行采样,只选取少部分数据实验。
超参数范围:一些超参数,如学习率、正则化项,优先在指数区间段进行超参数搜索。
经验参数:
-
学习率,一般从 1 开始,衰减系数一般是 0.5
-
网络层数:从 1 层开始
-
每层节点数:16、32、128
-
子训练集:128
-
限制最大梯度:5、10、15
-
dropout:0.5
-
L2 正则:1
-
词向量大小:128、256
-
正负样本比例:比例平衡
其他:
调参模式和工具
调参模式:
-
一、同一时刻只训练几个以内的模型来实时调参,适用于海量数据,但因计算设备有限无法同时训练多个模型时。
我们得观察模型的状况,一旦参数调整导致误差变大,就还原到上一次的状态
-
二、如果计算设备够,就可以同时训练多个的模型。
只需要给每个模型设置不同的超参数值,而后物竞天择,挑出表现好的即可。
调参工具:
隐藏层输入值归一化
对隐藏层的输入值 z 归一化:
- 使 z 的平均值成为 0
u = 1 m ∑ i = 1 m z ( i ) u=\frac{1}{m}\sum\limits_{i=1}^{m}z^{(i)} u=m1i=1∑mz(i)
z = z − u z=z-u z=z−u
- 使 z 的方差变成 1
σ = 1 m ∑ i = 1 m ( z ( i ) ) 2 \sigma=\frac{1}{m}\sum\limits_{i=1}^{m} ( z^{(i)} )^{2} σ=m1i=1∑m(z(i))2
z = z − u σ + ϵ z=\frac{z-u}{\sqrt{\sigma+\epsilon}} z=σ+ϵz−u
- 使 z 的分布区域可以被任意挪动
z = γ z + β z=\gamma z+\beta z=γz+β
隐藏层输入值归一化的好处:
- 便于梯度下降更快找到最小值
- 提升隐藏层学习效率
softmax
如果我们想让神经网络从二分类(是或否)变成多分类,就需要 softmax。
softmax 激活函数:
- 用输出层神经元相关的 z 算出一个临时变量 t( t = e z t = e^{z} t=ez)
- 用 z 算出 a(
a
=
t
n
p
.
s
u
m
(
t
)
a=\frac{t}{np.sum(t)}
a=np.sum(t)t)
评估与决策
正交化
神经网络中需要调的地方太多,当遇到一个问题时,我们需要学会调东西解决这个问题,且不会产生别的问题。
你需要掌握些概念,如正交化。
正交化:一个任务可切分出独立的子任务,其中一个任务改变时,其他子任务不受影响。
- 子任务:训练集表现好
- 子任务:测试集表现好
- 子任务:应用时表现好
如果训练集的准确度上升了,且没有把测试集、应用时的的准确度拉下去,那这就是正交化设计。
F1 值
衡量神经网络的准确程度,我们会设置一个指标。
衡量准确程度,通常有 3 3 3:
- p r e c i s i o n precision precision:精确率,简称 P P P 值;
- r e c a l l recall recall:召回率,简称 R R R 值;
- F 1 F_{1} F1:精确率、召回率的调和平均值,简称 F 1 F_{1} F1 值。
这些名词都属于预测,要理解他们的计算方法,得先理解混淆矩阵。
比如,我们预测老王家的孩子是男、是女。
预测\答案 | 男 | 女 |
---|---|---|
男 | 男男 | 男女 |
女 | 女男 | 女女 |
一共有 4 4 4 种组合:
-
男男:预测是 男,答案是 男;
-
男女:预测是 男,答案是 女;
-
女女:预测是 女,答案是 女;
-
女男:预测是 女,答案是 男;
记男性为正类P,女性为负类N:
预测\答案 | P | N |
---|---|---|
P | TP | FP |
N | FN | TN |
只要混淆矩阵确立了,3 个准确指标就都确定了。
-
p r e c i s i o n = 预测结果中正类数量占全部结果的比率 = T P T P + F P precision = 预测结果中正类数量占全部结果的比率=\frac{TP}{TP+FP} precision=预测结果中正类数量占全部结果的比率=TP+FPTP
-
r e c a l l = 正类样本被找出来的比率 = T P T P + F N recall = 正类样本被找出来的比率=\frac{TP}{TP+FN} recall=正类样本被找出来的比率=TP+FNTP
通常 p r e c i s i o n precision precision 越高, r e c a l l recall recall 就越低,反之亦然,而我们习惯使用一个指标。
所以,我们需要一个综合性的指标,精确率、召回率的调和平均值 F 1 F_{1} F1。
- F 1 = 2 ∗ P ∗ R P + R F_{1}=\frac{2*P*R}{P+R} F1=P+R2∗P∗R
如果 P、R 一个值特别低,另一个特别高,那么 F1 也会特别低,只有俩个值都很高时,得到的值才会很高。
选择神经网络时,不仅看预测准确度,任何算法都有俩个指标,所需要的时间、空间。
最重要的是那个,在规定的时间、空间,且预测效果最好。
这种情况下,F1 又被称为优化指标(择优),时间、空间称为满足指标(必达)。
恰当拟合:欠拟合、过拟合的分界线
我们训练神经网络的目的,是为了恰当拟合。
只是恰当拟合的标准在哪里呢?
是神经网络能达到的理论最高,此时无论我们做什么都无法提高了,但这个最优误差是 0.1%,还是 0.001% 我们是不清楚的。
所以,我们会参考人类的最优误差,借助人类误差,推测理论上最优误差。
而后能分析系统的拟合度了:
- 最优误差 - 训练集误差 = 欠拟合误差
- 训练集误差 - 验证集误差 = 过拟合误差
于是:
- 欠拟合误差 > 过拟合误差,解决欠拟合问题
- 欠拟合误差 < 过拟合误差,解决过拟合问题
而后就针对那个情况做处理。
神经网络调音师的自我修养
手工分析
特别的一点,我们得在项目开始前对几个数据集进行手工分析,确保没什么错误。
数据集总有一些未知的错误,如果咋们可以发现他们,那对性能的提升不是一点半点。
系统性标记错误:把老虎标成猴子,把青蛙标成猫,把码头标成纸巾……MIT、Amazon 的一项研究表明,ImageNet 等十个主流机器学习数据集的测试集平均错误率高达 3.4%。