TensorFlow学习之实现MNIST识别(实现断点重训)-----详细注解版

这篇博客详细介绍了在TensorFlow中实现MNIST手写数字识别,包括训练过程中的断点重训功能。通过分析mnist_inference.py、mnist_train.py和mnist_eval.py三个关键文件,讲解了tf.Variable和tf.get_variable在变量创建与管理上的区别,以及tf.name_scope和tf.variable_scope在命名空间管理上的作用。文章还总结了这两者如何协同工作,确保变量命名的唯一性和重用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先介绍一下:这里有三个文件:

mnist_inference.py      实现了神经网络的前向传播

mnist_train.py   实现了神经网络的训练(可直接运行,训练模型,实现了断点重训)

mnist_eval.py 实现了模型评估(评估模型的准确率)

下面直接贴代码:代码中有详细的注释。。。。。

mnist_inference.py文件代码:

# coding: utf-8
import tensorflow as tf

#输入层
INPUT_NODE = 784
#输出层
OUTPUT_NODE = 10
#隐藏层
LAYER1_NODE = 500

#初始化权重
def get_weight_variable(shape, regularizer):
    
    #tf.get_variable(name, shape, initializer): name就是变量的名称,shape是变量的维度,initializer是变量初始化的方式,初始化的方式有以下几种:
    # tf.constant_initializer:常量初始化函数
    # tf.random_normal_initializer:正态分布
    #tf.truncated_normal_initializer:截取的正态分布
    # tf.random_uniform_initializer:均匀分布
    # tf.zeros_initializer:全部是0
    # tf.ones_initializer:全是1
    # tf.uniform_unit_scaling_initializer:满足均匀分布,但不影响输出数量级的随机值
    weights = tf.get_variable("weights", shape, initializer=tf.truncated_normal_initializer(stddev=0.1))
    if regularizer != None: tf.add_to_collection('losses', regularizer(weights))
    return weights

#前向传播
def inference(input_tensor, regularizer):
    with tf.variable_scope('layer1'):

        weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], regularizer)
        biases = tf.get_variable("biases", [LAYER1_NODE], initializer=tf.constant_initializer(0.0))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    with tf.variable_scope('layer2'):
        weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], regularizer)
        biases = tf.get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
        layer2 = tf.matmul(layer1, weights) + biases

    return layer2


mnist_train.py 文件代码:

#-*- coding: UTF-8 -*-
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import mnist_inference
import os

BATCH_SIZE = 100 #一次训练使用的batch的大小,该数值越大越接近梯度下降,越小越接近随机梯度下降
LEARNING_RATE_BASE = 0.8 #基础的学习率
LEARNING_RATE_DECAY = 0.99 #学习率的衰减率
REGULARIZATION_RATE = 0.0001 #正则化项的系数,即lambda
TRAINING_STEPS = 30000 #训练轮数
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率
MODEL_SAVE_PATH="MNIST_model/"
MODEL_NAME="mnist_model"


def train(mnist):

    """
    placeholder
    为占位符,是一个抽象的概念。用于表示输入输出数据的格式。
    告诉系统:这里有一个值/向量/矩阵,现在我没法给你具体数值,不过我正式运行的时候会补上的!
    例如x和y_ 因为没有具体数值,所以只要指定尺寸即可,None表示为可以取任意值,此处不作规定
    """
    x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    #正则化
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE) #调用L2正则化函数
    y = mnist_inference.inference(x, regularizer) #定义y为前向传播输出

    # global_step是干啥的?在滑动平均学习率变化时使用,系统自动更新这个参数值。
    # 学习率第一次训练开始变化,globalSteps每次自动加1,
    # 将该变量设置为不可训练的,当调用返回训练列表时就不会返回该变量
    global_step = tf.Variable(0, trainable=False)

    """
    使用 tf.train.ExponentialMovingAverage 滑动平均操作的意义在于提高模型在测试数据上的健壮性(robustness)。
    此处应用了滑动平均,滑动平均为每一个应用了该算法的参数设置了一个影子变量,该影子变量与
    原变量独立存储,影子变量的初始值就是这个变量的初始值,当原变量改变时,影子变量通过滑动平均算法进行相应的改变,影子变量比原始变量
    改变的相对要平缓一些,而当迭代次数越多时,影子变量改变的相对于越缓慢。不管怎么说,算法得
    创建原始变量和影子变量两个值,并且这两个值都进行相应的更新。
    更新的顺序为:
    1.原始变量通过计算交叉熵与正则化进行后向传播更新各参数
    2.影子变量根据原始变量的更新来进行相对平缓的更新
    所以当需要使用这个滑动平均值时,需要明确调用average()函数。 
    shadow_variable=decay×shadow_variable+(1−decay)×variable
    decay=min{decay,(1+num_updates)/(10+num_updates)}
    decay 控制着模型更新的速度,越大越趋于稳定。实际运用中,decay 一般会设置为十分接近 1 的常数(0.99或0.999)。
    为了使得模型在训练的初始阶段更新得更快,ExponentialMovingAverage 还提供了 global_step 参数来动态设置 decay 的大小
    """
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)#初始化滑动平均类,variable_avergers就是类产生对象

    # tf.trainable_variables返回的是需要训练的变量列表
    # tf.all_variables返回的是所有变量的列表
    variables_averages_op = variable_averages.apply(tf.trainable_variables())# #定义一个更新变量滑动平均的操作,每次执行这个操作时参数中的变量都会被更新

    """
    当分类问题只有一个正确答案时可以使用sparse_softmax_cross_entropywith_logits()来计算
    交叉熵,函数第一个参数为对于训练数据神经网络输出层的输出,第二个参数为训练数据的正确答案
    但是第二个参数必须为一个数字,而不是一个数组,所以必须使用arg_max()函数来将这样一个数组
    转换为其对应的数字,转换规则为数组第几个数字最大则将其转换成几(从0开始),第二个参数1表示
    取最大值只在第一个维度中进行。
    
    tf.nn.softmax_cross_entropy_with_logits(logits, labels, name=None)
    除去name参数用以指定该操作的name,与方法有关的一共两个参数:
    第一个参数logits:就是神经网络最后一层的输出,如果有batch的话,它的大小就是[batchsize,num_classes],单样本的话,大小就是num_classes
    第二个参数labels:实际的标签,大小同上
    具体的执行流程大概分为两步:
    第一步是先对网络最后一层的输出做一个softmax,这一步通常是求取输出属于某一类的概率,对于单样本而言,输出就是一个num_classes大小的向量([Y1,Y2,Y3...]其中Y1,Y2,Y3...分别代表了是属于该类的概率)
    第二步是softmax的输出向量[Y1,Y2,Y3...]和样本的实际标签做一个交叉熵
    注意!!!这个函数的返回值并不是一个数,而是一个向量
    """        
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)#计算在当前batch中所有样例的交叉熵的均值
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))#损失函数为交叉熵加上正则化项


    """
    设置指数衰减的学习率使用exponential_decay()函数
    第一个参数为基础学习率
    第二个参数为当前迭代的轮数
    第三个参数为过完所有训练数据需要迭代多少轮
    第四个参数为学习率衰减的速率
    staircase=true表明每decay_staps计算学习率的变化,更新原始学习率
    如果starticase=False,那就是每一步都更新学习速率
    衰减公式:learning_rate=learning_rate_base∗decay_rate(global_step/decay_steps)
    """    
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,
        global_step,
        mnist.train.num_examples / BATCH_SIZE,
        LEARNING_RATE_DECAY,
        staircase=True)


    """
    Optimizer为优化器,有很多种,GradientDescent为使用GradientDescent算法的优化器,
    给定的参数learning_rate为学习率.
    minimize()传入需要减小的目标函数与当前迭代的轮数(可选)
    使用其来优化损失函数,注意在这个函数中会把global_step的值加1。
    """        
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    """
    控制依赖
    with g.control_dependencies([a, b, c]): 
                        # `d` 和 `e` 将在 `a`, `b`, 和`c`执行完之后运行 
            d = … 
            e = … 
    在训练神经网络模型时,每过一遍数据既要通过反向传播来更新神经网络中的参数,又要更新每一个参数的滑动平均值。
    为了一次完成多个操作,TensorFlow提供了tf.control_dependencies和tf.group两种机制。
    下面两行程序和train_op = tf.group(train_step, variables_averages_op)是等价的
    """    
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')


    saver = tf.train.Saver() #用于保存神经网络结构,构造方法可以传参数,参数可以是dict和list。不传参数时默认保存所有变量
    with tf.Session() as sess:
        tf.initialize_all_variables().run() #初始化所有变量
        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH) #获取checkpoints对象
        if ckpt and ckpt.model_checkpoint_path:##判断ckpt是否为空,若不为空,才进行模型的加载,否则从头开始训练
            saver.restore(sess,ckpt.model_checkpoint_path)#恢复保存的神经网络结构,实现断点续训
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE) #产生这一轮的训练数据
            _, 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(argv=None):
    mnist = input_data.read_data_sets('../MLtest2/MNIST_data', one_hot=True)#调用mnist
    train(mnist)

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



mnist_eval.py 文件代码:


# coding: utf-8
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

import mnist_inference
import mnist_train
import os
#1. 每10秒加载一次最新的模型

# 加载的时间间隔。
EVAL_INTERVAL_SECS = 10
MODEL_SAVE_PATH="MNIST_model/"
MODEL_NAME="mnist_model"

def evaluate(mnist):
    with tf.Graph().as_default() as g:
        #构造计算准确率的计算图
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
        validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}
        y = mnist_inference.inference(x, None)
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        #影子
        variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
        variables_to_restore = variable_averages.variables_to_restore() # Returns a map of names to Variables to restore
        saver = tf.train.Saver(variables_to_restore)#构造器添加操作保存和恢复变量,这里是要恢复的变量
        
        ckpt = tf.train.get_checkpoint_state(mnist_train.MODEL_SAVE_PATH)
        while True:
            with tf.Session() as sess:
                sess.run(tf.initialize_all_variables())#初始化上面计算准确率的计算图中的变量
                if ckpt and ckpt.model_checkpoint_path:
                    #加载已经保存的训练模型,这个方法运行构造器为恢复变量所添加的操作。它需要启动图的Session。恢复的变量不需要经过初始化,恢复作为初始化的一种方法。
                    saver.restore(sess, ckpt.model_checkpoint_path)#注意这里虽然恢复的变量你不需要初始化,但是上面还是要有定义的,这样恢复的时候,相当于把上面定义的变量初始化了。
                    #从路径中获取已经训练的步数,方便输出
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
                    #算出模型的正确率
                    accuracy_score = sess.run(accuracy ,feed_dict=validate_feed)
                    print("After %s training step(s), validation accuracy = %g" % (global_step, accuracy_score))
                    
            time.sleep(EVAL_INTERVAL_SECS)
#主程序
def main(argv=None):
    mnist = input_data.read_data_sets("../MLtest2/MNIST_data", one_hot=True)
    evaluate(mnist)

if __name__ == '__main__':
    main()
"""
Extracting ../../../datasets/MNIST_data/train-images-idx3-ubyte.gz
Extracting ../../../datasets/MNIST_data/train-labels-idx1-ubyte.gz
Extracting ../../../datasets/MNIST_data/t10k-images-idx3-ubyte.gz
Extracting ../../../datasets/MNIST_data/t10k-labels-idx1-ubyte.gz
"""


知识点总结:

1、tf.Variable(<variable_name>),tf.get_variable(<variable_name>)的作用与区别:
tf.Variable(<variable_name>)会自动检测命名冲突并自行处理,但tf.get_variable(<variable_name>)则遇到重名的变量创建且变量名没有设置为共享变量时,则会报错。
tf.Variable(<variable_name>)和tf.get_variable(<variable_name>)都是用于在一个name_scope下面获取或创建一个变量的两种方式,区别在于:
tf.Variable(<variable_name>)用于创建一个新变量,在同一个name_scope下面,可以创建相同名字的变量,底层实现会自动引入别名机制,两次调用产生了其实是两个不同的变量。

tf.get_variable(<variable_name>)用于获取一个变量,并且不受name_scope的约束。当这个变量已经存在时,则自动获取;如果不存在,则自动创建一个变量。

tf.get_variable_scope().reuse == False时,作用域就是为创建新变量所设置的,创建变量的全称将会由当前变量作用域名+所提供的名字所组成,并且还会检查来确保没有任何变量使用这个全称.如果这个全称已经有一个变量使用了,那么方法将会抛出ValueError错误。

tf.get_variable_scope().reuse == True时,作用域是为重用变量所设置,这种情况下,调用就会搜索一个已经存在的变量,他的全称和当前变量的作用域名+所提供的名字是否相等.如果不存在相应的变量,就会抛出ValueError 错误.如果变量找到了,就返回这个变量

2、tf.name_scope(<scope_name>)与tf.variable_scope(<scope_name>):
tf.name_scope(<scope_name>):主要用于管理一个图里面的各种op,返回的是一个以scope_name命名的context manager。一个graph会维护一个name_space的
堆,每一个namespace下面可以定义各种op或者子namespace,实现一种层次化有条理的管理,避免各个op之间命名冲突。
tf.variable_scope(<scope_name>):一般与tf.name_scope()配合使用,用于管理一个graph中变量的名字,避免变量之间的命名冲突,tf.variable_scope(<scope_name>)允许在一个variable_scope下面共享变量。

上面两者之间的联系:

1. 使用tf.Variable()的时候,tf.name_scope()tf.variable_scope() 都会给Variableopname属性加上前缀。
2. 使用tf.get_variable()的时候,tf.name_scope()就不会给 tf.get_variable()创建出来的Variable加前缀。

3.tf.get_variable_scope() 返回的只是 variable_scope,不管 name_scope


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值