【教程】TensorFlow2 最新迁移学习教程和实战

【导读】阅读深度学习的科学论文总是很有趣且富有教育意义。尤其是当它在你正在工作的当前项目的区域中时。但是,这些论文通常包含难以训练的体系结构和解决方案。特别是如果你想尝试,比如说,ImageNet大型视觉识别(ILSCVR)比赛的一些优胜者。我可以记得读过有关VGG16的文章,并想过“这太酷了,但是我的GPU将会死掉”。为了使我们的生活更轻松,TensorFlow 2提供了许多预先训练的模型,您可以快速使用它们。在本文中,我们将探讨如何使用一些著名的卷积神经网络框架来做到这一点。

       此时,人们可能会想:“什么是预训练模型?”。 本质上,预训练模型是一个保存的网络,该网络先前已在某个大型数据集(例如ImageNet数据集)上进行了训练。 它们可以在tensorflow.keras.applications模块中找到。 有两种方法可以使用它们。 你可以把它作为开箱即用的解决方案,也可以把它用于转移学习。 由于大型数据集通常用于某些全局解决方案,因此您可以自定义预训练模型并将其专门用于某些问题。 这样,您可以利用一些最著名的神经网络,而不会浪费太多的时间和训练资源。 此外,您可以通过修改所选图层的行为来微调这些模型。 这将在以后的文章中介绍。

Architectures

01


  在本文中,我们使用三个预先训练的模型来求解分类示例:VGG16,GoogLeNet(Inception)和ResNet。这些框架中的每一个都是ILSCVR竞赛的获胜者。VGG16在2014年与GoogLeNet一起获得了最佳成绩,而ResNet在2015年赢得了冠军。这些模型是TensorFlow 2(即tensorflow.keras.applications)模块的一部分。让我们更深入地了解一下这些框架。

      VGG16是我们考虑的第一个架构。它是K.Simonyan和a.Zisserman在《用于大规模图像识别的非常深卷积网络》一文中提出的一种大型卷积神经网络。该网络在ImageNet数据集中实现了92.7%的top-5测试准确性。但是,它经过了数周的训练。这是该模型的高级概述:

      GoogLeNet也称为Inception。这是因为它利用了两个概念:1×1卷积和初始模块。第一个概念1×1卷积用作降维模块。通过减少维数,计算量也会减少,这意味着可以增加网络的深度和宽度。GoogLeNet并未使用每个卷积层的固定大小,而是使用了Inception模块:

      如您所见,1×1卷积层,3×3卷积层,5×5卷积层和3×3最大池化层一起执行它们的操作,然后将它们的结果再次在输出时堆叠在一起。GoogLeNet总共有22层,看起来像这样:

      剩余网络或ResNet是我们在本文中将要使用的最终体系框架。以前的框架存在的问题是它们很深。它们有很多层,因此很难训练(逐渐消失的梯度)。因此,ResNet通过所谓的“身份快捷方式连接”或剩余块解决了该问题:

      本质上,ResNet遵循VGG的3×3卷积层设计,其中每个卷积层后面都有一个批处理归一化层和ReLU激活功能。但是,不同之处在于,在最终ReLu之前,我们向ResNet注入了输入。变体之一是,输入值通过1×1卷积层。

Dataset

02


      在本文中,我们使用“猫与狗”数据集。该数据集包含23,262张猫和狗的图像。

      您可能会注意到图像没有被标准化,并且它们具有不同的形状。很棒的事情是,它可以作为TensorFlow数据集的一部分使用。因此,请确保已在您的环境中安装了TensorFlow数据集:

pip install tensorflow-dataset

      与库中的其他数据集不同,此数据集未分为训练数据和测试数据,因此我们需要自己执行拆分。您可以在此处找到有关数据集的更多信息。

Implementation

03


      此实现分为几个部分。首先,我们实现负责加载数据并准备数据的类。然后,我们导入经过预训练的模型并构建一个可修改其顶层的类。最后,我们运行训练过程评估过程。当然,在一切之前,我们必须导入一些库并定义一些全局常量:

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow_datasets as tfds

IMG_SIZE = 160
BATCH_SIZE = 32
SHUFFLE_SIZE = 1000
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)

       好吧,让我们深入研究实施!

Data Loader

04


      这个类负责加载数据并准备进行处理。下面是它的样子:

class DataLoader(object):
   def __init__(self, image_size, batch_size):
       self.image_size = image_size
       self.batch_size = batch_size

       # 80% train data, 10% validation data, 10% test data
       split_weights = (8, 1, 1)
       splits = tfds.Split.TRAIN.subsplit(weighted=split_weights)

       (self.train_data_raw, self.validation_data_raw, self.test_data_raw), self.metadata = tfds.load(
           'cats_vs_dogs', split=list(splits),
           with_info=True, as_supervised=True)

       # Get the number of train examples
       self.num_train_examples = self.metadata.splits['train'].num_examples * 80 / 100
       self.get_label_name = self.metadata.features['label'].int2str

       # Pre-process data
       self._prepare_data()
       self._prepare_batches()

   # Resize all images to image_size x image_size
   def _prepare_data(self):
       self.train_data = self.train_data_raw.map(self._resize_sample)
       self.validation_data = self.validation_data_raw.map(self._resize_sample)
       self.test_data = self.test_data_raw.map(self._resize_sample)

   # Resize one image to image_size x image_size
   def _resize_sample(self, image, label):
       image = tf.cast(image, tf.float32)
       image = (image / 127.5) - 1
       image = tf.image.resize(image, (self.image_size, self.image_size))
       return image, label

   def _prepare_batches(self):
       self.train_batches = self.train_data.shuffle(1000).batch(self.batch_size)
       self.validation_batches = self.validation_data.batch(self.batch_size)
       self.test_batches = self.test_data.batch(self.batch_size)

   # Get defined number of  not processed images
   def get_random_raw_images(self, num_of_images):
       random_train_data_raw = self.train_data_raw.shuffle(1000)
       return random_train_raw_data.take(num_of_images)

      这个类有很多事情要做。它有几种方法,其中一种是“public”的:

_prepare_data: 用于调整数据集图像大小和规范化的内部方法。从构造函数使用。

_resize_sample:用于调整单个图像大小的内部方法。

_prepare_batches:用于从图像创建批次的内部方法。创建用于训练和评估过程的train_batches,validation_batches和test_batches。

get_random_raw_images :用于从未经处理的原始数据中获取一定数量的随机图像的方法。

       但是,大多数事情发生在类的构造函数中。让我们仔细看看。

def __init__(self, image_size, batch_size):

   self.image_size = image_size
   self.batch_size = batch_size

   # 80% train data, 10% validation data, 10% test data
   split_weights = (8, 1, 1)
   splits = tfds.Split.TRAIN.subsplit(weighted=split_weights)

   (self.train_data_raw, self.validation_data_raw, self.test_data_raw), self.metadata = tfds.load(
       'cats_vs_dogs', split=list(splits),
       with_info=True, as_supervised=True)

   # Get the number of train examples
   self.num_train_examples = self.metadata.splits['train'].num_examples*80/100
   self.get_label_name = self.metadata.features['label'].int2str

   # Pre-process data
   self._prepare_data()
   self._prepare_batches()

      首先,我们定义通过参数注入的图像和批大小。然后,由于数据集尚未拆分为训练和测试数据,因此我们使用拆分权重拆分数据。这确实是TensorFlow数据集引入的一项很酷的功能,因为我们始终处于TensorFlow生态系统之内,而不必涉及其他库,例如Pandas或SciKit Learn。一旦我们执行了数据分割,我们将计算训练样本的数量,并调用helper函数为训练准备数据。在此之后,我们要做的就是实例化此类的对象并从加载的数据中获得乐趣:

data_loader = DataLoader(IMG_SIZE, BATCH_SIZE, SHUFFLE_SIZE)

plt.figure(figsize=(10, 8))
i = 0
for img, label in data_loader.get_random_raw_images(20):
   plt.subplot(4, 5, i+1)
   plt.imshow(img)
   plt.title("{} - {}".format(data_loader.get_label_name(label), img.shape))
   plt.xticks([])
   plt.yticks([])
   i += 1
plt.tight_layout()
plt.show()

输出:

Base Models & Wrapper

05


      下一件事是装载预先训练过的模型。如前所述,这些模型位于tensorflow.kearas.applications中。加载它们非常直接:

vgg16_base = tf.keras.applications.VGG16(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
googlenet_base = tf.keras.applications.InceptionV3(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
resnet_base = tf.keras.applications.ResNet101V2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')

      这就是我们如何创建三种感兴趣架构的基础模型。注意,对于每个模型,include_top参数都被定义为False。这意味着这些模型用于特征提取。一旦我们有了它们,我们需要修改这些模型的顶层,以便将它们应用于我们的具体问题。我们使用Wrapper类来实现。此类接受注入的预训练模型,并添加一个全局平均轮询层和一个密集层。本质上,最后的全连接层用于我们的二进制分类(汽车或狗)。Wrapper类将所有这些东西放到一个模型中:

class Wrapper(tf.keras.Model):
   def __init__(self, base_model):
       super(Wrapper, self).__init__()

       self.base_model = base_model
       self.average_pooling_layer = tf.keras.layers.GlobalAveragePooling2D()
       self.output_layer = tf.keras.layers.Dense(1)

   def call(self, inputs):
       x = self.base_model(inputs)
       x = self.average_pooling_layer(x)
       output = self.output_layer(x)
       return output

      然后,我们可以创建用于分类猫与狗数据集的真实模型并编译这些模型:

base_learning_rate = 0.0001

vgg16_base.trainable = False
vgg16 = Wrapper(vgg16_base)
vgg16.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
             loss='binary_crossentropy',
             metrics=['accuracy'])

googlenet_base.trainable = False
googlenet = Wrapper(googlenet_base)
googlenet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
             loss='binary_crossentropy',
             metrics=['accuracy'])

resnet_base.trainable = False
resnet = Wrapper(resnet_base)
resnet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
             loss='binary_crossentropy',
             metrics=['accuracy'])

      注意,我们已将基本模型标记为不可训练。这意味着在训练过程中,我们将只训练添加的顶层,而底层的权重不会改变。

Training

06


      在进入整个训练过程之前,让我们回顾一下以下事实:原则上,这些模型的大部分已经过训练。因此,我们可以做的是执行评估过程,看看我们的着陆点:

loss1, accuracy1 = vgg16.evaluate(data_loader.validation_batches, steps = 20)
loss2, accuracy2 = googlenet.evaluate(data_loader.validation_batches, steps = 20)
loss3, accuracy3 = resnet.evaluate(data_loader.validation_batches, steps = 20)

print("--------VGG16---------")
print("Initial loss: {:.2f}".format(loss1))
print("Initial accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

print("--------GoogLeNet---------")
print("Initial loss: {:.2f}".format(loss2))
print("Initial accuracy: {:.2f}".format(accuracy2))
print("---------------------------")

print("--------ResNet---------")
print("Initial loss: {:.2f}".format(loss3))
print("Initial accuracy: {:.2f}".format(accuracy3))
print("---------------------------")

      有趣的是,无需事先训练任何这些模型,我们就可以得到不错的结果(准确性为50%):

      以50%的精度开始并不是一件坏事。因此,让我们运行培训过程,看看我们会变得更好吗。首先我们训练VGG16

history = vgg16.fit(data_loader.train_batches,
                   epochs=10,
                   validation_data=data_loader.validation_batches)

History看起来像这样:

然后我们训练GoogLeNet

history = googlenet.fit(data_loader.train_batches,
                   epochs=10,
                   validation_data=data_loader.validation_batches)

训练过程的History如下:

最后,我们训练ResNet

history = resnet.fit(data_loader.train_batches,
                   epochs=10,
                   validation_data=data_loader.validation_batches)

这是该过程的History历:

      由于我们只培训顶层而不是整个网络,因此对这三个模型的培训仅持续了几个小时,而不是几个星期。

Evaluation

07


      我们发现,一开始,无需任何培训,我们就能获得大约50%的准确性。让我们看看训练后的情况:

loss1, accuracy1 = vgg16.evaluate(data_loader.test_batches, steps = 20)
loss2, accuracy2 = googlenet.evaluate(data_loader.test_batches, steps = 20)
loss3, accuracy3 = resnet.evaluate(data_loader.test_batches, steps = 20)

print("--------VGG16---------")
print("Loss: {:.2f}".format(loss1))
print("Accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

print("--------GoogLeNet---------")
print("Loss: {:.2f}".format(loss2))
print("Accuracy: {:.2f}".format(accuracy2))
print("---------------------------")

print("--------ResNet---------")
print("Loss: {:.2f}".format(loss3))
print("Accuracy: {:.2f}".format(accuracy3))
print("---------------------------")

 输出:

      我们可以看到,所有三个模型都取得了非常好的效果,其中ResNet的准确率高达97%,位居前列。

Conclusion

08


      在本文中,我们演示了如何使用TensorFlow进行迁移学习。我们创建了playground,在其中我们可以尝试对数据进行不同的预训练框架,并在短短几个小时后获得良好的结果。在我们的示例中,我们使用了三种著名的卷积框架,并针对特定问题快速对其进行了修改。

 

原文地址连接:

https://rubikscode.net/2019/11/11/transfer-learning-with-tensorflow-2/


更多关于“TensorFlow”的论文知识资料,请登录专知网站www.zhuanzhi.ai,查看:

https://www.zhuanzhi.ai/topic/2001275668108870/vip



-END-
专 · 知


专知,专业可信的人工智能知识分发,让认知协作更快更好!欢迎注册登录专知www.zhuanzhi.ai,或者点击“阅读原文”使用,获取更多AI知识资料!
欢迎微信扫一扫加入专知人工智能知识星球群,获取最新AI专业干货知识资料和与专家交流咨询
请加专知小助手微信(扫一扫如下二维码添加), 获取专知VIP会员码 ,加入专知人工智能主题群,咨询技术商务合作~
点击“阅读原文”,注册使用专知
展开全文
Top
微信扫码咨询专知VIP会员