Tensorflow estimator 训练和迁移学习(二)

以Hnd数据集做迁移训练

数据准备

Hnd数据集下载:http://www.ee.surrey.ac.uk/CVSSP/demos/chars74k/EnglishHnd.tgz

修改model_fn,可以加载权重数据

先将no_top部分拆开,作为特征提取部分的卷积层,在用数量很少的训练集训练时参数就不允许被修改了

import numpy as np
import tensorflow as tf
import os
from models import BASE_DIR
from models.utils import dataset_input_fn


def cnn_model_no_top(features, trainable):
    """
    :param features: 原始输入
    :param mode: estimator模式
    :param trainable: 该层的变量是否可训练
    :return: 不含最上层全连接层的模型
    """
    input_layer = tf.reshape(features, [-1, 28, 28, 1])
    conv1 = tf.layers.conv2d(inputs=input_layer, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu, trainable=trainable)
    pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
    conv2 = tf.layers.conv2d(inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu, trainable=trainable)
    pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
    pool2_flat = tf.reshape(pool2, shape=[-1, 7 * 7 * 64])
    return pool2_flat

def cnn_model_fn(features, labels, mode, params):
    """
    用于构造estimator的model_fn
    :param features: 输入
    :param labels: 标签
    :param mode: 模式
    :param params: 用于迁移学习和微调训练的参数
        nb_classes
        transfer
        finetune
        checkpoints
        learning_rate
    :return: EstimatorSpec
    """
    logits_name = "predictions"
    # 把labels转换成ont-hot 形式
    labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=params["nb_classes"])
    # 迁移学习不允许修改底层的参数
    model_no_top = cnn_model_no_top(features["x"], trainable=not (params.get("transfer") or params.get("finetune")))
    with tf.name_scope("finetune"):
        # 此层在第二次迁移学习时允许修改参数,将第二次迁移学习称作微调了
        dense = tf.layers.dense(inputs=model_no_top, units=1024, activation=tf.nn.relu, trainable=params.get("finetune"))
    dropout = tf.layers.dropout(inputs=dense, rate=0.4, training=(mode == tf.estimator.ModeKeys.TRAIN))
    # 最上层任何训练都可以修改参数
    logits = tf.layers.dense(inputs=dropout, units=params.get("nb_classes"), name=logits_name)
    predictions = {
        "classes": tf.argmax(input=logits, axis=1),
        "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
    }

    if mode == tf.estimator.ModeKeys.PREDICT:
        return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)

    # 使用softmax交叉熵作为损失函数
    loss = tf.losses.softmax_cross_entropy(onehot_labels=labels, logits=logits)
    if mode == tf.estimator.ModeKeys.TRAIN:
        # 加载已有的存档点参数的方法
        if params.get("checkpoints") and isinstance(params.get("checkpoints"), (tuple, list)):
            for ckpt in params.get("checkpoints"):
                # [0]是存档点路径,[1]为是否加载倒数第二个全连接层参数
                if ckpt[1]:
                    print("restoring base ckpt")
                    variables_to_restore = tf.contrib.slim.get_variables_to_restore(exclude=[logits_name])
                    tf.train.init_from_checkpoint(ckpt[0], {v.name.split(':')[0]: v for v in variables_to_restore})
                    print("restored base ckpt")
                else:
                    print("restoring transferred ckpt")
                    variables_to_restore = tf.contrib.slim.get_variables_to_restore(exclude=[logits_name,
                                                                                             "finetune", ])
                    tf.train.init_from_checkpoint(ckpt[0], {v.name.split(':')[0]: v for v in variables_to_restore})
                    print("restored transferred ckpt")

        global_step = tf.train.get_or_create_global_step()
        optimizer = tf.train.AdamOptimizer(learning_rate=params.get("learning_rate", 0.0001))
        train_op = optimizer.minimize(loss, global_step)
        return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

    eval_metric_ops = {
        'accuracy': tf.metrics.accuracy(labels=tf.argmax(labels, 1),
                                        predictions=predictions['classes'],
                                        name='accuracy')
    }
    return tf.estimator.EstimatorSpec(
        mode=mode,
        loss=loss,
        eval_metric_ops=eval_metric_ops
    )

开始训练

首先准备训练数据和验证数据

if __name__ == '__main__':
    # 训练MNIST的estimator
    mnist_classifier = tf.estimator.Estimator(model_fn=cnn_model_fn, model_dir="./mnist_model", params={
        "nb_classes": 10,
    })
    mnist = tf.contrib.learn.datasets.load_dataset("mnist")
    train_data = mnist.train.images
    train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
    eval_data = mnist.test.images
    eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

    # 训练
    train_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={"x": train_data},
        y=np.asarray(train_labels),
        batch_size=50, num_epochs=50, shuffle=True
    )

    mnist_classifier.train(train_input_fn)
    # 验证
    eval_input_fn = tf.estimator.inputs.numpy_input_fn(
        x={"x": eval_data},
        y=np.asarray(eval_labels),
        batch_size=50, num_epochs=2, shuffle=True
    )

    eval_result = mnist_classifier.evaluate(eval_input_fn)
    print(eval_result)  # MNIST数据集上的准确率

    # 第一次迁移学习,即只重新训练最上层的全连接层
    mnist_classifier = tf.estimator.Estimator(model_fn=cnn_model_fn, model_dir="./mnist_transfer_model", params={
        "transfer": True,
        "nb_classes": 2,
        "checkpoints": [
            (os.path.join(BASE_DIR, "models", "mnist", "mnist_model"), True),  # (dir, load_dense_layer)
        ]
    })

    train_input_fn = dataset_input_fn(
        folders=[
            os.path.join("D:\\训练集\\Hnd\\Img", "Sample041"),  # e
            os.path.join("D:\\训练集\\Hnd\\Img", "Sample045"),  # i
        ],
        labels=[0, 1],
        height=28, width=28, channels=1,
        scope_name="train",
        epoch=100, batch_size=50,
        feature_name="x"
    )

    mnist_classifier.train(train_input_fn)

    eval_input_fn = dataset_input_fn(
        folders=[
            os.path.join("D:\\验证集\\Hnd\\Img", "Sample041"),  # e
            os.path.join("D:\\验证集\\Hnd\\Img", "Sample045"),  # i
        ],
        labels=[0, 1],
        height=28, width=28, channels=1,
        scope_name="eval",
        epoch=1, batch_size=50,
        feature_name="x"
    )
    result = mnist_classifier.evaluate(eval_input_fn)
    print(result)

    # 第二次迁移学习,训练所有全连接层
    mnist_classifier = tf.estimator.Estimator(model_fn=cnn_model_fn, model_dir="./mnist_finetune_model", params={
        "finetune": True,
        "nb_classes": 2,
        "checkpoints": [(os.path.join(BASE_DIR, "models", "mnist", "mnist_model"), True),  # (dir, load_dense_layer)
                        (os.path.join(BASE_DIR, "models", "mnist", "mnist_transfer_model"), False)
                        ]
    })

    train_input_fn = dataset_input_fn(
        folders=[
            os.path.join("D:\\训练集\\Hnd\\Img", "Sample041"),  # e
            os.path.join("D:\\训练集\\Hnd\\Img", "Sample045"),  # i
        ],
        labels=[0, 1],
        height=28, width=28, channels=1,
        scope_name="train",
        epoch=100, batch_size=50,
        feature_name="x"
    )

    mnist_classifier.train(train_input_fn)

    eval_input_fn = dataset_input_fn(
        folders=[
            os.path.join("D:\\验证集\\Hnd\\Img", "Sample041"),  # e
            os.path.join("D:\\验证集\\Hnd\\Img", "Sample045"),  # i
        ],
        labels=[0, 1],
        height=28, width=28, channels=1,
        scope_name="eval",
        epoch=1, batch_size=50,
        feature_name="x"
    )
    result = mnist_classifier.evaluate(eval_input_fn)
    print(result)

需要的dataset_input_fn 如下:

import os
from functools import partial
import tensorflow as tf
from tensorflow.python.ops.image_ops_impl import ResizeMethod


# 函数的功能时将filename对应的图片文件读进来,并缩放到统一的大小,再normalize
def load_image_tf(filename, label, height, width, channels=3):
    image_string = tf.read_file(filename)
    image_decoded = tf.image.decode_image(image_string, channels)
    image_decoded.set_shape([None, None, None])
    image_decoded = tf.image.central_crop(image_decoded, 1)
    image_decoded = tf.image.resize_images(image_decoded, tf.constant([height, width], tf.int32),
                                           method=ResizeMethod.NEAREST_NEIGHBOR)
    image_resized = tf.image.resize_image_with_crop_or_pad(image_decoded, height, width)
    image_resized = tf.reshape(image_resized, [height, width, channels])
    image_resized = tf.divide(image_resized, 255)
    image_resized = tf.subtract(image_resized, 0.5)
    image_resized = tf.multiply(image_resized, 2.)
    return image_resized, label


def read_folder(folders, labels):
    if not isinstance(folders, (list, tuple, set)):
        raise ValueError("folders 应为list 或 tuple")
    all_files = []
    all_labels = []
    for i, f in enumerate(folders):
        files = os.listdir(f)
        for file in files:
            all_files.append(os.path.join(f, file))
            all_labels.append(labels[i])
    dataset = tf.data.Dataset.from_tensor_slices((all_files, all_labels))
    return dataset, len(all_files)


def dataset_input_fn(folders, labels, epoch, batch_size,
                     height, width, channels,
                     scope_name="dataset_input",
                     feature_name=None):
    def fn():
        with tf.name_scope(scope_name):
            dataset, l = read_folder(folders, labels)
            dataset = dataset.map(partial(load_image_tf, height=height, width=width, channels=channels))
            dataset = dataset.shuffle(buffer_size=l).repeat(epoch).batch(batch_size)
            iterator = dataset.make_one_shot_iterator()
            one_element = iterator.get_next()
            if feature_name:
                return {str(feature_name): one_element[0]}, one_element[1]
            return one_element[0], one_element[1]
    return fn

你可能感兴趣的:(Tensorflow estimator 训练和迁移学习(二))