前言
当我们开始学习编程的时候, 第一件事往往是学习打印 "Hello World". 就好比编程入门有 Hello World, 机器学习入门有 MNIST.
MNIST 是一个入门级的计算机视觉数据集, 它包含各种手写数字图片:
它也包含每一张图片对应的标签, 告诉我们这个是数字几. 比如, 上面这四张图片的标签分别是 5,0,4,1.
其实训练一个简单的手写数字识别模型的代码很短, 我的示例代码总共也就 50 行, 除去注释, 空格之类的估计连 30 行也没有, 但是去理解包含在代码中的设计思想是很重要的, 因此这篇笔记我会将我对每段代码的理解都记录下来.
参考:
MNIST 机器学习入门
机器学习 - 损失函数
MNIST 数据集
MNIST 数据集的官网是 Yann LeCun's website http://yann.lecun.com/exdb/mnist/ . 虽然 python 提供了直接下载这个数据集的代码, 但是考虑到国内网络的原因, 建议点这下载数据集 https://pan.baidu.com/s/11i2ddnxPwR7CFEvN8qmuYg , 然后导入到项目根目录下就可以了.
下载下来的数据集被分成两部分: 60000 行的训练数据集 (mnist.train) 和 10000 行的测试数据集(mnist.test). 这样的划分很重要, 在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能, 从而更加容易把设计的模型推广到其他数据集上(泛化).
正如前面提到的一样, 每一个 MNIST 数据单元有两部分组成: 一张包含手写数字的图片和一个对应的标签. 我们把这些图片设为 xs, 把这些标签设为 ys. 训练数据集和测试数据集都包含 xs 和 ys, 比如训练数据集的图片是 mnist.train.images, 训练数据集的标签是 mnist.train.labels.
每一张图片包含 28 像素 X28 像素. 我们可以用一个数字数组来表示这张图片:
我们把这个数组展开成一个向量, 长度是 28x28 = 784. 如何展开这个数组 (数字间的顺序) 不重要, 只要保持各个图片采用相同的方式展开就可以了.
因此, 在 MNIST 训练数据集中, mnist.train.images 是一个形状为 [60000, 784] 的张量, 第一个维度数字用来索引图片, 第二个维度数字用来索引每张图片中的像素点. 在此张量里的每一个元素, 都表示某张图片里的某个像素的强度值, 值介于 0 和 1 之间.
相对应的 MNIST 数据集的标签是介于 0 到 9 的数字, 用来描述给定图片里表示的数字. 为了用于这个教程, 我们使标签数据是 "one-hot vectors". 一个 one-hot 向量除了某一位的数字是 1 以外其余各维度数字都是 0. 所以在此教程中, 数字 n 将表示成一个只有在第 n 维度 (从 0 开始) 数字为 1 的 10 维向量. 比如, 标签 0 将表示成([1,0,0,0,0,0,0,0,0,0,0]). 因此, mnist.train.labels 是一个 [60000, 10] 的数字矩阵.
现在, 我们准备好可以开始构建我们的模型啦!
Softmax 回归介绍
(因为这段很枯燥, 而且我也解释不太好, 所以干脆直接从 Tensorflow 的网站上复制粘贴来了, 如果不想看的可以直接跳过到模型实现, 最后写代码的时候只要知道用 softmax 函数就可以了)
我们知道 MNIST 的每一张图片都表示一个数字, 从 0 到 9. 我们希望得到给定图片代表每个数字的概率. 比如说, 我们的模型可能推测一张包含 9 的图片代表数字 9 的概率是 80% 但是判断它是 8 的概率是 5%(因为 8 和 9 都有上半部分的小圆), 然后给予它代表其他数字的概率更小的值.
这是一个使用 softmax 回归 (softmax regression) 模型的经典案例. softmax 模型可以用来给不同的对象分配概率. 即使在之后, 我们训练更加精细的模型时, 最后一步也需要用 softmax 来分配概率.
softmax 回归 (softmax regression) 分两步: 第一步
为了得到一张给定图片属于某个特定数字类的证据(evidence), 我们对图片像素值进行加权求和. 如果这个像素具有很强的证据说明这张图片不属于该类, 那么相应的权值为负数, 相反如果这个像素拥有有利的证据支持这张图片属于这个类, 那么权值是正数.
下面的图片显示了一个模型学习到的图片上每个像素对于特定数字类的权值. 红色代表负数权值, 蓝色代表正数权值.
我们也需要加入一个额外的偏置量(bias), 因为输入往往会带有一些无关的干扰量. 因此对于给定的输入图片 x 它代表的是数字 i 的证据可以表示为
其中 代表权重,代表数字 i 类的偏置量, j 代表给定图片 x 的像素索引用于像素求和. 然后用 softmax 函数可以把这些证据转换成概率 y:
这里的 softmax 可以看成是一个激励 (activation) 函数或者链接 (link) 函数, 把我们定义的线性函数的输出转换成我们想要的格式, 也就是关于 10 个数字类的概率分布. 因此, 给定一张图片, 它对于每一个数字的吻合度可以被 softmax 函数转换成为一个概率值. softmax 函数可以定义为:
展开等式右边的子式, 可以得到:
但是更多的时候把 softmax 模型函数定义为前一种形式: 把输入值当成幂指数求值, 再正则化这些结果值. 这个幂运算表示, 更大的证据对应更大的假设模型 (hypothesis) 里面的乘数权重值. 反之, 拥有更少的证据意味着在假设模型里面拥有更小的乘数系数. 假设模型里的权值不可以是 0 值或者负值. Softmax 然后会正则化这些权重值, 使它们的总和等于 1, 以此构造一个有效的概率分布.(更多的关于 Softmax 函数的信息, 可以参考 Michael Nieslen 的书里面的这个部分, 其中有关于 softmax 的可交互式的可视化解释.)
对于 softmax 回归模型可以用下面的图解释, 对于输入的 xs 加权求和, 再分别加上一个偏置量, 最后再输入到 softmax 函数中:
如果把它写成一个等式, 我们可以得到:
我们也可以用向量表示这个计算过程: 用矩阵乘法和向量相加. 这有助于提高计算效率.(也是一种更有效的思考方式)
更进一步, 可以写成更加紧凑的方式:
实现模型
在使用 TensorFlow 之前, 首先导入它:
import tensorflow as tf
然后导入数据集并载入数据
- from tensorflow.examples.tutorials.mnist import input_data
- # 载入数据
- mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
我们通过操作符号变量来描述这些可交互的操作单元, 可以用下面的方式创建一个:
x = tf.placeholder(tf.float32, [None, 784])
x 不是一个特定的值, 而是一个占位符 placeholder, 我们在 TensorFlow 运行计算时输入这个值. 我们希望能够输入任意数量的 MNIST 图像, 每一张图展平成 784 维的向量. 我们用 2 维的浮点数张量来表示这些图, 这个张量的形状是[None,784 ].(这里的 None 表示此张量的第一个维度可以是任何长度的.)
我们的模型也需要权重和偏量, 当然我们可以把它们当做是另外的输入(使用占位符), 但 TensorFlow 有一个更好的方法来表示它们: Variable . 一个 Variable 代表一个可修改的张量, 存在在 TensorFlow 的用于描述交互性操作的图中. 它们可以用于计算输入值, 也可以在计算中被修改. 对于各种机器学习应用, 一般都会有模型参数, 可以用 Variable 表示.
- W = tf.Variable(tf.zeros([784,10]))
- b = tf.Variable(tf.zeros([10]))
我们赋予 tf.Variable 不同的初值来创建不同的 Variable: 在这里, 我们都用全为零的张量来初始化 W 和 b. 因为我们要学习 W 和 b 的值, 它们的初值可以随意设置.
注意, W 的维度是[784,10], 因为我们想要用 784 维的图片向量乘以它以得到一个 10 维的证据值向量, 每一位对应不同数字类. b 的形状是[10], 所以我们可以直接把它加到输出上面.
现在, 我们可以实现我们的模型啦. 只需要一行代码!
prediction = tf.nn.softmax(tf.matmul(x, W)+b)
首先, 我们用 tf.matmul(X,W)表示 x 乘以 W, 对应之前等式里面的 Wx, 这里 x 是一个 2 维张量拥有多个输入. 然后再加上 b, 把和输入到 tf.nn.softmax 函数里面.
训练模型
为了训练我们的模型, 我们首先需要定义一个指标来评估这个模型是好的. 其实, 在机器学习, 我们通常定义指标来表示一个模型是坏的, 这个指标称为成本 (cost) 或损失 (loss), 然后尽量最小化这个指标. 但是, 这两种方式是相同的. 损失函数有很多种, 在这里我们采用平方损失函数, 通常我们会用均方差(MSE) 作为衡量指标, 公式如下:
为了计算损失函数, 我们首先需要添加一个新的占位符用于输入正确值:
y = tf.placeholder(tf.float32, [None, 10])
然后定义损失函数(loss):
- # 二次代价函数
- loss = tf.reduce_mean(tf.square(y-prediction))
- # 使用梯度下降法
- train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
- # 初始化变量
- init = tf.global_variables_initializer()
- with tf.Session() as sess:
- sess.run(init)
- # 每个批次的大小
- batch_size = 100
- # 计算一共有多少个批次
- n_batch = mnist.train.num_examples // batch_size
- with tf.Session() as sess:
- sess.run(init)
- for epoch in range(30):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
- # 结果存放在一个布尔型列表中
- # argmax 返回一维张量中最大的值所在的位置
- correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
- # 求准确率
- accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
- with tf.Session() as sess:
- sess.run(init)
- for epoch in range(30):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
- acc = sess.run(accuracy, feed_dict={
- x: mnist.test.images, y: mnist.test.labels})
- print("Iter"+str(epoch)+",Testing Accuracy"+str(acc))
- import datetime
- # 3.2 MNIST 数据集分类简单版本
- import tensorflow as tf
- from tensorflow.examples.tutorials.mnist import input_data
- start = datetime.datetime.now()
- # 载入数据
- mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
- # 每个批次的大小
- batch_size = 100
- # 计算一共有多少个批次
- n_batch = mnist.train.num_examples // batch_size
- # 定义两个 placeholder
- x = tf.placeholder(tf.float32, [None, 784])
- y = tf.placeholder(tf.float32, [None, 10])
- # 创建一个简单的神经网络
- W = tf.Variable(tf.zeros([784, 10]))
- b = tf.Variable(tf.zeros([10]))
- prediction = tf.nn.softmax(tf.matmul(x, W)+b)
- # 二次代价函数
- loss = tf.reduce_mean(tf.square(y-prediction))
- # 使用梯度下降法
- train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
- # 初始化变量
- init = tf.global_variables_initializer()
- # 结果存放在一个布尔型列表中
- # argmax 返回一维张量中最大的值所在的位置
- correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
- # 求准确率
- accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
- with tf.Session() as sess:
- sess.run(init)
- for epoch in range(30):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
- acc = sess.run(accuracy, feed_dict={
- x: mnist.test.images, y: mnist.test.labels})
- print("Iter"+str(epoch)+",Testing Accuracy"+str(acc))
- end = datetime.datetime.now()
- print((end-start).seconds)
来源: https://juejin.im/post/5c063e0de51d451dae3bcb51