数据增强是一种用于提高计算机视觉问题神经网络模型的性能和减少泛化误差的技术。
当使用拟合模型进行预测时,也可以应用图像数据增强技术,以允许模型对测试数据集中每幅图像的多个不同版本进行预测。对增强图像的预测可以取平均值,从而获得更好的预测性能。
在本文章中,您将发现测试时的增强,以改进用于图像分类任务的模型的性能。
完成本文章后,您将知道:
TTA是数据增广技术的应用,通常用于在训练中进行预测。
如何在Keras中从头开始实现测试时增强。
如何使用TTA来提高卷积神经网络模型在标准图像分类任务中的性能。
测试时数据增强(Test-Time Augmentation)
数据增强是一种在模型训练期间通常使用的方法,它使用训练数据集中修改过的样本副本来扩展训练集。
通常使用图像数据来执行数据增强,其中通过执行一些图像操作技术来创建训练数据集中的图像副本,例如缩放、翻转、移动等等。
人工扩展的训练数据集可以产生一个更熟练的模型,因为深度学习模型的性能通常会随着训练数据集的大小继续扩大。此外,训练数据集中图像的修改或增强版本可以帮助模型以不受位置、光照等影响的方式提取和学习特征。
测试时数据增强,简称TTA,是对测试数据集进行数据扩展的一种应用。
具体来说,它涉及到为测试集中的每个图像创建多个扩增副本,让模型对每个图像做出预测,然后返回这些预测的集合。
选择增强是为了让模型有最好的机会对给定的图像进行正确分类,而且模型必须对图像的副本数量进行预测的次数通常很少,比如少于10或20。
通常,执行一个简单的测试时间增加,例如移位、裁剪或图像翻转。
2015年,ILSVRC数据集上取得了当时最先进的成果,题为“Very Deep Convolutional Networks for Large-Scale Image Recognition,”的论文作者使用了水平翻转TTA:
我们还通过水平翻转图像来增强测试集;对原始图像和翻转图像的soft-max类后验图像进行平均,得到图像的最终分数。
类似地,在2015年发表的一篇题为《Rethinking the Inception Architecture for Computer Vision》的论文中,谷歌的作者使用了几剪切技术,他们称之为multi-crop评价。
Keras中TTA
Keras深度学习库并没有提供测试时间增强功能,但是可以很容易地实现。
ImageDataGenerator类可用于测试。例如,将下面的数据生成器配置为水平翻转图像数据增强。
代码语言:javascript复制 # configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)
然后,扩展可以分别应用到测试数据集中的每个样本。
首先,单个图像的维数可以从rows[channels]扩展到samples[cols ] [channels],单个图像的样本数为1。这将图像数组转换为包含一个图像的样本数组。
代码语言:javascript复制 # convert image into dataset
samples = expand_dims(image, 0)
接下来,可以创建一个迭代器,并且可以使用批大小指定要生成的增强图像的数量,比如10个。
代码语言:javascript复制 # prepare iterator
it = datagen.flow(samples, batch_size=10)
然后可以将迭代器传递给模型的predict_generator()函数,以便做出预测。具体来说,将生成一批10张增强图像,模型将对每一张图像进行预测。
代码语言:javascript复制 # make predictions for each augmented image
yhats = model.predict_generator(it, steps=10, verbose=0)
最后,可以进行集成预测。在图像多类分类的情况下,对每幅图像进行预测,每个预测包含属于每类图像的概率。
可以使用软投票进行集成预测,其中每个类的概率在预测中求和,通过计算求和预测的argmax()来进行类预测,并返回最大求和概率的索引或类标签。
代码语言:javascript复制 # sum across predictions
summed = numpy.sum(yhats, axis=0)
# argmax across classes
return argmax(summed)
我们可以将这些元素绑定到一个函数中,该函数将接受配置的数据生成器、拟合模型和单个图像,并使用测试时间扩展返回一个类预测(整数)。
代码语言:javascript复制 # make a prediction using test-time augmentation
def tta_prediction(datagen, model, image, n_examples):
# convert image into dataset
samples = expand_dims(image, 0)
# prepare iterator
it = datagen.flow(samples, batch_size=n_examples)
# make predictions for each augmented image
yhats = model.predict_generator(it, steps=n_examples, verbose=0)
# sum across predictions
summed = numpy.sum(yhats, axis=0)
# argmax across classes
return argmax(summed)
现在我们知道了如何使用测试时扩展在Keras中进行预测,让我们通过一个示例来演示这种方法。
数据集和基线模型
我们可以使用标准的计算机视觉数据集和卷积神经网络来演示测试时间的增强。
在此之前,我们必须选择数据集和基线模型。
我们将使用CIFAR-10数据集,包含60000张32×32像素的彩色照片,对象来自10类,如青蛙、鸟、猫、船等。CIFAR-10是一个易于理解的数据集,广泛用于机器学习领域的计算机视觉算法的基准测试。
我们还将使用卷积神经网络(CNN)模型,该模型能够在这个问题上获得良好(优于随机)的结果,但不是最先进的结果。这将足以演示测试时间增强可以提供的性能提升。
通过调用cifar10.load_data()函数,可以通过Keras API轻松加载CIFAR-10数据集,该函数返回一个元组,该元组包含分割为输入(图像)和输出(类标签)组件的训练和测试数据集。
代码语言:javascript复制 # load dataset
(trainX, trainY), (testX, testY) = load_data()
在建模之前,最好将0-255范围内的像素值归一化到0-1范围内。这确保了输入很小并且接近于零,反过来,这意味着模型的权值将保持很小,从而导致更快更好的学习。
代码语言:javascript复制 # normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255
类标签是整数,在建模之前必须转换为一个热编码。
这可以使用to_categorical() Keras实用程序函数来实现。
代码语言:javascript复制 # one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
现在,我们准备为这个多分类问题定义一个模型。
该模型有一个卷积层,包含32个滤波器映射,使用整流线性激活的3×3内核,“相同的”填充,因此输出与输入的大小和权重初始化相同。接下来是批处理规范化层和最大池化层。
这个模式通过卷积、批处理范数和最大池化层重复,尽管过滤器的数量增加到64个。然后输出被平展,然后由密集层解释,最后提供给输出层作出预测。
代码语言:javascript复制 # define model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(32, 32, 3)))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax'))
采用Adam优化器。
采用分类交叉熵损失函数进行多类分类,并在训练过程中监测分类精度。
代码语言:javascript复制 # compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
该模型进行3个训练批次,使用的是128幅图像没批。
代码语言:javascript复制 # fit model
model.fit(trainX, trainY, epochs=3, batch_size=128)
在测试数据集上评估模型。
代码语言:javascript复制 # evaluate model
_, acc = model.evaluate(testX, testY, verbose=0)
print(acc)
下面列出了完整的示例,它可以在几分钟内轻松地在CPU上运行。
代码语言:javascript复制 # baseline cnn model for the cifar10 problem
from keras.datasets.cifar10 import load_data
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import BatchNormalization
# load dataset
(trainX, trainY), (testX, testY) = load_data()
# normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
# define model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(32, 32, 3)))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax'))
# compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainY, epochs=3, batch_size=128)
# evaluate model
_, acc = model.evaluate(testX, testY, verbose=0)
print(acc)
实例运行表明,该模型能够很好地快速学习问题。
测试集的精确度达到了66%,这是可以接受的,但不是很好。所选择的模型配置已经开始过度拟合,可以从正则化和进一步调优的使用中受益。然而,这为演示测试时增强提供了一个很好的起点。
代码语言:javascript复制 Epoch 1/3
50000/50000 [==============================] - 64s 1ms/step - loss: 1.2135 - acc: 0.5766
Epoch 2/3
50000/50000 [==============================] - 63s 1ms/step - loss: 0.8498 - acc: 0.7035
Epoch 3/3
50000/50000 [==============================] - 63s 1ms/step - loss: 0.6799 - acc: 0.7632
0.6679
神经网络是一种随机算法,相同的模型在相同的数据上多次拟合可能会找到不同的权值集,反过来,每次都有不同的性能。
为了平衡模型性能的估计,我们可以更改示例,多次重新运行模型的拟合和评估,并报告测试数据集上得分分布的平均值和标准偏差。
首先,我们可以定义一个名为load_dataset()的函数,它将加载CIFAR-10数据集并为建模做好准备。
代码语言:javascript复制 # load and return the cifar10 dataset ready for modeling
def load_dataset():
# load dataset
(trainX, trainY), (testX, testY) = load_data()
# normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
return trainX, trainY, testX, testY
接下来,我们可以定义一个名为define_model()的函数,它将为CIFAR-10数据集定义一个模型,准备好适合它并进行计算。
代码语言:javascript复制 # define the cnn model for the cifar10 dataset
def define_model():
# define model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(32, 32, 3)))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax'))
# compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
return model
接下来,定义一个evaluate_model()函数,它将在训练数据集上适合定义的模型,然后在测试数据集上评估它,返回运行的估计分类精度。
代码语言:javascript复制 # fit and evaluate a defined model
def evaluate_model(model, trainX, trainY, testX, testY):
# fit model
model.fit(trainX, trainY, epochs=3, batch_size=128, verbose=0)
# evaluate model
_, acc = model.evaluate(testX, testY, verbose=0)
return acc
接下来,我们可以定义一个新的函数来重复定义、拟合和评估一个新模型,并返回精度分数的分布。
下面的repeated_evaluation()函数实现了这一点,获取数据集并使用默认的10个重复求值。
代码语言:javascript复制 # repeatedly evaluate model, return distribution of scores
def repeated_evaluation(trainX, trainY, testX, testY, repeats=10):
scores = list()
for _ in range(repeats):
# define model
model = define_model()
# fit and evaluate model
accuracy = evaluate_model(model, trainX, trainY, testX, testY)
# store score
scores.append(accuracy)
print('> %.3f' % accuracy)
return scores
最后,我们可以调用load_dataset()函数来准备数据集,然后再调用repeated_evaluation()来获得精度得分的分布,可以通过报告平均值和标准偏差来汇总。
代码语言:javascript复制 # load dataset
trainX, trainY, testX, testY = load_dataset()
# evaluate model
scores = repeated_evaluation(trainX, trainY, testX, testY)
# summarize result
print('Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
将所有这些结合在一起,下面列出了在cifar10数据集上重复评估CNN模型的完整代码示例。
代码语言:javascript复制 # baseline cnn model for the cifar10 problem, repeated evaluation
from numpy import mean
from numpy import std
from keras.datasets.cifar10 import load_data
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import BatchNormalization
# load and return the cifar10 dataset ready for modeling
def load_dataset():
# load dataset
(trainX, trainY), (testX, testY) = load_data()
# normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
return trainX, trainY, testX, testY
# define the cnn model for the cifar10 dataset
def define_model():
# define model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(32, 32, 3)))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax'))
# compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
return model
# fit and evaluate a defined model
def evaluate_model(model, trainX, trainY, testX, testY):
# fit model
model.fit(trainX, trainY, epochs=3, batch_size=128, verbose=0)
# evaluate model
_, acc = model.evaluate(testX, testY, verbose=0)
return acc
# repeatedly evaluate model, return distribution of scores
def repeated_evaluation(trainX, trainY, testX, testY, repeats=10):
scores = list()
for _ in range(repeats):
# define model
model = define_model()
# fit and evaluate model
accuracy = evaluate_model(model, trainX, trainY, testX, testY)
# store score
scores.append(accuracy)
print('> %.3f' % accuracy)
return scores
# load dataset
trainX, trainY, testX, testY = load_dataset()
# evaluate model
scores = repeated_evaluation(trainX, trainY, testX, testY)
# summarize result
print('Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
在CPU硬件上运行这个例子可能需要一些时间,但在GPU硬件上运行速度更快。
对每次重复评价报告模型的准确性,并报告最终的平均模型性能。
在这种情况下,我们可以看到选择的模型配置的平均精度约为68%,这接近于单个模型运行时的估计。
代码语言:javascript复制 > 0.690
> 0.662
> 0.698
> 0.681
> 0.686
> 0.680
> 0.697
> 0.696
> 0.689
> 0.679
Accuracy: 0.686 (0.010)
现在我们已经为一个标准数据集开发了一个基线模型,让我们看看如何使用TTA扩展这个示例。
TTA的例子
我们现在可以更新CIFAR-10上CNN模型的重复评估,以使用测试时间增强。
上面关于如何在Keras中TTA的一节中开发的tta_predict()函数可以直接使用。
代码语言:javascript复制 # make a prediction using test-time augmentation
def tta_prediction(datagen, model, image, n_examples):
# convert image into dataset
samples = expand_dims(image, 0)
# prepare iterator
it = datagen.flow(samples, batch_size=n_examples)
# make predictions for each augmented image
yhats = model.predict_generator(it, steps=n_examples, verbose=0)
# sum across predictions
summed = numpy.sum(yhats, axis=0)
# argmax across classes
return argmax(summed)
我们可以通过定义ImageDataGenerator配置并为测试数据集中的每个图像调用tta_prediction()来开发一个TTA的函数。
重要的是要考虑可能使模型适合CIFAR-10数据集的图像增强类型。对照片进行微小修改的增强可能是有用的。这可能包括缩放、移动和水平翻转等增强功能。
在本例中,我们将只使用水平翻转。
代码语言:javascript复制 # configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)
我们将配置图像生成器来创建7张照片,从这些照片可以对测试集中每个示例进行平均预测。
下面的tta_evaluate_model()函数配置ImageDataGenerator,然后枚举测试数据集,为测试数据集中的每个图像制作一个类标签预测。然后,通过将预测的类标签与测试数据集中的类标签进行比较来计算精度。这需要我们通过使用argmax()逆转load_dataset()中执行的一个热编码。
代码语言:javascript复制 # evaluate a model on a dataset using test-time augmentation
def tta_evaluate_model(model, testX, testY):
# configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)
# define the number of augmented images to generate per test set image
n_examples_per_image = 7
yhats = list()
for i in range(len(testX)):
# make augmented prediction
yhat = tta_prediction(datagen, model, testX[i], n_examples_per_image)
# store for evaluation
yhats.append(yhat)
# calculate accuracy
testY_labels = argmax(testY, axis=1)
acc = accuracy_score(testY_labels, yhats)
return acc
然后,evaluate_model()函数可以被更新为调用tta_evaluate_model()以获得模型精度分数。
代码语言:javascript复制 # fit and evaluate a defined model
def evaluate_model(model, trainX, trainY, testX, testY):
# fit model
model.fit(trainX, trainY, epochs=3, batch_size=128, verbose=0)
# evaluate model using tta
acc = tta_evaluate_model(model, testX, testY)
return acc
将所有这些结合在一起,下面列出了使用TTA的CNN for CIFAR-10重复评估的完整示例
代码语言:javascript复制 # cnn model for the cifar10 problem with test-time augmentation
import numpy
from numpy import argmax
from numpy import mean
from numpy import std
from numpy import expand_dims
from sklearn.metrics import accuracy_score
from keras.datasets.cifar10 import load_data
from keras.utils import to_categorical
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import BatchNormalization
# load and return the cifar10 dataset ready for modeling
def load_dataset():
# load dataset
(trainX, trainY), (testX, testY) = load_data()
# normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
return trainX, trainY, testX, testY
# define the cnn model for the cifar10 dataset
def define_model():
# define model
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(32, 32, 3)))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(10, activation='softmax'))
# compile model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
return model
# make a prediction using test-time augmentation
def tta_prediction(datagen, model, image, n_examples):
# convert image into dataset
samples = expand_dims(image, 0)
# prepare iterator
it = datagen.flow(samples, batch_size=n_examples)
# make predictions for each augmented image
yhats = model.predict_generator(it, steps=n_examples, verbose=0)
# sum across predictions
summed = numpy.sum(yhats, axis=0)
# argmax across classes
return argmax(summed)
# evaluate a model on a dataset using test-time augmentation
def tta_evaluate_model(model, testX, testY):
# configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)
# define the number of augmented images to generate per test set image
n_examples_per_image = 7
yhats = list()
for i in range(len(testX)):
# make augmented prediction
yhat = tta_prediction(datagen, model, testX[i], n_examples_per_image)
# store for evaluation
yhats.append(yhat)
# calculate accuracy
testY_labels = argmax(testY, axis=1)
acc = accuracy_score(testY_labels, yhats)
return acc
# fit and evaluate a defined model
def evaluate_model(model, trainX, trainY, testX, testY):
# fit model
model.fit(trainX, trainY, epochs=3, batch_size=128, verbose=0)
# evaluate model using tta
acc = tta_evaluate_model(model, testX, testY)
return acc
# repeatedly evaluate model, return distribution of scores
def repeated_evaluation(trainX, trainY, testX, testY, repeats=10):
scores = list()
for _ in range(repeats):
# define model
model = define_model()
# fit and evaluate model
accuracy = evaluate_model(model, trainX, trainY, testX, testY)
# store score
scores.append(accuracy)
print('> %.3f' % accuracy)
return scores
# load dataset
trainX, trainY, testX, testY = load_dataset()
# evaluate model
scores = repeated_evaluation(trainX, trainY, testX, testY)
# summarize result
print('Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
考虑到重复评估和用于评估每个模型的较慢的手动测试时间增加,运行这个示例可能需要一些时间。
在这种情况下,我们可以看到性能从没有增加测试时间的测试集上的68.6%提高到增加测试时间的测试集上的69.8%。
代码语言:javascript复制 > 0.719
> 0.716
> 0.709
> 0.694
> 0.690
> 0.694
> 0.680
> 0.676
> 0.702
> 0.704
Accuracy: 0.698 (0.013)
TTA如何调优
选择能给模型性能带来最大提升的扩展配置可能是一项挑战。
不仅有许多可选择的扩展方法和每种方法的配置选项,而且在一组配置选项上适合和评估模型的时间可能会花费很长时间,即使适合快速的GPU。
相反,我建议对模型进行一次调整并将其保存到文件中。例如:
代码语言:javascript复制 # save model
model.save('model.h5')
然后从单独的文件加载模型,并在一个小的验证数据集或测试集的一个小子集上评估不同的测试时间增强方案。
例如:
代码语言:javascript复制 ...
# load model
model = load_model('model.h5')
# evaluate model
datagen = ImageDataGenerator(...)
...
一旦找到了一组能够带来最大提升的扩展选项,您就可以在整个测试集中评估模型,或者像上面那样进行重复的评估实验。
测试时间扩展配置不仅包括ImageDataGenerator的选项,还包括为测试集中每个示例生成平均预测的图像数量。
在上一节中,我使用这种方法来选择测试时间的增加,发现7个示例比3个或5个更好,而且随机缩放和随机移动似乎会降低模型的精度。
记住,如果你也为训练数据集使用图像数据增强,并且这种增强使用一种涉及计算数据集统计数据的像素缩放(例如,你调用datagen.fit()),那么这些相同的统计数据和像素缩放技术也必须在测试时间增强中使用。
总结
在本文章中,您将发现测试时增强可以提高用于图像分类任务的模型的性能。
具体来说,你学会了:
测试时间增广是数据增广技术的应用,通常用于在训练中进行预测。
如何在Keras中从头开始实现测试时间增强。
如何使用测试时间增强来提高卷积神经网络模型在标准图像分类任务中的性能。
原文地址:https://machinelearningmastery.com/how-to-use-test-time-augmentation-to-improve-model-performance-for-image-classification/
deephub翻译组
译者注:以上为keras的实现,pytorch中有现成的包可以调用,虽然效率不是很高,但是对于减少开发难度和学习来说已经够用了,使用pytorch的小伙伴可以看这里:https://github.com/qubvel/ttach