在 Intel® Arc™ GPU 上进行 TensorFlow 的迁移学习。
使用 Intel® 消费者 GPU 和 Windows* Subsystem for Linux 2 进行快速简便的训练和推理。
迁移学习是一种深度学习 (DL) 方法,它允许将预训练模型用于新数据集。新数据必须与原始数据足够相似,以便模型权重中学习到的特征能够很好地应用。如果情况如此,那么与完全训练模型相比,迁移学习可以大大减少使用新数据集进行训练的时间。这是因为使用了“无头模型”,其中大部分层保留预训练的静态权重,而最后一层模型被替换为针对新数据集训练的全新密集层。
Intel® Arc™ A 系列独立 GPU 提供了一种在 PC 上快速运行深度学习工作负载的简便方法,可同时支持 TensorFlow* 和 PyTorch* 模型。在本文中,我们将在 Intel Arc GPU 上运行 Intel® Extension for TensorFlow (ITEX),并在 Windows* 上使用预构建的 ITEX Docker 镜像来简化设置。在本示例中,我们将使用 TensorFlow Hub 的 EfficientNetB0 模型,该模型已在 ImageNet 数据集上预训练。我们的新数据集将是 TensorFlow Datasets 中的“Stanford Dogs”,其中包含的狗图像比 ImageNet 提供的标签更具体地描述了品种。为了调整 Stanford Dogs 的深度学习网络,我们将添加一个将在 Intel Arc A770 GPU 上快速训练的新密集层。然后,我们可以将其与完全训练 EfficientNetB0 进行比较,以了解迁移学习的速度有多快。
设置
之前的博客 Running TensorFlow Stable Diffusion on Intel Arc GPUs 展示了如何在 Windows Subsystem for Linux 2 (WSL2) 中运行的 Ubuntu 容器中设置,以访问 Windows 主机上的 Intel Arc GPU。在本示例中,我们将通过使用 Docker 和 WSL2 以及预构建的 ITEX Docker 镜像来简化 ITEX 插件的安装。本文使用的硬件是配备 13 代 Intel® Core™ i9 PC 和安装的 Intel Arc A770 16 GB 独立 GPU 卡。我们将在 Windows 11 上的 Docker 容器中使用运行中的 ITEX。
先决条件
首先,应在 Windows 11 上安装 WSL2,并运行 Ubuntu-22.04 容器。说明 可以在这里找到。Windows 上的 Docker Desktop 提供了一种简便的方法来下载 Docker 镜像并使用底层的 WSL2 子系统运行 Docker 容器。我们将使用一个已配置好 ITEX 的镜像,但首先需要在 Windows 上安装 Docker。您可以选择使用 Docker Desktop for Windows(安装说明),或者在 WSL2 中的 Linux 中安装 Docker(安装示例)。
在 Docker 容器中运行 Jupyter Notebook*
在本示例中,我们将从 Ubuntu(在 WSL2 中运行)中运行 Docker。首先,启动您的 Ubuntu WLS2 容器。这将为您提供一个 Unix shell 提示符,您可以在其中拉取 ITEX 的预构建 Docker 镜像。
$ docker pull intel/intel-extension-for-tensorflow:xpu
您可以使用以下命令检查 ITEX Docker 镜像是否已下载:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
intel/intel-extension-for-tensorflow xpu 2fc4b6a6fad7 8 days ago 7.42GB
接下来,使用 docker run 命令使用 intel/intel-extension-for-tensorflow:xpu
镜像启动容器。
$ docker run -ti -p 9999:9999 --device /dev/dxg --mount type=bind,src=/usr/lib/wsl,dst=/usr/lib/wsl -e LD_LIBRARY_PATH=/usr/lib/wsl/lib intel/intel-extension-for-tensorflow:xpu
docker run 命令的选项如下:
-p 9999:9999
通过 Docker 容器传递标准端口,以便在 Windows 浏览器中使用 Jupyter Notebook。--device /dev/dxg
将 DirectX 驱动程序传递到 Docker 容器中,以便可以访问 Intel Arc A770 GPU。--mount type=bind,src=/usr/lib/wsl,dst=/usr/lib/wsl -e LD_LIBRARY_PATH=/usr/lib/wsl/lib
是将 WSL2 的共享库目录传递到 Docker 容器的选项。
或者,如果您使用的是 Docker Desktop for Windows,则可以直接在 PowerShell 或 DOS Shell 中运行上述 Docker 命令。
一旦 ITEX Docker 容器运行起来,就需要 pip 安装一些软件包,包括 Jupyter Notebook:
root:/# pip install jupyter 'ipywidgets>=7.6.5' 'matplotlib>=3.3.4' scipy 'tensorflow_hub>=0.12.0' 'tensorflow-datasets>=4.4.0'
然后,启动 Jupyter Notebook:
root:/# jupyter notebook --allow-root --ip 0.0.0.0 --port 9999
Jupyter Notebook 命令的选项允许它以一种方式运行,即您可以使用 Microsoft Edge* 浏览器连接到 Jupyter Notebook 服务器。在您的 Edge 浏览器中,打开 Jupyter 输出中列出的 URL,它应该看起来像:
http://127.0.0.1:8888/?token=...
使用 Intel Arc Alchemist A770 GPU 进行迁移学习
此示例可以在 Jupyter Notebook 中运行,以轻松可视化狗的图像在训练之前、期间和之后的分类方式。我们将把每个代码部分作为一个单元格来运行,以查看效果。随时可以在您自己的笔记本中跟随操作。
要导入的主要软件包是 TensorFlow、TensorFlow Hub(提供对标准预训练模型的访问)和 TensorFlow Datasets(提供各种用途的标准训练和测试集)。还导入了 Matplotlib 和 NumPy,但它们仅在用于可视化图像及其分类的辅助函数中使用。
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_datasets as tfds
import matplotlib.pyplot as plt
import numpy as np
[…]
2023-08-03 16:44:35.519530: I itex/core/devices/gpu/itex_gpu_runtime.cc:129] Selected platform: Intel(R) Level-Zero
2023-08-03 16:44:35.519589: I itex/core/devices/gpu/itex_gpu_runtime.cc:154] number of sub-devices is zero, expose root device.
ITEX 插件在导入 TensorFlow 包期间会自动发现。如果存在 Intel GPU,它将自动设置为默认设备(使用 Intel® oneAPI Level Zero 层),如上面的输出消息所示。
接下来是一些您可以为您的环境设置的用户参数:
batch_size = 32
dataset_directory = '/tmp/efficienetB0_datasets' # Location of downloaded dataset
output_directory = '/tmp/efficienetB0_saved_model' # Location of saved model
在此运行中,我们将使用 TensorFlow Hub 的 EfficientNetB0。我们将只使用无头模型(也称为“特征向量模型”),即带有权重的预训练层,但移除了最后一层。我们还需要指定每个图像的分辨率。在这种情况下,EfficientNetB0 在 224 x 224 像素的图像上进行训练。这将用于调整数据集中的图像大小。
TensorFlow Hub 上关于 EfficientNet 系列模型的文档 可以在这里找到,其中包含指向模型和特征向量模型(又名无头模型)的链接集。
# "efficientnet_b0" model on TensorFlow Hub
model_handle = "https://tfhub.dev/google/efficientnet/b0/classification/1"
feature_vector_handle = "https://tfhub.dev/google/efficientnet/b0/feature-vector/1"
image_size = 224
以下辅助函数 show_predictions()
可视化批次中的前十张图像,以及预测的标签和实际标签。正确标记的图像显示为绿色,而错误标记的图像显示为红色,正确标签显示在括号中。我们将在几个点上使用 show_predictions()
来查看迁移学习训练期间的进度。
# Helper routine to show 10 images from the batch with visual indicators for correct prediction
def show_predictions(image_batch, label_batch, class_names):
predicted_batch = model.predict(image_batch) # Predict using the sample batch
predicted_id = np.argmax(predicted_batch, axis=-1)
predicteds = [class_names[id] for id in predicted_id]
actuals = [class_names[int(id)] for id in label_batch]
print("Correct predictions are shown in green")
print("Incorrect predictions are shown in red with the actual label in parenthesis")
plt.figure(figsize=(10,9)) # Display the results
plt.subplots_adjust(hspace=0.5)
for n in range(0, 10): # Could also print a whole batch using (batch_size - 2)
plt.subplot(6,5,n+1)
plt.imshow(image_batch[n])
correct_prediction = actuals[n] == predicteds[n]
color = "darkgreen" if correct_prediction else "crimson"
title = predicteds[n].title() I am running a few minutes late; my previous meeting is running over.
if correct_prediction I am running a few minutes late; my previous meeting is running over.
else "{}\\n({})".format(predicteds[n], actuals[n])
plt.title(title, fontsize=9, color=color)
plt.axis('off')
_ = plt.suptitle("Model predictions")
plt.show()
现在我们来设置数据集。这里我们使用 TensorFlow Datasets 加载“Stanford Dogs”,这是一个包含比 ImageNet 中更精确的狗品种标签的狗图像集。首次运行此代码时,它会将数据集下载到我们上面指定的目录。这需要 5-10 分钟,您应该会看到一些进度条显示数据集正在下载和处理。在同一 Docker 容器中的后续运行将使用缓存的数据集,并且运行速度非常快。
数据集可用后,将其分为训练(75%)和测试(25%)的区段。然后分别缓存、分批并设置为预取。训练数据集也会被打乱,这会导致密集层训练速度的随机性。最后,保存数据集的类别名称以备后用。
Stanford Dogs 数据集 可以在这里找到。选择此数据集是因为它足够大,需要几个 epoch 才能通过迁移学习训练到 90%,但下载时间合理。TF-Datasets 上还有更多数据集可供使用,包括更小和更大的。
[train_ds, test_ds], info = tfds.load("stanford_dogs", # Load dataset from TensorFlow Datasets
data_dir=dataset_directory,
split=["train[:75%]", "train[:25%]"],
as_supervised=True,
shuffle_files=True,
with_info=True)
def preprocess_image(image, label): # Convert images to float32 and resize to match the model
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.image.resize_with_pad(image, image_size, image_size)
return (image, label)
train_ds = train_ds.map(preprocess_image)
test_ds = test_ds.map(preprocess_image)
# Training data is shuffled for randomness
train_ds = train_ds.cache()
train_ds = train_ds.shuffle(info.splits['train'].num_examples)
train_ds = train_ds.batch(batch_size)
train_ds = train_ds.prefetch(tf.data.AUTOTUNE)
# Test data does not need to be shuffled, and caching is done after batching
test_ds = test_ds.batch(batch_size)
test_ds = test_ds.cache()
test_ds = test_ds.prefetch(tf.data.AUTOTUNE)
class_names = info.features["label"].names # Get class names for the dataset
接下来,我们从 TensorFlow Hub 加载无头 EfficientNetB0 模型。此模型包含我们将保持不变的预训练权重(基于 ImageNet 数据集的训练)。我们还附加了一个将完全训练的密集层。该层的大小与数据集中类的数量相匹配。然后编译模型并打印出形状。请注意,无头模型显示为单个 KerasLayer,即使它由许多具有预训练权重的层组成。最终的完整模型具有 4,203,284 个参数,其中只有 153,720 个将进行训练,其余在无头模型层中是静态的。
# Feature vector layers will *not* be trained (because they already are!)
feature_extractor_layer = hub.KerasLayer(feature_vector_handle,
input_shape=(image_size, image_size, 3),
trainable=False)
model = tf.keras.Sequential([
feature_extractor_layer,
tf.keras.layers.Dense(len(class_names)) # Add a dense layer, which is what will be trained
])
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['acc'])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
keras_layer (KerasLayer) (None, 1280) 4049564
dense (Dense) (None, 120) 153720
=================================================================
Total params: 4,203,284
Trainable params: 153,720
Non-trainable params: 4,049,564
_________________________________________________________________
在开始训练之前,让我们看看在没有训练密集层的情况下,我们的模型在预测正确的狗品种标签方面的表现如何。首先,我们对测试数据集进行一次推理运行。
with tf.device('/xpu:0'): model.evaluate(test_ds, batch_size=batch_size)
94/94 [==============================] - 29s 158ms/step - loss: 2.4268 - acc: 0.0087
然后,我们使用辅助函数显示第一个批次中的前十张图像。
batch = next(iter(test_ds)) # Get a batch of the dataset to use for testing
image_batch, label_batch = batch
show_predictions(image_batch, label_batch, class_names)
1/1 [==============================] - 1s 834ms/step
Correct predictions are shown in green
Incorrect predictions are shown in red with the actual label in parenthesis
正如您所看到的,在没有训练密集层的情况下,所有预测都是不正确的,显示为红色,这是符合预期的。顶部的标签是错误的分类,而底部的标签(在括号中)是正确预测的内容。
现在,让我们进行一些训练。只有密集层中的权重会被调整。我们在这里只运行一个 epoch,以便以后可以看到部分训练对标签预测的影响。请注意,正在使用的设备是 /xpu:0
,即主机 PC 上安装的 Intel Arc A770 GPU。
with tf.device('/xpu:0'): model.fit(train_ds, epochs=1, shuffle=True, verbose=1)
282/282 [==============================] - 28s 85ms/step - loss: 0.8545 - acc: 0.6581
一个 epoch 后,仅通过迁移学习训练密集层即可获得 65.81% 的准确率!如果您从头开始使用 Stanford Dogs 完全训练 EfficientNetB0,第一个 epoch 后的准确率仅约为 1.99%。另请注意,第一个 epoch 大约需要 28 秒。这比后续的训练 epoch 要慢,因为 GPU 必须预热(即,数据被传输到 GPU 内存并为后续运行存储)。
现在,让我们看看第一个批次的预测。首先,我们对测试数据集进行一次推理运行。
with tf.device('/xpu:0'): model.evaluate(test_ds, batch_size=batch_size)
94/94 [==============================] - 4s 41ms/step - loss: 0.3159 - acc: 0.8697
然后,我们使用辅助函数显示预测。
show_predictions(image_batch, label_batch, class_names)
1/1 [==============================] - 0s 65ms/step
Correct predictions are shown in green
Incorrect predictions are shown in red with the actual label in parentheses
正如我们所看到的,批次中的前十张图像中有九张预测正确。这是由于当前对测试数据集的推理达到了 86.97% 的准确率。随着我们提高训练准确率,我们应该会看到更多的图像被正确预测。在您的运行中,您可能会看到更多或更少的错误预测,因为训练数据集是打乱的,这会导致训练准确率存在一定的随机性。
现在,让我们再进行两个训练 epoch。
with tf.device('/xpu:0'): model.fit(train_ds, epochs=2, shuffle=True, verbose=1)
Epoch 1/2
282/282 [==============================] - 12s 41ms/step - loss: 0.2812 - acc: 0.8581
Epoch 2/2
282/282 [==============================] - 12s 40ms/step - loss: 0.1967 - acc: 0.9029
仅经过三个 epoch 的训练后,我们可以看到迁移学习的准确率现在为 90.29%。如果我们在 Intel Arc A770 GPU 上使用 Stanford Dogs 从头开始完全训练 EfficientNetB0,则需要大约 30 个 epoch 才能达到 90% 的准确率。因此,我们通过迁移学习更快地达到了更高的准确率。
这之后的两个 epoch 在 Intel Arc A770 GPU 上仅花费了 12 秒,因为数据已经在第一个 epoch 中复制到 GPU 内存。迁移学习快速的 epoch 周转时间也归因于只需要训练密集层。在 Intel Arc A770 GPU 上对 EfficientNetB0 的所有层进行完全训练,每个 epoch 大约需要 61 秒(第一个 epoch 之后)。因此,迁移学习提供了更快的训练时间(收敛所需的 epoch 更少)和更快的 epoch 时间(需要训练的参数更少)。
Intel Arc A770 GPU 比 Intel Core i9 CPU 提供了显著的速度提升。比较显示,GPU 上的迁移学习训练比 CPU 快 10 倍以上。
现在,让我们看看仅经过三个 epoch 的训练后预测结果如何。
with tf.device('/xpu:0'): model.evaluate(test_ds, batch_size=batch_size)
94/94 [==============================] - 4s 42ms/step - loss: 0.1411 - acc: 0.9410
测试数据集的准确率已经达到了 94.10%!
show_predictions(image_batch, label_batch, class_names)
1/1 [==============================] - 0s 56ms/step
Correct predictions are shown in green
Incorrect predictions are shown in red with the actual label in parentheses
在 94.10% 的准确率下,测试数据集的前十张图像被正确预测。在 Intel Arc A770 GPU 上使用 Stanford Dogs 从头开始完全训练 EfficientNetB0 以达到 90% 的准确率,大约需要 30 个 epoch(31 分钟)。迁移学习仅需几分钟即可取得相同的结果!