零基础⼊门深度学习(1)-感知器
⽆论即将到来的是⼤数据时代还是⼈⼯智能时代,亦或是传统⾏业使⽤⼈⼯智能在云上处理⼤数据的时代,作为⼀个有理想有追求的程序员,不懂深度学习(Deep Learning)这个超热的技术,会不会感觉马上就out了?现在救命稻草来了,《零基础⼊门深度学习》系列⽂章旨在讲帮助爱编程的你从零基础达到⼊门级⽔平。零基础意味着你不需要太多的数学知识,只要会写程序就⾏了,没错,这是专门为程序员写的⽂章。虽然⽂中会有很多公式你也许看不懂,但同时也会有更多的代码,程序员的你⼀定能看懂的(我周围是⼀狂热的Clean Code程序员,所以我写的代码也不会很差)。
⽂章列表
深度学习是啥
在⼈⼯智能领域,有⼀个⽅法叫机器学习。在机器学习这个⽅法⾥,有⼀类算法叫神经⽹络。神经⽹络如下图所⽰:
上图中每个圆圈都是⼀个神经元,每条线表⽰神经元之间的连接。我们可以看到,上⾯的神经元被分成了多层,层与层之间的神经元有连接,⽽层内之间的神经元没有连接。最左边的层叫做输⼊层,这
层负责接收输⼊数据;最右边的层叫输出层,我们可以从这层获取神经⽹络输出数据。输⼊层和输出层之间的层叫做隐藏层。
隐藏层⽐较多(⼤于2)的神经⽹络叫做深度神经⽹络。⽽深度学习,就是使⽤深层架构(⽐如,深度神经⽹络)的机器学习⽅法。
那么深层⽹络和浅层⽹络相⽐有什么优势呢?简单来说深层⽹络能够表达⼒更强。事实上,⼀个仅有⼀个隐藏层的神经⽹络就能拟合任何⼀个函数,但是它需要很多很多的神经元。⽽深层⽹络⽤少得多的神经元就能拟合同样的函数。也就是为了拟合⼀个函数,要么使⽤⼀个浅⽽宽的⽹络,要么使⽤⼀个深⽽窄的⽹络。⽽后者往往更节约资源。
深层⽹络也有劣势,就是它不太容易训练。简单的说,你需要⼤量的数据,很多的技巧才能训练好⼀个深层⽹络。这是个⼿艺活。
感知器
看到这⾥,如果你还是⼀头雾⽔,那也是很正常的。为了理解神经⽹络,我们应该先理解神经⽹络的组成单元——神经元。神经元也叫做感知器。感知器算法在上个世纪50-70年代很流⾏,也成功解决了很多问题。并且,感知器算法也是⾮常简单的。
感知器的定义
下图是⼀个感知器:
可以看到,⼀个感知器有如下组成部分:
输⼊权值⼀个感知器可以接收多个输⼊,每个输⼊上有⼀个权值,此外还有⼀个偏置项,就是上图中的。
激活函数感知器的激活函数可以有很多选择,⽐如我们可以选择下⾯这个阶跃函数来作为激活函数:
输出感知器的输出由下⾯这个公式来计算
公式
如果看完上⾯的公式⼀下⼦就晕了,不要紧,我们⽤⼀个简单的例⼦来帮助理解。
例⼦:⽤感知器实现and函数
我们设计⼀个感知器,让它来实现and运算。程序员都知道,and是⼀个⼆元函数(带有两个参数和),下⾯是它的真值表:
000
010
100
111
为了计算⽅便,我们⽤0表⽰false,⽤1表⽰true。这没什么难理解的,对于C语⾔程序员来说,这是天经地义的。
我们令,⽽激活函数就是前⾯写出来的阶跃函数,这时,感知器就相当于and函数。不明⽩?我们验算⼀下:
输⼊上⾯真值表的第⼀⾏,即,那么根据公式(1),计算输出:
也就是当都为0的时候,为0,这就是真值表的第⼀⾏。读者可以⾃⾏验证上述真值表的第⼆、三、四⾏。
例⼦:⽤感知器实现or函数
同样,我们也可以⽤感知器来实现or运算。仅仅需要把偏置项的值设置为-0.3就可以了。我们验算⼀下,下⾯是or运算的真值表:
000
011
101
111
我们来验算第⼆⾏,这时的输⼊是,带⼊公式(1):
也就是当时,为1,即or真值表第⼆⾏。读者可以⾃⾏验证其它⾏。
感知器还能做什么
事实上,感知器不仅仅能实现简单的布尔运算。它可以拟合任何的线性函数,任何线性分类或线性回归问题都可以⽤感知器来解决。前⾯的布尔运算可以看作是⼆分类问题,即给定⼀个输⼊,输出0(属于分类0)或1(属于分类1)。如下⾯所⽰,and运算是⼀个线性分类问题,即可以⽤⼀条直线把分类0(false,红叉表⽰)和分类1(true,绿点表⽰)分开。
然⽽,感知器却不能实现异或运算,如下图所⽰,异或运算不是线性的,你⽆法⽤⼀条直线把分类0和分类1分开。
感知器的训练
现在,你可能困惑前⾯的权重项和偏置项的值是如何获得的呢?这就要⽤到感知器训练算法:将权重项和偏置项初始化为0,然后,利⽤下⾯的感知器规则迭代的修改
和,直到训练完成。
其中:
是与输⼊
对应的权重项,
是偏置项。事实上,可以把
看作是值永远为1的输⼊
所对应的权重。是训练样本的实际值,⼀般称之为label。⽽是感知器的输出值,它是根据公式(1)计算得出。是⼀个称为学习速率的常数,其作⽤是控制每⼀步调整权的幅度。
每次从训练数据中取出⼀个样本的输⼊向量
,使⽤感知器计算其输出
,再根据上⾯的规则来调整权重。每处理⼀个样本就调整⼀次权重。经过多轮迭代后(即全部的训练数据被反复处理多轮),就可以训练出感知器的权重,使之实现⽬标函数。编程实战:实现感知器
完整代码请参考GitHub: (python2.7)
对于程序员来说,没有什么⽐亲⾃动⼿实现学得更快了,⽽且,很多时候⼀⾏代码抵得上千⾔万语。接下来我们就将实现⼀个感知器。
下⾯是⼀些说明:
使⽤python语⾔。python在机器学习领域⽤的很⼴泛,⽽且,写python程序真的很轻松。
⾯向对象编程。⾯向对象是特别好的管理复杂度的⼯具,应对复杂问题时,⽤⾯向对象设计⽅法很容易将复杂问题拆解为多个简单问题,从⽽解救我们的⼤脑。
没有使⽤numpy。numpy实现了很多基础算法,对于实现机器学习算法来说是个必备的⼯具。但为了降低读者理解的难度,下⾯的代码只⽤到了基本的python(省去您去学习numpy的时间)。
下⾯是感知器类的实现,⾮常简单。去掉注释只有27⾏,⽽且还包括为了美观(每⾏不超过60个字符)⽽增加的很多换⾏。
1. class Perceptron(object):
2. def __init__(self, input_num, activator):
3. '''
4. 初始化感知器,设置输⼊参数的个数,以及激活函数。
5. 激活函数的类型为double -> double
6. '''
7. self.activator = activator
8. # 权重向量初始化为0
9. self.weights = [0.0 for _ in range(input_num)]
10. # 偏置项初始化为0
11. self.bias = 0.0
12.
13. def __str__(self):
14. '''
15. 打印学习到的权重、偏置项
16. '''
17. return 'weights\t:%s\nbias\t:%f\n' % (self.weights, self.bias)
18.
19.
20. def predict(self, input_vec):
21. '''
22. 输⼊向量,输出感知器的计算结果
23. '''
24. # 把input_vec[x1,]和weights[w1,w2,w3,...]打包在⼀起
25. # 变成[(x1,w1),(x2,w2),(x3,w3),...]
26. # 然后利⽤map函数计算[x1*w1, x2*w2, x3*w3]
27. # 最后利⽤reduce求和
28. return self.activator(
29. reduce(lambda a, b: a + b,
30. map(lambda (x, w): x * w,
31. zip(input_vec, self.weights))
32. , 0.0) + self.bias)
33.
34. def train(self, input_vecs, labels, iteration, rate):
35. '''
36. 输⼊训练数据:⼀组向量、与每个向量对应的label;以及训练轮数、学习率
零基础自学什么好
37. '''
38. for i in range(iteration):
39. self._one_iteration(input_vecs, labels, rate)
40.
41. def _one_iteration(self, input_vecs, labels, rate):
42. '''
43. ⼀次迭代,把所有的训练数据过⼀遍
44. '''
45. # 把输⼊和输出打包在⼀起,成为样本的列表[(input_vec, label), ...]
46. # ⽽每个训练样本是(input_vec, label)
47. samples = zip(input_vecs, labels)
48. # 对每个样本,按照感知器规则更新权重
49. for (input_vec, label) in samples:
50. # 计算感知器在当前权重下的输出
51. output = self.predict(input_vec)
52. # 更新权重
53. self._update_weights(input_vec, output, label, rate)
54.
55. def _update_weights(self, input_vec, output, label, rate):
56. '''
57. 按照感知器规则更新权重
58. '''
59. # 把input_vec[x1,x2,x3,...]和weights[w1,w2,w3,...]打包在⼀起
60. # 变成[(x1,w1),(x2,w2),(x3,w3),...]
61. # 然后利⽤感知器规则更新权重
62. delta = label - output
63. self.weights = map(
64. lambda (x, w): w + rate * delta * x,
65. zip(input_vec, self.weights))
66. # 更新bias
67. self.bias += rate * delta
接下来,我们利⽤这个感知器类去实现and函数。
1. def f(x):
2. '''
3. 定义激活函数f
4. '''
5. return 1 if x > 0 else 0
6.
7.
8. def get_training_dataset():
9. '''
10. 基于and真值表构建训练数据
11. '''
12. # 构建训练数据
13. # 输⼊向量列表
14. input_vecs = [[1,1], [0,0], [1,0], [0,1]]
15. # 期望的输出列表,注意要与输⼊⼀⼀对应
16. # [1,1] -> 1, [0,0] -> 0, [1,0] -> 0, [0,1] -> 0
17. labels = [1, 0, 0, 0]
18. return input_vecs, labels
19.
20.
21. def train_and_perceptron():
22. '''
23. 使⽤and真值表训练感知器
24. '''
25. # 创建感知器,输⼊参数个数为2(因为and是⼆元函数),激活函数为f
26. p = Perceptron(2, f)
27. # 训练,迭代10轮, 学习速率为0.1
28. input_vecs, labels = get_training_dataset()
29. p.train(input_vecs, labels, 10, 0.1)
30. #返回训练好的感知器
31. return p
32.
33.
34. if __name__ == '__main__':
35. # 训练and感知器
36. and_perception = train_and_perceptron()
37. # 打印训练获得的权重
38. print and_perception
39. # 测试
40. print '1 and 1 = %d' % and_perception.predict([1, 1])
41. print '0 and 0 = %d' % and_perception.predict([0, 0])
42. print '1 and 0 = %d' % and_perception.predict([1, 0])
43. print '0 and 1 = %d' % and_perception.predict([0, 1])
将上述程序保存为perceptron.py⽂件,通过命令⾏执⾏这个程序,其运⾏结果为:
神奇吧!感知器竟然完全实现了and函数。读者可以尝试⼀下利⽤感知器实现其它函数。
⼩结
终于看(写)到⼩结了...,⼤家都累了。对于零基础的你来说,⾛到这⾥应该已经很烧脑了吧。没关系,休息⼀下。值得⾼兴的是,你终于已经⾛出了深度学习⼊门的第⼀步,这是巨⼤的进步;坏消息是,这仅仅是最简单的部分,后⾯还有⽆数艰难险阻等着你。不过,你学的困难往往意味着别⼈学的也困难,掌握⼀门⾼门槛的技艺,进可糊⼝退可,是很值得的。
下⼀篇⽂章,我们将讨论另外⼀种感知器:线性单元,并由此引出⼀种可能是最最重要的优化算法:
梯度下降算法。
参考资料
1. Tom M. Mitchell, "机器学习", 曾华军等译, 机械⼯业出版社
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论