作者 | Mohamed-Achref Maiza
来源 | Medium
编辑 | 代码医生团队
本文介绍一些在训练多标签图像分类器时可能会感兴趣的概念和工具。完整的代码可以在GitHub上找到。
https://github.com/ashrefm/multi-label-soft-f1
目录
近年来,机器学习在解决之前无法想象的规模的复杂预测任务方面显示出巨大的成功。开始使用它进行业务转型的最简单方法是,识别简单的二进制分类任务,获取足够的历史数据并训练一个好的分类器以在现实世界中很好地进行概括。总有某种方法可以将预测性业务问题归为是/否问题。如果收集标记的数据,则可以通过监督学习来解决所有这些二元问题。
还可以设计更复杂的监督学习系统来解决非二进制分类任务:
多标签分类在计算机视觉应用中也很常见。在捕捉新电影的海报(动作,戏剧,喜剧等)时,会利用直觉和印象来猜测新电影的内容。可能曾经在地铁站中遇到过这种情况,想从墙上的海报中猜测电影的类型。如果假设在推理过程中使用的是海报的颜色信息,饱和度,色相,图像的纹理,演员的身体或面部表情以及可以识别类型的任何形状或设计,那么也许从海报中提取那些重要图案并以类似方式从中学习的一种数值方法。如何建立可预测电影类型的深度学习模型?看看可以在TensorFlow 2.0中使用的一些技术!
TensorFlow 2.0的有趣之处
当TensorFlow在2015年由Google首次发布时,它迅速成为了世界上最受欢迎的开源机器学习库“一个为开发人员,企业和研究人员提供了一个全面的工具生态系统,希望将最新技术推向世界。谷歌宣布在今年9月底正式发布TensorFlow 2.0。新版本增加了主要功能和改进:
个人非常喜欢在TensorFlow 1.x中构建自定义估算器,因为它们提供了高度的灵活性。因此,很高兴看到Estimator API得到扩展。现在可以通过转换现有的Keras模型来创建估算器。
TensorFlow 2.0现在可用
数据集(来自其海报的电影体裁)
该数据集托管在Kaggle上,并包含来自IMDB网站的电影海报。MovieGenre.csv可以下载一个csv文件。它包含每个电影的以下信息:IMDB ID,IMDB链接,标题,IMDB得分,类型和下载电影海报的链接。在此数据集中,每个电影海报可以至少属于一种流派,并且最多可以分配3个标签。海报总数约为4万张。
https://www.kaggle.com/neha1703/movie-genre-from-its-poster
需要注意的重要一点是,并非所有电影流派都以相同数量表示。其中一些可能很少出现,这对于任何ML算法而言都是艰巨的挑战。可以决定忽略少于1000个观察值的所有标签(简短,西方,音乐,体育,黑色电影,新闻,脱口秀,真人秀,游戏秀)。这意味着由于缺少对这些标签的观察,因此不会训练该模型预测这些标签。
建立快速输入管道
如果熟悉keras.preprocessing,则可能知道图像数据迭代器(例如,ImageDataGenerator和DirectoryIterator)。这些迭代器对于图像目录包含每个类的一个子目录的多类分类非常方便。但是,在多标签分类的情况下,不可能拥有符合该结构的图像目录,因为一个观察可以同时属于多个类别。
这就是tf.data API占上风的地方。
首先,需要编写一些函数来解析图像文件,并生成代表特征的张量和代表标签的张量。
IMG_SIZE = 224 # Specify height and width of image to match the input format of the model
CHANNELS = 3 # Keep RGB color channels to match the input format of the model
def parse_function(filename, label):
"""Function that returns a tuple of normalized image array and labels array.
Args:
filename: string representing path to image
label: 0/1 one-dimensional array of size N_LABELS
"""
# Read an image from a file
image_string = tf.io.read_file(filename)
# Decode it into a dense vector
image_decoded = tf.image.decode_jpeg(image_string, channels=CHANNELS)
# Resize it to fixed shape
image_resized = tf.image.resize(image_decoded, [IMG_SIZE, IMG_SIZE])
# Normalize it from [0, 255] to [0.0, 1.0]
image_normalized = image_resized / 255.0
return image_normalized, label
要在数据集上训练模型,希望数据为:
使用tf.data.Dataset抽象可以轻松添加这些功能。
BATCH_SIZE = 256 # Big enough to measure an F1-score
AUTOTUNE = tf.data.experimental.AUTOTUNE # Adapt preprocessing and prefetching dynamically to reduce GPU and CPU idle time
SHUFFLE_BUFFER_SIZE = 1024 # Shuffle the training data by a chunck of 1024 observations
AUTOTUNE将使预处理和预取工作负载适应模型训练和批量消耗。要预取的元素数量应等于(或可能大于)单个训练步骤消耗的批次数量。AUTOTUNE将提示tf.data运行时在运行时动态调整值。
现在可以创建一个函数来为TensorFlow生成训练和验证数据集。
def create_dataset(filenames, labels, is_training=True):
"""Load and parse dataset.
Args:
filenames: list of image paths
labels: numpy array of shape (BATCH_SIZE, N_LABELS)
is_training: boolean to indicate training mode
"""
# Create a first dataset of file paths and labels
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
# Parse and preprocess observations in parallel
dataset = dataset.map(parse_function, num_parallel_calls=AUTOTUNE)
if is_training == True:
# This is a small dataset, only load it once, and keep it in memory.
dataset = dataset.cache()
# Shuffle the data each buffer size
dataset = dataset.shuffle(buffer_size=SHUFFLE_BUFFER_SIZE)
# Batch the data for multiple steps
dataset = dataset.batch(BATCH_SIZE)
# Fetch batches in the background while the model is training.
dataset = dataset.prefetch(buffer_size=AUTOTUNE)
return dataset
train_ds = create_dataset(X_train, y_train_bin)
val_ds = create_dataset(X_val, y_val_bin)
每一批将是一对数组(一个包含要素,另一个包含标签)。特征数组将具有包含缩放像素的形状(BATCH_SIZE,IMG_SIZE,IMG_SIZE,CHANNELS)。标签数组的形状为(BATCH_SIZE,N_LABELS),其中N_LABELS是目标标签的最大数量,每个值表示影片中是否具有特定流派(0或1个值)。
使用TF.Hub迁移学习
可以在称为迁移学习的过程中使用经过预先训练的模型,而不是从头开始构建和训练新模型。视觉应用的大多数预训练模型都是在ImageNet上训练的,ImageNet是一个大型图像数据库,具有1400万幅图像,分为2万多个类别。迁移学习背后的想法是,由于这些模型是在大型和一般分类任务的上下文中进行训练的,因此可以通过提取和迁移先前学习的有意义的特征,将其用于解决更具体的任务。需要做的就是获取一个预先训练的模型,然后在其之上简单地添加一个新的分类器。新分类头将从头开始进行培训,以便将物镜重新用于多标签分类任务。
Aknowledgement
TensorFlow核心团队在共享预训练的模型和有关如何将其与tf.kerasAPI 一起使用的教程方面做得很好。
中心
迁移学习FrançoisChollet
什么是TensorFlow Hub?
在软件开发中必不可少的一个概念是重新使用通过库提供的代码的想法。可以加快开发速度并提高效率。对于从事计算机视觉或NLP任务的机器学习工程师,知道从头开始训练复杂的神经网络体系结构需要花费多长时间。TensorFlow Hub是一个允许发布和重用预制ML组件的库。使用TF.Hub,重新训练预训练模型的顶层以识别新数据集中的类变得很容易。TensorFlow Hub还可以分发没有顶层分类层的模型。这些可以用来轻松地进行转移学习。
下载无头模型
来自tfhub.dev的任何与Tensorflow 2兼容的图像特征矢量URL都可能对数据集很有趣。唯一的条件是确保准备的数据集中图像特征的形状与要重用的模型的预期输入形状相匹配。
首先,准备特征提取器。将使用MobileNet V2的预训练实例,其深度乘数为1.0,输入大小为224x224。实际上,MobileNet V2是一大类神经网络体系结构,其主要目的是加快设备上的推理速度。它们的大小不同,具体取决于深度乘数(隐藏的卷积层中的要素数量)和输入图像的大小。
import tensorflow_hub as hub
feature_extractor_url = "https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/feature_vector/4"
feature_extractor_layer = hub.KerasLayer(feature_extractor_url,
input_shape=(IMG_SIZE,IMG_SIZE,CHANNELS))
在这里使用的特征提取器接受形状为(224、224、3)的图像,并为每个图像返回1280个长度的向量。
应该冻结要素提取器层中的变量,以便训练仅修改新的分类层。通常,与处理特征提取器的原始数据集相比,使用非常小的数据集时,这是一个好习惯。
feature_extractor_layer.trainable = False
仅当训练数据集很大且与原始ImageNet数据集非常相似时,才建议微调特征提取器。
附上分类头
现在,可以将特征提取器层包装在tf.keras.Sequential模型中,并在顶部添加新层。
model = tf.keras.Sequential([
feature_extractor_layer,
layers.Dense(1024, activation='relu', name='hidden_layer'),
layers.Dense(N_LABELS, activation='sigmoid', name='output')
])
模型摘要如下所示:
MobileNet中的2.2M参数已冻结,但在密集层中有1.3K可训练的参数。需要在最终的神经元中应用S型激活函数,以计算出每种流派的概率得分。这样就可以依靠多个逻辑回归在同一模型中同时进行训练。每个最终神经元将充当一个单一类别的单独的二进制分类器,即使提取的特征对于所有最终神经元而言都是相同的。
使用此模型生成预测时,应该期望每个流派都有一个独立的概率得分,并且所有概率得分不一定总和为1。这与在多类分类中使用softmax层(其中概率得分的总和)不同。输出等于1。
模型训练与评估
在准备好数据集并通过在预先训练的模型之上附加多标签神经网络分类器来构成模型之后,可以继续进行训练和评估,但首先需要定义两个主要功能:
假设要使用Macro F1-score @ threshold 0.5来评估模型的性能。它是每个标签固定概率阈值为0.5时获得的所有F1分数的平均值。如果它们在多标签分类任务中具有相同的重要性,则对所有标签取平均值是非常合理的。在此根据TensorFlow中的大量观察结果提供此指标的实现。
def macro_f1(y, y_hat, thresh=0.5):
"""Compute the macro F1-score on a batch of observations (average F1 across labels)
Args:
y (int32 Tensor): labels array of shape (BATCH_SIZE, N_LABELS)
y_hat (float32 Tensor): probability matrix from forward propagation of shape (BATCH_SIZE, N_LABELS)
thresh: probability value above which we predict positive
Returns:
macro_f1 (scalar Tensor): value of macro F1 for the batch
"""
y_pred = tf.cast(tf.greater(y_hat, thresh), tf.float32)
tp = tf.cast(tf.math.count_nonzero(y_pred * y, axis=0), tf.float32)
fp = tf.cast(tf.math.count_nonzero(y_pred * (1 - y), axis=0), tf.float32)
fn = tf.cast(tf.math.count_nonzero((1 - y_pred) * y, axis=0), tf.float32)
f1 = 2*tp / (2*tp + fn + fp + 1e-16)
macro_f1 = tf.reduce_mean(f1)
return macro_f1
此度量不可微分,因此不能用作损失函数。相反可以将其转换为可以最小化的可区分版本。将由此产生的损失函数称为软F1损失宏!
通常,使用传统的二进制交叉熵来优化模型是可以的,但是宏soft-F1损失带来了非常重要的好处,决定在某些情况下利用这些好处。
使用宏soft F1损失训练模型
指定学习率和训练时期数(整个数据集的循环数)。
LR = 1e-5 # Keep it small when transfer learning
EPOCHS = 30
编译模型以配置训练过程。
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=LR),
loss=macro_soft_f1,
metrics=[macro_f1])
现在,可以传递(特征,标签)的训练数据集以适合模型,并指定一个单独的数据集进行验证。验证集的性能将在每个时期之后进行测量。
history = model.fit(train_ds,
epochs=EPOCHS,
validation_data=create_dataset(X_val, y_val_bin))
30个时期后,可能会在验证集上看到收敛。
显示预测
看看将模型用于验证集中某些已知电影的海报时的预测结果。
注意到该模型可以正确实现“浪漫”。是因为“爱的事”海报上的红色标题吗?
该模型建议使用“泰坦之战”的新标签怎么办?“ Sci-Fi”标签似乎很准确,并且与这部电影有关。请记住在原始数据集中,每个海报最多给出3个标签。也许可以通过使用模型来推荐更有用的标签!
导出Keras模型
训练和评估模型后,可以将其导出为TensorFlow保存的模型,以备将来使用。
from datetime import datetime
t = datetime.now().strftime("%Y%m%d_%H%M%S")
export_path = "./models/soft-f1_{}".format(t)
tf.keras.experimental.export_saved_model(model, export_path)
print("Model with macro soft-f1 was exported in this path: '{}'".format(export_path))
可以稍后通过指定包含.pb文件的导出目录的路径来重新加载tf.keras模型。
reloaded = tf.keras.experimental.load_from_saved_model(export_path,
custom_objects={'KerasLayer':hub.KerasLayer})
注意custom_objects词典中的“ KerasLayer”对象。这是用于构成模型的TF.Hub模块。
总结