作者:张小鸡 人工智能爱好者社区专栏作者
知乎ID:https://www.zhihu.com/people/mr.ji
个人公众号:鸡仔说
每一个认真生活的人,都应该认真学一下机器学习。骄傲者从中获得谦卑,浮躁者从中获得平静,就犹如体验过波涛风浪或生死洗礼一样。从此,便真真地知道了:自己是个弱鸡。
哔哔完了,进入正题,在此之前,希望你已经对基础的神经网络有一个了解,而反向传播算法,其实是一个寻求最优解的过程。包括我在内的很多人,一开始都认为其中的原理很难,但其实不是,反向传播算法之所以被广泛应用,除了有效性外,私以为还得益于它的容易理解。看过《土拨鼠之日》的人,一定希望拥有和男主一样的奇特经历。因为它的奇妙之处,是可以一次次地返回去实验,如果失败了,则得到反馈,再优化。相应的,反向传播算法也是如此,一次次的返回去重来,得到最优解。
下面咱们从从最简单三层神经网络开始,即输入层,隐含层和输出层。输入层包含两个神经元i1, i2,隐含层包含两个神经元h1, h2,输出层同样是两个神经元o1, o2,输入层和隐含层的偏置项分别为b1, b2,激活函数默认为sigmod函数。
现在我们对它们附上初始值,如下图所示:
其中:
输入数据 i1=0.05,i2=0.10;
输出数据 o1=0.01,o2=0.99;
初始权重 w1=0.15,w2=0.20,w3=0.25,w4=0.30;
w5=0.40,w6=0.45,w7=0.50,w8=0.55
还记得我们的最终目标吧?给出初始值i1,i2,求出使得离目标输出最接近的系数值,即(wi, bi)
一步步的推导过程我再次不赘述,可以参考下原文,传送门在文末参考
我说一下具体的代码实现部分的一些细节。Matt Mazur大佬的代码清晰易读,而且分类特别赞,我们回顾一下上面的结构,发现整个神经网络系统就分为两个部分:神经元和连接神经元的线(即权重和偏置项),分工也很清晰明了。神经元负责的有:
向前传播:加权和,激活函数,输出函数,误差
反向传播:误差偏导,输出函数到激活函数的偏导,激活层到加权和的偏导
它们之间的连线,也就是网络层,负责对神经元的操控,它只需要负责向前传播和得到输出的结果。有了如上的结构,便可以来搭建整个网络了,黑喂狗~~
首先编写神经元:
1class Neuron():
2
3 def __init__(self, bias):
4 self.bias = bias
5 self.weights = []
6
7 def calculate_output(self, inputs):
8 self.inputs = inputs
9 self.out_put = self.activate(self.calculate_total_net_input())
10 return self.out_put
11
12 def calculate_total_net_input(self):
13 total = 0
14 for i in range(len(self.inputs)):
15 total += self.weights[i] * self.inputs[i]
16 return total + self.bias
17
18 def activate(self, total_net_input):
19 return 1/(1+math.exp(-total_net_input))
20
21 def calculate_error(self, target_output):
22 return 0.5 * (target_output - self.out_put) ** 2
23
24 # ∂E/∂yⱼ
25 def calculate_pd_error_wrt_output(self, target_output):
26 return - (target_output - self.out_put)
27
28 # dyⱼ/dzⱼ = yⱼ * (1 - yⱼ)
29 def calculate_pd_active_wrt_total_net_input(self):
30 return self.out_put * (1 - self.out_put)
31
32 # ∂zⱼ/∂wᵢ = wᵢ
33 def calculate_pd_total_net_input_wrt_weight(self, index):
34 return self.inputs[index]
35
36 # δ = ∂E/∂zⱼ = ∂E/∂yⱼ * dyⱼ/dzⱼ
37 def calculate_pd_error_wrt_total_net_input(self, target_output):
38 return self.calculate_pd_error_wrt_output(target_output) * self.calculate_pd_active_wrt_total_net_input()
接下来我们编写网络层部分的代码:
1class NeuronLayer():
2
3 def __init__(self, num_neurons, bias):
4 self.bias = bias
5 self.neurons = []
6 for i in range(num_neurons):
7 self.neurons.append(Neuron(bias))
8
9 def feed_forward(self, inputs):
10 outputs = []
11 for neuron in self.neurons:
12 outputs.append(neuron.calculate_output(inputs))
13 return outputs
14
15 def get_outs(self):
16 outputs = []
17 for neuron in self.neurons:
18 outputs.append(neuron.out_put)
19 return outputs
最后,我们由它们来组合我们的整个网络结构:
1class NeuralNetwork():
2 LEARNING_RATE = 0.5
3
4 def __init__(self, num_inputs, num_hiddens, num_outputs, hidden_layer_weights=None, hidden_layer_bias=None, output_layer_weights=None, output_layer_bias=None):
5 self.num_inputs = num_inputs
6
7 self.hidden_layer = NeuronLayer(num_hiddens, hidden_layer_bias)
8 self.output_layer = NeuronLayer(num_outputs, output_layer_bias)
9
10 self.init_weights_from_inputs_to_hidden_layer_neurons(hidden_layer_weights)
11 self.init_weights_from_hidden_layer_neurons_to_output_layer_neurons(output_layer_weights)
12
13 def init_weights_from_inputs_to_hidden_layer_neurons(self, hidden_layer_weights):
14 weight_num = 0
15 for h in range(len(self.output_layer.neurons)):
16 for i in range(self.num_inputs):
17 if not hidden_layer_weights:
18 self.hidden_layer.neurons[h].weights.append(random.random())
19 else:
20 self.hidden_layer.neurons[h].weights.append(hidden_layer_weights[weight_num])
21 weight_num += 1
22
23 def init_weights_from_hidden_layer_neurons_to_output_layer_neurons(self, output_layer_weights):
24 weight_num = 0
25 for o in range(len(self.output_layer.neurons)):
26 for h in range(len(self.hidden_layer.neurons)):
27 if not output_layer_weights:
28 self.output_layer.neurons[o].weights.append(random.random())
29 else:
30 self.output_layer.neurons[o].weights.append(output_layer_weights[weight_num])
31 weight_num += 1
32
33 def feed_forward(self, inputs):
34 hidden_layer_outputs = self.hidden_layer.feed_forward(inputs)
35 return self.output_layer.feed_forward(hidden_layer_outputs)
36
37 def train(self, train_inputs, train_outputs):
38 self.feed_forward(train_inputs)
39
40 pd_errors_wrt_output_neuron_total_net_input = [0]*len(self.output_layer.neurons)
41 for o in range(len(self.output_layer.neurons)):
42 pd_errors_wrt_output_neuron_total_net_input[o] = self.output_layer.neurons[o].calculate_pd_error_wrt_total_net_input(train_outputs[o])
43
44 pd_errors_wrt_hidden_neuron_total_net_input = [0] * len(self.hidden_layer.neurons)
45 for h in range(len(self.hidden_layer.neurons)):
46 d_error_wrt_hidden_neuron_output = 0
47 for o in range(len(self.output_layer.neurons)):
48 d_error_wrt_hidden_neuron_output += pd_errors_wrt_hidden_neuron_total_net_input[o] * self.output_layer.neurons[o].weights[h]
49
50 pd_errors_wrt_hidden_neuron_total_net_input[h] += d_error_wrt_hidden_neuron_output * self.hidden_layer.neurons[h].calculate_pd_active_wrt_total_net_input()
51
52 for o in range(len(self.output_layer.neurons)):
53 for w_ho in range(len(self.output_layer.neurons[o].weights)):
54
55 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)
56
57 self.output_layer.neurons[o].weights[w_ho] -= self.LEARNING_RATE * pd_error_wrt_weight
58
59 for h in range(len(self.hidden_layer.neurons)):
60 for w_ih in range(len(self.hidden_layer.neurons[h].weights)):
61 # ∂Eⱼ/∂wᵢ = ∂E/∂zⱼ * ∂zⱼ/∂wᵢ
62 pd_error_wrt_weight = pd_errors_wrt_hidden_neuron_total_net_input[h] * self.hidden_layer.neurons[
63 h].calculate_pd_total_net_input_wrt_weight(w_ih)
64
65 # Δw = α * ∂Eⱼ/∂wᵢ
66 self.hidden_layer.neurons[h].weights[w_ih] -= self.LEARNING_RATE * pd_error_wrt_weight
以上便是通过简单的网络结构,理解反向传播算法的相关细节。从我学习和理解的过程来看,其实真正花耐心去理解,是不难的,往往真正击败我们的,真正击败我们的,往往是浮躁。道长且阻,一起加油~
GitHub地址:
https://github.com/hacksman/simple_neural_network
参考:
A Step by Step Backpropagation Example(原文):
https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
推荐阅读:
机器学习算法的随机数据生成
如果知乎被腾讯收购,会擦出什么火花?
2018年终精心整理|人工智能爱好者社区历史文章合集(作者篇)
2018年终精心整理 | 人工智能爱好者社区历史文章合集(类型篇)
公众号后台回复关键词学习
回复 免费 获取免费课程
回复 直播 获取系列直播课
回复 Python 1小时破冰入门Python
回复 人工智能 从零入门人工智能
回复 深度学习 手把手教你用Python深度学习
回复 机器学习 小白学数据挖掘与机器学习
回复 贝叶斯算法 贝叶斯与新闻分类实战
回复 数据分析师 数据分析师八大能力培养
回复 自然语言处理 自然语言处理之AI深度学习