Tensorflow基础概念与编程(第四讲)

MNIST数字识别的问题

MNIST是一个非常有名的手写体数字识别数据集,MNIST是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据;在MNIST中每一张图片都代表了0-9中的一个数字,图片的大小都是28*28;虽然这个数据集中只有训练集和测试集,但是为了测试的效果,我们一般会从训练数据集中划分出一部分作为验证数据集

from tensorflow.examples.tutorials.mnist import input_data

# 输入数据集
mnist = input_data.read_data_sets('/path/to/MNIST_DATA/', one_hot=True)
# 打印训练数据集
print(mnist.train.num_examples)
# 打印测试集数据
print(mnist.test.num_examples)
# 打印验证集的训练数据
print(mnist.validation.num_examples)
batch_size = 100
xs, ys = mnist.train.next_batch(batch_size)
print(xs.shape)
print(ys.shape)

运行结果:

Extracting /path/to/MNIST_DATA/train-images-idx3-ubyte.gz
Extracting /path/to/MNIST_DATA/train-labels-idx1-ubyte.gz
Extracting /path/to/MNIST_DATA/t10k-images-idx3-ubyte.gz
Extracting /path/to/MNIST_DATA/t10k-labels-idx1-ubyte.gz
55000
10000
5000
(100, 784)
(100, 10)

从上面代码中可以看出,通过input_data.read_data_sets函数生成的类自动将数据集分为train、test、validation三个数据集;处理后的数据输入都是长度为784的一维数组,因为神经网络的输入是一个特征向量,所以把二维图像的像素放到一个以为数组中可以方便tensorflow将图片的像素矩阵提供给神经网络的输入,像素矩阵中的元素的取值范围是[0,1],它代表了颜色的深浅,其中0表示白色背景,1表示黑色背景;为了方便使用SGD,input_data.read_data_sets还提供了mnist.train.next_batch函数,它可以从所有的训练数据中读取一小部分作为一个训练batch。

神经网络模型训练及不同模型结果对比

Tensorflow训练神经网络

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


# MNIST数据集相关的常数
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001  # 描述模型复杂度的正则化在损失函数的系数
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99   # 滑动平均衰减率

# 一个辅助函数,给定神经网络的输入和所有参数,计算神经网络中的前向传播结果
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 当没有滑动平均类,直接使用参数当前的取值
    if avg_class == None:
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
        pred = tf.matmul(layer1, weights2) + biases2
    else:
        layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))
        pred = tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)
    return pred

# 训练模型的过程
def train(mnist):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x_input')
    y = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y_output')
    weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
    biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
    weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))
    
    # 计算在当前参数下神经网络前向传播的结果
    pred = inference(x, None, weights1, biases1, weights2, biases2)
    # 计算使用了滑动平均之后的前向传播结果
    global_step = tf.Variable(0, trainable=False)
    variable_average = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    # tf.trainable_variables函数返回的就是图上的所有变量的集合
    average_op = variable_average.apply(tf.trainable_variables())
    pred1 = inference(x, variable_average, weights1, biases1, weights2, biases2)
    
    # 计算交叉熵作为刻画预测值和真实值之间差距的损失函数,这里使用sparse_softmax_cross_entropy_with_logits函数来计算交叉熵,当分类
    # 问题只有一个正确答案的时候,可以使用这个函数来加速交叉熵的计算
    # 这个函数的第一个参数是神经网络不包括softmax层的前向传播的结果,第二个是训练数据的正确答案
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=pred, labels=tf.argmax(y, 1))
    # 计算在当前batch中所有样例的交叉熵平均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    
    # 计算L2正则化损失函数
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 计算模型的正则化损失,一般只计算神经网络边上的权重的正则化损失,而不使用偏执项
    # 总损失等于交叉熵和正则化的损失的和
    loss = cross_entropy_mean + regularizer(weights1) + regularizer(weights2)
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,  # 基础的学习率
        global_step,  # 当前迭代的轮数
        mnist.train.num_examples / BATCH_SIZE,  # 过完所有的训练数据集所需要的迭代次数
        LEARNING_RATE_DECAY)
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    # 在训练神经网络模型时,每过一遍数据既需要通过反向传播来更新神经网络中的参数,又要更新每一个参数的滑动平均值
    # 为了一次完成多个操作,可以使用tf.group()来完成
    train_op = tf.group(optimizer, average_op)
    
    # 检验使用滑动平均模型的神经网络前向传播是否正确,tf.argmax(pred1, 1)计算每一个样例的答案
    correct_prediction = tf.equal(tf.argmax(pred1, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    
    # 初始会话并开始训练
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        # 准备验证数据
        validate_feed = {x:mnist.validation.images, y:mnist.validation.labels}
        # 准备测试数据
        test_feed = {x:mnist.test.images, y:mnist.test.labels}
        for i in range(1, TRAINING_STEPS):
            if i % 1000 == 0:
                # 计算滑动平均模型在验证数据上的结果
                # 当神经网络模型比较复杂或者验证数据集数据比较大的时候,太大的batch会导致时间过长甚至发生内存溢出的错误
                validation_acc = sess.run(accuracy, feed_dict=validate_feed)
                print('validation_acc:', validation_acc)
                
            # 产生这一轮的batch数据,并进行训练
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x:xs, y:ys})
            
        # 在训练结束之后,在测试数据上检测神经网络模型的最终正确率
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print('test_acc:', test_acc)
        
# 主程序入口
def main(argv=None):
    # 声明处理MNIST数据集的类,这个类在初始化的时候会自动下载数据
    mnist = input_data.read_data_sets('/path/to/MNIST_DATA/', one_hot=True)
    train(mnist)
    

if __name__ == '__main__':
    tf.app.run()

运行结果:

Extracting /path/to/MNIST_DATA/train-images-idx3-ubyte.gz
Extracting /path/to/MNIST_DATA/train-labels-idx1-ubyte.gz
Extracting /path/to/MNIST_DATA/t10k-images-idx3-ubyte.gz
Extracting /path/to/MNIST_DATA/t10k-labels-idx1-ubyte.gz
validation_acc: 0.9762
validation_acc: 0.9816
validation_acc: 0.9824
validation_acc: 0.9842
validation_acc: 0.9846
validation_acc: 0.9838
validation_acc: 0.9838
validation_acc: 0.9846
validation_acc: 0.9854
test_acc: 0.9839

使用验证数据集判断模型效果

  • 参数的设置没有什么规律可循,配置神经网络的参数都是需要通过实验进行调整的;
  • 一般来说,我们是通过在测试集上的效果来看最终的结果的,但是对于深度学习来说我们一般都会有大量的数据,而且测试集的数据非常宝贵,我们一般会将训练数据集分为训练集和测试集
  • 除了使用验证数据集,我们还可以使用交叉验证来对模型进行评估
  • 一般来说,模型在验证集上出现的结果和测试集上的结果大致一样,但是又不能一概而论,还要看验证集和测试集之间的数据分布情况

变量管理

从上面的代码中可以看到,定义inference函数的时候传入了很多的参数;然而当神经网络的结构更加复杂、参数更多的时候,就需要一个更好的方式来传递和管理神经网络中的参数了;tensorflow提供了通过变量名称来创建或者获取一个变量的机制。通过这个机制,在不同的函数中可以直接通过变量的名字来进行传递,就不需要将变量通过参数的形式到处传递

之前介绍了tf.Variable()函数来创建变量,除了这个函数,我们也可以用tf.get_variable()函数来创建或者获取变量;当tf.get_variable()用来创建变量的时候,这个函数的功能和tf.Variable基本上是等价的:
 

v = tf.get_variable('v', shape=[1], initializer=tf.constant_initializer(2.0))

tf.get_variable函数调用时需提供维度信息以及初始化方法

tensorflow中提供了七种不同的初始化方法:

tf.get_variable函数与tf.Variable函数最大的功能区别就是指定变量名称的参数,对于tf.Variable来说,变量名称是一个可选参数吗,但是对于tf.get_variable函数来说名称是一个必填的参数,tf.get_variable会根据这个名字去创建或者获取变量

  • 如果创建过程中之前已经存在了这个名称的变量,那么get_variable创建变量就会报错
  • 如果variable_scope中的参数reuse为True,那么如果变量名称不存在,那么get_variable就会报错

variable_scope创建一个上下文管理器,管理在这个里面所出现或待创建的一切变量

with tf.variable_scope('foo'):
    v = tf.get_variable('v', shape=[1], initializer=tf.constant_initializer(1.0))  # 创建一个变量
    
# with tf.variable_scope('foo'):
    # v = tf.get_variable('v', [1])   # 报错, 之前已经存在了一个变量名称为v,再次创建报错
    
with tf.variable_scope('foo', reuse=True):
    v1 = tf.get_variable('v', [1])
    print(v1==v)

with tf.variable_scope('bar', reuse=True):
    v = tf.get_variable('v', [1])  # 报错,这个管理器里边没有这个变量

tf.variable_scope函数生成的上下文管理器也会创建一个tensorflow中的命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀;所以tf.variable_scope函数除了可以控制tf.get_variable执行的功能,这个函数也提供了一个管理变量命名空间的方式,以下代码显示了如何通过tf.variabel_scope来管理变量的名称

import tensorflow as tf
v1 = tf.get_variable('v', [1])
print(v1.name)

with tf.variable_scope('foo'):
    v2 = tf.get_variable('v', [1])
    print(v2.name)
# 输出为foo/v:0  表示这是命名空间foo里边的变量

with tf.variable_scope('foo'):
    with tf.variable_scope('bar'):
        v3 = tf.get_variable('v', [1])
        print(v3.name)
# foo/bar/v:0

with tf.variable_scope('', reuse=True):
    v5 = tf.get_variable('foo/bar/v', [1])
    # 可以直接通过命名空间名称的变量名来获得其他命名空间下的变量

tensorflow持久化

之前给出的样例就是样例代码在训练完后之后就直接退出了,并没有将训练得到的数据保存下来方便下次直接调用

持久化代码实现

tensorflow提供了一个非常简单的API来保存和还原一个神经网络模型,这个API就是tf.train.Saver类,看代码:

import tensorflow as tf

v1 = tf.Variable([1.0, 2.0])
v2 = tf.Variable(tf.constant([1.0, 3.], shape=[2]))
result = v1 + v2
# 声明tf.train.Saver类用于保存模型
saver = tf.train.Saver()
with tf.Session() as sess:
    tf.global_variables_initializer().run()
    saver.save(sess, '/path/to/model/model.ckpt')

以上代码实现了持久化一个简单的tensorflow模型的功能,在这段代码中saver.save函数将tensorflow模型保存在/path/to/model/model.ckpt文件中;tensorflow模型一般会存在后缀为.ckpt的文件中,虽然以上程序只指定了一个文件路径,但是在这个文件目录下会出现三个文件,这是因为tensorflow会将计算图的结构和图上的参数取值分开保存

import tensorflow as tf

v1 = tf.Variable([1.0, 2.0])
v2 = tf.Variable(tf.constant([1.0, 3.], shape=[2]))
result = v1 + v2
# 声明tf.train.Saver类用于保存模型
saver = tf.train.Saver()
with tf.Session() as sess:
    # 加载已经保存的模型,并通过已经保存的模型中变量的值来计算加法
    saver.restore(sess, '/path/to/model/model.ckpt')
    print(sess.run(result))

saver.save函数将模型中的参数也一并保存了,在restore的时候也是用的保存的参数进行运算,也就是上面的restore之后如果要输出v1的值,那么输出的依然是保存时候的v1的值

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值