引用链接1:https://www.cnblogs.com/lliuye/p/9183914.html
引用链接2:https://blog.csdn.net/qq_31707969/article/details/79361540
引用链接3:吴恩达深度学习视频3.9节
本文记录笔者实现用python的numpy包实现三层神经网络的手法,里面包括了:正向传播的知识,反向传播在编程中的实现,以及为什么隐藏层有不止一个节点的论证。
问题的背景是我看了引用2中的内容,在实现只有一个隐藏层的神经网络的时候,他的反向传播的手法是这样的:
for iteration in range(number_of_training_iterations):
# 把训练集传入神经网络.
output = self.think(training_set_inputs)
# 计算损失值(期望输出与实际输出之间的差。
error = training_set_outputs - output
# 损失值乘上sigmid曲线的梯度,结果点乘输入矩阵的转置
# 这意味着越不可信的权重值,我们会做更多的调整
# 如果为零的话,则误区调制
adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))
# 调制权值
self.synaptic_weights += adjustment
注:__sigmoid_derivative()函数是对sigmoid函数的求导,返回的是关于sigmoid函数的函数,原型是:
def __sigmoid(self, x):
return 1 / (1 + exp(-x))
本人的反向传播手法是:
for iteration in range(epoch):
#forward transform
z1=dot(inputs,self.w1)#4x1
a1=self.sigmoid(z1)
#Back transform
d_a=a1-y#4,1
#d_z=d_a*self.sigmoid_dv(z1)#4x1,here i write the d_a*self.sigmoid_dv(a1),imwrong!
d_z=d_a*self.sigmoid_dv(a1)
d_w1=dot(inputs.T,d_z)
#update the w
self.w1 -=d_w1
print("the new w is:"+str(self.w1))
print("End")
细心的网友可以看到我的思路和作者的基本吻合,详细的反向传播的步骤如下图:
单层的反向传播很简单,就是对a,z,w求导就可以了,但是三层神经网络的时候就会遇到一系列问题:
反向传播的时候哪些是dot(),哪些是点乘,以及每一个a,z,w,的偏导的矩阵形状是什么样的?
下面就来记录自己在代码中的经验。
对于三层神经网络,如下图:
如上图中z1到a3是表示的前向传播的公式, 从dz3到dw1表示的反向传播的公式,公式的推导过程在图一中已经证明过了,如果有不懂的可以去看吴恩达老师的深度学习关系反向传播3.9节的视频,里面有反向传播的推导(如果觉得这里很难的话,一点也不奇怪,因为老师说反向传播是神经网络最难的问题之一)
下面记录下我在编程中出现的问题:
1.
发现在第一轮迭代的时候,反向传播中,计算损失函数L对Z的偏导的时候:d_z=d_a*self.sigmoid_dv(z1)这一步求错了,传入的参数应该是a1,至于是为什么,总结如下:
在正向传播中:
Z=wx,a=sigmoid(Z),L=-(yloga+(1-y)log(1-a))
反向传播的时候:
当我对Z求导的这一步出错了,因为我原程序中封装了对sigmoid函数sigmoid_dv求导的过程,return(x*(1-x)),这里的x是sigmoid函数,而不是我所传入的参数(一个值),所以我在d_z=d_a*self.sigmoid_dv(z1)这一步传入的不是Z1,而应该是a1,因为sigmoid_dv函数返回的不是求导后的结果,返回的而是关于sigmoid函数的表达式。
2.
在反向传播的时候求出损失函数对a的偏导的时候,得到的结果应该是a-y, 之后更新权重w的时候应该是w -=w,而作者在求出损失函数对a的偏导的时候,得到的结果是y-a.并且用w+=w更新权重。
如果a-y是负的话,那么w-=w应该会涨,而w+=w会降,反之同理,所以在找梯度最低点的时候可以从左边下降也可以从右边下降,但是我的做法是跟作者一个方向,但是找的是上升的过程。
3.
在反向传播中,因为z=wx,a=sigmoid(z),当我们求L对z的偏导的时候,这里用dz2举例,即dz2:=d_a2*(self.sigmoid_dv(a2)),要注意这里的d_a2与后面的求导结果是点乘,而不是dot运算,点乘是python中的广播。一定要注意区别,
4.
在反向传播中,要注意转置运算:
d_z3=(a3-y)*self.sigmoid_dv(a3)#4,1
d_w3=dot(a2.T,d_z3)#4,4x4,1=4,1
d_a2=dot(d_z3,(self.w3.T))#4,1x1,4=4,4
可以看到在求d_w3的时候 是用“a2.Tdot的d_z3”,在求d_a2的时候是用d_z3dot的w3.T" 这是因为前向传播的时候表达式是:
z3=dot(a2,self.w3),即相当于mat.[a2] *mat.[self.w3], 在反向传播的时候要保证原来的元素位置保持不变并且要在原来的基础上加上转置符号(至于这里是为什么我也就没再仔细研究了,我记住就可以了)
for iteration in range(epoch):
# num+=1
#forward transform
z1=dot(inputs,self.w1)#4,3x3,5=4,5
a1=self.sigmoid(z1)#4,5
z2=dot(a1,self.w2)#4,5x5,4=4,4
a2=self.sigmoid(z2)#4,4
z3=dot(a2,self.w3)#4,4x4,1=4,1
a3=self.sigmoid(z3)#4,1
#Back transform
#d_z 和d_a矩阵形状是一样的,并且a取决于z的矩阵形状,且z和d_z的形状是一样的,否则在之后的权值更新时做不了加减法
#所以当我们确定矩阵形状的时候:先在正向传播的时候确定z的形状,继而就确定了a的形状,之后反向传播的时候也就确定了dz的形状
d_z3=(a3-y)*self.sigmoid_dv(a3)#4,1
d_w3=dot(a2.T,d_z3)#4,4x4,1=4,1
d_a2=dot(d_z3,(self.w3.T))#4,1x1,4=4,4
d_z2=d_a2*(self.sigmoid_dv(a2))#4,4x4,4=4,4
d_w2=dot((a1.T),d_z2)#5,4x4,4=5,4
d_a1=dot(d_z2,self.w2.T)#dot((d_z2,self.w2.T)) #5,4x4,4=5,4### 4,4x4,5=4x5
d_z1=(d_a1*(self.sigmoid_dv(a1)))#5,4x4,5=5,5###4,5x4,5
d_w1=dot((inputs.T),d_z1)#3,4x5,5#3,4X4,5=3,5
#update the w
self.w1 -=d_w1
self.w2 -=d_w2
self.w3 -=d_w3
#print("num is\n"+str(num))
好了,上面的内容大部分已经解释完反向传播的要点了。
下面记录一下在神经网络中,为什么隐藏层有不只有一个神经元?
异或问题的背景;
其中x1,x2表示输入,h(x)表示异或后的输出结果。
而我们传统的逻辑回归可以通过改变参数,来实现“与”、“或”、“非”简单操作,但是不可以去直接实现异或的问题。
什么是逻辑回归?
逻辑回归可以分为线性变换部分与非线性变换部分。而只有输入层与输出层且输出层只有一个神经元的神经网络的结构便于逻辑回归一致。只不过在神经网络中,线性变换(求和)与非线性变换被集成在一个神经元(隐藏层或输出层)中。如下图所示:
上述话语可以将逻辑回归总结如下:
1.逻辑回归可以是线性变换的表达式也可以是非线性变换的
2.逻辑回归与神经网路不同的是逻辑回归线性变换(求和)与非线性变换不在同一个神经元中。
传统的逻辑回归是解决不了异或问题的,所以人们又开始研究起了神经网络,因为神经网络中,在下一层可以有两个节点来分别对上一层进行运算:
运用上述神经网络模型后,我们现在来解决异或问题:
图片来自有引用1,这里为什么不直接让你们看作者的是因为作者在这里有一个地方写错了:
1.可以看到上述x1=x2=0的时候,h(x)=1,而异或的结果应该是0,所以在第2层或运算的结果后还需要对其进行一个取负值的操作才能得到正确的异或结果。1
2.在上述网络模型中的输入层和隐藏层的神经元中不应该存在1,如果存在1的话与x1,x2参与“与”运算“或非”运算后,结果会与表格中的不同,并且计算完之后到了隐藏层也不应该有1,本人提出的模型如下:
综上总结,神经网络python这块就到这里,下周的目标是掌握pytorch框架并且搭建一个简单的卷积神经网络!
附件是代码:
链接:https://pan.baidu.com/s/1nwt5mcgeaW35dnmiCruw0Q
提取码:1hih
2019-7-25更新
在只有一层隐藏层的时候,我的代码有个地方写错了,
```
for iteration in range(epoch):
#forward transform
z1=dot(inputs,self.w1)#4x1
a1=self.sigmoid(z1)
#Back transform
d_z=a1-y
d_w1=dot(inputs.T,d_z)
#update the w
self.w1 -=d_w1
print("the new w is:"+str(self.w1))
print("End")
```