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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容