TensorFlow从1到2(九)迁移学习

迁移学习基本概念

迁移学习是这两年比较火的一个话题,主要原因是在当前的机器学习中,样本数据的获取是成本最高的一块。而迁移学习可以有效的把原有的学习经验(对于模型就是模型本身及其训练好的权重值)带入到新的领域,从而不需要过多的样本数据,也能达到大批量数据所达成的效果,进一步节省了学习的计算量和时间。

MobileNet V2是由谷歌在2018年初发布的一个视觉模型,在Keras中已经内置的并使用ImageNet完成了训练,可以直接拿来就用,这个我们在本系列第五篇中已经提过了。MobileNet V2有轻量和高效的特点。通过Inverted residual block结构,可以用较少的运算量得到较高的精度,很适合移动端的机器学习需求。论文在这里
在ImageNet数据集上,MobileNet V2能达到92.5%的识别正确率。本篇中,我们以此模型为基础,介绍一个典型的迁移学习实现方法。并通过调整模型完成优化。

问题描述

MobileNet V2原本是识别图片中主题的名称。在ImageNet中,有大量的经过标注的照片,标注的信息也非常详细。比如我们熟悉的猫猫、狗狗,ImageNet并不简单的标注为cat或者dog,而是更详细的标注为加菲、德牧这样精确到具体品种的信息。
我们这里使用调整之后的MobileNet V2模型,用于对图片内容的猫猫和狗狗分类。不去管原本是哪种具体的品种,只分成cat/dog两个大类。
这个问题的描述实际上隐藏了两个重点:

  • 迁移学习并不是无限制、随意实现的。原有学习数据和数据的场景,同当前的问题,是有共同点、可借鉴可迁移的。
  • 把cat、dog的具体品种忽略,简单的分成两类,并不能认为就是把问题简化了。要知道人工智能并不是人,举例来说,其实机器学习模型自己,并不知道“藏獒”跟“狗”之间有什么关系。在机器学习的模型中,会认为这是两个不同的分类。

从简单开始,先展示一下识别

如同本系列第五篇一样,先使用最简短的代码熟悉一下MobileNet V2。
我们这个例子所使用的数据,是使用tensorflow_datasets模块来自动下载、解压、管理的。所以请先安装这个扩展包:

$ pip3 install tfds-nightly 

程序在第一次运行的时候,会自动下载微软的实验数据集。请尽量使用程序自动下载,因为下载之后会自动解压。数据集的保存路径为:“~/tensorflow_datasets/”,这个是tensorflow_datasets默认的。
数据集中是随机尺寸的图片,程序第一步会将图片统一到224x224的尺寸,这个是预置的MobileNet V2模型所决定的。
我们从样本中取头两个图片显示在屏幕上,并且使用模型预测图片内容。请参考源代码中的注释阅读程序,也可以对照一下第五篇的vgg-19/ResNet50模型预测图片内容的程序,这些模型的使用方法几乎是相同的。

#!/usr/bin/env python3

# 引入所使用到的扩展库
from __future__ import absolute_import, division, print_function
import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_datasets as tfds

keras = tf.keras

# 载入训练数据,载入时按照80%:10%:10%的比例拆分为训练、验证、测试三个数据集
# 本程序只是演示识别图片,区分为三类并没有直接意义,但下面的程序训练模型会使用到
SPLIT_WEIGHTS = (8, 1, 1)
splits = tfds.Split.TRAIN.subsplit(weighted=SPLIT_WEIGHTS)
(raw_train, raw_validation, raw_test), metadata = tfds.load(
    'cats_vs_dogs', split=list(splits),
    with_info=True, as_supervised=True)
# 图片标注
get_label_name = metadata.features['label'].int2str

# 显示头两幅图片
i = 1
plt.figure('Dog & Cat', figsize=(8, 4))
for image, label in raw_train.take(2):
    plt.subplot(1, 2, i)
    plt.imshow(image)
    plt.title(get_label_name(label))
    i += 1
plt.show()

# 所有图片转为224x224的尺寸,适应模型的要求
IMG_SIZE = 224
def format_example(image, label):
    image = tf.cast(image, tf.float32)
    image = (image/127.5) - 1
    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
    return image, label
train = raw_train.map(format_example)

# 载入模型,第一次执行会下载h5文件
# 预测和迁移学习所用的模型并不完全相同,所以会下载两次
test_model = tf.keras.applications.MobileNetV2(weights='imagenet')

# 预测头两张照片并显示结果
for image, _ in train.take(2):
    img = np.expand_dims(image, axis=0)
    predict_class = test_model.predict(img)
    desc = tf.keras.applications.mobilenet_v2.decode_predictions(predict_class, top=3)
    print(desc[0])

我们执行程序看看预测结果:

$ ./cats_dogs_1predict.py
[('n02089078', 'black-and-tan_coonhound', 0.46141574), ('n02105412', 'kelpie', 0.15314996), ('n02106550', 'Rottweiler', 0.092713624)]
[('n02123045', 'tabby', 0.29928064), ('n02123159', 'tiger_cat', 0.08147916), ('n02096177', 'cairn', 0.047330838)]


程序准确的预测出了结果。

迁移学习改造

我们进行猫、狗分类训练,先了解一下样本集。样本集的图片没有什么区别,刚才我们见到了。标注则非常简单,就是1(代表Dog)或者0(代表Cat)。上面的程序中,我们使用get_label_name(label)将数字转换为可读的字符串。这个标注比ImageNet要简单的多。
MobileNet V2模型默认是将图片分类到1000类,每一类都有各自的标注。因为本问题分类只有两类,所以在代码上,我们构建模型的时候增加include_top=False参数,表示我们不需要原有模型中最后的神经网络层(分类到1000类),以便我们增加自己的输出层。当然这样在第一次执行程序的时候,需要重新下载另外一个不包含top层的h5模型数据文件。
随后我们在原有模型的后面增加一个池化层,对数据降维。最后是一个1个节点的输出层,因为我们需要的结果只有两类。
到了迁移学习的重点了,我们的基础模型的各项参数变量,我们并不想改变,因为这样才能保留原来大规模训练的优势,从而保留原来的经验。我们在程序中使用model.trainable = False,设置在训练中,基础模型的各项参数变量不会被新的训练修改数据。
接着我们把数据集分为训练、验证、测试三个数据集,用测试集数据和验证集数据对新的模型进行训练和过程验证。随后对完成训练的模型,使用测试集数据进行评估。
请看源代码:

#!/usr/bin/env python3

# 引入所使用到的扩展库
from __future__ import absolute_import, division, print_function
import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_datasets as tfds

keras = tf.keras

# 载入训练数据,载入时按照80%:10%:10%的比例拆分为训练、验证、测试三个数据集
SPLIT_WEIGHTS = (8, 1, 1)
splits = tfds.Split.TRAIN.subsplit(weighted=SPLIT_WEIGHTS)
(raw_train, raw_validation, raw_test), metadata = tfds.load(
    'cats_vs_dogs', split=list(splits),
    with_info=True, as_supervised=True)

# cat/dog两类图片,复杂度要低于1000类的图片,所以分辨率可以略低,这样也能节省训练时间
# 所有图片重新调整为160x160点阵
IMG_SIZE = 160
def format_example(image, label):
    image = tf.cast(image, tf.float32)
    # 数据规范化为-1到+1的浮点数
    image = (image/127.5) - 1
    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
    return image, label

train = raw_train.map(format_example)
validation = raw_validation.map(format_example)
test = raw_test.map(format_example)

BATCH_SIZE = 32
SHUFFLE_BUFFER_SIZE = 1000
train_batches = train.shuffle(SHUFFLE_BUFFER_SIZE).batch(BATCH_SIZE)
validation_batches = validation.batch(BATCH_SIZE)
test_batches = test.batch(BATCH_SIZE)

# 输入形状就是160x160x3,最后3为RGB3字节色
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False,  # 使用不包含原有1000类输出层的模型
                                               weights='imagenet')
# 设置基础模型:MobileNetV2的各项权重参数不会被训练所更改
base_model.trainable = False
# 输出模型汇总信息
base_model.summary()

# 增加输出池化层
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()

# 输出层
prediction_layer = keras.layers.Dense(1)
# 定义最终完整的模型
model = tf.keras.Sequential([
    base_model,
    global_average_layer,
    prediction_layer
])
# 学习梯度
base_learning_rate = 0.0001
# 编译模型
model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
              loss='binary_crossentropy',
              metrics=['accuracy'])

# 各部分数据数量
num_train, num_val, num_test = (
  metadata.splits['train'].num_examples*weight/10
  for weight in SPLIT_WEIGHTS
)
# 迭代次数
initial_epochs = 10
steps_per_epoch = round(num_train)//BATCH_SIZE
# 验证和评估次数
validation_steps = 20

# 显示一下未经训练的初始模型评估结果
loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps)
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))

# 训练
history = model.fit(train_batches.repeat(),
                    epochs=initial_epochs,
                    steps_per_epoch=steps_per_epoch,
                    validation_data=validation_batches.repeat(),
                    validation_steps=validation_steps)
# 评估
loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps)
print("Train1ed loss: {:.2f}".format(loss0))
print("Train1ed accuracy: {:.2f}".format(accuracy0))

因为数据集比较大,模型也比较复杂,所以程序执行起来时间很长。最终的评估结果类似为:

Train1ed loss: 0.38
Train1ed accuracy: 0.95

如果不对比来看,这个结果还不错啦。不过准确率比原来对更为复杂的ImageNet数据集的评估值还要低很多,显然还有很大的优化空间。

模型优化

在整个模型中,我们自己增加的部分很少,优化的余地并不多。考虑到原有ImageNet图片库的样本,大多并非猫和狗。所以完全保留原有的模型参数可能对MobileNet V2来讲也是资源上的浪费。
因此我们采取的优化策略就是开放一部分模型的网络层,允许在进一步的训练中,调整其权重值,从而期望更好的结果。
在MobileNet V2模型中,一共有155层卷积或者神经网络。这个值可以使用len(model.layers)来查看。我们仍然锁定前面的100层,把后面的网络层打开,允许训练修改其参数。
在随后新模型的训练中,也不需要全部重头开始训练,model.fit方法中,可以指定initial_epoch参数,接着前面的训练继续进行。
请看源代码:

    ...继续前面的代码最后输入...
# 打开允许修改基础模型的权重参数
base_model.trainable = True
# 仍然锁死前100层的权重不允许修改
fine_tune_at = 100
# 冻结fine_tune_at之前的层
for layer in base_model.layers[:fine_tune_at]:
    layer.trainable = False
# 重新编译模型
model.compile(loss='binary_crossentropy',
              optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate/10),
              metrics=['accuracy'])
# 输出模型的概况,注意观察有些层被锁定,有些层可以更改
model.summary()
# 在前次训练的基础上再训练10次
fine_tune_epochs = 10
total_epochs = initial_epochs + fine_tune_epochs
# 训练模型
history_fine = model.fit(train_batches.repeat(),
                         steps_per_epoch=steps_per_epoch,
                         epochs=total_epochs,
                         initial_epoch=initial_epochs,
                         validation_data=validation_batches.repeat(),
                         validation_steps=validation_steps)
# 评估新模型
loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps)
print("final loss: {:.2f}".format(loss0))
print("final accuracy: {:.2f}".format(accuracy0))

执行优化后的程序,其中model.summary()的输出信息:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
mobilenetv2_1.00_160 (Model) (None, 5, 5, 1280)        2257984
_________________________________________________________________
global_average_pooling2d (Gl (None, 1280)              0
_________________________________________________________________
dense (Dense)                (None, 1)                 1281
=================================================================
Total params: 2,259,265
Trainable params: 1,863,873
Non-trainable params: 395,392

Non-trainable params这一部分,就是指我们锁定不参与新数据训练的参数数量。如果你注意看前面一部分base_model.summary()的输出,所有的参数都被锁定不参与训练。
最后的评估结果为:

final loss: 0.23
final accuracy: 0.97

看起来虽然略好,但似乎优化效果并不明显。
其实这主要是观察数值不够直观造成的,我们继续为程序增加绘图功能:

    ...继续前面的代码最后输入...
# 优化前训练迭代数据
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

# 优化后训练迭代数据
acc += history_fine.history['accuracy']
val_acc += history_fine.history['val_accuracy']

loss += history_fine.history['loss']
val_loss += history_fine.history['val_loss']

# 使用训练数据绘图
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.ylim([0.8, 1])
plt.plot([initial_epochs-1,initial_epochs-1],
          plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.ylim([0, 1.0])
plt.plot([initial_epochs-1,initial_epochs-1],
         plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

我们为了讲解方便,把最终的程序分成了几个部分,实际上为了节省时间,是可以合并在一起执行的,这样不需要重复训练很多次。
从绘图结果看,优化的效果还是很明显的:

两张图,中间都有一条绿线分隔开优化前和优化后的训练数据。在前半段,正确率和损失值的优化过程是明显比较慢的,而且训练集和验证集两条线的分离也说明有过拟合的现象。在后半段,有一个明显的阶梯表现出来模型性能明显改善,训练集和验证集也更接近。说明各项指标都有效改善了。
(待续...)

原文地址:https://www.cnblogs.com/andrewwang/p/10782784.html

时间: 2024-10-03 04:32:56

TensorFlow从1到2(九)迁移学习的相关文章

『TensorFlow』迁移学习_他山之石,可以攻玉

目的: 使用google已经训练好的模型,将最后的全连接层修改为我们自己的全连接层,将原有的1000分类分类器修改为我们自己的5分类分类器,利用原有模型的特征提取能力实现我们自己数据对应模型的快速训练.实际中对于一个陌生的数据集,原有模型经过不高的迭代次数即可获得很好的准确率. 实战: 实机文件夹如下,两个压缩文件可以忽略: 花朵图片数据下载: 1 curl -O http://download.tensorflow.org/example_images/flower_photos.tgz 已经

tensorflow实现迁移学习

此例程出自<TensorFlow实战Google深度学习框架>6.5.2小节 卷积神经网络迁移学习. 数据集来自http://download.tensorflow.org/example_images/flower_photos.tgz ,及谷歌提供的Inception-v3模型https://storage.googleapis.com/download.tensorflow.org/models/inception_dec_2015.zip . 自行下载和解压. 解压后的文件夹包含5个子

TensorFlow keras 迁移学习

数据的读取 import tensorflow as tf from tensorflow.python import keras from tensorflow.python.keras.preprocessing.image import ImageDataGenerator class TransferModel(object): def __init__(self): #标准化和数据增强 self.train_generator = ImageDataGenerator(rescale=

1、VGG16 2、VGG19 3、ResNet50 4、Inception V3 5、Xception介绍——迁移学习

ResNet, AlexNet, VGG, Inception: 理解各种各样的CNN架构 本文翻译自ResNet, AlexNet, VGG, Inception: Understanding various architectures of Convolutional Networks,原作者保留版权 卷积神经网络在视觉识别任务上的表现令人称奇.好的CNN网络是带有上百万参数和许多隐含层的"庞然怪物".事实上,一个不好的经验规则是:网络越深,效果越好.AlexNet,VGG,Inc

【火炉炼AI】深度学习006-移花接木-用Keras迁移学习提升性能

[火炉炼AI]深度学习006-移花接木-用Keras迁移学习提升性能 (本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0) 上一篇文章我们用自己定义的模型来解决了二分类问题,在20个回合的训练之后得到了大约74%的准确率,一方面是我们的epoch太小的原因,另外一方面也是由于模型太简单,结构简单,故而不能做太复杂的事情,那么怎么提升

【火炉炼AI】深度学习009-用Keras迁移学习提升性能(多分类问题)

[火炉炼AI]深度学习009-用Keras迁移学习提升性能(多分类问题) (本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0) 本文是仿照前面的文章[火炉炼AI]深度学习006-移花接木-用Keras迁移学习提升性能,原文是针对二分类问题,使用迁移学习的方式来提升准确率,此处我用迁移学习的方式来提升多分类问题的准确率. 同时,在我前

7种常见的迁移学习的代码+论文+简介

最近调研了不少迁移学习的工作,分享给大家.因为我感觉迁移学习在NLP领域的很多任务中有很大的利用价值,毕竟高质量的标注数据是很少的,而人工标注费时费力,而且质量不一定好. 1. Domain Adaptation 领域自适应 1.1 动机 领域自适应是一种迁移学习,它将不同源领域的数据或特征映射到同一个特征空间,以便于利用其源领域数据或特征来增强目标领域的训练,进而达到更好的训练效果.领域自适应学习不要求训练数据和测试数据一定具有相同的分布. 1.2 目的 利用数据或特征更多的源领域来提升数据或

【深度学习】迁移学习Transfer Learning

我们通常是拿到一个任务,譬如图像分类.识别等,搜集好数据后就开始直接用模型进行训练,但是现实情况中,由于设备的局限性.时间的紧迫性等导致我们无法从头开始训练,迭代一两百万次来收敛模型,所以这个时候迁移学习就派上用场了. 什么是迁移学习? 迁移学习通俗来讲,就是运用已有的知识来学习新的知识,核心是找到已有知识和新知识之间的相似性,用成语来说就是举一反三.由于直接对目标域从头开始学习成本太高,我们故而转向运用已有的相关知识来辅助尽快地学习新知识.比如,已经会下中国象棋,就可以类比着来学习国际象棋:已

迁移学习、fine-tune和局部参数恢复

参考:迁移学习——Fine-tune 一.迁移学习 就是把已训练好的模型参数迁移到新的模型来帮助新模型训练. 模型的训练与预测: 深度学习的模型可以划分为 训练 和 预测 两个阶段. 训练 分为两种策略:一种是白手起家从头搭建模型进行训练,一种是通过预训练模型进行训练. 预测 相对简单,直接用已经训练好的模型对数据集进行预测即可. 优点: 1)站在巨人的肩膀上:前人花很大精力训练出来的模型在大概率上会比你自己从零开始搭的模型要强悍,没有必要重复造轮子. 2)训练成本可以很低:如果采用导出特征向量