image

《机器学习，看完就明白了》传送门

# 获取数据源

MNIST数据集是一个开源的手写数据库。它提供了大量的数据样本作为训练集和验证集。这个数据集拥有 60000 个训练样本，和 10000 个测试样本。
MNIST 官网（一个很 low 的网站）传送门：http://yann.lecun.com/exdb/mnist/

image_mnist_web

from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets

【mnist.py 传送门】

# 开始构建网络

## 定义几个辅助函数

# 定义一个用于创建 权重 变量的函数
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
var = tf.Variable(initial)
# 记录每一个权重，因为后面要使用正则化
# 至于原因，后面具体再说
return var
# 定义一个用于创建 偏置量 变量的函数
def bias_variable(shape):
# 初始化值为0.1
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
# 构建卷积函数
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

tf.truncated_normal(shape, mean, stddev)

image_truncated

## 构建网络结构

import tensorflow as tf
class CnnModel_MNIST:
def __init__(self):
# 创建占位tensor，用于装载数据
self.x_data = tf.placeholder(tf.float32, [None, 784])
self.y_data = tf.placeholder(tf.float32, [None, 10])
# -----------------------构建第一层卷积-----------------------
with tf.name_scope('hidden1'):
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
# -----------------------构建第二层卷积------------------------
with tf.name_scope('hidden2'):
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
# -----------------------构建密集(全)链接层------------------------
with tf.name_scope('FC1'):
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
# -----------------------加入Dropout------------------------
with tf.name_scope('dropout'):
self.keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)
# -----------------------构建输出层------------------------
with tf.name_scope('output'):
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
self.y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

### 创建数据输入的占位符

x_data = tf.placeholder(tf.float32, [None, 784])
y_data = tf.placeholder(tf.float32, [None, 10])

• x_data 是用来容纳训练数据的，它的 shape 形状在这里被莫名其妙的定义为 [None, 784]。其实这里是有学问的，且听 CoorChice 慢慢道来。第一维定义为 None 表示不确定，后面会被实际的数值替代。这样做是因为我们一开始并不知道会有多少张图片数据会被输入。或者当我们采取 mini-batch 的梯度下降策略时，可以自由的设置 batch 的大小。
第二个维度定义为 784，这完全是因为我们数据集中的图片大小被统一为了 28*28 。
• y_data 是用来容纳训练数据的标签的，它的 shape 之所以被定义为 [None, 10] ，是因为它的第一维为 None 与 x_data 具有相同理由，而第二维为 10 是因为我们总共有 0~9 共 10 种类别的数字。

### 构建第一层网络

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

#### 权值 w 的构建，

W_conv1 = weight_variable([5, 5, 1, 32])

• 每个卷积核的大小为 5×5
• 输入通道数为 1，因为我们用的图片是灰度图。如果是用不带透明通道的 rgb 彩色图该值就设为 3，如果在带了透明通道的 rgba 彩色图该值就设为 4
• 该层输出通道数为 32，即该层有 32 个卷积核。对于隐藏层中每个卷积层中的卷积核大小如何的确定，再次强调，这是一个玄学，凭感觉设置。最靠谱的方法是用一些公开的网络模型，照着巨人们的设置，毕竟是经过反复尝试论证出来的。

#### 偏置量 b 的构建

b_conv1 = bias_variable([32])

b 的大小和卷积核个数相对应就行了。什么意思呢？就是每个卷积核和输入卷积后，再加上一个偏置量就好。回顾一下卷积核的结构。

wx + b

#### 改变输入数据的形状

x_image = tf.reshape(self.x_data, [-1, 28, 28, 1])

#### 构建卷积并加上激活函数

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

• conv2d(x_image, W_conv1)，将输入与该层的所有卷积核进行卷积运算
image

image_conv_compute

image_conv_compute2

• conv2d(x_image, W_conv1) + b_conv1，每次卷积后加上一个偏置量
• tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)，最后加上一个激活函数，增加非线性的变换

y = max(x, 0)

image

ReLu激活函数的好处在于，由于它在第一象限就是 x，所以能够大量的减少计算，从而加速收敛。同时它天生就能减小梯度消失发生的可能性，不过梯度爆炸还是可能会发生。

#### 池化

h_pool1 = max_pool_2x2(h_conv1)

max_pooling 实际就是取一个 2×2 张量中的最大值，这样能够过滤掉一些并不是很重要的噪声。

image

### 构建第二层网络

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

in = h_pool1.get_shape()[-1].value

[-1] 表示不管 h_pool1 的形状如何，都取它最后一维的大小。

### 构建全链接层

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

#### 定义 w 和 b

W_fc1 = weight_variable([7 * 7 * 64, 1024])

b_fc1 = bias_variable([1024])

#### 变形输入

h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])

#### 构建线性函数，加上 ReLu 函数

h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

image

• 全链接层增加模型的复杂度，因为增加了很多神经元来扩充特征集。也因此，它有助于提升模型的准确率。
• 但随着特征数量的爆炸式增加，训练速度必然会变慢。而且如果全链接层设置的神经元数量过多，会出现过拟合的现象。所以，需要适当的设置，不能一味的贪多。

#### 加入 Dropout

self.keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)

Dropout 就是一种很流行的方案。

h_fc1_drop = tf.nn.dropout(h_fc1, self.keep_prob)

image

### 构建输出层

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
self.y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

image

image

image_graph

# 就要开始训练了

# coding=utf-8
import time
from input_data import *
from cnn_utils import *
from cnn_model import CnnMnistNetwork
train_times = 35000
base_path = "../mnist/"
save_path = base_path + str(train_times) + "/"
# 读取数据
mnist = read_data_sets("MNIST_data/", one_hot=True)
# 创建网络
network = CnnMnistNetwork()
x_data = network.x_data
y_data = network.y_data
y_conv = network.y_conv
keep_prob = network.keep_prob
# ------------------------构建损失函数---------------------
with tf.name_scope("cross_entropy"):
# 创建正则化对象，此处使用的是 L2 范数
regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 50000))
# 应用正则化到参数集上
reg_term = tf.contrib.layers.apply_regularization(regularization)
# 在损失函数中加入正则化项
cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)
tf.scalar_summary('loss', cross_entropy)
with tf.name_scope("train_step"):
# 使用 Adam 进行损失函数的梯度下降求解
# ------------------------构建模型评估函数---------------------
with tf.name_scope("accuracy"):
with tf.name_scope("correct_prediction"):
# 对比预测结果和标签
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_data, 1))
with tf.name_scope("accuracy"):
# 计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.scalar_summary('accuracy', accuracy)
# 创建会话
sess = tf.InteractiveSession()
# 合并 summary
summary_merged = tf.merge_all_summaries()
train_writer = tf.train.SummaryWriter(save_path + "graph/train", sess.graph)
test_writer = tf.train.SummaryWriter(save_path + "graph/test")
start_time = int(round(time.time() * 1000))
# 初始化参数
sess.run(tf.initialize_all_variables())
for i in range(train_times):
# 从训练集中取出 50 个样本进行一波训练
batch = mnist.train.next_batch(50)
if i % 100 == 0:
summary, train_accuracy = sess.run([summary_merged, accuracy],
feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 1.0})
consume_time = int(round(time.time() * 1000)) - start_time
print("当前共训练 " + str(i) + "次, 累计耗时：" + str(consume_time) + "ms，实时准确率为：%g" % (train_accuracy))
# 记录训练时数据，每训练1000次保存一次训练信息
if i % 1000 == 0:
run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
# 训练一次，dropout 的参数设置为 0.5
summary, _ = sess.run([summary_merged, train_step],
feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5}, options=run_options,
else:
summary, _ = sess.run([summary_merged, train_step],
feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5})
# 每训练 2000 次保存一次模型
if i != 0 and i % 2000 == 0:
test_accuracy = int(
accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)
# 在测试集计算准确率
summary, test_accuracy = sess.run([summary_merged, accuracy],
feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0})
print("测试集准确率：%g" % (test_accuracy))
print("训练完成！")
train_writer.close()
test_writer.close()
# 保存模型
save_model(save_path, sess, train_times)

《机器学习，看完就明白了》传送门

## 构建损失函数

# 创建正则化对象，此处使用的是 L2 范数
regularization = tf.contrib.layers.l2_regularizer(scale=(5.0 / 50000))
# 应用正则化到参数集上
reg_term = tf.contrib.layers.apply_regularization(regularization)
# 在损失函数中加入正则化项
cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)
tf.scalar_summary('loss', cross_entropy)
with tf.name_scope("train_step"):
# 使用 Adam 进行损失函数的梯度下降求解

cross_entropy = (-tf.reduce_sum(y_data * tf.log(y_conv)) + reg_term)

image

image

c0就是原本的损失函数部分，这里就是 交叉熵，这部分又被称作是 经验风险。后面的一部分就是我们的 L2正则化式了，它实际就是把每个权重平方后求和，然后除以 w 的数量，在乘以个重要度系数。正则化的部分又叫作 结构风险，因为它是基于 w 计算出的一个数值，加在 交叉熵 上，从而每次增大交叉熵的值，也就是增大梯度，达到惩罚loss的效果。它一定程度上削弱了网络中特征值的作用，从而使模型的泛化性提高，也就能进一步的避免过拟合发生的可能。

## 构建评估模型

# 对比预测结果和标签
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_data, 1))
# 计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

## 开始训练啦！

for i in range(train_times):
# 从训练集中取出 50 个样本进行一波训练
batch = mnist.train.next_batch(50)
summary, _ = sess.run([summary_merged, train_step],
feed_dict={x_data: batch[0], y_data: batch[1], keep_prob: 0.5})
# 每训练 2000 次保存一次模型
if i != 0 and i % 2000 == 0:
test_accuracy = int(
accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)

if i != 0 and i % 2000 == 0:
test_accuracy = int(
accuracy.eval(feed_dict={x_data: mnist.test.images, y_data: mnist.test.labels, keep_prob: 1.0}) * 100)
save_model(base_path + str(i) + "_" + str(test_accuracy) + "%/", sess, i)

CoorChice 每训练两千次，在测试集上测试一下，然后保存一下模型。这是良好的习惯。因为一旦训练起来，很多不可控的因素，多保存些模型，后面还可以挑最合的。

image

# 使用模型进行识别

# coding=utf-8
import numpy as np
from PIL import Image
import os
from cnn_model import CnnMnistNetwork
import tensorflow.python as tf
train_times = 20000
num = 5
image_path = "num_images_test/num"
CKPT_DIR = "../mnist/" + str(train_times) + "_99%"
# 将数字图片缩放为标准的 28*28，接着进行灰度处理
img = Image.open(image_path + str(num) +".png").resize((28, 28), Image.ANTIALIAS).convert("L")
# os.system("open " + image_path + str(num) + ".png")
flatten_img = np.reshape(img, 784)
arr = np.array([1 - flatten_img])
print(arr)
# 创建模型对应的网络
network = CnnMnistNetwork()
x_data = network.x_data
y = network.y_conv
keep_prob = network.keep_prob
# 创建会话
sess = tf.InteractiveSession()
# 初始化参数
sess.run(tf.initialize_all_variables())
saver = tf.train.Saver()
ckpt = tf.train.get_checkpoint_state(CKPT_DIR)
if ckpt and ckpt.model_checkpoint_path:
# 读取恢复模型
saver.restore(sess, ckpt.model_checkpoint_path)
# 载入数据，进行识别
y = sess.run(y, feed_dict={x_data: arr, keep_prob:1.0})
# 取最大可能
result = str(np.argmax(y, 1))
print("\n期望结果" + str(num) + ", 预测结果：" + result)
os.system("open num_images_test/num" + result[1] + ".png")
else:
print("没有模型")

image

# 闲扯两句

MNIST 数据的训练相当于是机器学习的 HelloWorld 程序，我们构建了一个 4 层的简单的网络进行训练识别，最后得到的模型准确率也是不错的。