简单线性模型
本教程主要介绍基于TensorFlow简单线性模型处理MNIST 数据集,这里我们使用jupyter notebook。MNIST数据集是基本的手写数字集合,主要分为以下4个部分:
train-images-idx3-ubyte: training set images
train-labels-idx1-ubyte: training set labels
t10k-images-idx3-ubyte: test set images
t10k-labels-idx1-ubyte: test set labels
MNIST数据集一共包含三个部分:训练数据集(55,000份,mnist.train)、测试数据集(10,000份,mnist.test)和验证数据集(5,000份,mnist.validation)。一般来说,训练数据集是用来训练模型,验证数据集可以检验所训练出来的模型的正确性和是否过拟合,测试集是不可见的(相当于一个黑盒),但我们最终的目的是使得所训练出来的模型在测试集上的效果(这里是准确性)达到最佳。每一张都是抗锯齿(Anti-aliasing)的灰度图,图片大小28*28像素,数字部分被归一化为20*20大小,位于图片的中间位置,保持了原来形状的比例。
通过Tensorflow定义基本的线性模型,帮助我们熟悉基本的线性代数矩阵处理以及分类方法。
导入使用框架
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix
导入数据
将Mnist数据集下载到指定路径中,如果路径中已经存在,那就直接读取。
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets("MNIST_data",one_hot=True)
Extracting MNIST_data\train-images-idx3-ubyte.gz
Extracting MNIST_data\train-labels-idx1-ubyte.gz
Extracting MNIST_data\t10k-images-idx3-ubyte.gz
Extracting MNIST_data\t10k-labels-idx1-ubyte.gz
print("Size of:")
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(data.test.labels)))
print("- Validation-set:\t{}".format(len(data.validation.labels)))
Size of:
- Training-set: 55000
- Test-set: 10000
- Validation-set: 5000
One-Hot Encoding
### One-Hot编码,又称为一位有效编码,主要是采用位状态寄存器来对个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。在实际的机器学习的应用任务中,特征有时候并不总是连续值,有可能是一些分类值,如性别可分为“male”和“female”。在机器学习任务中,对于这样的特征,通常我们需要对其进行特征数字化,如下面的例子:有如下三个特征属性:性别:[“male”,”female”] 地区:[“Europe”,”US”,”Asia”] 浏览器:[“Firefox”,”Chrome”,”Safari”,”Internet Explorer”] 对于某一个样本,如[“male”,”US”,”Internet Explorer”],我们需要将这个分类值的特征数字化,最直接的方法,我们可以采用序列化的方式:[0,1,3]。但是这样的特征处理并不能直接放入机器学习算法中。
One-Hot Encoding的处理方法
### 对于上述的问题,性别的属性是二维的,同理,地区是三维的,浏览器则是思维的,这样,我们可以采用One-Hot编码的方式对上述的样本“[“male”,”US”,”Internet Explorer”]”编码,“male”则对应着[1,0],同理“US”对应着[0,1,0],“Internet Explorer”对应着[0,0,0,1]。则完整的特征数字化的结果为:[1,0,0,1,0,0,0,0,1]。这样导致的一个结果就是数据会变得非常的稀疏。
data.test.labels[0:5,:]
array([[ 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.]])
MNIST中的一个数据样本包含两块:手写体图片和对于的label。这里我们用xs和ys分别代表图片和对应的label,训练数据集和测试数据集都有xs和ys,我们使用 mnist.train.images 和 mnist.train.labels 表示训练数据集中图片数据和对于的label数据。
但是,这里我们可以先简单地使用一个长度为28 * 28 = 784的一维数组来表示图像,因为下面仅仅使用softmax regression来对图片进行识别分类(尽管这样做会损失图片的二维空间信息,所以实际上最好的计算机视觉算法是会利用图片的二维信息的)
MNIST的训练数据集可以是一个形状为55000 * 784位的tensor,也就是一个多维数组,第一维表示图片的索引,第二维表示图片中像素的索引(”tensor”中的像素值在0到1之间)。如下图:
MNIST中的数字手写体图片的label值在1到9之间,是图片所表示的真实数字。这里用One-hot vector来表述label值,vector的长度为label值的数目,vector中有且只有一位为1,其他为0.为了方便,我们表示某个数字时在vector中所对应的索引位置设置1,其他位置元素为0. 例如用[0,0,0,1,0,0,0,0,0,0]来表示3。所以,mnist.train.labels是一个55000 * 10的二维数组。如下:
data.test.cls = np.array([label.argmax() for label in data.test.labels])
argmax()
def argmax(self, axis=None, fill_value=None, out=None):
返回沿着某个维度最大值的位置
Returns array of indices of the maximum values along the given axis.
Masked values are treated as if they had the value fill_value.
Parameters
----------
axis : {None, integer}
If None, the index is into the flattened array, otherwise along
the specified axis
fill_value : {var}, optional
Value used to fill in the masked values. If None, the output of
maximum_fill_value(self._data) is used instead.
out : {None, array}, optional
Array into which the result can be placed. Its type is preserved
and it must be of the right shape to hold the output.
Returns
-------
index_array : {integer_array}
Examples
--------
>>> a = np.arange(6).reshape(2,3)
>>> a.argmax()
5
>>> a.argmax(0)
array([1, 1, 1])
>>> a.argmax(1)
array([2, 2])
"""
if fill_value is None:
fill_value = maximum_fill_value(self._data)
d = self.filled(fill_value).view(ndarray)
return d.argmax(axis, out=out)
data.test.cls[0:5]
array([7, 2, 1, 0, 4], dtype=int64)
数据维度
# MNIST images在每一个维度上有28个像素点
img_size = 28
# images 数据转换成一维数据
img_size_flat = img_size * img_size
# 包含(高度,宽度)的元组,原来reshape 数组
img_shape = (img_size,img_size)
# 分类数目,每个类包含10个数字
num_classes = 10
显示图片函数
函数用 3x3 图像窗口 显示9张图片,同时显示图像代表的数字
def plot_images(images, cls_true,cls_pred=None):
assert len(images) == len(cls_true) == 9
#建立3x3 sub-plot
fig, axes = plt.subplots(3,3)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# plot image.
ax.imshow(images[i].reshape(img_shape),cmap='binary')
# show ture and predicted classes.
if cls_pred is None:
xlabel = "True: {0}".format(cls_true[i])
else:
xlabel = "True: {0}, Pred:{1}".format(cls_true[i],cls_pred[i])
ax.set_xlabel(xlabel)
# Remove ticks from the plot
ax.set_xticks([])
ax.set_yticks([])
显示图片检查数据是否正确
# Get the first 10 images from the test-set.
images = data.test.images[0:9]
# Get the true classes for those images.
cls_true = data.test.cls[0:9]
# Plot the images and labels using our helper-function above.
plot_images(images=images,cls_true=cls_true)
图计算
TensorFlow程序中图的创建类似于一个 [施工阶段],而在 [执行阶段] 则利用一个session来执行图中的节点。很常见的情况是,在 [施工阶段] 创建一个图来表示和训练神经网络,而在 [执行阶段] 在图中重复执行一系列的训练操作。
Placeholder variables
通过创建 placeholder 来具体指定一个可变的批次维度(variable batch dimention)。shape 的 None 元素与可变大小的维度(a variable-sized dimension)相对应。要给节点输入数据时用 placeholder,在 TensorFlow 中用placeholder 来描述等待输入的节点,只需要指定类型即可,然后在执行节点的时候用一个字典来“喂”这些节点。相当于先把变量 hold 住,然后每次从外部传入data,注意 placeholder 和 feed_dict 是绑定用的。这里简单提一下 feed 机制, 给 feed 提供数据,作为 run()调用的参数, feed 只在调用它的方法内有效, 方法结束, feed 就会消失。
import tensorflow as tf
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
ouput = tf.mul(input1, input2)
with tf.Session() as sess:
### print(sess.run(ouput, feed_dict={input1: [7.], input2: [2.]}))
1.我们首先为输入图片定义相应的placeholder。这样我们可以更改传入到图模型中。这里所谓的张量,其实就是多维向量或者矩阵。数据类型为float32,shape设置成[None, img_size_float],None表示这个向量可能保持一个可变数目的图片,每张图片是一个img_size_flat长度的向量。
x = tf.placeholder(tf.float32, [None,img_size_flat])
2.随后我们把包含图片以及和它关联的真实标签赋予到x中。新建保持变量y的shape是[None,num_classes],表示变量包含了可变数目的长度为num_classes(实际上就是10)的标签。
shape就是几行几列。
y_true = tf.placeholder(tf.float32,[None,num_classes])
3.最后我们建立保持变量用于存放x中表示每一张图片的真实分类(数字)。数据类型为整数,列设置为[None],表示这个变量是可变长度的一维向量。
y_true_cls = tf.placeholder(tf.int64,[None])
优化变量
区别于保持变量,还有一些需要被优化的变量,通过这些变量,可以在训练数据的时候有更好的表现。
其中第一个优化变量为weights。weights变量的shape是[img_size_flat,num_classes],所以它是一个2阶张量,并且初始化为0.
weights = tf.Variable(tf.zeros([img_size_flat,num_classes]))
第二个优化变量为biases。这是一个长度为num_classes的一阶张量。
biases = tf.Variable(tf.zeros([num_classes]))
模型
模型运算结果为[num_images,num_classes]矩阵,因为x是[num_images,img_size_flat]矩阵,weights是[img_size_flat,num_classes]矩阵,最后加上biases向量。
with tf.device('/gpu:0'):
logits = tf.matmul(x,weights) + biases
Very Important:现在logits 是一个num_images 行,num_classes列的矩阵,第i行j列的元素表示第i张图片,是分类j的相似程度。具体的就是代表第i张图片是数字j的相似程度。
但是,这个元素有可能很大或者很小,所以我们需要将这些元素正则化到0~1之间。这里我们采用softmax函数,把正则化之后的数据保存到y_pred中。
y_pred = tf.nn.softmax(logits)
提取每一行中最大的数值的位置,这个值就代表着张图片的数字
y_pred_cls = tf.argmax(y_pred,dimension=1)
优化cost
为了模型能够更好的分类我们的输入图片,我们必须调整weighs和biases变量。因此,我们需要知道目前模型预测的怎么样,这是通过比较模型的预测值y_pred,以及真实值y_ture.
cross-entropy 是分类中性能衡量的一个函数。它是一个非负的连续函数,如果这个模型的预测值和真实值完全匹配,那么这个函数值为0,因此我们优化目标就是最小化cross-entropy函数的值,这是通过调整模型的weights和biases来实现的。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=y_true)
虽然我们计算了每一张图片的分类结果与真实结果之间的误差,但是我们需要一个用于计算全局误差的变量,这里我们采用最简单的取评价值的一个方式。
cost = tf.reduce_mean(cross_entropy)
优化方法
我们采用梯度下降法来优化这个cost值,步长设置为0.5。
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.5).minimize(cost)
性能
我们需要为用户显示性能衡量以及进度方式。下面这个数组是预测值是否与实际值一致的boolean向量。
correct_predition = tf.equal(y_pred_cls, y_true_cls)
最后把这个boolean向量转换成float数组,False = 0, True = 1,accuracy即为这个float数组的平局值。
accuracy = tf.reduce_mean(tf.cast(correct_predition,tf.float32))
运行模型
建立Session
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
初始化变量
sess.run(tf.initialize_all_variables())
WARNING:tensorflow:From <ipython-input-24-9f5df2c7784a>:1: initialize_all_variables (from tensorflow.python.ops.variables) is deprecated and will be removed after 2017-03-02.
Instructions for updating:
Use `tf.global_variables_initializer` instead.
迭代优化函数
模型训练集总共有50000张图片,计算所有的图片的梯度需要很长的时间,因此我们采用SGD优化方法,这种优化方法仅仅在每一步优化过程中采用图片的一小部分来进行。
batch_size = 100
def optimize(num_iterations):
for i in range(num_iterations):
# Get a batch of training examples.
# x_batch now holds a batch of images and
# y_true_batch are the true labels for those images.
x_batch, y_true_batch = data.train.next_batch(batch_size)
# Put the batch into a dict with the proper names
# for palcehodler variables int the tensorflow graph.
# Note that the placeholder for y_true_cls is not set
# because it is not used during training.
feed_dict_train = {x:x_batch,
y_true: y_true_batch}
# Run the optimizer using this batch of training data.
# TensorFlow assigns the variables in feed_dict_train
# to the placeholder variables and then runs the optimizer.
sess.run(optimizer,feed_dict=feed_dict_train)
性能显示函数
feed_dict_test = {x: data.test.images,
y_true:data.test.labels,
y_true_cls:data.test.cls}
def print_accuracy():
acc = sess.run(accuracy, feed_dict=feed_dict_test)
print("Accuracy on test-set:{0:.1%}".format(acc))
confusion matrix 显示函数
def print_confusion_matrix():
# Get the true classifications for the test-set.
cls_true = data.test.cls
# Get the predicted classifications for the test-set.
cls_pred = sess.run(y_pred_cls, feed_dict=feed_dict_test)
# Get the confusion matrix using sklearn.
cm = confusion_matrix(y_true=cls_true,
y_pred=cls_pred)
#print the confusion matrix as text.
print(cm)
# Plot the confusion matrix as an image
plt.imshow(cm,interpolation='nearest',cmap=plt.cm.Blues)
# Make various adjustments to the plot.
plt.tight_layout()
plt.colorbar()
tick_marks = np.arange(num_classes)
plt.xticks(tick_marks,range(num_classes))
plt.yticks(tick_marks,range(num_classes))
plt.xlabel('Predicted')
plt.ylabel('True')
def plot_example_errors():
# Use tensorflow to get a list of boolean values
# whether each test-image has been correctly classified,
# and a list for the predicted class of each imge.
correct, cls_pred = sess.run([correct_predition,y_pred_cls],
feed_dict=feed_dict_test)
# Negate the boolean array.
incorrect = (correct == False)
# Get the images from the test-set taht have been
# incorrectly classified.
images = data.test.images[incorrect]
# Get the predicted classes for those images.
cls_pred = cls_pred[incorrect]
# Get the true classes for those images.
cls_true = data.test.cls[incorrect]
# Plot the first 9 images.
plot_images(images=images[0:9],cls_true=cls_true[0:9],cls_pred=cls_pred[0:9])
weights显示函数
def plot_weights():
# Get the values for the weights from the tensorflow variable.
w = sess.run(weights)
# Get the lowest and highest values for the weights.
# This is used to correct the color intensity across
# the images so they can be compared with each other.
W_min = np.min(w)
W_max = np.max(w)
# Create figure with 3x4 sub-plots
fig, axes = plt.subplots(3, 4)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# Only use the weights for the first 10 sub-plots
if i < 10:
# Get the weights for the i'th digit and reshape it.
# Note that w.shape == (img_size_flat,10)
image = w[:,i].reshape(img_shape)
# Set the label for the sub-plot.
ax.set_xlabel("Weights:{0}".format(i))
# Plot the image
ax.imshow(image,vmin=W_min,vmax=W_max,cmap='seismic')
# Remove ticks from each sub-plot.
ax.set_xticks([])
ax.set_yticks([])
print_accuracy()
Accuracy on test-set:9.8%
optimize(num_iterations=1)
通过一次优化迭代,识别率有明显的上升,达到40.7%。
print_accuracy()
Accuracy on test-set:40.7%
plot_example_errors()
weights如下显示:正值为红色,负值为蓝色。这些weights可以直观的认为是图像过滤器。
例如:如果图像中心显示有红色的圆圈,而圆圈的中心为蓝色,这个就是数字0.
同样的,如果图像中心一条红色的线,而周围都是蓝色的区域,那么这个数字就是1.
通过1次迭代,实际上只训练了所有图片中的100张。如果训练几千张图片之后,weights将会变得非常难识别,因为这样会有更多手写数字体被融合在一起。
plot_weights()
优化10次之后
optimize(num_iterations=9)
print_accuracy()
Accuracy on test-set:78.2%
plot_example_errors()
plot_weights()
1000次迭代之后
optimize(num_iterations=990)
print_accuracy()
Accuracy on test-set:91.8%
plot_example_errors()
模型已经迭代了1000优化,每一次迭代使用训练集中的100张图片。由于图片种类太多,weights现在已经非常难区分了,因此我们怀疑模型是否真的理解了数字怎么样从基本的线条组成,或者模型只是记住了不同的像素变化。
plot_weights()
我们可以通过所谓的confusion matrix来看误分类的细节。例如,数组5的图像有时会被分类成其他可能的数字,但不会是3,6,8。
print_confusion_matrix()
[[ 957 0 3 2 0 5 11 1 1 0]
[ 0 1108 2 2 1 2 4 2 14 0]
[ 4 9 914 19 15 5 13 14 35 4]
[ 1 0 16 928 0 28 2 14 13 8]
[ 1 1 3 2 939 0 10 2 6 18]
[ 10 3 3 33 10 784 17 6 19 7]
[ 8 3 3 2 11 14 915 1 1 0]
[ 3 9 21 9 7 1 0 959 2 17]
[ 8 8 8 38 11 40 14 18 825 4]
[ 11 7 1 13 75 13 1 39 4 845]]