这是我的机器学习笔记,想要通过笔记记录的方式更快的上手这门学科。这篇文章主要涉及到了RNN算法的入门。 首先,我们做如下的约定:
数学符号
在这篇文章中,我们将一个行向量表示成
1
×
n
1 \times n
1×n 的矩阵
在这篇文章中,我们将一个列向量表示成
m
×
1
m \times 1
m×1 的矩阵
以上说明是为了明确一点,对于给定维度 [ m , n ] [m,n] [m,n]的矩阵,第一个数字表示行数。(在很多机器学习的笔记中,有可能出现相反的定义,所以在文章开头先做解释)
X
i
<
k
>
X^{<k>}_i
Xi<k> 表示第k个数据输入中的第i个元素。
A
i
<
k
>
A^{<k>}_i
Ai<k> 表示第k个数据输出中的第i个元素。
Y
i
<
k
>
Y^{<k>}_i
Yi<k> 表示第k个预测输出中的第i个元素。
B
B
B 表示bias
W
<
k
>
W^{<k>}
W<k> 参数矩阵 w.r.t
X
<
k
>
X^{<k>}
X<k>
MNIST
我们这里不再赘述这个数据集。 对于这个数据集里面的图片,我们拿到的将会是一个 784 × 1 784 \times 1 784×1的列向量, 它代表着一个像素为 28 × 28 28 \times 28 28×28的灰度图片。 如果我们想要利用MLP来对这个数据集进行拟合,那么并不需要对数据集进行转换。 如果我们要利用CNN 或是RNN来进行拟合的话,则需要用reshape命令将其还原至 28 × 28 28 \times 28 28×28的方阵。
MLP
最基本的RNN算法实际上是一种特殊的MLP。我们先简单回顾一下MLP算法。
如下图所示:
我们将一个
5
×
1
5 \times 1
5×1的矩阵输入,令变量
M
M
M为一个
4
×
5
4 \times 5
4×5的矩阵,
B
B
B 为bias,则
A
=
M
X
+
B
A = MX + B
A=MX+B
我们的到输出A, A 是一个
4
×
1
4 \times 1
4×1 的矩阵。 最后在通过一些激活函数或是再增加一个W矩阵得到最终的预测值
Y
^
\hat{Y}
Y^. 本质上这是一种线性回归模型。
RNN basic
在上一小节中,我们简单回顾了MLP。 在这类算法中,我们实际上将数据集中的各个数据视为相对独立,并不互相干扰,尽管他们共用了矩阵M以及bias B。但是在实际生活中,数据之间并不相互独立, 例如在中文联想中,上下文的语境关系往往是最为重要的。RNN可以很好的解决这类问题,我们将通过tensorflow代码与数学原理的结合来介绍这个算法。
首先,RNN其实可以视为一种特殊的MLP。 如下图所示:
这个图是RNN—BASIC的一个大致框架。 其中的数学原理如下:
假定我们得到了MNIST数据集,对于其中的图片,我们先利用reshape命令将其还原至
28
×
28
28 \times 28
28×28的方阵。对于这个方阵,我们将其分割成28个列向量, 如下图所示(在 tensorflow 中,我们会使用更便捷的方式)
接着,我们来分析下cell-1里面的数学背景。
对于这个cell来说,它得到了两个输入, 一个是
X
<
1
>
X^{<1>}
X<1>, 另一个是
A
<
0
>
A^{<0>}
A<0>。 由于有两个输入, 我们需要定义两个W矩阵来进行运算, 我们分别定义为
W
a
→
a
,
W
x
→
a
W_{a \rightarrow a}, W_{x \rightarrow a}
Wa→a,Wx→a.
另外我们还需要定义一个bias, 用B来表示。
第一层的输出计算为:
A
<
1
>
=
tanh
(
W
a
→
a
A
<
0
>
+
W
x
→
a
X
<
1
>
+
B
)
A^{<1>} = \tanh(W_{a \rightarrow a}A^{<0>} + W_{x \rightarrow a}X^{<1>} + B)
A<1>=tanh(Wa→aA<0>+Wx→aX<1>+B)
同理,接下去的计算遵循一样的公式:
A
<
k
>
=
tanh
(
W
a
→
a
A
<
k
−
1
>
+
W
x
→
a
X
<
k
>
+
B
)
A^{<k>} = \tanh(W_{a \rightarrow a}A^{<k-1>} + W_{x \rightarrow a}X^{<k>} + B)
A<k>=tanh(Wa→aA<k−1>+Wx→aX<k>+B)
所有的cell将会共享两个W矩阵以及一个B矩阵。
对于实际的预测输出,我们可以对输出矩阵A加上激活函数。
当然,我们还需要初始化
A
<
0
>
A^{<0>}
A<0>, 可以令其为0矩阵。
我们回到MNIST 数据集。 对于一张
28
×
28
28 \times 28
28×28的图片, 我们已经分割出28个列向量, 每个列向量的维度为
28
×
1
28 \times 1
28×1.
现在我们要引入几个名词(这些名词是在TENSORFLOW语句中使用的)
t
i
m
e
_
s
t
e
p
s
=
28
time\_steps = 28
time_steps=28: 这里我们分割出了28个列向量,意味着我们在时间这一维度有28。 意思是,对于一张图片,我们实际进行了28次的计算,每次计算中只包含一个列向量。
n
u
m
_
i
n
p
u
t
s
=
28
num\_inputs = 28
num_inputs=28: 这里我们分割出的列向量为
28
×
1
28 \times 1
28×1, 因此这个参数是28
n
u
m
_
u
n
i
t
s
=
100
num\_units = 100
num_units=100:
这是一个参数,意思是输出A的维数。 回顾到MLP中,A实际上也是一个列向量,我们这里定义A的维度为
n
u
m
_
u
n
i
t
s
×
1
num\_units \times 1
num_units×1。
W
a
→
a
W_{a \rightarrow a}
Wa→a 矩阵的维数应该是
n
u
m
_
u
n
i
t
s
×
n
u
m
_
u
n
i
t
s
num\_units \times num\_units
num_units×num_units, 即
100
×
100
100 \times 100
100×100, 这样的话,
W
a
→
a
A
k
−
1
W_{a \rightarrow a}A^{{k-1}}
Wa→aAk−1 将会输出一个
100
×
1
100 \times 1
100×1 的列向量。
W
x
→
a
W_{x \rightarrow a}
Wx→a 矩阵的维数应该是
n
u
m
_
u
n
i
t
s
×
n
u
m
_
i
n
p
u
t
s
num\_units \times num\_inputs
num_units×num_inputs, 即
100
×
28
100 \times 28
100×28, 这样的话,
W
a
→
a
X
k
W_{a \rightarrow a}X^{{k}}
Wa→aXk 将会输出一个
100
×
1
100 \times 1
100×1 的列向量。
现在我们可以利用tensorflow来做一个RNN中的cell:
导入数据
#loading data
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot = True)
#########################
images = mnist.train.images
labels = mnist.train.labels
images_test = mnist.test.images
labels_test = mnist.test.labels
#####################
设置占位符
x = tf.placeholder(tf.float32, [None, 28, 28])
y = tf.placeholder(tf.float32, [None, 10])
设置参数
batch_size = 256
epochs = 10
num_units = 100
我们只进行10次迭代,用来作为演示。
Basic Cell
basic_cell = tf.keras.layers.SimpleRNNCell(units = num_units)
这里我们只需要输入一个参数,其余参数保持默认即可,如果有兴趣可以转至:
TensorFlow tf.keras.layers.SimpleRNN
虽然是英文,但是都很简单明了。这个语句会帮助初始化所有参量,根据之前的数学背景,所有参量的维度实际上只依赖于num_units, 其余的维度参数将会根据输入数据来调整。
构造 多个basic cell
outputs, states = tf.nn.dynamic_rnn(basic_cell, x, dtype = tf.float32)
这个语句是整个rnn的核心,它会动态地来连接各个basic RNN cell,在我们的例子中,它会连接出28个cells。 另外,这个语句将会返回出两个元素。
在这个程序中,我们一开始输入了batch_size=256 张图片,每个图片包含28个 28*1 的列向量。
outputs & states
outputs:这个元素中包含了图3中的 所有的输出A(
A
<
0
>
A^{<0>}
A<0> 除外), 我们可以对output进行softmax来得到所有的预测值Y(这个例子里面不需要,因为我们在处理的是一个 many to one 的问题,我们仅仅需要最终的一个结果,而不需要在每个时间step上面做输出)维度自然为(batch_size = 256, steps = 28, num_units = 100)
states:这个元素仅仅包含了图3中的
A
<
k
>
A^{<k>}
A<k>即最后一个cell的输出。 维度为( batch_size = 256, layers = 1, num_units = 100 )
multi-layer case
之前,我们仅仅讨论的是最为简单的case, 是一个单层的RNN模型, 为了更加方便的理解整个outputs&states, 我们这里插入一段 multilayer RNN 的case。
在这个case下:
outputs: 维度(batch_size = 256, steps = 28, num_units = 100)因为只包含了每个step上面最后一层的输出,因此就算是multilayer 也不影响它的维度。
states:维度( batch_size = 256, layers = 2, num_units = 100)因为包含了每一层最后一个step的输出,因此在multiplayer下面,维度会变化, 即包含了 (
A
<
13
>
A^{<13>}
A<13>,
A
<
23
>
A^{<23>}
A<23>)。
FC layer
由于我们的问题是一个many to one 的case, 一张图片, 28个输入, 我们只需要最终一个输出,因此,我们只需要states这个参量, 对其进行FC 映射到一个 10*1 的列向量(对应0~9)
logits = tf.contrib.layers.fully_connected(states, 10, activation_fn = None)
loss and optimizer
这部分就是普通的softmax以及Adam(Adam参数全部默认)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits = logits, labels = y))
optimizer = tf.train.AdamOptimizer().minimize(loss)
精度检验
correct_predict = tf.equal(tf.argmax(logits, 1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_predict, tf.float32))
开始训练
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(epochs + 1):
for _ in range(mnist.train.num_examples // batch_size):
x_batch, y_batch = mnist.train.next_batch(batch_size)
x_batch = x_batch.reshape([-1, 28, 28])
sess.run(optimizer, feed_dict = {x: x_batch, y: y_batch})
acc_train = sess.run(accuracy, feed_dict = {x: x_batch, y : y_batch})
acc_test = sess.run(accuracy, feed_dict = {x: images_test, y: labels_test})
print(epoch, "acc_train = ", acc_train, "acc_test = ", acc_test)
注释:
在上面的语句中
x_batch = x_batch.reshape([-1, 28, 28])
我们用reshape将输入转换成方阵, 维度定义为 (batch_size, time_steps, num_inputs)
程序结果
0 acc_train = 0.96875 acc_test = 0.934
1 acc_train = 0.9921875 acc_test = 0.9412
2 acc_train = 0.96875 acc_test = 0.9533
3 acc_train = 0.96875 acc_test = 0.9583
4 acc_train = 0.9453125 acc_test = 0.963
5 acc_train = 0.9921875 acc_test = 0.963
6 acc_train = 0.9765625 acc_test = 0.965
7 acc_train = 0.984375 acc_test = 0.9609
8 acc_train = 0.984375 acc_test = 0.9717
9 acc_train = 0.96875 acc_test = 0.9675
10 acc_train = 0.984375 acc_test = 0.9733
11 acc_train = 0.9765625 acc_test = 0.9615
12 acc_train = 1.0 acc_test = 0.9713
13 acc_train = 0.984375 acc_test = 0.9692
14 acc_train = 0.984375 acc_test = 0.9746
15 acc_train = 0.9609375 acc_test = 0.9747
16 acc_train = 0.984375 acc_test = 0.9737
17 acc_train = 0.9921875 acc_test = 0.9732
18 acc_train = 0.96875 acc_test = 0.9704
19 acc_train = 0.984375 acc_test = 0.9742
20 acc_train = 0.9765625 acc_test = 0.9711
在这篇文章中,我们使用了最简单的RNN模型并利用这个模型对MNIST数据集进行了验证。 RNN 收敛速度很快,并且参数使用较 CNN来说更少。之后的文章中,我还会更新其他的RNN模型。