(二,2NN-QI)Tensorflow--卷积神经网络代码解剖

第一章:神经网络理论知识

【第二章】Tensorflow

二、卷积神经网络

卷积神经网络主要用于图像的处理,卷积层通过滤波器捕捉图像中的“线段”,然后通过不同的线段组成一副可以识别的图片。和全链接神经网络相比,卷积神经网络主要多了卷积层和池化层(pool),卷积层和池化层都是通过滤波器对图片的特征进行捕捉,只不过卷积层通过滤波器做的是卷积计算,而池化层通过滤波器做的max-pooling或着average-pooling等计算。具体卷积神经网络,见我的第一章的文章。。。。这篇文章主要讲的通过Tensorflow怎么实现卷积神经网络,常见的卷积神经网络的模型有LeNet-5和Inception-v3,下面将通过代码仔细刨析这两个模型。

一、LeNet-5

LeNet-5是一个7层(输入层不计入总层数)的经典的卷积神经网络模型,最初来源于论文【Lecun Y,Bottou L,Bengio Y,et al. Gradient-based learning applied to document recognition[J].proceedings of the IEEE,1998】,其具体结构为:
输入层—>卷积层—>池化层—>卷积层—>池化层—>全连接层—>全连接层—>输出层。
LeNet-5的实现过程和(二,1)中的方法一样,通过前向传播、训练、验证,由三个文档组成一套完整的模型过程。所以和上一个全连接层神经网络相比,我们需要大幅修改的就是前向传播文档,因为神经网络的结构体现在前向传播过程中。

为了和全连接神经网络作比较,该模型使用的数据集仍然是MNIST_DATA数据集。

1.mnist_inference

第一部分,设置参数

# —*— coding: utf-8 -*-

import  tensorflow as tf

INPUT_NODE = 784
OUTPUT_NODE = 10

IMAGE_SIZE = 28
# 通道数,因为mnist_data数据集是黑白图片,所以我们设置通道数为1,也就是每次使用1个滤波器对输入层进行扫面
NUM_CHANNELS = 1

NUM_LABELS = 10

# 第一层
# 卷积层的尺寸和深度。
CONV1_DEEP = 32
CONV1_SIZE = 5

# 第二层卷积层的尺寸和深度
CONV2_DEEP = 64
CONV2_SIZE = 5

#全连接层的节点个数
FC_SIZE = 512

# 定义卷积神经网络的前向传播过程。这里添加了一个新的参数train,用于区分训练过程和测试
# 过程。在这个程序中将用到dropout方法,dropout可以进一步提升模型可靠性并防止过拟合,
# dropout过程值在调试时使用

def inference(input_tensor,train,regularizer):
    # 声明第一层卷积层的变量并实现前向传播过程。这个过程和6.3.1节中介绍的一致。
    # 通过使用不同的命名空间来隔离不同层的变量,这可以让每一层中的变量命名只需要
    # 考虑当前层的作用,而不需要担心重命名的问题。和标准LeNet-5模型不大一样,这里定义
    # 卷积层输入为28x28x1的原始MNIST图片像素。因为卷积层中使用了全0填充,
    # 所以输出为28x28x32的矩阵。

第一层:
在这里插入图片描述
第一层卷积神经网络,由于图片是黑白的所以只有一层,大小为28×28,滤波器使用的是5×5,相当于同时有32个5×5的单层滤波器对图片按步长为1进行进行扫描,由于使用了padding,所以最后得出了28×28×32的矩阵。
⌊ n + 2 P − f S + 1 ⌋ × ⌊ n + 2 P − f S + 1 ⌋ \lfloor\frac{n+2P-f}{S}+1\rfloor \times \lfloor\frac{n+2P-f}{S}+1\rfloor Sn+2Pf+1×Sn+2Pf+1
滤波器为5时,padding设置为2,所以输出的大小为:
⌊ n + 2 P − f S + 1 ⌋ = ⌊ 28 + 4 − 5 1 + 1 ⌋ = 28 \lfloor\frac{n+2P-f}{S}+1\rfloor=\lfloor\frac{28+4-5}{1}+1\rfloor=28 Sn+2Pf+1=128+45+1=28
padding的设置

  1. 滤波器(卷积核\过滤器)大小:3, Padding大小: 1
  2. 滤波器(卷积核\过滤器)大小:5, Padding大小: 2
  3. 滤波器(卷积核\过滤器)大小:7, Padding大小: 3
  4. 池化层padding一般设置为0.
    with tf.variable_scope('layer1-conv1'):
        conv1_weights = tf.get_variable(
            "weight",[CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP],
            initializer = tf.truncated_normal_initializer(stddev=0.1)
        )
        conv1_biases = tf.get_variable(
            "biases",[CONV1_DEEP],initializer=tf.constant_initializer(0.0)
        )
        # 使用边长为5,深度为32的过滤器,过滤器移动的步长为1,且使用全0填充。
        conv1 = tf.nn.conv2d(
            input_tensor,conv1_weights,strides=[1,1,1,1],padding='SAME'
        )
        relu1 = tf.nn.relu(tf.nn.bias_add(conv1,conv1_biases))

第二层: 池化层,滤波器的大小为2×2,因为移动步长为2,所以输出矩阵的大小为
⌊ n + 2 P − f S + 1 ⌋ × ⌊ n + 2 P − f S + 1 ⌋ \lfloor\frac{n+2P-f}{S}+1\rfloor \times \lfloor\frac{n+2P-f}{S}+1\rfloor Sn+2Pf+1×Sn+2Pf+1
n为图片的大小28,P为padding填充大小为1,f为滤波器的大小2,S为滤波器步长为2.
⌊ n + 2 P − f S + 1 ⌋ = ⌊ 28 + 0 − 2 2 + 1 ⌋ = 14 \lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{28+0-2}{2}+1\rfloor=14 Sn+2Pf+1=228+02+1=14

    # 实现第二层池化层的前向传播过程,这里虚啊弄最大池化层,池化层过滤器的边长为2,
    # 使用全0填充前移动的步长为2。这一层的输入是上一层的输出,也就是28x28x32的矩阵。
    # 输出为14x14x32的矩阵。
    with tf.name_scope('layer2-pool'):
        pool1 = tf.nn.max_pool(relu1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

第三层:
在这里插入图片描述

⌊ n + 2 P − f S + 1 ⌋ = ⌊ 14 + 4 − 2 1 + 1 ⌋ = 14 \lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{14+4-2}{1}+1\rfloor=14 Sn+2Pf+1=114+42+1=14

    # 声明第三次层卷积的变量并实现前向传播过程。这一层的输入为14x14x32的矩阵。
    # 输出为14x14x64的矩阵。
    with tf.variable_scope('layer3-conv2'):
        conv2_weights = tf.get_variable(
            'weight',[CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],
            initializer = tf.truncated_normal_initializer(stddev=0.1)
        )
        conv2_biases = tf.get_variable(
            "bias",[CONV2_DEEP],
            initializer = tf.constant_initializer(0.0)
        )

        # 使用边长为5,深度为64的过滤器,过滤器移动的步长为1,且使用全0填充。
        conv2 = tf.nn.conv2d(
            pool1,conv2_weights,strides=[1,1,1,1],padding='SAME'
        )
        relu2 = tf.nn.relu(tf.nn.bias_add(conv2,conv2_biases))

第四层:池化层 padding=0
⌊ n + 2 P − f S + 1 ⌋ = ⌊ 14 + 0 − 2 2 + 1 ⌋ = 7 \lfloor\frac{n+2P-f}{S}+1\rfloor= \lfloor\frac{14+0-2}{2}+1\rfloor=7 Sn+2Pf+1=214+02+1=7

    # 实现第四层池化层的前向传播过程。这一层和第二层的结构是一样的。这一层的输入为14x14x64的矩阵,
    # 输出为7x7x64的矩阵。
    with tf.name_scope('layer4-pool2'):
        pool2 = tf.nn.max_pool(
            relu2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME'
        )

第五层:
全连接层的输入为上一层的输出,7×7×64的矩阵。
第四层的输出为7×7×64的矩阵,而第五层时全连接层,输入为一个向量,所以在第五层输入的时候,应该先将这个7×7×64的矩阵拉直成一个向量。这个向量的维度时3136(7×7×64).输出是一个FC_SIZE(512)大小的矩阵。正则化和dropout都加在全连接层。

    # 将第四层池化层的输出转化为第5层全连接层的输入格式。第四层的输出为7x7x64的矩阵,
    # 然而第五层全连接层需要输入格式为向量,所以在这里需要将这个7x7x64的矩阵拉直为
    # 一个向量,pool2,get_shape函数可以得到第四层输出矩阵的维度而不需要手工计算。
    # 注意因为每一层神经网络的输入输出都为一个batch的矩阵,所以这里得到的维度也包含
    # 了一个batch中数据的个数。
    pool_shape = pool2.get_shape().as_list()

    # 计算将矩阵拉直成向量之后的长度,这个长度就是矩阵长宽及深度的乘积。注意这里pool_shape[0]
    # 为一个batch中数据的个数。
    nodes = pool_shape[1]*pool_shape[2] * pool_shape[3]

    # 通过tf.reshape 函数将第四层的输出变成一个batch的向量。
    reshaped = tf.reshape(pool2,[pool_shape[0],nodes])

    # 声明第五层全链接层的变量并实现前向传播过程。这一层的输入是拉直hi后的一组向量,向量长度为3136
    # 输出是一组长度为512的向量。这一层和之前在第5章中介绍的基本一致,唯一的区别就是引入了dropout
    # 的概念。dropout在训练时会随机将部分节点的输出改为0。dropout可以避免过拟合问题,从而使得模型
    # 在测试数据上的效果更好。dropout一般只在全链接层而不是卷积层或者池化层使用。
    with tf.variable_scope('layer5-fcl'):
        fcl_weights = tf.get_variable(
            "weight",[nodes,FC_SIZE],
            initializer= tf.truncated_normal_initializer(stddev=0.1)
        )
        # 只有全连接层的却暗中需要加入正则化
        if regularizer is not None:
            tf.add_to_collection('losses',regularizer(fcl_weights))
        fc1_biases = tf.get_variable(
            "bias",[FC_SIZE],initializer=tf.constant_initializer(0.1)
        )
        fc1 = tf.nn.relu(tf.matmul(reshaped,fcl_weights)+fc1_biases)
        if train: fc1 = tf.nn.dropout(fc1,0.5)

第六层:
输入512,输出10

    # 声明第六层全链接层的变量并实现前向传播过程。这一层的输出为一组长度为512的向量,
    # 输出为一组长度为10的向量。这一成的输出通过softmax之后就得到了最后的分类结果。
    with tf.variable_scope('layer6-fc2'):
        fc2_weights = tf.get_variable(
            "weight",[FC_SIZE,NUM_LABELS],
            initializer=tf.truncated_normal_initializer(stddev=0.1)
        )
        if regularizer is not None:
            tf.add_to_collection('losses',regularizer(fc2_weights))
        fc2_biases = tf.get_variable(
            "bias",[NUM_LABELS],
            initializer=tf.constant_initializer(0.1)
        )
        logit = tf.matmul(fc1,fc2_weights)+fc2_biases

    # 返回第六层的输出
    return logit

上述代码的前向传播过程仍然没有softmax层,和之前一样,softmax层放到最后的train过程当中。

下面给出mnist_train.py 和mnist_eval.py 的代码

2.mnist_train

mnist_train ,大部分代码和上一节中的单层神经网络的train文件,是一致的,需要修改的就是在输入部分。

#-*- coding:utf-8 -*-
import os
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 加载mnist_inference.py中的定义的常量和前向传播的函数。
import LeNet5_mnist_inference

# 配置神经网络的参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.01
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AEVERAGE_DECAY = 0.99

# 模型保存的路径和文件名
MODEL_SAVE_PATH = "Google/chapter5/model/"
MODEL_NAME = "model.ckpy"

def train(mnist):
    # 定义输入输出placeholder
    x = tf.placeholder(
        tf.float32, [
            BATCH_SIZE,
            LeNet5_mnist_inference.IMAGE_SIZE,
            LeNet5_mnist_inference.IMAGE_SIZE,
            LeNet5_mnist_inference.NUM_CHANNELS],
        name = "x-input"
    )
    y_ = tf.placeholder(
        tf.float32,[None, LeNet5_mnist_inference.OUTPUT_NODE],name='y-input'
    )
    regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
    #直接使用mnist_inference.py中定义的前向传播过程。
    y = LeNet5_mnist_inference.inference(x, False, regularizer)
    global_step = tf.Variable(0,trainable=False)

    # 和5.2.1节样例中类似地定义损失函数、学习率、滑动平均操作以及训练过程。
    variable_averages = tf.train.ExponentialMovingAverage(
        MOVING_AEVERAGE_DECAY,global_step
    )
    variable_averages_op = variable_averages.apply(
        tf.trainable_variables()
    )
    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)
    loss = cross_entropy_mean + 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
    )
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
    with tf.control_dependencies([train_step,variable_averages_op]):
        train_op = tf.no_op(name='train')

    # 初始化Tensorflow持久化类。
    saver = tf.train.Saver()
    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        # 在训练过程中不再测试模型在验证数据上的表现,验证和测试的过程将会有一个独立的程序来完成。
        for i in range(TRAINING_STEPS):

            xs,ys = mnist.train.next_batch(BATCH_SIZE)
            reshaped_xs = np.reshape(xs, (BATCH_SIZE,
                                          LeNet5_mnist_inference.IMAGE_SIZE,
                                          LeNet5_mnist_inference.IMAGE_SIZE,
                                          LeNet5_mnist_inference.NUM_CHANNELS))
            _, loss_value,step=sess.run([train_op,loss,global_step],feed_dict={x:reshaped_xs,y_:ys})

            # 每1000轮保存一次模型。
            if i%1000 == 0:
                '''
                输出当前的训练情况。这里只输出了模型在当前训练batch上的损失函数大小。通过损失函数的大小可以
                大概链接训练的情况。在验证数据集上的正确率信息会有一个单独的程序来生成。
                '''
                print("After %d training step(s),loss on training"
                      "batch is %g."%(step,loss_value))
                '''
                保存当前的模型。这一这里给吃了global_step参数,这样可以让每一被保存模型的文件名末尾
                加上训练的论数,比如“model.ckpy-1000”表示训练1000轮之后得到的模型
                '''
                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("Google/chapter5/MNIST_data", one_hot=True)
    train(mnist)

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

3.mnist_eval.py

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

# 加载mnist_inference.py 和mnist_train.py中定义的常量和函数
import LeNet5_mnist_inference
import LeNet5_mnist_train


#每10秒加载一次最新的模型,并在测试数据上测试最新模型的正确率
EVAL_INTERVAL_SECS = 10
BATCH_SIZE = 100
def evaluate(mnist):
    with tf.Graph().as_default() as g:
        # 定义输入输出的格式
        x = tf.placeholder(
            tf.float32, [
                mnist.validation.images.shape[0],
                LeNet5_mnist_inference.IMAGE_SIZE,
                LeNet5_mnist_inference.IMAGE_SIZE,
                LeNet5_mnist_inference.NUM_CHANNELS],
            name="x-input"
        )
        y_ = tf.placeholder(tf.float32, [None, LeNet5_mnist_inference.OUTPUT_NODE], name='y-input')
        xs, ys = mnist.validation.images, mnist.validation.labels
        reshape_xs = np.reshape(xs, (-1,
                                     LeNet5_mnist_inference.IMAGE_SIZE,
                                     LeNet5_mnist_inference.IMAGE_SIZE,
                                     LeNet5_mnist_inference.NUM_CHANNELS))

        validate_feed = {x:reshape_xs,
                         y_:ys} #mnist.validation.images 维度为[5000,784]#
        '''
        直接通过调用封装好的函数来计算前向传播的结果。因为测试时
        不关注正则化损失的值,所以这里用于计算正则化损失函数被
        设置为None
        '''
        y = LeNet5_mnist_inference.inference(x, None, None)

        '''
        使用前向传播的结果计算正确率。如果需要对未知的样例进行分类,
        那么使用tf.argmax(y,1)就可以得到输入样例的预测类别了。
        '''
        correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))# tf.cast功能为转化数据格式

        '''
        通过变量重命名的方式来加载模型,这样在前向创博的过程中就不需要
        调用求滑动平均的函数来获取平均值了。这样就可以完全公用mnist_inference.py
        中定义的前向传播过程。
        '''
        variable_averages = tf.train.ExponentialMovingAverage(
            LeNet5_mnist_train.MOVING_AEVERAGE_DECAY
        )
        variable_to_restore = variable_averages.variables_to_restore()
        saver = tf.train.Saver(variable_to_restore)

        '''
        每隔EVAL_INTERVAL_SECS秒调用一次计算正确率的过程以检验训练过程中正确率的变化。
        '''
        while True:
            with tf.Session() as sess:
                # tf.train.get_checkpoint_state函数会通过checkpoint文件自动找到
                # 目录中最新模型的文件名。
                ckpt = tf.train.get_checkpoint_state(
                    LeNet5_mnist_train.MODEL_SAVE_PATH
                )
                if ckpt and ckpt.model_checkpoint_path:
                    # 加载模型。
                    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))
                else:
                    print('No checkpoint file found')
                    return
            time.sleep(EVAL_INTERVAL_SECS)

def main(argv=None):
    mnist=input_data.read_data_sets("Google/chapter5/MNIST_data", one_hot=True)
    evaluate(mnist)

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

二、Inception-v3模型

在上述代码中,我们发现滤波器有3×3的,5×5的,也有7×7的,那么究竟滤波器选择多大的呢,inception模块给出了一个方案,那就是同时使用不同尺寸的过滤器,然后再将得到的矩阵拼接起来。虽然过滤器的大小不同,但如果所有过滤器都使用全0填充,且步长为1,那么前向传播得到的结果矩阵长和宽都与输入矩阵一致。其中醉经的模型是inceptionv-3,该模型总共有46层,由11个inception模块组成,其具体的构成参见上一章神经网络基础知识,这是一个深度神经网络,如果我们从头开始下训练这个模型,那么这将是一个费事费力的过程,所以在这里我们通过迁移学习来实现inception-v3的使用,即从网络中下载好Google以训练好的数据,我们只去修改它的输出层,从而实现对inception-v3模型的利用。

这节内容我们使用的数据flower_photos数据集,这个数据集一共有5个子文件夹,每一个文件夹分别为一种花的名称,平均每一种花有734章图片,每一张图片为不同大小的RGB的彩色图片。

首先下载数组

wget http://download.tensorflow.org/example_images/flower_photos.tgz
tar xzf flower_photos.tgz

首先,我们将图片转换为模型需要的输入数据。

1.datatranfer.py

继续解剖代码:
第一部分,导入模块

# -*- coding:utf-8 -*-

import glob
import os.path
import tensorflow as tf
import numpy as np
from tensorflow.python.platform import gfile

第二部分,设置参数
参数包括需要导入数据的位置,输出数据的位置、测试集和验证集所占数据的比例。

#原始输入数据的目录,这个目录下有5个子目录,每个子目录地下保存属于该类的所有图片。
INPUT_DATA= 'flower/flower_photos'
# 输出文件地址。将整理后的图片数据通过numpy的格式保存。在第7章周昂将更加详细地介绍数据预处理,
# 这里先通过numpy来保存
OUTPUT_FILE = 'flower/flower_processed_data.npy'

# 测试数据和验证数据比例
VALIDATION_PERCENTAGE = 10
TEST_PERCFNTAGE = 10

第三部分,数据转换函数

1.将文件夹中所有图片的名称放入一个列表当中。

# 读取数据并将数据分割成训练数据,验证数据和测试数据
def create_image_lists(sess,testing_percentage,validation_percentage):
    sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]
    is_root_dir = True
    print(sub_dirs)
    # 初始化各个数据集。
    training_image = []
    training_labels = []
    testing_image = []
    testing_labels = []
    validation_images = []
    validation_labels = []
    current_labels = 0

    # 读取所有的子目录
    for sub_dir in sub_dirs:
        if is_root_dir:
            is_root_dir = False
            continue
        # 读取一个子目录中所有图片的文件。
        extensions = ['jpg','jpeg','JPEG']
        file_list =[]
        dir_name = os.path.basename(sub_dir)
        for extension in extensions:
            file_glob = os.path.join(INPUT_DATA,dir_name,'*.'+extension)
            file_list.extend(glob.glob(file_glob))
        if not file_list: continue
        print('processing:',dir_name)
        i=0

2.遍历列表中的每一个元素
转换图片的类型。

        #处理图片数据。
        for file_name in file_list:
            i += 1
            # 读取并解析图片,将图片转化为299x299以便inception_v3模型来处理。
            # 更多关于图片预处理的内容将在第7章中介绍。
            image_raw_data = gfile.FastGFile(file_name,'rb').read()
            image = tf.image.decode_jpeg(image_raw_data)
            if image.dtype != tf.float32:
                image = tf.image.convert_image_dtype(
                    image,dtype = tf.float32
                )
            image = tf.image.resize_images(image,[299,299])
            image_value = sess.run(image)

3,划分数据集
将转换好格式的数据,按比例随机分配给测试集,验证集和训练集,比例为1:1:8

            # 随机划分数据集。
            chance = np.random.randint(100)
            if chance < validation_percentage:
                validation_images.append(image_value)
                validation_labels.append(current_labels)
            elif chance < (testing_percentage + validation_percentage):
                testing_image.append(image_value)
                testing_labels.append(current_labels)
            else:
                training_image.append(image_value)
                training_labels.append(current_labels)
            if i % 200 == 0:
                print(i,"images processed.")
        current_labels += 1

4.随机打乱数据集

    # 将训练数据随机打乱以获得更好的训练效果。
    state = np.random.get_state()
    np.random.shuffle(training_image)
    np.random.set_state(state)
    np.random.shuffle(training_labels)
    return np.asarray([training_image,training_labels,
                       validation_images,validation_labels,
                       testing_image,testing_labels])

5,主程序入口

# 数据整理主函数-
def main():
    with tf.Session() as sess:
        processed_data = create_image_lists(
            sess, TEST_PERCFNTAGE,VALIDATION_PERCENTAGE)
        # 通过numpy格式保存处理后的。
        np.save(OUTPUT_FILE,processed_data)

if __name__ == '__main__':
    main()

然后进行迁移学习,首先可以谷歌提供的训练好的inception_v3模型

wget http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz
# 解压之后可以得到训练好的inception_v3.ckpt文件。

2.transferlearing.py
第一部分,导入模块

# -*- coding: utf-8 -*-

import glob
import os.path
import numpy as np
import tensorflow as tf
from tensorflow.python.platform import gfile
import tensorflow.contrib.slim as slim

#加载通过Tehnsorflow-Slim
#
# 定义好的inception_v3mox。
import tensorflow.contrib.slim.python.slim.nets.inception_v3 as inception_v3

#处理好之后的数据文件。
INPUT_DATA = 'flower/flower_processed_data.npy'



#保存训练好的模型的路径,这里可以将使用新数据训练得到的完整模型保存下来,
# 如果计算资源充足,还可以在寻来你完最后的全链接层之后再训练所有网络层,这样可以使得新模型更加贴近新数据。
TRAIN_FILE = 'save_model'
#谷歌提供的训练好的模型文件地址
CKPT_FILE = 'inception_v3.ckpt'

# 定义训练中使用的参数
LEARNING_RATE = 0.0001
STEPS = 300
BATCH = 32
N_CLASSES = 5

#不需要从谷歌训练好的模型中加载的参数。这里就是最后的全连接层,因为再先得问题中要重新训练这一层中得参数。这里给出的是参数得前缀。
CHECKPOINT_EXCLUDE_SCOPES = 'inceptionV3/Logits,InceptionV3/AuxLogits'
#需要训练的网络层参数名称,再fine-tuning的过程中就是最后的全连接层。这里给出的是参数的前缀
TRAINABLE_SCOPES = 'InceptionV3/Logits,InceptionV3/AuxLogits'

# 获取所有需要从谷歌训练好的模型中加载的参数
def get_tuned_variables():
    exclusions = [scope.strip() for scope in CHECKPOINT_EXCLUDE_SCOPES.split(',')]

    variables_to_restore = []
    # 枚举inception-v3模型中所有的参数,然后判断是否需要从加载列表中移除。
    for var in slim.get_model_variables():
        excluded = False
        for exclusion in exclusions:
            if var.op.name.startswith(exclusion):
                exluded = True
                break
            if not excluded:
                variables_to_restore.append(var)
        return variables_to_restore

# 获取所有需要训练的变量列表
def get_trainable_variable():
    scopes = [scope.strip() for scope in TRAINABLE_SCOPES.split(',')]
    variables_to_train = []
    #枚举所有需要训练参数前缀,并通过这些前缀找到所有的参数。
    for scope in scopes:
        variables = tf.get_collection(
            tf.GraphKeys.TRAINABLE_VARIABLES,scope
        )
        variables_to_train.extend(variables)
    return variables_to_train

def main(self):
    # 加载预处理好的数据。
    processed_data = np.load(INPUT_DATA)
    training_images = processed_data[0]
    n_training_example = len(training_images)
    training_labels = processed_data[1]
    validation_images = processed_data[2]
    validation_labels = processed_data[3]
    testing_images = processed_data[4]
    testing_labels = processed_data[5]
    print("%d training examples,%d validation examples and %d"
          "testing examples." %(
        n_training_example,len(validation_labels),len(testing_labels)
    ))
    # 定义inception-v3的输入,images为输入图片,labels为每一张图片对应的标签。
    images = tf.placeholder(
        tf.float32,[None,299,299,3],
        name = 'input_images'
    )
    labels = tf.placeholder(tf.int64,[None],name='labels')
    '''
    定义inception-v3模型。因为谷歌给出的只有模型参数取值,所以这里需要在这个代码中定义inception-v3
    的模型结构。虽然理论上需要区分训练和测试中使用的模型,也就是说在测试时应该使用is_training = False
    但是因为预先训练好的inception-v3模型总使用的batch normalization参数与新的数据会有差异,导数结果很差
    所以这里直接使用同一个模型来进行测试。
    '''
    with slim.arg_scope(inception_v3.inception_v3_arg_scope()):
        logits, _ =inception_v3.inception_v3(
            images,num_classes=N_CLASSES
        )
    # 获取需要训练的变量。
    trainable_variables = get_trainable_variable()
    # 定义交叉熵损失。注意在模型定义的时候以及将正则化损失加入损失集合了。
    tf.losses.softmax_cross_entropy(
        tf.one_hot(labels,N_CLASSES),logits,weights=1.0
    )
    # 定义训练过程。这里minmize的过程中指定了需要优化的变量集合。
    train_step = tf.train.RMSPropOptimizer(LEARNING_RATE).minmize(
        tf.losses.get_total_loss()
    )

    # 计算正确率。
    with tf.name_scope('evaluation'):
        correct_prediction = tf.equal(tf.argmax(logits,1),labels)
        evaluation_step = tf.reduce_mean(tf.cast(
            correct_prediction,tf.float32
        ))

    # 定义加载模型的函数
    load_fn = slim.assign_from_checkpoint_fn(
        CKPT_FILE,
        get_tuned_variables(),
        ignore_missing_vars=True
    )

    # 定义保存新的训练好的模型的函数。
    saver = tf.train.Saver()
    with tf.Session() as sess:
        # 初始化没有加载进来的变量。注意这个过程一定要在模型加载之前,否则初始化过程会将已经加载好的变量重新赋值。
        init = tf.global_variables_initializer()
        sess.run(init)

        # 加载谷歌已经训练好的模型。
        print('Loading tuned variables from %s' % CKPT_FILE)
        load_fn(sess)

        start = 0
        end = BATCH
        for i in range(STEPS):
            # 运行训练过程,这里不会更新全部的参数,只会更新指定的部分参数。
            sess.run(train_step,feed_dict={
                images:training_images[start:end],
                labels:training_labels[start:end]
            })

            # 输出日志
            if i % 30 == 0 or i + 1 == STEPS:
                saver.save(sess,TRAIN_FILE,global_step=i)
                validation_accuracy = sess.run(evaluation_step,feed_dict={
                    images:validation_images, labels:validation_labels
                })
                print('Step %d: Validation accuracy = %.lf%%'
                      %(i,validation_accuracy * 100.0))

            # 因为子数据预处理的时候已经做过了打乱数据的操作,所以这里只需要顺序
            # 使用训练数据就好。
            start = end
            if start == n_training_example:
                start = 0

            end = start + BATCH
            if end > n_training_example:
                end = n_training_example

        # 在最后的测试数据熵测试正确率
        test_accuracy = sess.run(evaluation_step,feed_dict={
            images: testing_images,labels:testing_labels
        })
        print('Final test accuracy = %.lf%%' %(test_accuracy*100))

if __name__ == '__main__':
    tf.app.run()
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值