MINST数据库是由Yann提供的手写数字数据库文件,其官方下载地址http://yann.lecun.com/exdb/mnist/
数据库的里的图像都是28*28大小的灰度图像,每个像素的是一个八位字节(0~255)
这个数据库主要包含了60000张的训练图像和10000张的测试图像,主要是下面的四个文件
其中:
训练集(包括5000的验证集):
Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
测试集:
Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)
我们学习的第一门程序设计语言可能都是从Hello World入手的,在人工智能下的tensorflow框架,也就是说人工智能的Hello World可能就是MNIST数据集识别,下面附上我的代码:
前向传播(mnist_forward.py):
import tensorflow as tf
import tensorflow.contrib as contrib
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
def get_weight(shape, regularizer):
# tf.truncated_normal()从截断的正态分布中输出随机值, shape表示生成张量的维度,stddev是标准差
w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
# 将每个变量的正则化损失加入集合losses中
if regularizer != None:
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
return w
def get_bias(shape):
b = tf.Variable(tf.zeros(shape))
return b
def forward(x, regularizer):
w1 = get_weight([INPUT_NODE, LAYER1_NODE], regularizer)
b1 = get_bias([LAYER1_NODE])
# tf.nn.relu()为激活函数,当x<0时,y=0; 当x>=0时,y=x
y1 = tf.nn.relu(tf.matmul(x, w1) + b1)
w2 = get_weight([LAYER1_NODE, OUTPUT_NODE], regularizer)
b2 = get_bias([OUTPUT_NODE])
y = tf.matmul(y1, w2) + b2
return y
简单说明:因为数据库的里的图像都是28*28大小的灰度图像,所以28*28=784,将这个矩阵变成一维向量便成为了一个784维的一维向量作为神经网络的输入节点INPUT_NODE,因为手写的数字的范围是0-9,有10个数字,所以输出节点有10个,因此OUTPUT_NODE=10,LAYER1_NODE便是隐藏节点,节点个数有500个,作为计算。
PS:我们需要获取权重和输出x,通过激活函数tf.nn.relu()进行以上运算,搭建出整个计算图,并返回。
反向传播(mnist_backward.py):
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_forward
import os
BATCH_SIZE = 200
LEARNING_RATE_BASE = 0.1 # 初始学习率
LEARNING_RATE_DECAY = 0.99 # 学习衰减率
REGULARIZER = 0.0001 # 正则率
STEPS = 50000 # 训练轮数
MOVING_AVERAGE_DECAY = 0.99 # 滑动平均衰减率
MODEL_SAVE_PATH = "./model/"# 模型保存路径
MODEL_NAME = "mnist_model" # 模型名称
def backward(mnist):
x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
# 正向传输推算出模型
y = mnist_forward.forward(x, REGULARIZER)
global_step = tf.Variable(0, trainable=False)
# 下面算出包括正则化的损失函数
# logits为神经网络输出层的输出
# 传入的label为一个一维的vector,长度等于batch_size,每一个值的取值区间必须是[0,num_classes),其实每一个值就是代表了batch中对应样本的类别
# tf.argmax(vector, 1):返回的是vector中的最大值的索引号
ce = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
# 矩阵中所有元素求平均值
cem = tf.reduce_mean(ce)
# tf.get_collection(‘losses’):返回名称为losses的列表
# tf.add_n(list):将列表元素相加并返回
loss = cem + tf.add_n(tf.get_collection('losses'))
# 定义指数衰减学习率
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY,
staircase=True
)
# 定义训练过程
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
# 定义滑动平均
# tf.train.ExponentialMovingAverage()这个函数用于更新参数,就是采用滑动平均的方法更新参数
# MOVING_AVERAGE_DECAY是衰减率,用于控制模型的更新速度,设置为接近1的值比较合理
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
# apply()方法添加了训练变量的影子副本
# 返回值:ExponentialMovingAverage对象,通过对象调用apply方法可以通过滑动平均模型来更新参数。
# tf.trainable_variables返回的是需要训练的变量列表
# tf.all_variables返回的是所有变量的列表
ema_op = ema.apply(tf.trainable_variables())
with tf.control_dependencies([train_step, ema_op]):
# 里面的内容需要在将train_step、ema_op执行完后才能执行
# tf.no_op()表示执行完 train_step, ema_op 操作之后什么都不做
train_op = tf.no_op(name='train')
saver = tf.train.Saver()
with tf.Session() as sess:
# 初始化所有变量
init_op = tf.global_variables_initializer()
sess.run(init_op)
for i in range(STEPS):
xs, ys = mnist.train.next_batch(BATCH_SIZE)
# xs= (200, 784)
# ys= (200, 10)
_, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
if i % 1000 == 0:
print("After %d training step(s) , loss on training batch is %g." % (step, loss_value))
saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)
def main():
mnist = input_data.read_data_sets("../MNIST-data", one_hot=True)
backward(mnist)
if __name__ == '__main__':
main()
# BATCH_SIZE = 200
# mnist = input_data.read_data_sets("../MNIST-data", one_hot=True)
# xs, ys = mnist.train.next_batch(BATCH_SIZE)
# print("xs=", xs.shape)
# print("ys=", ys.shape)
简单说明:该反向传播将通过滑动平均求 loss 的方法进行参数更新,应该在模型上运用滑动平均能够使整个模型更加健壮。特别注意,x为输出,y_为已知的正确答案,y为通过神经网络输出后的结果,最后将对比y和y_的值来计算模型的精度。
PS:采用反向传播函数,在Tensorflow中复现一张默认的计算图进行计算,时间间隔为5s,利用影子变量,我们对其之前保存的状态没1000轮输出一次,观察结果。
测试结果(我进行了以下对比实验):
case1:
Steps=50000, regularize=0.0001, learning_rate_base=0.1, learning_rate_decay=0.99
Steps=50000, regularize=0.0001, learning_rate_base=0.2, learning_rate_decay=0.99
观察结果:在其他参数不变的情况下,将之前的学习率0.1提高到0.2,在50000轮训练之后的,loss损失函数有了明显降低,说明精度在不断提高,说明适当提高模型的初始学习率可以提高模型精度。
Steps=50000, regularize=0.0001, learning_rate_base=0.2, learning_rate_decay=0.99
以上参数保持不变,我们以上采用的是SGD(梯度下降函数)来更新的参数,并且在提高学习率的同时,loss也在减少,由此我想到了或许采用一下ADAM算法或许会更好,因为ADAM的速度更快,于是我将SGD换成了ADAM,没想到是这个结果.....
思考半天之后我发现或许是我的参数设置不对头,或许该改改参数,熟悉ADAM算法的人应该一看就知道哪里出问题了,应为ADAM需要更小的学习率,于是我将模型的参数改为:
Steps=50000, regularize=0.0001, learning_rate_base=0.001, learning_rate_decay=0.99
结果如下:
天呐,不敢想象仅仅通过了前1000次计算就达到了用SGD50000次左右的结果让人难以执行,,,,
仅仅在2000次就达到了有史以来的最优值
最终结果:
在43000次左右达到了目前为止的全局最优