机器学习笔记1 MNIST RNN 的tensorflow 实现

这是我的机器学习笔记,想要通过笔记记录的方式更快的上手这门学科。这篇文章主要涉及到了RNN算法的入门。 首先,我们做如下的约定:

数学符号

在这篇文章中,我们将一个行向量表示成 1 × n 1 \times n 1×n 的矩阵
在这篇文章中,我们将一个列向量表示成 m × 1 m \times 1 m×1 的矩阵

以上说明是为了明确一点,对于给定维度 [ m , n ] [m,n] [m,n]的矩阵,第一个数字表示行数。(在很多机器学习的笔记中,有可能出现相反的定义,所以在文章开头先做解释)

X i &lt; k &gt; X^{&lt;k&gt;}_i Xi<k> 表示第k个数据输入中的第i个元素。
A i &lt; k &gt; A^{&lt;k&gt;}_i Ai<k> 表示第k个数据输出中的第i个元素。
Y i &lt; k &gt; Y^{&lt;k&gt;}_i Yi<k> 表示第k个预测输出中的第i个元素。
B B B 表示bias
W &lt; k &gt; W^{&lt;k&gt;} W<k> 参数矩阵 w.r.t X &lt; k &gt; X^{&lt;k&gt;} 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算法。
如下图所示:
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 model

这个图是RNN—BASIC的一个大致框架。 其中的数学原理如下:
假定我们得到了MNIST数据集,对于其中的图片,我们先利用reshape命令将其还原至 28 × 28 28 \times 28 28×28的方阵。对于这个方阵,我们将其分割成28个列向量, 如下图所示(在 tensorflow 中,我们会使用更便捷的方式)
MNIST
接着,我们来分析下cell-1里面的数学背景。

对于这个cell来说,它得到了两个输入, 一个是 X &lt; 1 &gt; X^{&lt;1&gt;} X<1>, 另一个是 A &lt; 0 &gt; A^{&lt;0&gt;} A<0>。 由于有两个输入, 我们需要定义两个W矩阵来进行运算, 我们分别定义为 W a → a , W x → a W_{a \rightarrow a}, W_{x \rightarrow a} Waa,Wxa.
另外我们还需要定义一个bias, 用B来表示。
第一层的输出计算为:
A &lt; 1 &gt; = tanh ⁡ ( W a → a A &lt; 0 &gt; + W x → a X &lt; 1 &gt; + B ) A^{&lt;1&gt;} = \tanh(W_{a \rightarrow a}A^{&lt;0&gt;} + W_{x \rightarrow a}X^{&lt;1&gt;} + B) A<1>=tanh(WaaA<0>+WxaX<1>+B)
同理,接下去的计算遵循一样的公式:
A &lt; k &gt; = tanh ⁡ ( W a → a A &lt; k − 1 &gt; + W x → a X &lt; k &gt; + B ) A^{&lt;k&gt;} = \tanh(W_{a \rightarrow a}A^{&lt;k-1&gt;} + W_{x \rightarrow a}X^{&lt;k&gt;} + B) A<k>=tanh(WaaA<k1>+WxaX<k>+B)
所有的cell将会共享两个W矩阵以及一个B矩阵。
对于实际的预测输出,我们可以对输出矩阵A加上激活函数。
当然,我们还需要初始化 A &lt; 0 &gt; A^{&lt;0&gt;} 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} Waa 矩阵的维数应该是 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}} WaaAk1 将会输出一个 100 × 1 100 \times 1 100×1 的列向量。
W x → a W_{x \rightarrow a} Wxa 矩阵的维数应该是 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}} WaaXk 将会输出一个 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 &lt; 0 &gt; A^{&lt;0&gt;} A<0> 除外), 我们可以对output进行softmax来得到所有的预测值Y(这个例子里面不需要,因为我们在处理的是一个 many to one 的问题,我们仅仅需要最终的一个结果,而不需要在每个时间step上面做输出)维度自然为(batch_size = 256, steps = 28, num_units = 100)
states:这个元素仅仅包含了图3中的 A &lt; k &gt; A^{&lt;k&gt;} A<k>即最后一个cell的输出。 维度为( batch_size = 256, layers = 1, num_units = 100 )

multi-layer case

之前,我们仅仅讨论的是最为简单的case, 是一个单层的RNN模型, 为了更加方便的理解整个outputs&states, 我们这里插入一段 multilayer RNN 的case。
Rnn_03.png

在这个case下:
outputs: 维度(batch_size = 256, steps = 28, num_units = 100)因为只包含了每个step上面最后一层的输出,因此就算是multilayer 也不影响它的维度。
states:维度( batch_size = 256, layers = 2, num_units = 100)因为包含了每一层最后一个step的输出,因此在multiplayer下面,维度会变化, 即包含了 ( A &lt; 13 &gt; A^{&lt;13&gt;} A<13>, A &lt; 23 &gt; A^{&lt;23&gt;} 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模型。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值