⽤Python写出LSTM-RNN的代码!
0. 前⾔
本⽂翻译⾃博客:,这次翻译已经获得trask本⼈的同意与⽀持,在此特别感谢trask。本⽂属于作者⼀边学习⼀边翻译的作品,所以在⽤词、理论⽅⾯难免会出现很多错误,假如您发现错误或者不合适的地⽅,可以给我留⾔,谢谢!
1. 概要
我的最佳学习法就是通过玩具代码,⼀边调试⼀边学习理论。这篇博客通过⼀个⾮常简单的python玩具代码来讲解递归神经⽹络。
那么依旧是废话少说,放‘码’过来!
[python]
01. import copy, numpy as np
02. np.random.seed(0)
03.
04. # compute sigmoid nonlinearity
05. def sigmoid(x):
06. output = 1/(p(-x))
07. return output
08.
09. # convert output of sigmoid function to its derivative
10. def sigmoid_output_to_derivative(output):
11. return output*(1-output)
12.
13.
14. # training dataset generation
15. int2binary = {}
16. binary_dim = 8
17.
18. largest_number = pow(2,binary_dim)
19. binary = np.unpackbits(
20. np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
21. for i in range(largest_number):
22. int2binary[i] = binary[i]
23.
24.
25. # input variables
26. alpha = 0.1
27. input_dim = 2
28. hidden_dim = 16
29. output_dim = 1
30.
31.
32. # initialize neural network weights
33. synapse_0 = 2*np.random.random((input_dim,hidden_dim)) - 1
34. synapse_1 = 2*np.random.random((hidden_dim,output_dim)) - 1
35. synapse_h = 2*np.random.random((hidden_dim,hidden_dim)) - 1
36.
37. synapse_0_update = np.zeros_like(synapse_0)
38. synapse_1_update = np.zeros_like(synapse_1)
39. synapse_h_update = np.zeros_like(synapse_h)
40.
41. # training logic
42. for j in range(10000):
43.
44. # generate a simple addition problem (a + b = c)
45. a_int = np.random.randint(largest_number/2) # int version
46. a = int2binary[a_int] # binary encoding
47.
48. b_int = np.random.randint(largest_number/2) # int version
49. b = int2binary[b_int] # binary encoding
50.
51. # true answer
52. c_int = a_int + b_int
53. c = int2binary[c_int]
54.
55. # where we'll store our best guess (binary encoded)
56. d = np.zeros_like(c)
57.
58. overallError = 0
59.
60. layer_2_deltas = list()
61. layer_1_values = list()
62. layer_1_values.s(hidden_dim))
63.
64. # moving along the positions in the binary encoding
65. for position in range(binary_dim):
66.
67. # generate input and output
68. X = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])
69. y = np.array([[c[binary_dim - position - 1]]]).T
70.
71. # hidden layer (input ~+ prev_hidden)
72. layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))
73.
74. # output layer (new binary representation)
75. layer_2 = sigmoid(np.dot(layer_1,synapse_1))
76.
77. # did we miss?... if so by how much?
78. layer_2_error = y - layer_2
79. layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))
80. overallError += np.abs(layer_2_error[0])
81.
82. # decode estimate so we can print it out
83. d[binary_dim - position - 1] = np.round(layer_2[0][0])
84.
85. # store hidden layer so we can use it in the next timestep
86. layer_1_values.append(copy.deepcopy(layer_1))
87.
88. future_layer_1_delta = np.zeros(hidden_dim)
89.
90. for position in range(binary_dim):
91.
92. X = np.array([[a[position],b[position]]])
93. layer_1 = layer_1_values[-position-1]
94. prev_layer_1 = layer_1_values[-position-2]
95.
96. # error at output layer
97. layer_2_delta = layer_2_deltas[-position-1]
98. # error at hidden layer
99. layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + \
100. layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1) 101. # let's update all our weights so we can try again
102. synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
103. synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
104. synapse_0_update += X.T.dot(layer_1_delta)
105.
106. future_layer_1_delta = layer_1_delta
107.
108.
109. synapse_0 += synapse_0_update * alpha
110. synapse_1 += synapse_1_update * alpha
111. synapse_h += synapse_h_update * alpha
112.
113. synapse_0_update *= 0
114. synapse_1_update *= 0
115. synapse_h_update *= 0
116.
117. # print out progress
118. if(j % 1000 == 0):
119. print"Error:" + str(overallError)
120. print"Pred:" + str(d)
121. print"True:" + str(c)
122. out = 0
123. for index,x in enumerate(reversed(d)):
124. out += x*pow(2,index)
125. print str(a_int) + " + " + str(b_int) + " = " + str(out)
126. print"------------"
运⾏输出:
Error:[ 3.45638663]
Pred:[0 0 0 0 0 0 0 1]
True:[0 1 0 0 0 1 0 1]
9 + 60 = 1
------------
Error:[ 3.63389116]
Pred:[1 1 1 1 1 1 1 1]
True:[0 0 1 1 1 1 1 1]
28 + 35 = 255
------------
Error:[ 3.91366595]
Pred:[0 1 0 0 1 0 0 0]
True:[1 0 1 0 0 0 0 0]
116 + 44 = 72
------------
Error:[ 3.72191702]
Pred:[1 1 0 1 1 1 1 1]
True:[0 1 0 0 1 1 0 1]
4 + 73 = 223
-
-----------
Error:[ 3.5852713]
Pred:[0 0 0 0 1 0 0 0]
True:[0 1 0 1 0 0 1 0]
71 + 11 = 8
------------
Error:[ 2.53352328]
Pred:[1 0 1 0 0 0 1 0]
True:[1 1 0 0 0 0 1 0]
81 + 113 = 162
------------
Error:[ 0.57691441]
Pred:[0 1 0 1 0 0 0 1]
True:[0 1 0 1 0 0 0 1]
81 + 0 = 81
------------
Error:[ 1.42589952]
Pred:[1 0 0 0 0 0 0 1]
True:[1 0 0 0 0 0 0 1]
4 + 12
5 = 129
------------
Error:[ 0.47477457]
Pred:[0 0 1 1 1 0 0 0]
True:[0 0 1 1 1 0 0 0]
39 + 17 = 56
------------
Error:[ 0.21595037]
Pred:[0 0 0 0 1 1 1 0]
True:[0 0 0 0 1 1 1 0]
11 + 3 = 14
------------
第⼀部分:什么是神经元记忆?
正向的背⼀边字母表……你能做到,对吧?
倒着背⼀遍字母表……唔……也许有点难。
那么试试你熟悉的⼀⾸歌词?……为什么正常顺序回忆的时候⽐倒着回忆更简单呢?你能直接跳跃到第⼆⼩节的歌词么?……唔唔……同样很难,是吧?
其实这很符合逻辑……你并不像计算机那样把字母表或者歌词像存储在硬盘⼀样的记住,你是把它们作为⼀个序列去记忆的。你很擅长于⼀个单词⼀个单词的去回忆起它们,这是⼀种条件记忆。你只有在拥有了前边部分的记忆了以后,才能想起来后边的部分。如果你对链表⽐较熟悉的话,OK,我们的记忆就和链表是类似的。
然⽽,这并不意味着当你不唱歌时,你的记忆中就没有这⾸歌。⽽是说,当你试图直接记忆起某个中间的部分,你需要花费⼀定的时间在你的脑海中寻(也许是在⼀⼤堆神经元⾥寻)。⼤脑开始在这⾸歌⾥到处寻你想要的中间部分,但是⼤脑之前并没有这么做过,所以它并没有⼀个能够指向中间这部分的索引。这就像住在⼀个附近都是岔路/死胡同的地⽅,你从⼤路上到某⼈的房⼦很简单,因为你经常那样⾛。但是把你丢在⼀家⼈的后院⾥,你却怎么也不到正确的道路了。可见你的⼤脑并不是⽤“⽅位”去寻,⽽是通过⼀⾸歌的开头所在的神经元去寻的。如果你想了解更多关于⼤脑的知识,可以访问:。
就像链表⼀样,记忆这样去存储是很有效的。这样可以通过脑神经⽹络很好的到相似的属性、优势。⼀些过程、难题、表⽰、查询也可以通过这种短期/伪条件记忆序列存储的⽅式,使其更加的⾼效。
去记忆⼀些数据是序列的事情(其实就是意味着你有些东西需要去记住!),假设有⼀个跳跳球,每个数据点就是你眼中跳跳球运动的⼀帧图像。如果你想训练⼀个神经⽹络去预测下⼀帧球会在哪⾥,那么知道上⼀帧球在哪⾥就会对你的预测很有帮助!这样的序列数据就是我们为什么要搭建⼀个递归神经⽹络。那么,⼀个神经⽹络怎么记住它之前的时间它看到了什么呢?
神经⽹络有隐藏层,⼀般来讲,隐藏层的状态只跟输⼊数据有关。所以⼀般来说⼀个神经⽹络的信息流就会像下⾯所⽰的这样:
input -> hidden ->output
这很明显,确定的输⼊产⽣确定的隐藏层,确定的隐藏层产⽣确定的输出层。这是⼀种封闭系统。但是,记忆改变了这种模式!记忆意味着隐藏层是,当前时刻的输⼊与隐藏层前⼀时刻的⼀种组合。
( input + prev_hidden ) -> hidden -> output
为什么是隐藏层呢?其实技术上来说我们可以这样:
random翻译( input + prev_input ) -> hidden -> output
然⽽,我们遗漏了⼀些东西。我建议你认真想想这两个信息流的不同。给你点提⽰,演绎⼀下它们分别是怎么运作的。这⾥呢,我们给出4步的递归神经⽹络流程看看它怎么从之前的隐藏层得到信息。
( input + empty_hidden ) -> hidden -> output
( input + prev_hidden ) -> hidden -> output
( input + prev_hidden ) -> hidden -> output
( input + prev_hidden ) -> hidden -> output
然后,我们再给出4步,从输⼊层怎么得到信息。
( input + empty_input ) -> hidden -> output
( input + prev_input ) -> hidden -> output
( input + prev_input ) -> hidden -> output
( input + prev_input ) -> hidden -> output
或许,如果我把⼀些部分涂上颜⾊,⼀些东西就显⽽易见了。那么我们再看看这4步隐藏层的递归:
( input + empty_hidden ) ->hidden -> output
( input + prev_hidden ) ->hid den -> output
( input + prev_hid den ) ->hi dd en -> output
( input + prev_hi dd en ) ->hi d de n -> output
……以及,4步输⼊层的递归:
( input + empty_input ) -> hidden -> output
( input + prev_input ) -> hid den -> output
( input + prev_input ) -> hid den -> output
( input + prev_input ) -> hid den -> output
看⼀下最后⼀个隐藏层(第四⾏)。在隐藏层递归中,我们可以看到所有见过的输⼊的存在。但是在
输⼊层递归中,我们仅仅能发现上次与本次的输⼊。这就是为什么我们⽤隐藏层递归建模。隐藏层递归能学习它到底去记忆什么,但是输⼊层递归仅仅能记住上次的数据点。
现在我们对⽐⼀下这两种⽅法,通过反向的字母表与歌词中间部分的练习。隐藏层根据越来越多的输⼊持续的改变,⽽且,我们到达这些隐藏状态的唯⼀⽅式就是沿着正确的输⼊序列。现在就到了很重要的⼀点,输出由隐藏层决定,⽽且只有通过正确的输⼊序列才能到达隐藏层。是不是很相似?
那么有什么实质的区别呢?我们考虑⼀下我们要预测歌词中的下⼀个词,假如碰巧在不同的地⽅有两个相同的词,“输出层递归”就会使你回忆不起来下⾯的歌词到底是什么了。仔细想想,如果⼀⾸歌有⼀句“我爱你”,以及“我爱萝⼘”,记忆⽹络现在试图去预测下⼀个词,那它怎么知道“我爱”后边到底是什么?可能是“你”,也可能是“萝⼘”。所以记忆⽹络必须要知道更多的信息,去识别这到底是歌词中的那⼀段。⽽“隐藏层递归”不会让你忘记歌词,就是通过这个原理。它巧妙地记住了它看到的所有东西(记忆更巧妙地是它能随时间逐渐忘却)。想看看它是怎么运作的,猛戳这⾥:
好的,现在停下来,然后确认你的脑袋是清醒的。
第⼆部分:RNN - 神经⽹路记忆
现在我们已经对这个问题有个直观的认识了,让我们下潜的更深⼀点(什么⿁,你在逗我?)。就像
在反向传播这篇博⽂()⾥介绍的那样,输⼊数据决定了我们神经⽹络的输⼊层。每⾏输⼊数据都被⽤来产⽣隐含层(通过正向传播),然后⽤每个隐含层⽣成输出层(假设只有⼀层隐含层)。就像我们刚才看到的,记忆意味着隐含层是输⼊与上⼀次隐含层的组合。那么怎么组合呢?其实就像神经⽹络的其他传播⽅法,⽤⼀个矩阵就⾏了,这个矩阵定义了之前隐含层与当前的关系。
从这张图中能看出来很多东西。这⾥只有三个权值矩阵,其中两个很相似(名字也⼀样)。SYNAPSE_0把输⼊数据传播到隐含层,SYNAPSE_1把隐含层数据传播到输出层。新的矩阵(SYNAPSE_h……要递归的),把隐含层(layer_1)传播到下⼀个时间点的隐含层(仍旧是layer_1)。
好的,现在停下来,然后确认你的脑袋是清醒的。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论