人脸识别智能小程序 | Tensorflow挑战Cifar-10图像分类任务 | 04

TF挑战cifar10

cifar100有两类标签,一个是大类标签,一个是小类标签,也就是一个是粗粒度标签,一个是细粒度标签,

上面的代码是解析cifar10的,前面已经讲解过,这里不再赘述。

使用 tf.train.string_input_producer 的方式来对打包好的TFRecord文件进行读取。

将所有的训练样本存放在 train.tfrecord,将所有的测试样本存放在 test.tfrecord。

TF训练框架搭建:

  • Data

首先是数据的读取和数据的打包。

  • Net

网络的搭建,这里采用slim来搭建网络结构,因为slim是对tf更加高层的封装,可以写更加简洁的代码。

  • Loss

Loss本身就是网络的一部分,这个会采用softmaxLoss来进行Loss定义。

另外还会定义正则化的Loss。

  • Summary

Summary完成了训练过程中日志的记录。

  • Session

Session完成了构造出网络结构后,如果对计算图中的结点进行计算,会通过Session在后端完成整个网络的BP。并通过Feed给网络数据,Fetch获得输出的张量。

训练代码,也就是baseline版本,要如何进行优化呢?上面提供了几个参考思路。

  • 更多的数据增强策略,比如:mixup等

通过数据增强的策略,来丰富样本量,进而提高模型对更多样本的泛化能力。

  • 更好的主干网络,比如:SENet等

有VGG,ResNet这样的主干网络,当然,SENet比ResNet的性能更好,可以实验哪个主干网络实验出来的效果更好。

  • 更好的标签策略,比如:Soft-label策略

cifar10的标签就是从0到9十个标签,这里可以采用soft-label的策略,比如有些物体既像飞机又像汽车,那么就可以采用软的策略,来对标签进行预处理。

  • 更好的loss设计,比如:采用分类+回归smooth-l1 loss等

另外可以采用更好的loss,这里采用交叉熵损失来完成图像分类的任务,另外大家也可以考虑结合回归的loss,来进行一个多个loss的约束。

  • 不同的优化器、参数初始化方法等

Cifar10数据读取与数据增强

文件项目的目录结构如上图

  • data: 存放数据 这里是打包好的TFRecord
  • logdirs:存放log日志信息
  • model:存放训练好的模型

readcifar10.py

import tensorflow as tf


def read(batchsize=64, type=1, no_aug_data=1):
    """
    读取TFRecord的脚本

    params
    batchsize:批大小 默认64
    type: test or train 表示从test中读数据还是从train中读数据 0表示train 1表示test
    no_aug_data: 是否进行数据增强 1 or 0
    """
    reader = tf.TFRecordReader()
    """
    搭建模型的时候每训练一定的次数,或每训练一个epoch的时候,这时候会从test文件进行一次测试
    """
    if type == 0:  # train
        file_list = ["data/train.tfrecord"]
    if type == 1:  # test
        file_list = ["data/test.tfrecord"]

    filename_queue = tf.train.string_input_producer(
        file_list, num_epochs=None, shuffle=True
    )
    _, serialized_example = reader.read(filename_queue)

    batch = tf.train.shuffle_batch([serialized_example], batchsize, capacity=batchsize * 10,
                                   min_after_dequeue=batchsize * 5)

    #
    feature = {'image': tf.FixedLenFeature([], tf.string),
               'label': tf.FixedLenFeature([], tf.int64)}

    features = tf.parse_example(batch, features=feature)

    images = features["image"]

    img_batch = tf.decode_raw(images, tf.uint8)
    img_batch = tf.cast(img_batch, tf.float32)
    img_batch = tf.reshape(img_batch, [batchsize, 32, 32, 3])

    """
    这里对train的数据进行数据增强 
    数据增强只对train数据
    """
    if type == 0 and no_aug_data == 1:
        # 随机裁剪
        distorted_image = tf.random_crop(img_batch,
                                         [batchsize, 28, 28, 3])
        # 随机对比度
        distorted_image = tf.image.random_contrast(distorted_image,
                                                   lower=0.8,
                                                   upper=1.2)
        # 随机色调
        distorted_image = tf.image.random_hue(distorted_image,
                                              max_delta=0.2)
        # 随机饱和度
        distorted_image = tf.image.random_saturation(distorted_image,
                                                     lower=0.8,
                                                     upper=1.2)
        # 对处理过的图像进行取值范围的约束
        img_batch = tf.clip_by_value(distorted_image, 0, 255)

    # 最后将图像resize回[32,32]
    img_batch = tf.image.resize_images(img_batch, [32, 32])
    label_batch = tf.cast(features['label'], tf.int64)

    # [-1,1] 将图片归一化
    img_batch = tf.cast(img_batch, tf.float32) / 128.0 - 1.0
    
    return img_batch, label_batch

TensorFlow+Slim网络结构搭建

def model(image, keep_prob=0.8, is_training=True):
    """
    在model中我们会定义网络结构
    params
    image: 输入的图片

    return 概率分布值 10 dim vector
    """

    # batchnorm的参数
    batch_norm_params = {
        "is_training": is_training,  # train:True test:False
        "epsilon": 1e-5,  # 这个值是防止batchnorm在归一化的时候除0
        "decay": 0.997,  # 衰减系数
        'scale': True,
        'updates_collections': tf.GraphKeys.UPDATE_OPS
    }

    """
    定义优化器
    用 slim.arg_scope()为目标函数设置默认参数.
    下面两个with,完成卷积默认参数的初始化和pooling层默认参数的初始化
    一个with就是一个参数域 网络可以使用通过的参数
    """
    with slim.arg_scope(
        [slim.conv2d],  # 这里是给 slim.conv2d 规定过了后面的参数
        weights_initializer=slim.variance_scaling_initializer(),  # 方差尺度不变来进行初始化
        activation_fn=tf.nn.relu,  # 默认激活函数为relu 在卷积之后加入激活函数
        weights_regularizer=slim.l2_regularizer(
            0.0001),  # 权值的正则化约束 正则项权值为0.0001
        normalizer_fn=slim.batch_norm,  # 在卷积机后加入 BatchNorm
            normalizer_params=batch_norm_params):

        with slim.arg_scope([slim.max_pool2d], padding="SAME"):  # 给 slim.max_pool2d 规定参数
            """
            接下来利用卷积层、池化层、全连接层搭建一个cifar10用于图像分类的全连接网
            """
            # 输入image 学习32个卷积核(channel) 卷积核大小 [3,3] 卷积层命名 conv1
            net = slim.conv2d(image, 32, [3, 3], scope='conv1')
            # 输入net 学习32个卷积核 卷积核大小 [3,3] 卷积层命名 conv2
            net = slim.conv2d(net, 32, [3, 3], scope='conv2')
            # 输入net 池化核大小[3,3] stride=2(进行两倍下采样)
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            """
            ===========
            上面 conv+conv+maxpool 算是一个基本单元,后面基本就是使用这样的基本单元进行堆叠
            ==========
            """
            # 每次经过pooling之后 卷积层channel的数量应该翻倍
            net = slim.conv2d(net, 64, [3, 3], scope='conv3')
            net = slim.conv2d(net, 64, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool2')
            net = slim.conv2d(net, 128, [3, 3], scope='conv5')
            net = slim.conv2d(net, 128, [3, 3], scope='conv6')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool3')
            # 经过8倍下采样之后 在加入一个卷积层
            net = slim.conv2d(net, 256, [3, 3], scope='conv7')
            """
            特征图维度 (n,h,w,c)
            对 h,w 进行均值的求解 得到 (n,1,1,c) 因为 reduce_mean 就是降维求平均
            """
            net = tf.reduce_mean(net, axis=[1, 2])  # nhwc--n11c
            """
            slim.flatten 将输入扁平化但保留batch_size,假设第一维是batch。
            将 (n,1,1,c) 转为 (n,c)
            """
            net = slim.flatten(net)
            # 全连接层 这里还不着急分类 (1,1024)可以作为从图像中提取出来的特征
            net = slim.fully_connected(net, 1024)
            """
            全连接层的参数太多 要加入dropout层 来进行正则化
            keep_prob 概率值 定义了我们对当前神经元选择的概率
            在训练和测试的时候,这个概率要取不同的值 训练的时候要取<1 测试的时候要=1
            """
            slim.dropout(net, keep_prob)
            # 输出为10 对应了10个分类类别
            net = slim.fully_connected(net, 10)
    return net  # 10 dim vec

这里的模型设计基本是沿用了VGG的设计思路,采用了\(3*3\)的小卷积核,每次pooling后对channel的数量进行翻倍,并且在全连接层后面加入了dropout层。另外在经过8倍下采样后采用了一个average pooling层,进行一个全图的池化,最后输出一个10维的概率分布值。

Loss、Optimal、Learning Rate、BN等定义

Loss层定义

def loss(logits, label):
    """
    loss使用交叉熵损失函数
    param
    logits: 预测出来的概率分布值
    label: 实际的label

    return 分类的loss
    """
    # 对label进行one-hot编码 定义one-hot长度为10
    one_hot_label = slim.one_hot_encoding(label, 10) 
    # 使用交叉熵损失函数 并传入 预测值 和 one-hot label
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    """
    在前面 slim.arg_scope 中定义了weights_regularizer=slim.l2_regularizer(0.0001)
    通过 tf.GraphKeys.REGULARIZATION_LOSSES 可以拿到这些loss正则化的集合
    """
    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 将这些正则化loss进行相加 计算出总体的l2_loss
    l2_loss = tf.add_n(reg_set)
    # 将 l2_loss添加到loss中
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()
    # 这里为了后面做日志,这里把l2_loss也传出
    return totalloss, l2_loss

Train部分代码编写

初代Train代码

import os
import tensorflow as tf
import readcifar10
from tqdm import tqdm
slim = tf.contrib.slim


slim = tf.contrib.slim


def model(image, keep_prob=0.8, is_training=True):
    """
    在model中我们会定义网络结构
    params
    image: 输入的图片

    return 概率分布值 10 dim vector
    """

    # batchnorm的参数
    batch_norm_params = {
        "is_training": is_training,  # train:True test:False
        "epsilon": 1e-5,  # 这个值是防止batchnorm在归一化的时候除0
        "decay": 0.997,  # 衰减系数
        'scale': True,
        'updates_collections': tf.GraphKeys.UPDATE_OPS
    }

    """
    定义优化器
    用 slim.arg_scope()为目标函数设置默认参数.
    下面两个with,完成卷积默认参数的初始化和pooling层默认参数的初始化
    一个with就是一个参数域 网络可以使用通过的参数
    """
    with slim.arg_scope(
        [slim.conv2d],  # 这里是给 slim.conv2d 规定过了后面的参数
        weights_initializer=slim.variance_scaling_initializer(),  # 方差尺度不变来进行初始化
        activation_fn=tf.nn.relu,  # 默认激活函数为relu 在卷积之后加入激活函数
        weights_regularizer=slim.l2_regularizer(
            0.0001),  # 权值的正则化约束 正则项权值为0.0001
        normalizer_fn=slim.batch_norm,  # 在卷积机后加入 BatchNorm
            normalizer_params=batch_norm_params):

        with slim.arg_scope([slim.max_pool2d], padding="SAME"):  # 给 slim.max_pool2d 规定参数
            """
            接下来利用卷积层、池化层、全连接层搭建一个cifar10用于图像分类的全连接网
            """
            # 输入image 学习32个卷积核(channel) 卷积核大小 [3,3] 卷积层命名 conv1
            net = slim.conv2d(image, 32, [3, 3], scope='conv1')
            # 输入net 学习32个卷积核 卷积核大小 [3,3] 卷积层命名 conv2
            net = slim.conv2d(net, 32, [3, 3], scope='conv2')
            # 输入net 池化核大小[3,3] stride=2(进行两倍下采样)
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            """
            ===========
            上面 conv+conv+maxpool 算是一个基本单元,后面基本就是使用这样的基本单元进行堆叠
            ==========
            """
            # 每次经过pooling之后 卷积层channel的数量应该翻倍
            net = slim.conv2d(net, 64, [3, 3], scope='conv3')
            net = slim.conv2d(net, 64, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool2')
            net = slim.conv2d(net, 128, [3, 3], scope='conv5')
            net = slim.conv2d(net, 128, [3, 3], scope='conv6')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool3')
            # 经过8倍下采样之后 在加入一个卷积层
            net = slim.conv2d(net, 256, [3, 3], scope='conv7')
            """
            特征图维度 (n,h,w,c)
            对 h,w 进行均值的求解 得到 (n,1,1,c) 因为 reduce_mean 就是降维求平均
            """
            net = tf.reduce_mean(net, axis=[1, 2])  # nhwc--n11c
            """
            slim.flatten 将输入扁平化但保留batch_size,假设第一维是batch。
            将 (n,1,1,c) 转为 (n,c)
            """
            net = slim.flatten(net)
            # 全连接层 这里还不着急分类 (1,1024)可以作为从图像中提取出来的特征
            net = slim.fully_connected(net, 1024)
            """
            全连接层的参数太多 要加入dropout层 来进行正则化
            keep_prob 概率值 定义了我们对当前神经元选择的概率
            在训练和测试的时候,这个概率要取不同的值 训练的时候要取<1 测试的时候要=1
            """
            slim.dropout(net, keep_prob)
            # 输出为10 对应了10个分类类别
            net = slim.fully_connected(net, 10)
    return net  # 10 dim vec


def loss(logits, label):
    """
    loss使用交叉熵损失函数
    param
    logits: 预测出来的概率分布值
    label: 实际的label

    return 分类的loss
    """
    # 对label进行one-hot编码 定义one-hot长度为10
    one_hot_label = slim.one_hot_encoding(label, 10)
    # 使用交叉熵损失函数 并传入 预测值 和 one-hot label
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    """
    在前面 slim.arg_scope 中定义了weights_regularizer=slim.l2_regularizer(0.0001)
    通过 tf.GraphKeys.REGULARIZATION_LOSSES 可以拿到这些loss正则化的集合
    """
    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 将这些正则化loss进行相加 计算出总体的l2_loss
    l2_loss = tf.add_n(reg_set)
    # 将 l2_loss添加到loss中
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()
    # 这里为了后面做日志,这里把l2_loss也传出
    return totalloss, l2_loss


def func_optimal(batchsize, loss_val):
    """
    定义优化器
    """
    global_step = tf.Variable(0, trainable=False)
    # 通过指数衰减的形式来定义学习率
    lr = tf.train.exponential_decay(0.01,
                                    global_step,
                                    decay_steps=50000 // batchsize,
                                    decay_rate=0.95,
                                    staircase=False)

    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

    with tf.control_dependencies(update_ops):
        op = tf.train.AdamOptimizer(lr).minimize(loss_val, global_step)

    """
    global_step 可以得到当前的迭代次数
    op sess.run()执行op可以完成对网络参数的调节
    lr 返回 便于log信息的记录
    """
    return global_step, op, lr


def train():
    batchsize = 64
    floder_log = 'logdirs'
    floder_model = 'model'

    if not os.path.exists(floder_log):
        os.mkdir(floder_log)

    if not os.path.exists(floder_model):
        os.mkdir(floder_model)

    # data
    tr_im, tr_label = readcifar10.read(batchsize, 0, 1)
    te_im, te_label = readcifar10.read(batchsize, 1, 0)

    # net
    """
    tf.placeholder 此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值
    训练数据 [None,32,32,3] None是考虑到batch_size是可以变化的
    """
    input_data = tf.placeholder(tf.float32, shape=[None, 32, 32, 3],
                                name='input_data')

    input_label = tf.placeholder(tf.int64, shape=[None],
                                 name='input_label')
    """
    keep_prob dropout的概率值
    训练过程 keep_prob 也就是dropout的概率值 为0.5 或小于1的值
    """
    keep_prob = tf.placeholder(tf.float32, shape=None,
                               name='keep_prob')

    is_training = tf.placeholder(tf.bool, shape=None,
                                 name='is_training')

    logits = model(
        input_data, keep_prob=keep_prob, is_training=is_training)

    # loss
    """
    损失函数 传入预测的结果和真实的标签
    """
    total_loss, l2_loss = loss(logits, input_label)

    # accurancy
    """
    tf.argmax 获取最大值的索引
    [n,10] axis=1 计算的是10维这个维度最大值对应的索引值
    """
    pred_max = tf.argmax(logits, 1)
    # 判断最大值索引和 label是否相等
    correct = tf.equal(pred_max, input_label)
    accurancy = tf.reduce_mean(tf.cast(correct, tf.float32))

    # op
    global_step, op, lr = func_optimal(batchsize, total_loss)

    with tf.Session() as sess:
        # 初始化参数 包括了局部变量和全局变量
        sess.run(tf.group(tf.global_variables_initializer(),
                          tf.local_variables_initializer()))
        # 启动文件队列写入的线程
        tf.train.start_queue_runners(sess=sess,
                                     coord=tf.train.Coordinator())

        epoch_val = 10

        # 读取文件队列中的数据,完成对网络的训练
        for i in tqdm(range(50000 * epoch_val)):
            train_im_batch, train_label_batch = \
                sess.run([tr_im, tr_label])

            feed_dict = {
                input_data: train_im_batch,
                input_label: train_label_batch,
                keep_prob: 0.8,
                is_training: True
            }

            # 注意 这里得到这这些值 都是一个batch_size的
            _, global_step_val, \
                lr_val, \
                total_loss_val, \
                accurancy_val = sess.run([op,
                                          global_step,
                                          lr,
                                          total_loss,
                                          accurancy],
                                         feed_dict=feed_dict)

            # 每隔一百次打印一次
            if i % 20 == 0:
                print("\n{},{},{},{}".format(global_step_val,
                lr_val, 
                total_loss_val,
                accurancy_val))

    return


if __name__ == '__main__':
    train()

Test部分代码编写

在完成一部分train,可以进行test

import os
import tensorflow as tf
import readcifar10
from tqdm import tqdm
slim = tf.contrib.slim


slim = tf.contrib.slim


def model(image, keep_prob=0.8, is_training=True):
    """
    在model中我们会定义网络结构
    params
    image: 输入的图片

    return 概率分布值 10 dim vector
    """

    # batchnorm的参数
    batch_norm_params = {
        "is_training": is_training,  # train:True test:False
        "epsilon": 1e-5,  # 这个值是防止batchnorm在归一化的时候除0
        "decay": 0.997,  # 衰减系数
        'scale': True,
        'updates_collections': tf.GraphKeys.UPDATE_OPS
    }

    """
    定义优化器
    用 slim.arg_scope()为目标函数设置默认参数.
    下面两个with,完成卷积默认参数的初始化和pooling层默认参数的初始化
    一个with就是一个参数域 网络可以使用通过的参数
    """
    with slim.arg_scope(
        [slim.conv2d],  # 这里是给 slim.conv2d 规定过了后面的参数
        weights_initializer=slim.variance_scaling_initializer(),  # 方差尺度不变来进行初始化
        activation_fn=tf.nn.relu,  # 默认激活函数为relu 在卷积之后加入激活函数
        weights_regularizer=slim.l2_regularizer(
            0.0001),  # 权值的正则化约束 正则项权值为0.0001
        normalizer_fn=slim.batch_norm,  # 在卷积机后加入 BatchNorm
            normalizer_params=batch_norm_params):

        with slim.arg_scope([slim.max_pool2d], padding="SAME"):  # 给 slim.max_pool2d 规定参数
            """
            接下来利用卷积层、池化层、全连接层搭建一个cifar10用于图像分类的全连接网
            """
            # 输入image 学习32个卷积核(channel) 卷积核大小 [3,3] 卷积层命名 conv1
            net = slim.conv2d(image, 32, [3, 3], scope='conv1')
            # 输入net 学习32个卷积核 卷积核大小 [3,3] 卷积层命名 conv2
            net = slim.conv2d(net, 32, [3, 3], scope='conv2')
            # 输入net 池化核大小[3,3] stride=2(进行两倍下采样)
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            """
            ===========
            上面 conv+conv+maxpool 算是一个基本单元,后面基本就是使用这样的基本单元进行堆叠
            ==========
            """
            # 每次经过pooling之后 卷积层channel的数量应该翻倍
            net = slim.conv2d(net, 64, [3, 3], scope='conv3')
            net = slim.conv2d(net, 64, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool2')
            net = slim.conv2d(net, 128, [3, 3], scope='conv5')
            net = slim.conv2d(net, 128, [3, 3], scope='conv6')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool3')
            # 经过8倍下采样之后 在加入一个卷积层
            net = slim.conv2d(net, 256, [3, 3], scope='conv7')
            """
            特征图维度 (n,h,w,c)
            对 h,w 进行均值的求解 得到 (n,1,1,c) 因为 reduce_mean 就是降维求平均
            """
            net = tf.reduce_mean(net, axis=[1, 2])  # nhwc--n11c
            """
            slim.flatten 将输入扁平化但保留batch_size,假设第一维是batch。
            将 (n,1,1,c) 转为 (n,c)
            """
            net = slim.flatten(net)
            # 全连接层 这里还不着急分类 (1,1024)可以作为从图像中提取出来的特征
            net = slim.fully_connected(net, 1024)
            """
            全连接层的参数太多 要加入dropout层 来进行正则化
            keep_prob 概率值 定义了我们对当前神经元选择的概率
            在训练和测试的时候,这个概率要取不同的值 训练的时候要取<1 测试的时候要=1
            """
            slim.dropout(net, keep_prob)
            # 输出为10 对应了10个分类类别
            net = slim.fully_connected(net, 10)
    return net  # 10 dim vec


def loss(logits, label):
    """
    loss使用交叉熵损失函数
    param
    logits: 预测出来的概率分布值
    label: 实际的label

    return 分类的loss
    """
    # 对label进行one-hot编码 定义one-hot长度为10
    one_hot_label = slim.one_hot_encoding(label, 10)
    # 使用交叉熵损失函数 并传入 预测值 和 one-hot label
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    """
    在前面 slim.arg_scope 中定义了weights_regularizer=slim.l2_regularizer(0.0001)
    通过 tf.GraphKeys.REGULARIZATION_LOSSES 可以拿到这些loss正则化的集合
    """
    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 将这些正则化loss进行相加 计算出总体的l2_loss
    l2_loss = tf.add_n(reg_set)
    # 将 l2_loss添加到loss中
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()
    # 这里为了后面做日志,这里把l2_loss也传出
    return totalloss, l2_loss


def func_optimal(batchsize, loss_val):
    """
    定义优化器
    """
    global_step = tf.Variable(0, trainable=False)
    # 通过指数衰减的形式来定义学习率
    lr = tf.train.exponential_decay(0.01,
                                    global_step,
                                    decay_steps=50000 // batchsize,
                                    decay_rate=0.95,
                                    staircase=False)

    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

    with tf.control_dependencies(update_ops):
        op = tf.train.AdamOptimizer(lr).minimize(loss_val, global_step)

    """
    global_step 可以得到当前的迭代次数
    op sess.run()执行op可以完成对网络参数的调节
    lr 返回 便于log信息的记录
    """
    return global_step, op, lr


def train():
    batchsize = 64
    floder_log = 'logdirs'
    floder_model = 'model'

    if not os.path.exists(floder_log):
        os.mkdir(floder_log)

    if not os.path.exists(floder_model):
        os.mkdir(floder_model)

    # data
    tr_im, tr_label = readcifar10.read(batchsize, 0, 1)
    te_im, te_label = readcifar10.read(batchsize, 1, 0)

    # net
    """
    tf.placeholder 此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值
    训练数据 [None,32,32,3] None是考虑到batch_size是可以变化的
    """
    input_data = tf.placeholder(tf.float32, shape=[None, 32, 32, 3],
                                name='input_data')

    input_label = tf.placeholder(tf.int64, shape=[None],
                                 name='input_label')
    """
    keep_prob dropout的概率值
    训练过程 keep_prob 也就是dropout的概率值 为0.5 或小于1的值
    """
    keep_prob = tf.placeholder(tf.float32, shape=None,
                               name='keep_prob')

    is_training = tf.placeholder(tf.bool, shape=None,
                                 name='is_training')

    logits = model(
        input_data, keep_prob=keep_prob, is_training=is_training)

    # loss
    """
    损失函数 传入预测的结果和真实的标签
    """
    total_loss, l2_loss = loss(logits, input_label)

    # accurancy
    """
    tf.argmax 获取最大值的索引
    [n,10] axis=1 计算的是10维这个维度最大值对应的索引值
    """
    pred_max = tf.argmax(logits, 1)
    # 判断最大值索引和 label是否相等
    correct = tf.equal(pred_max, input_label)
    accurancy = tf.reduce_mean(tf.cast(correct, tf.float32))

    # op
    global_step, op, lr = func_optimal(batchsize, total_loss)

    with tf.Session() as sess:
        # 初始化参数 包括了局部变量和全局变量
        sess.run(tf.group(tf.global_variables_initializer(),
                          tf.local_variables_initializer()))
        # 启动文件队列写入的线程
        tf.train.start_queue_runners(sess=sess,
                                     coord=tf.train.Coordinator())

        epoch_val = 10

        # 读取文件队列中的数据,完成对网络的训练
        for i in tqdm(range(50000 * epoch_val)): # 这里这个数量我还是没有理解
            train_im_batch, train_label_batch = \
                sess.run([tr_im, tr_label])

            feed_dict = {
                input_data: train_im_batch,
                input_label: train_label_batch,
                keep_prob: 0.8,
                is_training: True
            }

            # 注意 这里得到这这些值 都是一个batch_size的
            _, global_step_val, \
                lr_val, \
                total_loss_val, \
                accurancy_val = sess.run([op,
                                          global_step,
                                          lr,
                                          total_loss,
                                          accurancy],
                                         feed_dict=feed_dict)

            # 每隔一百次打印一次
            if i % 100 == 0:
                print("\n[trian]:{},{},{},{}".format(global_step_val,
                lr_val, 
                total_loss_val,
                accurancy_val))
            
            # 隔一段时间进行测试
            if i % (50000 // batchsize) == 0:
                test_loss = 0
                test_acc = 0
                for ii in range(10000//batchsize):
                    test_im_batch, test_label_batch = \
                        sess.run([te_im, te_label])
                    feed_dict = {
                        input_data: test_im_batch,
                        input_label: test_label_batch,
                        keep_prob: 1.0,
                        is_training: False
                    }

                    total_loss_val, global_step_val, \
                    accurancy_val = sess.run([total_loss,global_step,
                                              accurancy],
                                             feed_dict=feed_dict)

                    test_loss += total_loss_val
                    test_acc += accurancy_val

                print('[test]:', test_loss * batchsize / 10000,
                      test_acc* batchsize / 10000)
    return


if __name__ == '__main__':
    train()

Tensorboard+tf.summary

因为查看训练的消息只能在控制台查看,所以这里要记录日志信息。

训练好会输出日志,如下图。

如何查看该日志呢?在终端输入以下命令

tensorboard --logdir=logdirs

import os
import tensorflow as tf
import readcifar10
from tqdm import tqdm
slim = tf.contrib.slim


slim = tf.contrib.slim


def model(image, keep_prob=0.8, is_training=True):
    """
    在model中我们会定义网络结构
    params
    image: 输入的图片

    return 概率分布值 10 dim vector
    """

    # batchnorm的参数
    batch_norm_params = {
        "is_training": is_training,  # train:True test:False
        "epsilon": 1e-5,  # 这个值是防止batchnorm在归一化的时候除0
        "decay": 0.997,  # 衰减系数
        'scale': True,
        'updates_collections': tf.GraphKeys.UPDATE_OPS
    }

    """
    定义优化器
    用 slim.arg_scope()为目标函数设置默认参数.
    下面两个with,完成卷积默认参数的初始化和pooling层默认参数的初始化
    一个with就是一个参数域 网络可以使用通过的参数
    """
    with slim.arg_scope(
        [slim.conv2d],  # 这里是给 slim.conv2d 规定过了后面的参数
        weights_initializer=slim.variance_scaling_initializer(),  # 方差尺度不变来进行初始化
        activation_fn=tf.nn.relu,  # 默认激活函数为relu 在卷积之后加入激活函数
        weights_regularizer=slim.l2_regularizer(
            0.0001),  # 权值的正则化约束 正则项权值为0.0001
        normalizer_fn=slim.batch_norm,  # 在卷积机后加入 BatchNorm
            normalizer_params=batch_norm_params):

        with slim.arg_scope([slim.max_pool2d], padding="SAME"):  # 给 slim.max_pool2d 规定参数
            """
            接下来利用卷积层、池化层、全连接层搭建一个cifar10用于图像分类的全连接网
            """
            # 输入image 学习32个卷积核(channel) 卷积核大小 [3,3] 卷积层命名 conv1
            net = slim.conv2d(image, 32, [3, 3], scope='conv1')
            # 输入net 学习32个卷积核 卷积核大小 [3,3] 卷积层命名 conv2
            net = slim.conv2d(net, 32, [3, 3], scope='conv2')
            # 输入net 池化核大小[3,3] stride=2(进行两倍下采样)
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            """
            ===========
            上面 conv+conv+maxpool 算是一个基本单元,后面基本就是使用这样的基本单元进行堆叠
            ==========
            """
            # 每次经过pooling之后 卷积层channel的数量应该翻倍
            net = slim.conv2d(net, 64, [3, 3], scope='conv3')
            net = slim.conv2d(net, 64, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool2')
            net = slim.conv2d(net, 128, [3, 3], scope='conv5')
            net = slim.conv2d(net, 128, [3, 3], scope='conv6')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool3')
            # 经过8倍下采样之后 在加入一个卷积层
            net = slim.conv2d(net, 256, [3, 3], scope='conv7')
            """
            特征图维度 (n,h,w,c)
            对 h,w 进行均值的求解 得到 (n,1,1,c) 因为 reduce_mean 就是降维求平均
            """
            net = tf.reduce_mean(net, axis=[1, 2])  # nhwc--n11c
            """
            slim.flatten 将输入扁平化但保留batch_size,假设第一维是batch。
            将 (n,1,1,c) 转为 (n,c)
            """
            net = slim.flatten(net)
            # 全连接层 这里还不着急分类 (1,1024)可以作为从图像中提取出来的特征
            net = slim.fully_connected(net, 1024)
            """
            全连接层的参数太多 要加入dropout层 来进行正则化
            keep_prob 概率值 定义了我们对当前神经元选择的概率
            在训练和测试的时候,这个概率要取不同的值 训练的时候要取<1 测试的时候要=1
            """
            slim.dropout(net, keep_prob)
            # 输出为10 对应了10个分类类别
            net = slim.fully_connected(net, 10)
    return net  # 10 dim vec


def loss(logits, label):
    """
    loss使用交叉熵损失函数
    param
    logits: 预测出来的概率分布值
    label: 实际的label

    return 分类的loss
    """
    # 对label进行one-hot编码 定义one-hot长度为10
    one_hot_label = slim.one_hot_encoding(label, 10)
    # 使用交叉熵损失函数 并传入 预测值 和 one-hot label
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    """
    在前面 slim.arg_scope 中定义了weights_regularizer=slim.l2_regularizer(0.0001)
    通过 tf.GraphKeys.REGULARIZATION_LOSSES 可以拿到这些loss正则化的集合
    """
    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 将这些正则化loss进行相加 计算出总体的l2_loss
    l2_loss = tf.add_n(reg_set)
    # 将 l2_loss添加到loss中
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()
    # 这里为了后面做日志,这里把l2_loss也传出
    return totalloss, l2_loss


def func_optimal(batchsize, loss_val):
    """
    定义优化器
    """
    global_step = tf.Variable(0, trainable=False)
    # 通过指数衰减的形式来定义学习率
    lr = tf.train.exponential_decay(0.01,
                                    global_step,
                                    decay_steps=50000 // batchsize,
                                    decay_rate=0.95,
                                    staircase=False)

    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

    with tf.control_dependencies(update_ops):
        op = tf.train.AdamOptimizer(lr).minimize(loss_val, global_step)

    """
    global_step 可以得到当前的迭代次数
    op sess.run()执行op可以完成对网络参数的调节
    lr 返回 便于log信息的记录
    """
    return global_step, op, lr


def train():
    batchsize = 64
    floder_log = 'logdirs'
    floder_model = 'model'

    if not os.path.exists(floder_log):
        os.mkdir(floder_log)

    if not os.path.exists(floder_model):
        os.mkdir(floder_model)

    # 存放train和test的日志信息
    tr_summary = set()
    te_summary = set()

    # data
    tr_im, tr_label = readcifar10.read(batchsize, 0, 1)
    te_im, te_label = readcifar10.read(batchsize, 1, 0)

    # net
    """
    tf.placeholder 此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值
    训练数据 [None,32,32,3] None是考虑到batch_size是可以变化的
    """
    input_data = tf.placeholder(tf.float32, shape=[None, 32, 32, 3],
                                name='input_data')

    input_label = tf.placeholder(tf.int64, shape=[None],
                                 name='input_label')
    """
    keep_prob dropout的概率值
    训练过程 keep_prob 也就是dropout的概率值 为0.5 或小于1的值
    """
    keep_prob = tf.placeholder(tf.float32, shape=None,
                               name='keep_prob')

    is_training = tf.placeholder(tf.bool, shape=None,
                                 name='is_training')

    logits = model(
        input_data, keep_prob=keep_prob, is_training=is_training)

    # loss
    """
    损失函数 传入预测的结果和真实的标签
    """
    total_loss, l2_loss = loss(logits, input_label)

    # 记录loss
    tr_summary.add(tf.summary.scalar('train total loss', total_loss))
    tr_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    te_summary.add(tf.summary.scalar('train total loss', total_loss))
    te_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    # accurancy
    """
    tf.argmax 获取最大值的索引
    [n,10] axis=1 计算的是10维这个维度最大值对应的索引值
    """
    pred_max = tf.argmax(logits, 1)
    # 判断最大值索引和 label是否相等
    correct = tf.equal(pred_max, input_label)
    accurancy = tf.reduce_mean(tf.cast(correct, tf.float32))

    tr_summary.add(tf.summary.scalar('train accurancy', accurancy))
    te_summary.add(tf.summary.scalar('test accurancy', accurancy))

    # op
    global_step, op, lr = func_optimal(batchsize, total_loss)

    tr_summary.add(tf.summary.scalar('train lr', lr))
    te_summary.add(tf.summary.scalar('test lr', lr))

    # 图片数据是归一化过的 这里要处理回来
    tr_summary.add(tf.summary.image('train image', input_data * 128 + 128))
    te_summary.add(tf.summary.image('test image', input_data * 128 + 128))
    with tf.Session() as sess:
        # 初始化参数 包括了局部变量和全局变量
        sess.run(tf.group(tf.global_variables_initializer(),
                          tf.local_variables_initializer()))
        # 启动文件队列写入的线程
        tf.train.start_queue_runners(sess=sess,
                                     coord=tf.train.Coordinator())

        epoch_val = 10

        # 将日志进行合并
        tr_summary_op = tf.summary.merge(list(tr_summary))
        te_summary_op = tf.summary.merge(list(te_summary))

        summary_writer = tf.summary.FileWriter(floder_log, sess.graph)

        # 读取文件队列中的数据,完成对网络的训练
        for i in tqdm(range(50000 * epoch_val)):  # 这里这个数量我还是没有理解
            train_im_batch, train_label_batch = \
                sess.run([tr_im, tr_label])

            feed_dict = {
                input_data: train_im_batch,
                input_label: train_label_batch,
                keep_prob: 0.8,
                is_training: True
            }

            # 注意 这里得到这这些值 都是一个batch_size的
            _, global_step_val, \
                lr_val, \
                total_loss_val, \
                accurancy_val, tr_summary_str = sess.run([op,
                                                          global_step,
                                                          lr,
                                                          total_loss,
                                                          accurancy, tr_summary_op],
                                                         feed_dict=feed_dict)

            summary_writer.add_summary(tr_summary_str, global_step_val)

            # 每隔一百次打印一次
            if i % 100 == 0:
                print("\n[trian]:{},{},{},{}".format(global_step_val,
                                                     lr_val,
                                                     total_loss_val,
                                                     accurancy_val))

            # 隔一段时间进行测试
            if i % (50000 // batchsize) == 0:
                test_loss = 0
                test_acc = 0
                for ii in range(10000//batchsize):
                    test_im_batch, test_label_batch = \
                        sess.run([te_im, te_label])
                    feed_dict = {
                        input_data: test_im_batch,
                        input_label: test_label_batch,
                        keep_prob: 1.0,
                        is_training: False
                    }

                    total_loss_val, global_step_val, \
                    accurancy_val, te_summary_str = sess.run([total_loss,global_step,
                                              accurancy, te_summary_op],
                                             feed_dict=feed_dict)

                    summary_writer.add_summary(te_summary_str, global_step_val)

                    test_loss += total_loss_val
                    test_acc += accurancy_val

                print('[test]:', test_loss * batchsize / 10000,
                      test_acc * batchsize / 10000)
    return


if __name__ == '__main__':
    train()

观察loss,确实是在收敛,但是准确率的震荡还是比较大的。

那么处理的思路有:

  • 减小学习率或加大学习率衰减的值

修改优化器

模型恢复和模型存储

上面的程序没有加入模型存储的代码。

保存的模型代码结果如下

对于tf的model,就是包含了以下几个部分:

  • checkpoint: 指向最新的模型
  • .meta: 定义了graph的结构
  • .data:存放了网络中具体参数的值
  • .index: 完成对data和meta的索引
import os
import tensorflow as tf
import readcifar10
from tqdm import tqdm
slim = tf.contrib.slim


slim = tf.contrib.slim


def model(image, keep_prob=0.8, is_training=True):
    """
    在model中我们会定义网络结构
    params
    image: 输入的图片

    return 概率分布值 10 dim vector
    """

    # batchnorm的参数
    batch_norm_params = {
        "is_training": is_training,  # train:True test:False
        "epsilon": 1e-5,  # 这个值是防止batchnorm在归一化的时候除0
        "decay": 0.997,  # 衰减系数
        'scale': True,
        'updates_collections': tf.GraphKeys.UPDATE_OPS
    }

    """
    定义优化器
    用 slim.arg_scope()为目标函数设置默认参数.
    下面两个with,完成卷积默认参数的初始化和pooling层默认参数的初始化
    一个with就是一个参数域 网络可以使用通过的参数
    """
    with slim.arg_scope(
        [slim.conv2d],  # 这里是给 slim.conv2d 规定过了后面的参数
        weights_initializer=slim.variance_scaling_initializer(),  # 方差尺度不变来进行初始化
        activation_fn=tf.nn.relu,  # 默认激活函数为relu 在卷积之后加入激活函数
        weights_regularizer=slim.l2_regularizer(
            0.0001),  # 权值的正则化约束 正则项权值为0.0001
        normalizer_fn=slim.batch_norm,  # 在卷积机后加入 BatchNorm
            normalizer_params=batch_norm_params):

        with slim.arg_scope([slim.max_pool2d], padding="SAME"):  # 给 slim.max_pool2d 规定参数
            """
            接下来利用卷积层、池化层、全连接层搭建一个cifar10用于图像分类的全连接网
            """
            # 输入image 学习32个卷积核(channel) 卷积核大小 [3,3] 卷积层命名 conv1
            net = slim.conv2d(image, 32, [3, 3], scope='conv1')
            # 输入net 学习32个卷积核 卷积核大小 [3,3] 卷积层命名 conv2
            net = slim.conv2d(net, 32, [3, 3], scope='conv2')
            # 输入net 池化核大小[3,3] stride=2(进行两倍下采样)
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            """
            ===========
            上面 conv+conv+maxpool 算是一个基本单元,后面基本就是使用这样的基本单元进行堆叠
            ==========
            """
            # 每次经过pooling之后 卷积层channel的数量应该翻倍
            net = slim.conv2d(net, 64, [3, 3], scope='conv3')
            net = slim.conv2d(net, 64, [3, 3], scope='conv4')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool2')
            net = slim.conv2d(net, 128, [3, 3], scope='conv5')
            net = slim.conv2d(net, 128, [3, 3], scope='conv6')
            net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool3')
            # 经过8倍下采样之后 在加入一个卷积层
            net = slim.conv2d(net, 256, [3, 3], scope='conv7')
            """
            特征图维度 (n,h,w,c)
            对 h,w 进行均值的求解 得到 (n,1,1,c) 因为 reduce_mean 就是降维求平均
            """
            net = tf.reduce_mean(net, axis=[1, 2])  # nhwc--n11c
            """
            slim.flatten 将输入扁平化但保留batch_size,假设第一维是batch。
            将 (n,1,1,c) 转为 (n,c)
            """
            net = slim.flatten(net)
            # 全连接层 这里还不着急分类 (1,1024)可以作为从图像中提取出来的特征
            net = slim.fully_connected(net, 1024)
            """
            全连接层的参数太多 要加入dropout层 来进行正则化
            keep_prob 概率值 定义了我们对当前神经元选择的概率
            在训练和测试的时候,这个概率要取不同的值 训练的时候要取<1 测试的时候要=1
            """
            slim.dropout(net, keep_prob)
            # 输出为10 对应了10个分类类别
            net = slim.fully_connected(net, 10)
    return net  # 10 dim vec


def loss(logits, label):
    """
    loss使用交叉熵损失函数
    param
    logits: 预测出来的概率分布值
    label: 实际的label

    return 分类的loss
    """
    # 对label进行one-hot编码 定义one-hot长度为10
    one_hot_label = slim.one_hot_encoding(label, 10)
    # 使用交叉熵损失函数 并传入 预测值 和 one-hot label
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    """
    在前面 slim.arg_scope 中定义了weights_regularizer=slim.l2_regularizer(0.0001)
    通过 tf.GraphKeys.REGULARIZATION_LOSSES 可以拿到这些loss正则化的集合
    """
    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    # 将这些正则化loss进行相加 计算出总体的l2_loss
    l2_loss = tf.add_n(reg_set)
    # 将 l2_loss添加到loss中
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()
    # 这里为了后面做日志,这里把l2_loss也传出
    return totalloss, l2_loss


def func_optimal(batchsize, loss_val):
    """
    定义优化器
    """
    global_step = tf.Variable(0, trainable=False)
    # 通过指数衰减的形式来定义学习率
    lr = tf.train.exponential_decay(0.01,
                                    global_step,
                                    decay_steps=50000 // batchsize,
                                    decay_rate=0.95,
                                    staircase=False)

    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

    with tf.control_dependencies(update_ops):
        op = tf.train.AdamOptimizer(lr).minimize(loss_val, global_step)

    """
    global_step 可以得到当前的迭代次数
    op sess.run()执行op可以完成对网络参数的调节
    lr 返回 便于log信息的记录
    """
    return global_step, op, lr


def train():
    batchsize = 64
    floder_log = 'logdirs'
    floder_model = 'model'

    if not os.path.exists(floder_log):
        os.mkdir(floder_log)

    if not os.path.exists(floder_model):
        os.mkdir(floder_model)

    # 存放train和test的日志信息
    tr_summary = set()
    te_summary = set()

    # data
    tr_im, tr_label = readcifar10.read(batchsize, 0, 1)
    te_im, te_label = readcifar10.read(batchsize, 1, 0)

    # net
    """
    tf.placeholder 此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值
    训练数据 [None,32,32,3] None是考虑到batch_size是可以变化的
    """
    input_data = tf.placeholder(tf.float32, shape=[None, 32, 32, 3],
                                name='input_data')

    input_label = tf.placeholder(tf.int64, shape=[None],
                                 name='input_label')
    """
    keep_prob dropout的概率值
    训练过程 keep_prob 也就是dropout的概率值 为0.5 或小于1的值
    """
    keep_prob = tf.placeholder(tf.float32, shape=None,
                               name='keep_prob')

    is_training = tf.placeholder(tf.bool, shape=None,
                                 name='is_training')

    logits = model(
        input_data, keep_prob=keep_prob, is_training=is_training)

    # loss
    """
    损失函数 传入预测的结果和真实的标签
    """
    total_loss, l2_loss = loss(logits, input_label)

    # 记录loss
    tr_summary.add(tf.summary.scalar('train total loss', total_loss))
    tr_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    te_summary.add(tf.summary.scalar('train total loss', total_loss))
    te_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    # accurancy
    """
    tf.argmax 获取最大值的索引
    [n,10] axis=1 计算的是10维这个维度最大值对应的索引值
    """
    pred_max = tf.argmax(logits, 1)
    # 判断最大值索引和 label是否相等
    correct = tf.equal(pred_max, input_label)
    accurancy = tf.reduce_mean(tf.cast(correct, tf.float32))

    tr_summary.add(tf.summary.scalar('train accurancy', accurancy))
    te_summary.add(tf.summary.scalar('test accurancy', accurancy))

    # op
    global_step, op, lr = func_optimal(batchsize, total_loss)

    tr_summary.add(tf.summary.scalar('train lr', lr))
    te_summary.add(tf.summary.scalar('test lr', lr))

    # 图片数据是归一化过的 这里要处理回来
    tr_summary.add(tf.summary.image('train image', input_data * 128 + 128))
    te_summary.add(tf.summary.image('test image', input_data * 128 + 128))
    with tf.Session() as sess:
        # 初始化参数 包括了局部变量和全局变量
        sess.run(tf.group(tf.global_variables_initializer(),
                          tf.local_variables_initializer()))
        # 启动文件队列写入的线程
        tf.train.start_queue_runners(sess=sess,
                                     coord=tf.train.Coordinator())

        saver = tf.train.Saver(tf.global_variables(), max_to_keep=5)
        ckpt = tf.train.latest_checkpoint(floder_model)

        if ckpt:
            saver.restore(sess, ckpt)
        epoch_val = 10

        # 将日志进行合并
        tr_summary_op = tf.summary.merge(list(tr_summary))
        te_summary_op = tf.summary.merge(list(te_summary))

        summary_writer = tf.summary.FileWriter(floder_log, sess.graph)

        # 读取文件队列中的数据,完成对网络的训练
        for i in tqdm(range(50000 * epoch_val)):  # 这里这个数量我还是没有理解
            train_im_batch, train_label_batch = \
                sess.run([tr_im, tr_label])

            feed_dict = {
                input_data: train_im_batch,
                input_label: train_label_batch,
                keep_prob: 0.8,
                is_training: True
            }

            # 注意 这里得到这这些值 都是一个batch_size的
            _, global_step_val, \
                lr_val, \
                total_loss_val, \
                accurancy_val, tr_summary_str = sess.run([op,
                                                          global_step,
                                                          lr,
                                                          total_loss,
                                                          accurancy, tr_summary_op],
                                                         feed_dict=feed_dict)

            summary_writer.add_summary(tr_summary_str, global_step_val)

            # 每隔一百次打印一次
            if i % 100 == 0:
                print("\n[trian]:{},{},{},{}".format(global_step_val,
                                                     lr_val,
                                                     total_loss_val,
                                                     accurancy_val))

            # 隔一段时间进行测试
            if i % (50000 // batchsize) == 0:
                test_loss = 0
                test_acc = 0
                for ii in range(10000//batchsize):
                    test_im_batch, test_label_batch = \
                        sess.run([te_im, te_label])
                    feed_dict = {
                        input_data: test_im_batch,
                        input_label: test_label_batch,
                        keep_prob: 1.0,
                        is_training: False
                    }

                    total_loss_val, global_step_val, \
                    accurancy_val, te_summary_str = sess.run([total_loss,global_step,
                                              accurancy, te_summary_op],
                                             feed_dict=feed_dict)

                    summary_writer.add_summary(te_summary_str, global_step_val)

                    test_loss += total_loss_val
                    test_acc += accurancy_val

                print('[test]:', test_loss * batchsize / 10000,
                      test_acc * batchsize / 10000)
            
            if i % 1000 == 0:
                saver.save(sess, "{}/model.ckpt{}".format(floder_model, str(global_step_val)))

if __name__ == '__main__':
    train()

网络结构优化—resnet模型

上面就是一个完整的网络结构了,如果要对模型进行优化,通常需要修改哪几个地方?

  • 一个就是修改网络结构,这里也称之为主干网络结构,上面是非常简单的串联的网络结构,这里可以使用resnet来这种跳连的结构来进行替代。

  • 还有就是可以修改学习率参数,以及学习率衰减的策略,针对不同的策略,观察对网络性能的影响。

  • 加入更多数据增强的方法,采用不同的优化器来进行模型的训练。

resnet.py

import tensorflow as tf
slim = tf.contrib.slim

def resnet_blockneck(net, numout, down, stride, is_training):
    """
    resnet的基本单元
    params
    net: 输入的特征图
    numout: 输出channel的数量
    down:在resnet中1*1卷积核下采样的倍率
    stride:下采样步长
    is_training: batchnorm的参数
    """
    batch_norm_params = {
    'is_training': is_training,
    'decay': 0.997,
    'epsilon': 1e-5,
    'scale': True,
    'updates_collections': tf.GraphKeys.UPDATE_OPS,
    }
    # 下面定义了卷积层的作用域
    with slim.arg_scope(
                [slim.conv2d],
                weights_regularizer=slim.l2_regularizer(0.0001),
                weights_initializer=slim.variance_scaling_initializer(),
                activation_fn=tf.nn.relu,
                normalizer_fn=slim.batch_norm,
                normalizer_params=batch_norm_params):
        with slim.arg_scope([slim.batch_norm], **batch_norm_params):
            with slim.arg_scope([slim.conv2d, slim.max_pool2d], padding='SAME') as arg_sc:
                """
                对于resnet,首先要将输入的特征图进行备份
                """
                shortcut = net
                """
                并且对备份过之后的特征图需要进行卷积和池化的判断
                主要就是判断channel的数量是否相等 如果不相等就要使用1*1的卷积核进行卷积

                如果是stride=2那么就进行下采样 保证特征图的大小也是一样的
                """
                if numout != net.get_shape().as_list()[-1]:
                    shortcut = slim.conv2d(net, numout, [1, 1])

                if stride != 1:
                    shortcut = slim.max_pool2d(shortcut, [3, 3],
                                               stride=stride)
                """
                先是使用1*1卷积核对channel数量进行下降
                才经过3*3的卷积
                最后通过1*1的卷积还原到想要输出的channel的数量
                """
                net = slim.conv2d(net, numout // down, [1, 1])
                net = slim.conv2d(net, numout // down, [3, 3])
                net = slim.conv2d(net, numout, [1, 1])

                if stride != 1:
                    net = slim.max_pool2d(net, [3, 3], stride=stride)

                # 跳连结构
                net = net + shortcut

                return net


def model_resnet(net, keep_prob=0.5, is_training = True):
    with slim.arg_scope([slim.conv2d, slim.max_pool2d], padding='SAME') as arg_sc:
        """
        输入图像的时候,还是会使用标准卷积
        """
        net = slim.conv2d(net, 64, [3, 3], activation_fn=tf.nn.relu)
        net = slim.conv2d(net, 64, [3, 3], activation_fn=tf.nn.relu)
        """
        在网络结构中 对resnet基本单元进行了堆叠
        每次经过下采样 会对channel进行进行翻倍处理

        这里就是把之前的卷积单元替换成了resnet单元 其他都是一样的
        """
        net = resnet_blockneck(net, 128, 4, 2, is_training)
        net = resnet_blockneck(net, 128, 4, 1, is_training)
        net = resnet_blockneck(net, 256, 4, 2, is_training)
        net = resnet_blockneck(net, 256, 4, 1, is_training)
        net = resnet_blockneck(net, 512, 4, 2, is_training)
        net = resnet_blockneck(net, 512, 4, 1, is_training)

        net = tf.reduce_mean(net, [1, 2])
        net = slim.flatten(net)

        net = slim.fully_connected(net, 1024, activation_fn=tf.nn.relu, scope='fc1')
        net = slim.dropout(net, keep_prob, scope='dropout1')
        net = slim.fully_connected(net, 10, activation_fn=None, scope='fc2')

    return net

train_resnet.py

import tensorflow as tf
import readcifar10
import os
import resnet
from tqdm import tqdm

slim = tf.contrib.slim

def loss(logits, label):

    one_hot_label = slim.one_hot_encoding(label, 10)
    slim.losses.softmax_cross_entropy(logits, one_hot_label)

    reg_set = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    l2_loss = tf.add_n(reg_set)
    slim.losses.add_loss(l2_loss)

    totalloss = slim.losses.get_total_loss()

    return totalloss, l2_loss

def func_optimal(batchsize, loss_val):
    global_step = tf.Variable(0, trainable=False)
    lr = tf.train.exponential_decay(0.01,
                                    global_step,
                                    decay_steps= 50000// batchsize,
                                    decay_rate= 0.95,
                                    staircase=False)
    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

    with tf.control_dependencies(update_ops):
        op = tf.train.AdamOptimizer(lr).minimize(loss_val, global_step)
    return global_step, op, lr

def train():
    batchsize = 64
    floder_log = 'logdirs-resnet'
    floder_model = 'model-resnet'

    if not os.path.exists(floder_log):
        os.mkdir(floder_log)

    if not os.path.exists(floder_model):
        os.mkdir(floder_model)

    tr_summary = set()
    te_summary = set()

    ##data
    tr_im, tr_label = readcifar10.read(batchsize, 0, 1)
    te_im, te_label = readcifar10.read(batchsize, 1, 0)

    ##net
    input_data = tf.placeholder(tf.float32, shape=[None, 32, 32, 3],
                                name='input_data')

    input_label = tf.placeholder(tf.int64, shape=[None],
                                name='input_label')
    keep_prob = tf.placeholder(tf.float32, shape=None,
                                name='keep_prob')

    is_training = tf.placeholder(tf.bool, shape=None,
                               name='is_training')
    logits = resnet.model_resnet(input_data, keep_prob=keep_prob, is_training=is_training)

    ##loss

    total_loss, l2_loss = loss(logits, input_label)

    tr_summary.add(tf.summary.scalar('train total loss', total_loss))
    tr_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    te_summary.add(tf.summary.scalar('train total loss', total_loss))
    te_summary.add(tf.summary.scalar('test l2_loss', l2_loss))

    ##accurancy
    pred_max  = tf.argmax(logits, 1)
    correct = tf.equal(pred_max, input_label)
    accurancy = tf.reduce_mean(tf.cast(correct, tf.float32))
    tr_summary.add(tf.summary.scalar('train accurancy', accurancy))
    te_summary.add(tf.summary.scalar('test accurancy', accurancy))
    ##op
    global_step, op, lr = func_optimal(batchsize, total_loss)
    tr_summary.add(tf.summary.scalar('train lr', lr))
    te_summary.add(tf.summary.scalar('test lr', lr))

    tr_summary.add(tf.summary.image('train image', input_data * 128 + 128))
    te_summary.add(tf.summary.image('test image', input_data * 128 + 128))

    with tf.Session() as sess:
        sess.run(tf.group(tf.global_variables_initializer(),
                          tf.local_variables_initializer()))

        tf.train.start_queue_runners(sess=sess,
                                     coord=tf.train.Coordinator())

        saver = tf.train.Saver(tf.global_variables(), max_to_keep=5)

        ckpt = tf.train.latest_checkpoint(floder_model)

        if ckpt:
            saver.restore(sess, ckpt)

        epoch_val = 100

        tr_summary_op = tf.summary.merge(list(tr_summary))
        te_summary_op = tf.summary.merge(list(te_summary))

        summary_writer = tf.summary.FileWriter(floder_log, sess.graph)

        for i in tqdm(range(50000 * epoch_val)):
            train_im_batch, train_label_batch = \
                sess.run([tr_im, tr_label])
            feed_dict = {
                input_data:train_im_batch,
                input_label:train_label_batch,
                keep_prob:0.8,
                is_training:True
            }

            _, global_step_val, \
            lr_val, \
            total_loss_val, \
            accurancy_val, tr_summary_str = sess.run([op,
                                      global_step,
                                      lr,
                                      total_loss,
                                      accurancy, tr_summary_op],
                     feed_dict=feed_dict)

            summary_writer.add_summary(tr_summary_str, global_step_val)

            if i % 100 == 0:
                print("{},{},{},{}".format(global_step_val,
                                           lr_val, total_loss_val,
                                           accurancy_val))

            if i % (50000 // batchsize) == 0:
                test_loss = 0
                test_acc = 0
                for ii in range(10000//batchsize):
                    test_im_batch, test_label_batch = \
                        sess.run([te_im, te_label])
                    feed_dict = {
                        input_data: test_im_batch,
                        input_label: test_label_batch,
                        keep_prob: 1.0,
                        is_training: False
                    }

                    total_loss_val, global_step_val, \
                    accurancy_val, te_summary_str = sess.run([total_loss,global_step,
                                              accurancy, te_summary_op],
                                             feed_dict=feed_dict)

                    summary_writer.add_summary(te_summary_str, global_step_val)

                    test_loss += total_loss_val
                    test_acc += accurancy_val

                print('test:', test_loss * batchsize / 10000,
                      test_acc* batchsize / 10000)

            if i % 1000 == 0:
                saver.save(sess, "{}/model.ckpt{}".format(floder_model, str(global_step_val)))
    return

if __name__ == '__main__':
    train()

TF官方版本训练Cifar10分类任务

上面所有的步骤,在官方给出的源码中,其实都已经实现了!

要避免重复造轮子,可以对找官方给出的参考代码进行修改。

https://github.com/tensorflow/models/tree/master/research/slim

好好读读人家的README,会有巨大的收获!

小结

TF训练框架搭建:

*   Data
首先是数据的读取和数据的打包。

*   Net
网络的搭建,这里采用slim来搭建网络结构,因为slim是对tf更加高层的封装,可以写更加简洁的代码。

*   Loss
Loss本身就是网络的一部分,这个会采用softmaxLoss来进行Loss定义。
另外还会定义正则化的Loss。

*   Summary
Summary完成了训练过程中日志的记录。

*   Session
Session完成了构造出网络结构后,如果对计算图中的结点进行计算,会通过Session在后端完成整个网络的BP。并通过Feed给网络数据,Fetch获得输出的张量。

有哪些优化模型效果的思路?

训练代码,也就是baseline版本,要如何进行优化呢?上面提供了几个参考思路。

*   更多的数据增强策略,比如:mixup等=
通过数据增强的策略,来丰富样本量,进而提高模型对更多样本的泛化能力。

*   更好的主干网络,比如:SENet等
有VGG,ResNet这样的主干网络,当然,SENet比ResNet的性能更好,可以实验哪个主干网络实验出来的效果更好。

*   更好的标签策略,比如:Soft-label策略
cifar10的标签就是从0到9十个标签,这里可以采用soft-label的策略,比如有些物体既像飞机又像汽车,那么就可以采用软的策略,来对标签进行预处理。

*   更好的loss设计,比如:采用分类+回归smooth-l1 loss等
另外可以采用更好的loss,这里采用交叉熵损失来完成图像分类的任务,另外大家也可以考虑结合回归的loss,来进行一个多个loss的约束。

*   不同的优化器、参数初始化方法等
posted @ 2021-08-09 11:41  RowryCho  阅读(270)  评论(0编辑  收藏  举报