[转]BP+梯度下降算法推导

本篇博客来自其他博客以及论文消化吸收,假如读者熟悉BP网络正向传播,但是一直疑惑反向传播,那么可以继续看。

资料来源:

https://blog.****.net/SZU_Hadooper/article/details/78619575

https://www.jianshu.com/p/c7e642877b0e

http://www.cnblogs.com/charlotte77/p/5629865.html

假设一个网络如下:

[转]BP+梯度下降算法推导

第一层是输入层,包含两个神经元i1,i2,和截距项b1;注:截距项类似[转]BP+梯度下降算法推导中的常数项c。

第二层是隐含层,包含两个神经元h1,h2和截距项b2;

第三层是输出层,包含两个神经元o1,o2。

每条线上标的wi是层与层之间连接的权重,**函数我们默认为sigmoid函数。

现在对他们赋上初值,如下图:

[转]BP+梯度下降算法推导

 其中,输入数据  i1=0.05,i2=0.1;

    输出数据 o1=0.01,o2=0.99;

    初始权重  w1=0.15,w2=0.2,w3=0.25,w4=0.3;

                     w5=0.4,w6=0.45,w7=0.5,w8=0.55;

                             w=1

 目标:给出输入数据i1,i2(0.05和0.1),使输出尽可能与原始输出o1,o2(0.01和0.99)接近。



Step 1 前向传播

1.输入层······>隐含层:

计算神经元h1的输入加权和:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

计算神经元h1的输出:(此处用到**函数为sigmoid函数):

[转]BP+梯度下降算法推导

同理:

计算神经元h2的输入加权和:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

计算神经元h2的输出:(此处用到**函数为sigmoid函数):

[转]BP+梯度下降算法推导

2.隐含层---->输出层:

计算神经元o1的输入加权和:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

计算神经元o2的输出:(此处用到**函数为sigmoid函数):

[转]BP+梯度下降算法推导

计算神经元o2的输入加权和:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

计算神经元o2的输出:(此处用到**函数为sigmoid函数):

[转]BP+梯度下降算法推导

这样前向传播的过程就结束了,我们得到输出值为[0.75136507 , 0.772928465],与实际值[0.01 , 0.99]相差还很远,现在我们对误差进行反向传播,更新权值,重新计算输出。



Step 2 反向传播

1.计算总误差

总误差:(square error),取误差平方和然后除以2,为的是方便后面求导。

[转]BP+梯度下降算法推导

但是有两个输出,所以分别计算o1和o2的误差,总误差为两者之和:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

问题来了:怎么样才能使得[转]BP+梯度下降算法推导取得最小值呢??

解析:

(1)[转]BP+梯度下降算法推导可以看成是嵌套的函数,嵌套关系如下(中间有省略,示意即可):

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

(2)从上述关系可以发现,忽略中间关系,宏观上[转]BP+梯度下降算法推导可以看做是i1,i2作为变量,而w1,w2,w3,w4,w5,w6,w7.w8作为参数的函数。

(3)当输入数据时,i1和i2就有了具体值i1=0.01,i2=0.99。虽然w1,w2,w3,w4,w5,w6,w7.w8此时有值,但是可以将其看作是变量,将[转]BP+梯度下降算法推导看做是关于w1,w2,w3,w4,w5,w6,w7.w8的一个函数,问题就转换成了求当[转]BP+梯度下降算法推导为最小值时w1,w2,w3,w4,w5,w6,w7.w8的取值。

(4)[转]BP+梯度下降算法推导的最小值并不好找,所以借助高数中的梯度来一点点向[转]BP+梯度下降算法推导的最小值靠近。

(4)举例,设函数[转]BP+梯度下降算法推导,x,y为变量,m,n为参数,令x=2,y=6则函数变为[转]BP+梯度下降算法推导,求[转]BP+梯度下降算法推导取得最小值时m,和n的值。

 

补充:如何用梯度寻找最小值。

(1)梯度方向指的是函数值上升最快的方向,这个方向是局部的,什么意思?假设当前点沿着梯度方向走1米一直都是上升最快的,但是1米以后不再上升最快,梯度方向可能就变了,这也是为什么要设置[转]BP+梯度下降算法推导学习率(步长)的原因。

(2)另外,由于我们要的是最小值,应该找梯度下降最快的方向。所以梯度方向要取反,朝着最快的方向趋向最小值。

(3)梯度下降法有将求解带入局部最小值的可能。

(4)梯度概念就是求偏导数,本例中就是[转]BP+梯度下降算法推导分别对w1,w2,w3,w4,w5,w6,w7.w8求偏导数,这8个偏导数就是8个维度上的长度值,他们合向量方向就是梯度的方向。如果不理解,该补一补基础了,参考博文:https://blog.****.net/u010916338/article/details/81288309

(5)梯度下降算法单变量函数举例

假设单变量函数:

[转]BP+梯度下降算法推导

函数求导,可看做梯度:

[转]BP+梯度下降算法推导

设学习率为:

[转]BP+梯度下降算法推导

设初试点为:

[转]BP+梯度下降算法推导            注:0只是标记第一个位置,无其他含义。

根据梯度下降公式,下一个点的位置为:

[转]BP+梯度下降算法推导        注,[转]BP+梯度下降算法推导时的函数值,小于[转]BP+梯度下降算法推导时的函数值。

[转]BP+梯度下降算法推导       注,[转]BP+梯度下降算法推导时的函数值,小于[转]BP+梯度下降算法推导时的函数值。

[转]BP+梯度下降算法推导

(6)梯度下降算法多变量函数举例

假设多变量函数:

[转]BP+梯度下降算法推导

我们通过观察就能发现最小值其实就是 (0,0)点。但是接下来,我们会从梯度下降算法开始一步步计算到这个最小值!

函数梯度为:

[转]BP+梯度下降算法推导

设置学习率:

[转]BP+梯度下降算法推导

设置初试起点为:

[转]BP+梯度下降算法推导

根据梯度下降法求下一个点的位置:

[转]BP+梯度下降算法推导        注,[转]BP+梯度下降算法推导时的函数值,小于[转]BP+梯度下降算法推导时的函数值。

[转]BP+梯度下降算法推导    注,[转]BP+梯度下降算法推导时的函数值,小于[转]BP+梯度下降算法推导时的函数值。

[转]BP+梯度下降算法推导

 

2.隐含层······>输出层的权值更新:

以修改权重参数w5为例,先用整体误差对w5求偏导(链式法则),为什么这样求后续再介绍。

[转]BP+梯度下降算法推导

现在我们来分别计算每个式子的值:

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导       

[转]BP+梯度下降算法推导

补充:sigmod求导过程:

[转]BP+梯度下降算法推导

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

最后三者相乘:

[转]BP+梯度下降算法推导

更新w5权重值([转]BP+梯度下降算法推导是学习率,这里取值0.5):

[转]BP+梯度下降算法推导

同理更新w6,w7,w8:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

另外一点比较重要,如下所示:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导用来表示输出层误差(显然输出层误差并不是这样的,如此表示是因为后续隐藏层的误差没法计算,是为了形式上的统一。具体用途结合后续隐藏层权重修改部分理解。)

注:简化公式的原因是为了编码的统一,为的是神经网络层统一定义,显然输出层的误差很好理解,但是隐藏层的误差不好理解,但是可以在形式上统一。

3.隐含层······>隐含层的权值更新:

以修改权重参数w1为例,先用整体误差对w1求偏导(链式法则),为什么这样求后续再介绍。

[转]BP+梯度下降算法推导

现在我们来分别计算每个式子的值:

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

 

因为[转]BP+梯度下降算法推导到最后总误差计算有岔路,所以没有办法用链式求导法则进行统一的求导计算,两条路用的加权和函数不同,函数形式相同,但是参数不同。

解决思路如下,将整体误差拆分成两个小误差进行计算。

[转]BP+梯度下降算法推导

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

同理计算出:

[转]BP+梯度下降算法推导

则:

[转]BP+梯度下降算法推导

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导       

[转]BP+梯度下降算法推导

计算[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

最后三者相乘:

[转]BP+梯度下降算法推导

更新w1权重值([转]BP+梯度下降算法推导是学习率,这里取值0.5):

[转]BP+梯度下降算法推导

同理更新w2,w3,w4:

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

公式简化

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导

[转]BP+梯度下降算法推导(o为输出层神经元编号)

[转]BP+梯度下降算法推导(解析[转]BP+梯度下降算法推导[转]BP+梯度下降算法推导为隐藏层···>输出层的权重,因为[转]BP+梯度下降算法推导求导,所以剩下的就只有权重,左半边的[转]BP+梯度下降算法推导依然和输出层的公式简写一致,表示的是输出层的误差。)

[转]BP+梯度下降算法推导(将[转]BP+梯度下降算法推导看作是隐藏层的误差,记为:[转]BP+梯度下降算法推导,注意只是为了形式保持一致,便于编码。)

注:结合输出层公式简写来看,输出层和隐藏层对于权重的偏导数都可以简写成:误差乘以上层传入的数据(误差形式不同分别定义即可,但对于权重的偏导数却可以统一定义。)

注意:

       这样一次误差反向传播法就完成了,那么是不是可以接着输入新数据了呢?

       不是这样的,刚才输入的数据i1=0.05,i2=0.1还要在进行一次正向传输,如果输出误差仍然在可接受范围之外,那么就会一直循环上述操作,再反向传播,再正向传播。直到把误差压缩在可接受范围之内时结束,为了防止迭代次数过多而一直循环,还要设置迭代次数,当迭代次数用尽之后,也要自动跳出迭代。

        在这个例子中第一次迭代之后,总误差E(total)由0.298371109下降至0.291027924。迭代10000次后,总误差为0.000035085,输出为[0.015912196,0.984065734](原输入为[0.01,0.99]),证明效果还是不错的。

python代码实现:

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Oct 25 16:56:30 2018
  4. @author: hrh
  5. """
  6. #博客地址:http://www.cnblogs.com/charlotte77/p/5629865.html
  7. #coding:utf-8
  8. import random
  9. import math
  10. #
  11. # 参数解释:
  12. # "pd_" :偏导的前缀
  13. # "d_" :导数的前缀
  14. # "w_ho" :隐含层到输出层的权重系数索引
  15. # "w_ih" :输入层到隐含层的权重系数的索引
  16. #定义整个神经网络
  17. class NeuralNetwork:
  18. #设置步长,学习率
  19. LEARNING_RATE = 0.5
  20. def __init__(self, num_inputs, num_hidden, num_outputs, hidden_layer_weights = None, hidden_layer_bias = None, output_layer_weights = None, output_layer_bias = None):
  21. self.num_inputs = num_inputs
  22. #构造一个隐藏层
  23. self.hidden_layer = NeuronLayer(num_hidden, hidden_layer_bias)
  24. #构造一个输出层
  25. self.output_layer = NeuronLayer(num_outputs, output_layer_bias)
  26. #初始化输入层到隐藏层权重
  27. self.init_weights_from_inputs_to_hidden_layer_neurons(hidden_layer_weights)
  28. #初始化隐藏层到输出层权重
  29. self.init_weights_from_hidden_layer_neurons_to_output_layer_neurons(output_layer_weights)
  30. #初始化进入隐藏层的权重
  31. def init_weights_from_inputs_to_hidden_layer_neurons(self, hidden_layer_weights):#hidden_layer_weights是个列表
  32. weight_num = 0
  33. for h in range(len(self.hidden_layer.neurons)):#隐藏层神经元个数
  34. for i in range(self.num_inputs):#输入层神经元个数
  35. if not hidden_layer_weights:
  36. self.hidden_layer.neurons[h].weights.append(random.random())
  37. else:
  38. self.hidden_layer.neurons[h].weights.append(hidden_layer_weights[weight_num])#估计是有些权重需要提前设定
  39. weight_num += 1
  40. #初始化进入输出层的权重
  41. def init_weights_from_hidden_layer_neurons_to_output_layer_neurons(self, output_layer_weights):
  42. weight_num = 0
  43. for o in range(len(self.output_layer.neurons)):
  44. for h in range(len(self.hidden_layer.neurons)):
  45. if not output_layer_weights:
  46. self.output_layer.neurons[o].weights.append(random.random())
  47. else:
  48. self.output_layer.neurons[o].weights.append(output_layer_weights[weight_num])
  49. weight_num += 1
  50. def inspect(self):
  51. print('------')
  52. print('* Inputs: {}'.format(self.num_inputs))
  53. print('------')
  54. print('Hidden Layer')
  55. self.hidden_layer.inspect()
  56. print('------')
  57. print('* Output Layer')
  58. self.output_layer.inspect()
  59. print('------')
  60. #前向传播
  61. def feed_forward(self, inputs):
  62. hidden_layer_outputs = self.hidden_layer.feed_forward(inputs)
  63. return self.output_layer.feed_forward(hidden_layer_outputs)
  64. def train(self, training_inputs, training_outputs):
  65. self.feed_forward(training_inputs)
  66. #1. 求输出误差error对上一层神经元in的偏导数
  67. pd_errors_wrt_output_neuron_total_net_input = [0] * len(self.output_layer.neurons)
  68. for o in range(len(self.output_layer.neurons)):
  69. #求输出误差error对上一层神经元in的偏导数
  70. pd_errors_wrt_output_neuron_total_net_input[o] = self.output_layer.neurons[o].calculate_pd_error_wrt_total_net_input(training_outputs[o])
  71. #2. 求隐藏层误差,求隐藏层误差error对于上层input的偏导数。
  72. pd_errors_wrt_hidden_neuron_total_net_input = [0] * len(self.hidden_layer.neurons)
  73. for h in range(len(self.hidden_layer.neurons)):
  74. d_error_wrt_hidden_neuron_output = 0
  75. for o in range(len(self.output_layer.neurons)):
  76. #此处为计算隐藏层的输出误差,其实并不是真正的误差,只是在公式形式上和输出层误差形式类似。具体解释需要看博客。
  77. d_error_wrt_hidden_neuron_output += pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].weights[h]
  78. #隐藏层总误差对于隐藏层输入input的偏导数。
  79. pd_errors_wrt_hidden_neuron_total_net_input[h] = d_error_wrt_hidden_neuron_output * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_input()
  80. #3. 修改隐藏层到输出层的权重。
  81. for o in range(len(self.output_layer.neurons)):
  82. for w_ho in range(len(self.output_layer.neurons[o].weights)):
  83. #误差对输出层权重的偏导数。
  84. pd_error_wrt_weight = pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].calculate_pd_total_net_input_wrt_weight(w_ho)
  85. # Δw = α * ∂Eⱼ/∂wᵢ根据学习率修改输出层神经元权重。
  86. self.output_layer.neurons[o].weights[w_ho] -= self.LEARNING_RATE * pd_error_wrt_weight
  87. #4. 更新隐含层的权重系数
  88. for h in range(len(self.hidden_layer.neurons)):
  89. for w_ih in range(len(self.hidden_layer.neurons[h].weights)):
  90. #计算隐藏层误差对于输入层到隐藏层的权重的偏导数。
  91. pd_error_wrt_weight = pd_errors_wrt_hidden_neuron_total_net_input[h] * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_weight(w_ih)
  92. # Δw = α * ∂Eⱼ/∂wᵢ根据学习率修改隐藏层神经元权重。
  93. self.hidden_layer.neurons[h].weights[w_ih] -= self.LEARNING_RATE * pd_error_wrt_weight
  94. def calculate_total_error(self, training_sets):
  95. total_error = 0
  96. for t in range(len(training_sets)):
  97. training_inputs, training_outputs = training_sets[t]#list中的元素还是一个list
  98. self.feed_forward(training_inputs)
  99. for o in range(len(training_outputs)):
  100. total_error += self.output_layer.neurons[o].calculate_error(training_outputs[o])
  101. return total_error
  102. #定义一层神经元
  103. class NeuronLayer:
  104. def __init__(self, num_neurons, bias):
  105. #偏置值
  106. self.bias = bias if bias else random.random()#python三目运算符
  107. #定义神经元列表
  108. self.neurons = []
  109. #按照神经元个数初始化神经元列表,偏置值作为属性赋值给所有的神经元。
  110. for i in range(num_neurons):
  111. self.neurons.append(Neuron(self.bias))
  112. def inspect(self):
  113. print('Neurons:', len(self.neurons))
  114. for n in range(len(self.neurons)):
  115. print(' Neuron', n)
  116. for w in range(len(self.neurons[n].weights)):
  117. print(' Weight:', self.neurons[n].weights[w])
  118. print(' Bias:', self.bias)
  119. #一层神经元的前向输出
  120. def feed_forward(self, inputs):
  121. outputs = []
  122. for neuron in self.neurons:
  123. outputs.append(neuron.calculate_output(inputs))
  124. return outputs
  125. #一层神经元的前向输出,与上个函数功能相同,目前用途未知??????
  126. def get_outputs(self):
  127. outputs = []
  128. for neuron in self.neurons:
  129. outputs.append(neuron.output)
  130. return outputs
  131. #定义单个神经元
  132. class Neuron:
  133. #初始化设置截距
  134. def __init__(self, bias):
  135. #偏置
  136. self.bias = bias
  137. #上一层连接神经元的权重
  138. self.weights = []
  139. #计算神经元输出
  140. def calculate_output(self, inputs):
  141. #上一层输出都视为input,inputs是一个列表
  142. self.inputs = inputs
  143. #经过**函数后的输出
  144. self.output = self.squash(self.calculate_total_net_input())
  145. return self.output
  146. #计算神经元输入的加权平均和
  147. def calculate_total_net_input(self):
  148. total = 0
  149. for i in range(len(self.inputs)):
  150. #上一层都视为input,权重指的是上一层连接神经元的连接权重,而不是下一层连接神经元的权重。
  151. total += self.inputs[i] * self.weights[i]
  152. #最后每个神经元的总input都加上偏置项
  153. return total + self.bias
  154. # 定义**函数
  155. def squash(self, total_net_input):
  156. return 1 / (1 + math.exp(-total_net_input))
  157. #求输出误差error对上一层神经元in的偏导数
  158. def calculate_pd_error_wrt_total_net_input(self, target_output):
  159. return self.calculate_pd_error_wrt_output(target_output) * self.calculate_pd_total_net_input_wrt_input();
  160. #计算神经元输出与目标值误差计算
  161. def calculate_error(self, target_output):
  162. return 0.5 * (target_output - self.output) ** 2
  163. #求输出误差error对神经元out的偏导数
  164. def calculate_pd_error_wrt_output(self, target_output):
  165. return - (target_output - self.output)
  166. #求神经元输出out对于输入in的偏导数
  167. def calculate_pd_total_net_input_wrt_input(self):
  168. return self.output * (1 - self.output)
  169. #求输入in对权重w的偏导数,求完以后其实就是上层的输入值
  170. def calculate_pd_total_net_input_wrt_weight(self, index):
  171. return self.inputs[index]
  172. # 文中的例子:
  173. nn = NeuralNetwork(2, 2, 2, hidden_layer_weights=[0.15, 0.2, 0.25, 0.3], hidden_layer_bias=0.35, output_layer_weights=[0.4, 0.45, 0.5, 0.55], output_layer_bias=0.6)
  174. for i in range(10000):
  175. nn.train([0.05, 0.1], [0.01, 0.09])
  176. print(i, round(nn.calculate_total_error([[[0.05, 0.1], [0.01, 0.09]]]), 9))