本教程向您展示如何使用TensorFlow 2.0进行迁移学习。我们将介绍:
- 处理自定义数据集
- 使用Keras的应用程序API恢复backbone
- 从磁盘还原backbone
重现教程
本教程中的所有代码都可以在此repository找到。
python download_data.py \ --data_url=https://s3-us-west-2.amazonaws.com/lambdalabs-files/StanfordDogs120.tar.gz \ --data_dir=~/demo/data python transfer_dogs.py
定制数据
在本教程中,我们将对Stanford Dogs数据集中的图像进行分类。我们使用CSV文件重新整理了原始数据。第一列是图像的路径,第二列是类ID。csv文件位于中~/demo/data/StanfordDogs120/train.csv
。(如果您修改--data_dir
参数,这将改变。)
我们首先将csv文件加载到图像路径列表和标签列表中:
def load_csv(file): dirname = os.path.dirname(file) images_path = [] labels = [] with open(file) as f: parsed = csv.reader(f, delimiter=",", quotechar="'") for row in parsed: images_path.append(os.path.join(dirname, row[0])) labels.append(int(row[1])) return images_path, labels TRAIN_FILE = path_home + "/demo/data/StanfordDogs120/train.csv" train_images_path, train_labels = load_csv(TRAIN_FILE)
接下来,我们从这些列表中创建一个TensorFlow数据集:
train_dataset = tf.data.Dataset.from_tensor_slices((train_images_path, train_labels))
这是预处理pipeline:
- 从其路径读取图像。
- 由于图像的大小不是标准大小,因此我们调整了它们的大小,以便可以对它们进行批处理。在调整大小时,请务必保留每个图像的纵横比。否则,物体(在这种情况下为狗)将变形。在我们的实验中,失真导致测试精度降低了10%以上。
- 我们通过随机调整每个图像的大小来增加数据的宽度,该宽度均匀地选自一个分布之间,
[256, 512]
然后从其中随机裁剪出224x224子图像。在测试过程中,我们调整图像的大小,使其宽度为256,然后中心裁剪224x224的子图像。 - 在训练期间,我们执行随机水平翻转。
- 我们从所有图像中减去ImageNet的平均RGB值。
HEIGHT = 224 WIDTH = 224 RESIZE_SIDE_MIN = 256 RESIZE_SIDE_MAX = 512 R_MEAN = 123.68 G_MEAN = 116.78 B_MEAN = 103.94 def preprocess_for_train(x, y): x = tf.compat.v1.read_file(x) x = tf.image.decode_jpeg(x, dct_method="INTEGER_ACCURATE") resize_side = tf.random.uniform( [], minval=RESIZE_SIDE_MIN, maxval=RESIZE_SIDE_MAX + 1, dtype=tf.int32) x = _aspect_preserving_resize(x, resize_side) x = _random_crop([image], HEIGHT, WIDTH)[0] x.set_shape([HEIGHT, WIDTH, 3]) x = tf.cast(x, tf.float32) x = tf.image.random_flip_left_right(image) x = _mean_image_subtraction(x, [R_MEAN, G_MEAN, B_MEAN]) return x, y def preprocess_for_eval(x, y): x = tf.compat.v1.read_file(x) x = tf.image.decode_jpeg(x, dct_method="INTEGER_ACCURATE") x = _aspect_preserving_resize(x, RESIZE_SIDE_MIN) x = _central_crop([x], HEIGHT, WIDTH)[0] x.set_shape([HEIGHT, WIDTH, 3]) x = tf.cast(x, tf.float32) x = _mean_image_subtraction(image, [R_MEAN, G_MEAN, B_MEAN]) return x, y
自定义的调整大小功能在此脚本中实现。注意该shuffle
功能首先被应用。这意味着将改组应用于图像的路径,这比应用于图像本身的速度要快得多。
NUM_TRAIN_SAMPLES = len(train_images_path) train_dataset.shuffle(NUM_TRAIN_SAMPLES).map(preprocess_for_train).map(augmentation).batch(BS_PER_GPU, drop_remainder=True) test_dataset = test_dataset.map(preprocess_eval).batch(BS_PER_GPU, drop_remainder=True)
现在,我们可以从该数据集中采样:
for image, label in train_dataset.take(1): print(image.shape, label.shape) (batch_size, 224, 224, 3) (batch_size,)
这些是训练数据集生成的图像样本:
恢复骨干网(Keras应用程序)
Keras将许多深层倾斜模型与预训练的砝码一起包装到applications
模块中。这些模型可用于转移学习。要创建权重已恢复的模型
backbone = tf.keras.applications.ResNet50(weights = "imagenet", include_top=False) backbone.trainable = False
设置weights = "imagenet"
为恢复使用ImageNet训练的权重。设置include_top=False
为在还原过程中跳过顶层。在训练过程中,切记设置trainable
为False
冻结重量。当新数据集远小于用于训练骨干模型的原始数据集时,冻结骨干模型权重非常有用。通过冻结预训练的权重,模型不太可能过度拟合。
接下来,我们向主干添加附加几层。第一个是GlobalAveragePooling2D
一层,它将主干的输出作为输入。该层计算特征图的每通道均值,该操作在空间上是不变的。然后,应用辍学层以提高泛化性能。最终,具有softmax的完全连接的层将输出分类概率分布。
x = tf.keras.layers.GlobalAveragePooling2D(name='avg_pool')(x) x = tf.keras.layers.Dropout(0.5)(x) x = tf.keras.layers.Dense(NUM_CLASSES, activation='softmax', name='prediction')(x) model = tf.keras.models.Model(backbone.input, x, name='model')
为了训练这个模型,我们只是compile()
和fit()
它使用我们先前创建的数据集。
NUM_EPOCHS = 10 opt = tf.keras.optimizers.SGD() model.compile(optimizer=opt, loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit(train_dataset, epochs=NUM_EPOCHS, validation_data=test_dataset, validation_freq=1, callbacks=[tensorboard_callback, lr_schedule_callback])
学习速率表生成一个阶跃函数,该函数在第6个和第9个周期将初始学习速率(0.1)衰减10倍。经过十次培训,该网络达到了75%的测试准确性。
还原Backbone (from disk)
如果Keras应用程序模块中未包含骨干模型,则还可以通过.h5
文件(遵循HDF5规范)从磁盘还原它。
为了证明这一点,我们使用Keras应用程序模块还原ResNet50,将其作为.h5
文件保存在磁盘上,并将其作为主干还原。
model = tf.keras.applications.ResNet50(weights = "imagenet", include_top=True) model.save('ResNet50.h5') backbone = tf.keras.models.load_model('ResNet50.h5') backbone.trainable = False
要将新层附加到主干,需要指定输入层。在这种情况下,使用的是倒数第三层:
x = backbone.layers[-3].output x = tf.keras.layers.GlobalAveragePooling2D(name='avg_pool')(x) ... model = tf.keras.models.Model(backbone.input, x, name='model')
可以采用与先前的模型相同的方式训练该模型,该模型的主干已作为Keras应用程序进行了恢复。
总结
在本教程中,我们解释了如何在TensorFlow 2中执行转移学习。关键是从预先训练的模型中恢复主干并添加您自己的自定义层。为此,我们演示了两条路径:将backbone还原为Keras应用程序,并从.h5
文件中还原主干。后者更为通用,因为它可用于处理Keras应用程序中未包含的定制模型。
我们还展示了如何在主干上添加新层并实现自定义数据pipeline。
本教程中的所有代码都可以在此repo找到。
给大家介绍一下租用GPU做实验的方法,我们是在智星云租用的GPU,使用体验很好。具体大家可以参考:智星云官网: http://www.ai-galaxy.cn/,淘宝店:https://shop36573300.taobao.com/公众号: 智星AI,
参考文献:
https://lambdalabs.com/blog/tensorflow-2-0-tutorial-02-transfer-learning/
https://github.com/lambdal/TensorFlow2-tutorial/tree/master/02-transfer-learning