跟着这个教程一步步理解反向传播算法

浏览: 1432

作者:张小鸡  人工智能爱好者社区专栏作者

知乎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/

推荐阅读:

  1. 机器学习算法的随机数据生成

  2. 如果知乎被腾讯收购,会擦出什么火花?

  3. 2018年终精心整理|人工智能爱好者社区历史文章合集(作者篇)

  4. 2018年终精心整理 | 人工智能爱好者社区历史文章合集(类型篇)

公众号后台回复关键词学习

回复 免费                获取免费课程

回复 直播                获取系列直播课

回复 Python           1小时破冰入门Python

回复 人工智能         从零入门人工智能

回复 深度学习         手把手教你用Python深度学习

回复 机器学习         小白学数据挖掘与机器学习

回复 贝叶斯算法      贝叶斯与新闻分类实战

回复 数据分析师      数据分析师八大能力培养

回复 自然语言处理  自然语言处理之AI深度学习



推荐 0
本文由 人工智能爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册