单层的感知机不能解决“异或”问题。
按照这个思路,我们可以考虑在输入层和输出层之间添加一层神经元,将其称之为隐藏层(Hidden Layer,又称“隐含层”或“隐层”)。
神经网络模型
深度神经网络(Deep Neural Network,DNN)— 将若干个单层神经网络级联在一起,前一层的输出作为后一层的输入。
深度神经网络模型解决回归问题。
前向传播 — 从输入层出发,逐层推进,将上一层的输出与权重结合后,作为下一层的输入,并计算下一层的输出,如此进行,直到运算到输出层为止。
𝑓(0.2×(?0.43)+0.5×(?0.37))=0.43
𝑓(0.2×0.04+0.5×0.40)=0.55
𝑓(0.2×0.04+0.5×0.40)=0.55
𝑓(0."43"×(?0."51")+0."55"×(?0.35)+"0.48"×"0.26"))=0.43
𝑓(0."43"×(?0."34")+0."55"×(?0."44")+"0.48"×"0.28")=0.44
𝑓(0."43"×"0.55"+0."55"×(?0.31)+"0.48"×"0.06")=0."53"
𝑓(0."43"×"0.36"+0."44"×(?0.30)+"0.53"×"0.24")=0."54"
𝑓(0."43"×"0.27"+0."44"×"0.35"+"0.53"×(?"0.31"))=0."53"
计算损失 — 利用损失函数来调节网络中的权重,进而减小损失函数,即使得输入经过权重能准确的预测出实际值。
计算损失,在机器学习中的“有监督学习”算法里,在假设空间𝛤中,构造一个决策函数𝑓, 对于给定的输入𝑥,由𝑓(𝑥)给出相应的输出 ,这个实际输出值ˉ𝑌和原先预期值𝑌可能不一致。于是,需要定义了一个损失函数(Loss Function),也有人称之为代价函数(Cost Function)来度量两者之间的“落差”程度。这个损失函数通常记作𝐿(𝑌,𝑌??)=𝐿(𝑌,𝑓(𝑋)),为了方便起见,这个函数的值为非负数。损失函数值越小,说明实际输出ˉ𝑌和预期输出𝑌之间的差值就越小,也就说明构建的模型越好。因此神经网络学习的本质,其实就是利用损失函数来调节网络中的权重,进而减小损失函数,即使得输入经过权重能准确的预测出实际值。
对于不同类型的问题,通常会使用不同的损失函数。比如对于回归问题,一般会使用均方差损失MSE,或平均绝对误差损失MAE。均方差损失和平均绝对误差损失的计算公式如下方所示,𝑦_𝑖为维度𝑖的真值,𝑦??_𝑖为预测值,也就是输出。MSE计算的是每一项误差的平方,而MAE计算的是每一项误差的绝对值。𝑀𝑆𝐸=1/𝑛 ∑2_(𝑖=1)^𝑛?(𝑦_𝑖?𝑦??_𝑖 )^2 、𝑀𝐴𝐸=1/𝑛 ∑_(𝑖=1)^𝑛?|𝑦_𝑖?𝑦??_𝑖 |
对于分类问题,MSE和MAE也是可用的,但更多情况下,使用的是交叉熵损失函数。确切地说,是将Softmax激活函数和交叉熵损失CrossEntropy搭配使用,交叉熵函数表达式如图中"CrossEntropy"所示。这一点,我们在后续会进行讲解。
此处,使用MSE来作为损失函数来计算损失。在前向传播中通过给定的权重计算出的实际输出为0.54和0.53,因此总的损失应该是(0.3-0.54)的平方加上(0.8-0.53)的平方,再除以2,计算的到的结果是 0.066,即为我们在这一次前向传播过程中预测值与真值的偏差。
反向传播——沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。
反向传播,所谓反向传播,就是沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。以神经网络为例,从输出层出发,首先被更新的,应该是输出层与第二个隐藏层之间的权重矩阵 ,第二被更新的,是两个隐藏层之间的权重矩阵𝑤^′,最后被更新的是输入层和第一个隐藏层的权重矩阵𝑤,示意图如图所示。
在训练中,反向传播需要搭配优化算法才能实现参数的更新。接下来就来介绍一个经典优化策略——梯度下降。
我们训练的最终目标就是让损失函数的值最小,因为当损失函数值最小的时候,预测值最接近真值。当然最理想的情况是损失为零,此时预测值与真值完全相符。由于模型复杂,所以无法知道损失函数的全貌,那我们要怎么决定向哪个方向改变呢?
就像我们下山一样,如果遇到了雾,无法看见山路的全部走向,只能看见周围的一小片区域。那么就只能基于能看见的部分做一个决策。(忽略主观因素)如果想尽快下山的话,那么我们应该找最陡峭的方向,因为陡峭的路在海拔上变化更快。大方向确定了,那么现在可以在二维平面考虑这个问题。如果发现自己目前是在向上爬坡的,比如左图那样,说明我身后的海拔比我此时的海拔更低。因此,为了降低海拔,那么应当掉头向反方向走,这样更容易走到山脚,如图右边。
反之,如果此时发现自己是在向下走的,那么我应该继续前进,因为我前方的海拔比我此时的海拔低,我更容易走到山脚。在这个场景下,“上”坡和“下”坡,如果换用数学语言来描述,其实就是在坐标系下,这个山的斜率(梯度)符号。上坡就是斜率大于0,下坡就是斜率小于0。那上坡时,我们要选择的是 减小的方向,下坡时选择的是 增大的方向。也就是说我们总是沿着斜率的反方向来走的。
反向传播与更新参数——沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。
参数更新前权重矩阵和计算结果。
一次反向传播后权重矩阵和计算结果。
分类问题
In [34]
import numpy
def sigmoid (x): #激活函数
return 1/(1+numpy.exp(-x))
def der_sigmoid(x): #激活函数的导数
return sigmoid(x)*(1-sigmoid(x))
def mse_loss(y_tr,y_pre): #均方误差损失函数
return((y_tr - y_pre)**2).mean()
class nerualnetwo():
def __init__(self): #感知神经元的权值属性定义,初始化随机值,会导致模型以及预测结果每次都不同
self.w1 = numpy.random.normal()
self.w2 = numpy.random.normal()
self.w3 = numpy.random.normal()
self.w4 = numpy.random.normal()
self.w5 = numpy.random.normal()
self.w6 = numpy.random.normal()
self.b1 = numpy.random.normal()
self.b2 = numpy.random.normal()
self.b3 = numpy.random.normal()
def feedforward(self,x): #前向计算方法,#返回所有神经元值
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1f*self.w5+h2f*self.w6+self.b3
of = sigmoid(o1)
return h1,h1f,h2,h2f,o1,of
def simulate (self,x): #前向计算方法,返回预测值
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1f*self.w5+h2f*self.w6+self.b3
of = sigmoid(o1)
return of
def train(self,data,all_y_tr):
epochs = 1000 #迭代次数
learn_rate = 0.1 #学习率
#print(self.w1)
for i in range(epochs):
for x , y_tr in zip(data,all_y_tr):
valcell = self.feedforward(x) #使用当前权值前向计算所有神经元值
#h1-cell[0],h1f-cell[1],h2-cell[2],h2f-cell[3],o1-cell[4],of-cell[5]
y_pre = valcell[5] #当前预测结果
#反向传播求导计算,每个节点或权值仅仅需要求导一次,即可覆盖下方所有节点
der_L_y_pre = -2*(y_tr-y_pre) #损失函数L对y_pre求导
#y_pre对h1f求偏导,y_pre就是of, of=sg(o1),o1= h1f*self.w5+h2f*self.w6+self.b3(w5后面做常数处理)
#用下一层的输出对上一层的输出逐层求偏导, 目的是求出对权值的偏导
#(权值是刻画数据的元数据,机器学习的目的就是以数据为依据求出刻画自身的元数据)
der_y_pre_h1f = der_sigmoid(valcell[4])*self.w5
der_y_pre_h2f = der_sigmoid(valcell[4])*self.w6
#print(valcell,der_y_pre_h2f)
#h1f对w1,w2求偏导
der_h1f_w1 = der_sigmoid(valcell[0])*x[0]
der_h1f_w2 = der_sigmoid(valcell[0])*x[1]
#h2f对w3,w4求偏导
der_h2f_w3 = der_sigmoid(valcell[2])*x[0]
der_h2f_w4 = der_sigmoid(valcell[2])*x[1]
#y_pre对w5w6b3求偏导
der_y_pre_w5 = der_sigmoid(valcell[4])*valcell[1]
der_y_pre_w6 = der_sigmoid(valcell[4])*valcell[3]
der_y_pre_b3 = der_sigmoid(valcell[4])
#h1f对b1求偏导
der_h1f_b1 = der_sigmoid(valcell[0])
#h2f对b2求偏导
der_h2f_b2 = der_sigmoid(valcell[2])
#反向传播,以损失函数为起点反向传播(通过求导链式法则)到各个权值参数
#梯度下降,调整权值,按照学习速率逐渐下降,学习率与梯度的乘积为下降步长。
#即权重和偏置每向前一步,就需要走学习率和当前梯度的乘积这么远
self.w1 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_w1
self.w2 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_w2
self.w3 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_w3
self.w4 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_w4
self.w5 -= learn_rate * der_L_y_pre * der_y_pre_w5
self.w6 -= learn_rate * der_L_y_pre * der_y_pre_w6
self.b1 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_b1
self.b2 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_b2
self.b3 -= learn_rate * der_L_y_pre *der_y_pre_b3
if i % 10 ==0 :
#对训练数据数组作为simulate函数的参数依次进行处理得到当前预测值
y_pred = numpy.apply_along_axis(self.simulate,1,data)
#对真实值与预测值两个数组使用损失函数求均方误差作为参数的评价标准
loss = mse_loss (all_y_tr , y_pred)
print(i,loss)
梯度消失——反向传播的过程,会有若干个小数,且是小于0.25的数相乘的情况。
𝑧_0=𝑤_0 𝑥=0.06;𝑦_1=𝑓_1 (𝑧_0 )=0.515;𝑧_1=𝑤_1 𝑦_1=0.103𝑦_2=𝑓_2 (𝑧_1 )=0.525;𝑧_2=𝑤_2 𝑦_2=0.315;𝑦_3=𝑓_3 (𝑧_2 )=0.578
(𝜕𝑦_3)/(𝜕𝑤_0 )=(𝜕𝑦_3)/(𝜕𝑧_2 )×(𝜕𝑧_2)/(𝜕𝑦_2 )×(𝜕𝑦_2)/(𝜕𝑧_1 )×(𝜕𝑧_1)/(𝜕𝑦_1 )×(𝜕𝑦_1)/(𝜕𝑧_0 )×(𝜕𝑧_0)/(𝜕𝑤_0 )=𝑓_3^′×𝑤_2×𝑓_2^′×𝑤_1×𝑓_1^′×𝑥 =0.000036
Relu函数
梯度爆炸 — 当初始的权重过大(如大于10)时,在反向传播的过程中会造成梯度呈指数增长,在靠近输入层的位置,由于梯度过大,导致权重有非常大的更新。