TensorFlow2-迁移学习

TensorFlow2迁移学习

简介

迁移学习的思路是指在较大的数据集(如ImageNet)上训练模型,得到泛化能力较强的模型,将其应用到较小数据集上,继续训练微调参数,从而可以在其他任务上使用。

自定义数据集

实际上,对于大型数据集而言,不可能采用之前的方法加载数据集。之前是将整个数据集读入为一个张量,每次从中取出一个batch的子张量,由于深度学习数据集一般都比较大,将这样大的数据集读入内存和显存是不现实的,因此一般分批次io取出数据。

数据集格式

一般的CV数据集都是图片,且图片的存储格式有固定的规则,常见的主要有两种。第一种,分类别存放,每个类别一个目录,目录下放该类别的所有图片,如果有子类别则类似上面递归存储。另一种方法,所有文件在一个目录下,用一个csv文件记录所有图片信息,每一行有文件的id(一般是文件的相对目录),文件对应图片类别,其他annotation信息。显然第二种更加合理,现在,很多开放数据集都是综合二者进行存储。

数据处理

应对文件读取,设计了如下的几个函数(数据集采用第一种方式存储,但是为了加载方便,生成了第二种存储的包含文件路径和标签的csv文件)。可以看到,在TensorFlow2中,数据集的加载是非常灵活的,没有固定的格式,按需读入即可。

数据预处理对于数据集有时候是必要的。resize(图片大小修改)大多数时候是必要的;Data Augmention(数据增广)可以增加小数据集的数据量,缓解过拟合问题,常见的增广手段有旋转、翻转、裁减等;标准化对于像素数值的处理可以使得模型的拟合更加容易。

下面的代码包含自定义数据集加载的代码,主要针对Caltech101的数据集存放格式。

import tensorflow as tf
import pandas as pd
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'  # 不显示警告
random_seed = 2019


def preprocess(x, y):
    """
    数据预处理
    :param x: 图片路径
    :param y: 图片编码
    :return:
    """
    x = tf.io.read_file(x)
    x = tf.image.decode_jpeg(x, channels=3)
    x = tf.image.resize(x, [224, 224])

    x = tf.image.random_flip_left_right(x)

    # x: [0,255]=>[0,1]
    x = tf.cast(x, dtype=tf.float32) / 255.
    y = tf.convert_to_tensor(y)
    y = tf.one_hot(y, depth=102)

    return x, y


def load_data(desc_path, batch_size):
    """
    加载csv文件读入数据集
    :param desc_path: 描述文件的路径
    :param batch_size: 批尺寸大小
    :return:
    """
    df_desc = pd.read_csv(desc_path, encoding="utf8")
    sample_num = len(df_desc)  # 所有训练数据的样本数目
    images = df_desc['file_id']
    labels = df_desc['label'].astype('int')
    idx = tf.random.shuffle(tf.range(sample_num), seed=random_seed)
    # 按照8:2取训练集和验证集
    train_images, train_labels = tf.gather(images, idx[:int(images.shape[0] * 0.8)]), tf.gather(labels, idx[:int(labels.shape[0]*0.8)])
    valid_images, valid_labels = tf.gather(images, idx[int(images.shape[0] * 0.8):]), tf.gather(labels, idx[int(labels.shape[0]*0.8):])
    db_train = tf.data.Dataset.from_tensor_slices((train_images, train_labels))
    db_valid = tf.data.Dataset.from_tensor_slices((valid_images, valid_labels))
    db_train = db_train.shuffle(1000).map(preprocess).batch(batch_size)
    db_valid = db_valid.map(preprocess).batch(batch_size)
    return db_train, db_valid


if __name__ == '__main__':
    load_data("../data/desc.csv", 32)

迁移学习实战

在使用ResNet模型训练小数据集,效果一般很差(具体代码见文末Github),这是因为ResNet表示能力很强,很少的数据难以训练模型的大量参数。这有两种解决方法,第一种是采用较小的模型,第二种则是采用迁移学习方法,前者局限性很大,后者是相对更加常用的手段。

迁移学习的基本原理是CNN层次模型的下层学习到的特征是非常底层的纹理特征,同类型任务中这些特征是类似的,只需要重新学习高层特征即可(对于分类模型即只需要重新学习全连接分类器的特征)。经典的网络结构及预训练参数可以通过keras模块获取。

最终的训练代码如下,由于只有最后一层全连接层参与训练,收敛较快。

import tensorflow as tf
from model import vgg16, resnet50, densenet121
from data import load_data
from visualize import plot_history
print(tf.test.is_gpu_available())

def load_db(batch_size):
    """
    加载数据集
    :param batch_size:
    :return:
    """
    db_train, db_test = load_data("../data/desc.csv", batch_size)
    return db_train, db_test


def train(epochs):
    """
    训练模型
    :param epochs:
    :return:
    """

    vgg = vgg16((None, 224, 224, 3), 102)
    resnet = resnet50((None, 224, 224, 3), 102)
    densenet = densenet121((None, 224, 224, 3), 102)
    models = [vgg, resnet, densenet]
    train_db, valid_db = load_db(32)
    his = []
    for model in models:
        variables = model.trainable_variables
        optimizers = tf.keras.optimizers.Adam(1e-4)
        for epoch in range(epochs):
            # training
            total_num = 0
            total_correct = 0
            training_loss = 0
            for step, (x, y) in enumerate(train_db):
                with tf.GradientTape() as tape:
                    # train
                    out = model(x)
                    loss = tf.losses.categorical_crossentropy(y, out, from_logits=False)
                    loss = tf.reduce_mean(loss)
                    training_loss += loss
                    grads = tape.gradient(loss, variables)
                    optimizers.apply_gradients(zip(grads, variables))
                    # training accuracy
                    y_pred = tf.cast(tf.argmax(out, axis=1), dtype=tf.int32)
                    y_true = tf.cast(tf.argmax(y, axis=1), dtype=tf.int32)
                    correct = tf.reduce_sum(tf.cast(tf.equal(y_pred, y_true), dtype=tf.int32))
                    total_num += x.shape[0]
                    total_correct += int(correct)
                if step % 100 == 0:
                    print("loss is {}".format(loss))
            training_accuracy = total_correct / total_num

            # validation
            total_num = 0
            total_correct = 0
            for (x, y) in valid_db:
                out = model(x)
                y_pred = tf.argmax(out, axis=1)
                y_pred = tf.cast(y_pred, dtype=tf.int32)
                y_true = tf.argmax(y, axis=1)
                y_true = tf.cast(y_true, dtype=tf.int32)
                correct = tf.cast(tf.equal(y_pred, y_true), dtype=tf.int32)
                correct = tf.reduce_sum(correct)
                total_num += x.shape[0]
                total_correct += int(correct)
            validation_accuracy = total_correct / total_num
            print("epoch:{}, training loss:{:.4f}, training accuracy:{:.4f}, validation accuracy:{:.4f}".format(epoch, training_loss, training_accuracy, validation_accuracy))
            his.append({'accuracy': training_accuracy, 'val_accuracy': validation_accuracy})
    return his


if __name__ == '__main__':
    training_history = train(50)
    plot_history(training_history)

具体的训练日志可视化如下图。

在这里插入图片描述

补充说明

本文介绍了加载自定义数据集和迁移学习在TensorFlow2中的实现,更详细的可以查看官方文档。具体的代码同步至我的Github仓库欢迎star,如有疏漏,欢迎指正。

周先森爱吃素 CSDN认证博客专家 PyTorch Python 计算机视觉
大家好,我是CSDN博主周先森爱吃素,目前处于研究生阶段,主攻方向为多目标跟踪算法研究。加入CSDN以来,凭借热爱与坚持,以博文的方式分享所学,截至目前已发文近300篇,内容涉及Python开发、网络爬虫、Linux开发、机器学习、计算机视觉等领域,感谢大家的关注、点赞、评论和收藏,是你们的坚持,促使我在这条路上坚定不移地走下去。未来,我会更加专注于自己喜爱的方向,学习更多的知识,输出更加高质量的文章。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页