政安晨的个人主页:政安晨
欢迎 👍点赞✍评论⭐收藏
收录专栏: TensorFlow与Keras机器学习实战
希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!
本文目标:在计算机视觉中学习调整大小。
人们普遍认为,如果我们限制视觉模型像人类一样感知事物,它们的性能就能得到提高。例如,在这项工作中,Geirhos 等人发现,在 ImageNet-1k 数据集上预先训练的视觉模型偏重于纹理,而人类大多使用形状描述符来形成共同的感知。但这种观点是否总是适用,尤其是在提高视觉模型的性能时?
事实证明,情况并非总是如此。在训练视觉模型时,通常会将图像调整到较低的维度((224 x 224)、(299 x 299)等),以便进行小批量学习,同时也能减少计算量的限制。
在这一步骤中,我们通常使用双线性插值等调整图像大小的方法,调整后的图像对人眼来说不会失去太多感知特征。在《学习为计算机视觉任务调整图像大小》一书中,Talebi 等人指出,如果我们尝试为视觉模型而不是人眼优化图像的感知质量,其性能就能得到进一步提高。他们研究了以下问题:
对于给定的图像分辨率和模型,如何最好地调整给定图像的大小?
正如论文所示,这一想法有助于持续提高常见视觉模型(在 ImageNet-1k 上预先训练)的性能,如 DenseNet-121、ResNet-50、MobileNetV2 和 EfficientNets。在本示例中,我们将实现论文中提出的可学习图像大小调整模块,并使用 DenseNet-121 架构在猫狗数据集上进行演示。
设置
import os
os.environ["KERAS_BACKEND"] = "tensorflow"
import keras
from keras import ops
from keras import layers
import tensorflow as tf
import tensorflow_datasets as tfds
tfds.disable_progress_bar()
import matplotlib.pyplot as plt
import numpy as np
定义超参数
为了方便迷你批次学习,我们需要为给定批次中的图像设定一个固定的形状。这就是需要进行初始大小调整的原因。我们首先将所有图像的大小调整为(300 x 300),然后学习它们在(150 x 150)分辨率下的最佳表现形式。
INP_SIZE = (300, 300)
TARGET_SIZE = (150, 150)
INTERPOLATION = "bilinear"
AUTO = tf.data.AUTOTUNE
BATCH_SIZE = 64
EPOCHS = 5
在本例中,我们将使用双线性插值,但可学习图像调整模块并不依赖于任何特定的插值方法。我们也可以使用其他方法,例如双三次插值。
加载并准备数据集
在本例中,我们将只使用总训练数据集的 40%。
train_ds, validation_ds = tfds.load(
"cats_vs_dogs",
# Reserve 10% for validation
split=["train[:40%]", "train[40%:50%]"],
as_supervised=True,
)
def preprocess_dataset(image, label):
image = ops.image.resize(image, (INP_SIZE[0], INP_SIZE[1]))
label = ops.one_hot(label, num_classes=2)
return (image, label)
train_ds = (
train_ds.shuffle(BATCH_SIZE * 100)
.map(preprocess_dataset, num_parallel_calls=AUTO)
.batch(BATCH_SIZE)
.prefetch(AUTO)
)
validation_ds = (
validation_ds.map(preprocess_dataset, num_parallel_calls=AUTO)
.batch(BATCH_SIZE)
.prefetch(AUTO)
)
定义可学习的调整器实用程序
下图(提供:学习调整图像大小以完成计算机视觉任务)展示了可学习调整大小模块的结构:
def conv_block(x, filters, kernel_size, strides, activation=layers.LeakyReLU(0.2)):
x = layers.Conv2D(filters, kernel_size, strides, padding="same", use_bias=False)(x)
x = layers.BatchNormalization()(x)
if activation:
x = activation(x)
return x
def res_block(x):
inputs = x
x = conv_block(x, 16, 3, 1)
x = conv_block(x, 16, 3, 1, activation=None)
return layers.Add()([inputs, x])
# Note: user can change num_res_blocks to >1 also if needed
def get_learnable_resizer(filters=16, num_res_blocks=1, interpolation=INTERPOLATION):
inputs = layers.Input(shape=[None, None, 3])
# First, perform naive resizing.
naive_resize = layers.Resizing(*TARGET_SIZE, interpolation=interpolation)(inputs)
# First convolution block without batch normalization.
x = layers.Conv2D(filters=filters, kernel_size=7, strides=1, padding="same")(inputs)
x = layers.LeakyReLU(0.2)(x)
# Second convolution block with batch normalization.
x = layers.Conv2D(filters=filters, kernel_size=1, strides=1, padding="same")(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.BatchNormalization()(x)
# Intermediate resizing as a bottleneck.
bottleneck = layers.Resizing(*TARGET_SIZE, interpolation=interpolation)(x)
# Residual passes.
# First res_block will get bottleneck output as input
x = res_block(bottleneck)
# Remaining res_blocks will get previous res_block output as input
for _ in range(num_res_blocks - 1):
x = res_block(x)
# Projection.
x = layers.Conv2D(
filters=filters, kernel_size=3, strides=1, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization()(x)
# Skip connection.
x = layers.Add()([bottleneck, x])
# Final resized image.
x = layers.Conv2D(filters=3, kernel_size=7, strides=1, padding="same")(x)
final_resize = layers.Add()([naive_resize, x])
return keras.Model(inputs, final_resize, name="learnable_resizer")
learnable_resizer = get_learnable_resizer()
可视化可学习大小调整模块的输出结果
在这里,我们可以直观地看到经过调整器随机权重调整后的图像效果。
sample_images, _ = next(iter(train_ds))
plt.figure(figsize=(16, 10))
for i, image in enumerate(sample_images[:6]):
image = image / 255
ax = plt.subplot(3, 4, 2 * i + 1)
plt.title("Input Image")
plt.imshow(image.numpy().squeeze())
plt.axis("off")
ax = plt.subplot(3, 4, 2 * i + 2)
resized_image = learnable_resizer(image[None, ...])
plt.title("Resized Image")
plt.imshow(resized_image.numpy().squeeze())
plt.axis("off")
Corrupt JPEG data: 65 extraneous bytes before marker 0xd9
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
模型制作实用程序
def get_model():
backbone = keras.applications.DenseNet121(
weights=None,
include_top=True,
classes=2,
input_shape=((TARGET_SIZE[0], TARGET_SIZE[1], 3)),
)
backbone.trainable = True
inputs = layers.Input((INP_SIZE[0], INP_SIZE[1], 3))
x = layers.Rescaling(scale=1.0 / 255)(inputs)
x = learnable_resizer(x)
outputs = backbone(x)
return keras.Model(inputs, outputs)
可学习图像调整器模块的结构允许与不同的视觉模型灵活集成。
使用可学习的调整器编译和训练我们的模型
model = get_model()
model.compile(
loss=keras.losses.CategoricalCrossentropy(label_smoothing=0.1),
optimizer="sgd",
metrics=["accuracy"],
)
model.fit(train_ds, validation_data=validation_ds, epochs=EPOCHS)
Epoch 1/5
146/146 ━━━━━━━━━━━━━━━━━━━━ 1790s 12s/step - accuracy: 0.5783 - loss: 0.6877 - val_accuracy: 0.4953 - val_loss: 0.7173
Epoch 2/5
146/146 ━━━━━━━━━━━━━━━━━━━━ 1738s 12s/step - accuracy: 0.6516 - loss: 0.6436 - val_accuracy: 0.6148 - val_loss: 0.6605
Epoch 3/5
146/146 ━━━━━━━━━━━━━━━━━━━━ 1730s 12s/step - accuracy: 0.6881 - loss: 0.6185 - val_accuracy: 0.5529 - val_loss: 0.8655
Epoch 4/5
146/146 ━━━━━━━━━━━━━━━━━━━━ 1725s 12s/step - accuracy: 0.6985 - loss: 0.5980 - val_accuracy: 0.6862 - val_loss: 0.6070
Epoch 5/5
146/146 ━━━━━━━━━━━━━━━━━━━━ 1722s 12s/step - accuracy: 0.7499 - loss: 0.5595 - val_accuracy: 0.6737 - val_loss: 0.6321
<keras.src.callbacks.history.History at 0x7f126c5440a0>
可视化训练有素的可视化器的输出结果
plt.figure(figsize=(16, 10))
for i, image in enumerate(sample_images[:6]):
image = image / 255
ax = plt.subplot(3, 4, 2 * i + 1)
plt.title("Input Image")
plt.imshow(image.numpy().squeeze())
plt.axis("off")
ax = plt.subplot(3, 4, 2 * i + 2)
resized_image = learnable_resizer(image[None, ...])
plt.title("Resized Image")
plt.imshow(resized_image.numpy().squeeze() / 10)
plt.axis("off")
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
从图中可以看出,经过训练后,图像的视觉效果得到了改善。下表显示了使用大小调整模块与使用双线性插值相比的优势:
Model | Number of parameters (Million) | Top-1 accuracy |
---|---|---|
With the learnable resizer | 7.051717 | 67.67% |
Without the learnable resizer | 7.039554 | 60.19% |
欲了解更多详情,请查看此存储库。请注意,上述报告中的模型是在 90% 的《猫和狗》训练集上训练了 10 个历元,与本例不同。此外,请注意由于调整模块的大小而增加的参数数量是可以忽略不计的。为了确保性能的提高不是由于随机性造成的,我们使用相同的初始随机权重对模型进行了训练。
现在,一个值得一问的问题是--与基线相比,精度的提高难道不是因为在模型中增加了更多层次(毕竟调整器是一个微型网络)的结果吗?
为了证明事实并非如此,作者进行了以下实验:
—— 取一个预先训练好的模型,训练出一定的大小,比如(224 x 224)。
—— 现在,首先用它对调整为较低分辨率的图像进行预测。记录性能。
—— 在第二个实验中,在预训练模型的顶部插入调整器模块,并热启动训练。记录性能。
现在,作者认为使用第二种方案更好,因为它有助于模型学习如何根据给定的分辨率更好地调整表征。由于这些结果纯粹是经验性的,如果能再多做一些实验,比如分析跨渠道互动,效果会更好。值得注意的是,挤压和激励(SE)区块、全局上下文(GC)区块等元素也会给现有网络增加一些参数,但众所周知,它们有助于网络以系统的方式处理信息,从而提高整体性能。
注释:
—— 为了在视觉模型中加入形状偏差,Geirhos 等人结合自然图像和风格化图像对视觉模型进行了训练。由于输出结果似乎摒弃了纹理信息,因此研究这个可学习的大小调整模块是否能实现类似效果可能会很有趣。
—— 调整器模块可以处理任意分辨率和长宽比,这对于物体检测和分割等任务非常重要。
—— 还有一个与自适应图像大小调整密切相关的课题,即尝试在训练过程中自适应地调整图像/特征图的大小。EfficientV2 就采用了这一理念。