Machine-Learning-Mastery-计算机视觉教程-七-

56 阅读1小时+

Machine Learning Mastery 计算机视觉教程(七)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何使用测试时间扩充做出更好的预测

原文:machinelearningmastery.com/how-to-use-…

最后更新于 2020 年 4 月 3 日

数据增广是一种在为计算机视觉问题训练神经网络模型时,经常用来提高表现和减少泛化误差的技术。

当用拟合模型进行预测时,也可以应用图像数据扩充技术,以便允许模型对测试数据集中每个图像的多个不同版本进行预测。可以对增强图像上的预测进行平均,这可以导致更好的预测表现。

在本教程中,您将发现用于提高图像分类任务模型表现的测试时间扩展。

完成本教程后,您将知道:

  • 测试时间扩充是数据扩充技术的应用,通常在进行预测的训练中使用。
  • 如何在 Keras 中从零开始实现测试时间扩充?
  • 如何在标准图像分类任务中使用测试时间扩充来提高卷积神经网络模型的表现?

用我的新书计算机视觉深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

How to Use Test-Time Augmentation to Improve Model Performance for Image Classification

如何使用测试时间扩充来提高图像分类的模型表现 图片由达维宁提供,保留部分权利。

教程概述

本教程分为五个部分;它们是:

  1. 测试时间扩充
  2. Keras 试验时间扩充
  3. 数据集和基线模型
  4. 测试时间扩充示例
  5. 如何调整测试时间扩充配置

测试时间扩充

数据扩充是一种通常在模型训练期间使用的方法,它使用来自训练数据集的样本的修改副本来扩展训练集。

数据扩充通常使用图像数据来执行,其中训练数据集中的图像副本是通过执行一些图像操作技术来创建的,例如缩放、翻转、移位等等。

人工扩展的训练数据集可以产生更熟练的模型,因为深度学习模型的表现通常会随着训练数据集的大小而不断扩展。此外,训练数据集中图像的修改或增强版本有助于模型以对其位置、光照等不变的方式提取和学习特征。

测试时间扩充,简称 TTA,是数据扩充在测试数据集中的应用。

具体来说,它包括为测试集中的每个图像创建多个增强副本,让模型为每个副本做出预测,然后返回这些预测的集合。

选择增强是为了给模型提供对给定图像进行正确分类的最佳机会,模型必须预测的图像副本数量通常很少,例如少于 10 或 20 个。

通常,执行一个简单的测试时间扩充,如移位、裁剪或图像翻转。

在他们 2015 年发表的题为“用于大规模图像识别的非常深卷积网络”的论文中,作者使用了水平翻转测试时间扩充:

我们还通过水平翻转图像来扩充测试集;原始图像和翻转图像的软最大类后验值被平均以获得图像的最终分数。

类似地,在他们 2015 年发表的名为“重新思考计算机视觉的初始架构”的关于初始架构的论文中,谷歌的作者使用了裁剪测试时间扩充,他们称之为多裁剪评估。

Keras 试验时间扩充

Keras 深度学习库中并没有提供测试时增强,但是可以轻松实现。

ImageDataGenerator 类可用于配置测试时间扩充的选择。例如,下面的数据生成器被配置用于水平翻转图像数据扩充。

# configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)

然后,可以对测试数据集中的每个样本分别应用增强。

首先,对于单个图像,单个图像的维度可以从*【行】【列】【通道】扩展到【样本】【行】【列】【通道】*,其中样本数为 1。这将图像的数组转换为包含一个图像的样本数组。

# convert image into dataset
samples = expand_dims(image, 0)

接下来,可以为样本创建一个迭代器,批量大小可以用来指定要生成的增强图像的数量,例如 10 个。

# prepare iterator
it = datagen.flow(samples, batch_size=10)

迭代器然后可以传递给模型的 predict_generator() 函数,以便进行预测。具体来说,将生成一批 10 幅增强图像,模型将对每幅图像进行预测。

# make predictions for each augmented image
yhats = model.predict_generator(it, steps=10, verbose=0)

最后,可以进行集合预测。在图像多类别分类的情况下,对每个图像进行预测,并且每个预测包含图像属于每个类别的概率。

可以使用软投票进行集成预测,其中在预测中对每个类别的概率求和,并且通过计算求和预测的 argmax() 来进行类别预测,返回最大求和概率的指数或类别号。

# sum across predictions
summed = numpy.sum(yhats, axis=0)
# argmax across classes
return argmax(summed)

我们可以将这些元素绑定到一个函数中,该函数将采用一个已配置的数据生成器、拟合模型和单个图像,并将使用测试时间扩充返回一个类预测(整数)。

# 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 数据集,由来自 10 个类别的 60,000 张 32×32 像素彩色照片组成,如青蛙、鸟类、猫、船只等。CIFAR-10 是一个众所周知的数据集,广泛用于机器学习领域的计算机视觉算法基准测试。问题是“解决了”通过深度学习卷积神经网络,在测试数据集上的分类准确率达到 96%或 97%以上,在该问题上取得了最佳表现。

我们还将使用卷积神经网络,或 CNN,模型能够在这个问题上获得好的(比随机的更好的)结果,但不是最先进的结果。这将足以证明测试时间扩充所能提供的表现提升。

通过调用 cifar10.load_data() 函数,可以通过 Keras API 轻松加载 CIFAR-10 数据集,该函数返回一个元组,其中训练和测试数据集被拆分为输入(图像)和输出(类标签)组件。

# load dataset
(trainX, trainY), (testX, testY) = load_data()

在建模之前,最好将像素值从 0-255 范围归一化到 0-1 范围。这确保了输入很小并且接近于零,并且反过来意味着模型的权重将保持很小,从而导致更快和更好的学习。

# normalize pixel values
trainX = trainX.astype('float32') / 255
testX = testX.astype('float32') / 255

类标签是整数,在建模之前必须转换成一个热编码。

这可以使用*到 _ classic()*Keras 效用函数来实现。

# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)

我们现在准备为这个多类分类问题定义一个模型。

该模型有一个卷积层,包含 32 个具有 3×3 内核的滤波器映射,使用整流器线性激活、“相同的填充,因此输出与输入大小相同,并且 He 权重初始化。接下来是批处理规范化层和最大池层。

尽管滤波器的数量扩充到了 64 个,但这种模式在卷积层、批量范数层和最大池层重复使用。然后,输出在被密集层解释之前被展平,并最终被提供给输出层以进行预测。

# 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'])

该模型适用于三个训练时期,并且使用了 128 幅图像的大批量。

# fit model
model.fit(trainX, trainY, epochs=3, batch_size=128)

一旦拟合,就在测试数据集上评估模型。

# evaluate model
_, acc = model.evaluate(testX, testY, verbose=0)
print(acc)

下面列出了完整的示例,几分钟后就可以在 CPU 上轻松运行。

# 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%左右,这是可以的,但并不可怕。所选择的模型配置已经开始过度调整,并且可以受益于正则化的使用和进一步的调整。然而,这为演示测试时间扩充提供了一个很好的起点。

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 数据集并为建模做准备。

# 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 数据集定义一个模型,准备进行拟合,然后进行评估。

# 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() 函数,该函数将在训练数据集上拟合定义的模型,然后在测试数据集上对其进行评估,返回运行的估计分类准确率。

# 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

接下来,我们可以定义一个具有新行为的函数来重复定义、拟合和评估一个新模型,并返回准确度分数的分布。

下面的*repeat _ evaluation()*函数实现了这一点,取数据集,默认为 10 次重复评估。

# 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() 函数来准备数据集,然后*repeat _ evaluation()*得到一个准确率分数的分布,可以通过报告均值和标准差来总结。

# 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)))

将所有这些联系在一起,下面列出了在 MNIST 数据集上重复评估 CNN 模型的完整代码示例。

# 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)))

在现代中央处理器硬件上运行该示例可能需要一段时间,在图形处理器硬件上要快得多。

对于每次重复评估,报告模型的准确性,并报告最终的平均模型表现。

在这种情况下,我们可以看到所选模型配置的平均准确率约为 68%,接近单次模型运行的估计值。

> 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)

现在我们已经为标准数据集开发了一个基线模型,让我们看看如何更新这个例子来使用测试时增强。

测试时间扩充示例

我们现在可以在 CIFAR-10 上更新我们对 CNN 模型的重复评估,以使用测试时间扩充。

可以直接使用上面关于如何在 Keras 中实现测试时间扩充的部分中开发的 tta_prediction() 函数。

# 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()

重要的是,要考虑可能有利于模型适合 CIFAR-10 数据集的图像扩充类型。对照片进行细微修改的增强可能是有用的。这可能包括放大,如缩放、移动和水平翻转。

在这个例子中,我们将只使用水平翻转。

# configure image data augmentation
datagen = ImageDataGenerator(horizontal_flip=True)

我们将配置图像生成器来创建七张照片,根据这些照片,将对测试集中的每个示例进行平均预测。

下面的 tta_evaluate_model() 函数配置 ImageDataGenerator 然后枚举测试数据集,对测试数据集中的每个图像进行类标签预测。然后通过将预测的类别标签与测试数据集中的类别标签进行比较来计算准确度。这要求我们通过使用 argmax() 来反转在 load_dataset() 中执行的一个热编码。

# 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() ,以获得模型准确率分数。

# 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

将所有这些联系在一起,下面列出了一个完整的例子,它重复评估了美国有线电视新闻网对 CIFAR-10 的测试时间扩充。

# 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%的准确性。

> 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)

如何调整测试时间扩充配置

选择对模型表现提升最大的增强配置可能是一项挑战。

不仅有许多增强方法可供选择,而且每种方法都有配置选项,而且在一组配置选项上调整和评估模型的时间可能会很长,即使适合快速图形处理器。

相反,我建议拟合模型一次并保存到文件中。例如:

# save model
model.save('model.h5')

然后从一个单独的文件中加载模型,并在一个小的验证数据集或测试集的小子集上评估不同的测试时增强方案。

例如:

...
# load model
model = load_model('model.h5')
# evaluate model
datagen = ImageDataGenerator(...)
...

一旦你找到了一组提升最大的增强选项,你就可以在整个测试集上对模型进行评估,或者尝试一个如上所述的重复评估实验。

测试时间扩充配置不仅包括图像数据生成器的选项,还包括生成的图像数量,将根据这些图像对测试集中的每个示例进行平均预测。

我使用这种方法来选择上一节中的测试时间扩充,发现七个例子比三个或五个更好,随机缩放和随机移动似乎降低了模型的准确性。

请记住,如果您也对训练数据集使用图像数据扩充,并且该增强使用一种涉及计算数据集统计数据的像素缩放类型(例如,您称之为 datagen.fit() ),那么在测试时增强期间也必须使用相同的统计数据和像素缩放技术。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

应用程序接口

文章

摘要

在本教程中,您发现了用于提高图像分类任务模型表现的测试时间扩展。

具体来说,您了解到:

  • 测试时间扩充是数据扩充技术的应用,通常在进行预测的训练中使用。
  • 如何在 Keras 中从零开始实现测试时间扩充?
  • 如何在标准图像分类任务中使用测试时间扩充来提高卷积神经网络模型的表现?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

在 Keras 中将计算机视觉模型用于迁移学习

原文:machinelearningmastery.com/how-to-use-…

最后更新于 2020 年 8 月 18 日

深度卷积神经网络模型在非常大的数据集上训练可能需要几天甚至几周的时间。

简化这一过程的一种方法是重用为标准计算机视觉基准数据集(如 ImageNet 图像识别任务)开发的预训练模型的模型权重。表现最好的模型可以直接下载使用,也可以集成到新的模型中来解决你自己的计算机视觉问题。

在这篇文章中,你将发现在为计算机视觉应用开发卷积神经网络时,如何使用转移学习

看完这篇文章,你会知道:

  • 迁移学习包括使用在一个问题上训练的模型作为相关问题的起点。
  • 迁移学习是灵活的,允许直接使用预先训练好的模型,作为特征提取预处理,并集成到全新的模型中。
  • Keras 可以方便地访问 ImageNet 图像识别任务中的许多顶级模型,如 VGG、Inception 和 ResNet。

用我的新书计算机视觉深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

  • 2020 年 8 月更新:更新了 Keras 2.4.3 和 TensorFlow 2.3 的 API。

How to Use Transfer Learning when Developing Convolutional Neural Network Models

开发卷积神经网络模型时如何使用迁移学习 图片由 GoToVan 提供,保留部分权利。

概观

本教程分为五个部分;它们是:

  1. 什么是迁移学习?
  2. 图像识别中的迁移学习
  3. 如何使用预训练模型
  4. 迁移学习模型
  5. 使用预训练模型的示例

什么是迁移学习?

迁移学习通常指的是在一个问题上训练的模型以某种方式用于第二个相关问题的过程。

在深度学习中,迁移学习是一种技术,通过这种技术,神经网络模型首先在与正在解决的问题相似的问题上进行训练。来自训练模型的一个或多个层随后被用于在感兴趣的问题上训练的新模型中。

这通常在监督学习环境中理解,其中输入是相同的,但是目标可能是不同的性质。例如,我们可能在第一个设置中了解一组视觉类别,如猫和狗,然后在第二个设置中了解一组不同的视觉类别,如蚂蚁和黄蜂。

—第 536 页,深度学习,2016。

迁移学习具有减少神经网络模型的训练时间的优点,并且可以导致较低的泛化误差。

重用层中的权重可以用作训练过程的起点,并根据新问题进行调整。这种用法将迁移学习视为一种权重初始化方案。当第一个相关问题比感兴趣的问题有更多的标记数据时,这可能是有用的,并且问题结构的相似性可能在两种情况下都有用。

……目标是利用第一个设置中的数据来提取信息,这些信息可能在学习时有用,甚至在第二个设置中直接进行预测时有用。

—第 538 页,深度学习,2016。

图像识别中的迁移学习

已经为图像分类开发了一系列高表现模型,并在年度 ImageNet 大规模视觉识别挑战或 ILSVRC 上进行了演示。

考虑到比赛中使用的图像来源,这一挑战通常简称为 ImageNet ,在卷积神经网络的架构和训练方面带来了许多创新。此外,比赛中使用的许多模型已经在许可许可下发布。

这些模型可以作为计算机视觉应用中迁移学习的基础。

这是可取的,原因有很多,尤其是:

  • 有用的学习特征:模型已经学会了如何从照片中检测通用特征,假设它们是在 1000 个类别的 100 多万张图像上训练的。
  • 最先进的表现:模型实现了最先进的表现,并且在开发它们的特定图像识别任务中保持有效。
  • 易访问:模型权重以免费下载文件的形式提供,很多库提供了方便的 API,可以直接下载使用模型。

可以下载模型权重,并使用一系列不同的深度学习库(包括 Keras)在同一模型架构中使用。

如何使用预训练模型

预训练模型的使用只受你创造力的限制。

例如,模型可以按原样下载和使用,例如嵌入到应用程序中并用于对新照片进行分类。

或者,可以下载模型并用作特征提取模型。这里,来自模型输出层之前的层的模型输出被用作新分类器模型的输入。

回想一下,更靠近模型输入层的卷积层学习低级特征,例如线,层中间的层学习复杂的抽象特征,这些抽象特征组合了从输入中提取的低级特征,而更靠近输出的层在分类任务的上下文中解释提取的特征。

有了这种理解,就可以选择从现有的预训练模型中提取特征的详细程度。例如,如果一个新的任务与对照片中的对象进行分类有很大的不同(例如不同于 ImageNet),那么在几层之后,预训练模型的输出可能是合适的。如果新任务与照片中对象分类的任务非常相似,那么也许可以使用模型中更深层次的输出,或者甚至可以使用输出层之前的完全连接层的输出。

预训练的模型可以用作单独的特征提取程序,在这种情况下,输入可以由模型或模型的一部分预处理为每个输入图像的给定输出(例如,数字向量),然后当训练新模型时,该输出可以用作输入。

或者,预训练模型或模型的期望部分可以直接集成到新的神经网络模型中。在这种用法中,预训练的权重可以被冻结,以便它们不会随着新模型的训练而更新。或者,权重可以在新模型的训练期间被更新,可能具有较低的学习率,从而允许预训练模型在训练新模型时像权重初始化方案一样工作。

我们可以将其中一些使用模式总结如下:

  • 分类器:直接使用预先训练好的模型对新图像进行分类。
  • 独立特征提取器:预先训练好的模型,或者模型的某一部分,用于对图像进行预处理,提取相关特征。
  • 集成特征提取器:预先训练好的模型,或者模型的某一部分,被集成到一个新的模型中,但是预先训练好的模型的层在训练过程中被冻结。
  • 权重初始化:将预先训练好的模型,或者模型的某一部分,集成到一个新的模型中,预先训练好的模型的各层与新的模型协同训练。

在开发和训练深度卷积神经网络模型时,每种方法都是有效的,并且节省了大量时间。

可能不清楚哪种预训练模型的使用会在新的计算机视觉任务中产生最佳结果,因此可能需要一些实验。

迁移学习模型

也许有十几个或更多的图像识别顶级模型可以下载并用作图像识别和相关计算机视觉任务的基础。

可能有三种更受欢迎的型号如下:

  • VGG(例如 VGG16 或 VGG19)。
  • GoogLeNet(例如 inceptionv 3)。
  • 剩余网络(例如 ResNet50)。

这些模型被广泛用于迁移学习,不仅因为它们的表现,还因为它们是引入特定架构创新的例子,即一致和重复结构(VGG)、初始模块(谷歌网)和剩余模块(ResNet)。

Keras 提供了许多为图像识别任务开发的表现最佳的预训练模型。

它们可通过应用程序接口获得,包括加载具有或不具有预训练权重的模型的功能,以及以给定模型可能期望的方式准备数据的功能(例如,缩放尺寸和像素值)。

第一次加载预训练模型时,Keras 会下载所需的模型权重,考虑到您的互联网连接速度,这可能需要一些时间。重量存储在中。keras/models/ 位于您的主目录下,下次使用时将从该位置加载。

加载给定模型时,可以将“ include_top ”参数设置为 False ,在这种情况下,用于进行预测的模型的完全连接的输出层不会被加载,从而允许添加和训练新的输出层。例如:

...
# load model without output layer
model = VGG16(include_top=False)

此外,当“ include_top ”参数为 False 时,必须指定“ input_tensor ”参数,以允许模型的预期固定大小输入被更改。例如:

...
# load model and specify a new input shape for images
new_input = Input(shape=(640, 480, 3))
model = VGG16(include_top=False, input_tensor=new_input)

没有顶层的模型将直接从最后一个卷积层或池层输出激活。总结这些激活以便在分类器中使用或作为输入的特征向量表示的一种方法是添加全局池层,例如最大全局池或平均全局池。结果是一个矢量,可以用作输入的特征描述符。Keras 通过可以设置为“ avg 或“ max 的“参数”直接提供此功能。例如:

...
# load model and specify a new input shape for images and avg pooling output
new_input = Input(shape=(640, 480, 3))
model = VGG16(include_top=False, input_tensor=new_input, pooling='avg')

可以使用*预处理 _ 输入()*功能为给定模型准备图像;例如,以开发模型时对训练数据集中的图像执行的方式来执行像素缩放。例如:

...
# prepare an image
from keras.applications.vgg16 import preprocess_input
images = ...
prepared_images = preprocess_input(images)

最后,您可能希望在数据集上使用模型架构,但不使用预先训练的权重,而是使用随机权重初始化模型,并从零开始训练模型。

这可以通过将“权重”参数设置为“无”而不是默认的“ imagenet 来实现。此外,可以设置“参数来定义数据集中的类数量,然后在模型的输出层中为您配置这些类。例如:

...
# define a new model with random weights and 10 classes
new_input = Input(shape=(640, 480, 3))
model = VGG16(weights=None, input_tensor=new_input, classes=10)

现在我们已经熟悉了该应用编程接口,让我们看看如何使用 Keras 应用编程接口加载三个模型。

加载 VGG16 预训练模型

VGG16 模型是由牛津大学视觉图形小组(VGG)开发的,并在 2014 年发表的题为“用于大规模图像识别的非常深卷积网络”的论文中进行了描述

默认情况下,该模型期望将彩色输入图像重新缩放至 224×224 平方的大小。

模型可以按如下方式加载:

# example of loading the vgg16 model
from keras.applications.vgg16 import VGG16
# load model
model = VGG16()
# summarize the model
model.summary()

运行该示例将加载 VGG16 模型,并在需要时下载模型权重。

然后,该模型可以直接用于将照片分类为 1000 个类别之一。在这种情况下,总结模型架构以确认它被正确加载。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0
_________________________________________________________________
flatten (Flatten)            (None, 25088)             0
_________________________________________________________________
fc1 (Dense)                  (None, 4096)              102764544
_________________________________________________________________
fc2 (Dense)                  (None, 4096)              16781312
_________________________________________________________________
predictions (Dense)          (None, 1000)              4097000
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________

加载选项 3 预训练模型

InceptionV3 是初始架构的第三次迭代,最初是为 GoogLeNet 模型开发的。

这个模型是由谷歌的研究人员开发的,并在 2015 年的论文《重新思考计算机视觉的初始架构》中进行了描述

该模型期望彩色图像具有 299×299 的正方形形状。

模型可以按如下方式加载:

# example of loading the inception v3 model
from keras.applications.inception_v3 import InceptionV3
# load model
model = InceptionV3()
# summarize the model
model.summary()

运行该示例将加载模型,如果需要的话下载权重,然后总结模型架构以确认它被正确加载。

为了简洁起见,这里省略了输出,因为它是一个具有许多层的深度模型。

加载 ResNet50 预训练模型

剩余网络,简称 ResNet,是一种利用包含快捷连接的剩余模块的模型。

它是由微软的研究人员开发的,并在 2015 年的论文《图像识别的深度残差学习》中进行了描述

该模型期望彩色图像具有 224×224 的正方形形状。

# example of loading the resnet50 model
from keras.applications.resnet50 import ResNet50
# load model
model = ResNet50()
# summarize the model
model.summary()

运行该示例将加载模型,如果需要的话下载权重,然后总结模型架构以确认它被正确加载。

为了简洁起见,这里省略了输出,因为它是一个深度模型。

使用预训练模型的示例

现在我们已经熟悉了如何在 Keras 中加载预先训练好的模型,让我们来看一些如何在实践中使用它们的例子。

在这些示例中,我们将使用 VGG16 模型,因为它使用起来相对简单,理解起来也很简单。

在这些例子中,我们还需要一张照片。下面是一张狗的照片,由贾斯汀·摩根拍摄,根据许可许可提供。

Photograph of a Dog

一只狗的照片

下载照片,并将其放入当前工作目录,文件名为“dog.jpg”。

作为分类器的预训练模型

预训练的模型可以直接用于将新照片分类为 ILSVRC 中图像分类任务的 1000 个已知类别之一。

我们将使用 VGG16 模型对新图像进行分类。

首先,照片需要加载并重新整形为模型预期的 224×224 的正方形,像素值按照模型预期的方式缩放。该模型对一组样本进行操作,因此对于一幅具有 224×224 像素和三个通道的图像,加载图像的维度需要扩展 1。

# load an image from file
image = load_img('dog.jpg', target_size=(224, 224))
# convert the image pixels to a numpy array
image = img_to_array(image)
# reshape data for the model
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# prepare the image for the VGG model
image = preprocess_input(image)

接下来,可以加载模型并进行预测。

这意味着做出了属于 1000 个类别中的每一个的照片的预测概率。在这个例子中,我们只关心最有可能的类,因此我们可以解码预测,并检索具有最高概率的类的标签或名称。

# predict the probability across all output classes
yhat = model.predict(image)
# convert the probabilities to class labels
label = decode_predictions(yhat)
# retrieve the most likely result, e.g. highest probability
label = label[0][0]

将所有这些联系在一起,下面的完整示例加载了一张新照片,并预测了最有可能的类别。

# example of using a pre-trained model as a classifier
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
from keras.applications.vgg16 import decode_predictions
from keras.applications.vgg16 import VGG16
# load an image from file
image = load_img('dog.jpg', target_size=(224, 224))
# convert the image pixels to a numpy array
image = img_to_array(image)
# reshape data for the model
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# prepare the image for the VGG model
image = preprocess_input(image)
# load the model
model = VGG16()
# predict the probability across all output classes
yhat = model.predict(image)
# convert the probabilities to class labels
label = decode_predictions(yhat)
# retrieve the most likely result, e.g. highest probability
label = label[0][0]
# print the classification
print('%s (%.2f%%)' % (label[1], label[2]*100))

运行这个例子预测的不仅仅是狗;它还以 33.59%的概率预测了特定品种的‘T0’杜宾犬,这实际上可能是正确的。

Doberman (33.59%)

作为特征提取预处理器的预训练模型

预先训练好的模型可以作为一个独立的程序从新照片中提取特征。

具体而言,照片的提取特征可以是模型将用来描述照片中特定特征的数字向量。然后,这些特性可以用作开发新模型的输入。

VGG16 型号的最后几层是输出层之前的全连接层。这些层将提供一组复杂的特征来描述给定的输入图像,并且可以在为图像分类或相关的计算机视觉任务训练新模型时提供有用的输入。

可以为模型加载和准备图像,就像我们在前面的例子中所做的那样。

我们将使用模型的分类器输出部分加载模型,但是手动移除最终的输出层。这意味着具有 4,096 个节点的第二个最后一个完全连接的层将是新的输出层。

# load model
model = VGG16()
# remove the output layer
model = Model(inputs=model.inputs, outputs=model.layers[-2].output)

这个由 4,096 个数字组成的向量将用于表示给定输入图像的复杂特征,然后可以将其保存到文件中以供以后加载,并用作训练新模型的输入。我们可以把它保存为泡菜文件。

# get extracted features
features = model.predict(image)
print(features.shape)
# save to file
dump(features, open('dog.pkl', 'wb'))

将所有这些结合在一起,下面列出了将该模型用作独立特征提取模型的完整示例。

# example of using the vgg16 model as a feature extraction model
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
from keras.applications.vgg16 import decode_predictions
from keras.applications.vgg16 import VGG16
from keras.models import Model
from pickle import dump
# load an image from file
image = load_img('dog.jpg', target_size=(224, 224))
# convert the image pixels to a numpy array
image = img_to_array(image)
# reshape data for the model
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
# prepare the image for the VGG model
image = preprocess_input(image)
# load model
model = VGG16()
# remove the output layer
model = Model(inputs=model.inputs, outputs=model.layers[-2].output)
# get extracted features
features = model.predict(image)
print(features.shape)
# save to file
dump(features, open('dog.pkl', 'wb'))

运行该示例加载照片,然后准备模型作为特征提取模型。

从加载的照片中提取特征,并打印特征向量的形状,显示它有 4,096 个数字。该功能随后被保存到当前工作目录中的新文件 dog.pkl 中。

(1, 4096)

可以对新训练数据集中的每张照片重复该过程。

预训练模型作为模型中的特征提取器

我们可以直接使用预训练模型中的部分或全部层作为新模型的特征提取组件。

这可以通过加载模型,然后简单地添加新层来实现。这可能涉及添加新的卷积和池层来扩展模型的特征提取能力,或者添加新的完全连接的分类器类型层来学习如何在新的数据集上解释提取的特征,或者一些组合。

例如,我们可以通过将“ include_top ”参数指定为“ False ”来加载没有模型的分类器部分的 VGG16 模型,并将新数据集中图像的首选形状指定为 300×300。

# load model without classifier layers
model = VGG16(include_top=False, input_shape=(300, 300, 3))

然后,我们可以使用 Keras 函数 API 在 VGG16 模型中的最后一个池层之后添加一个新的扁平化层,然后定义一个新的分类器模型,该模型具有一个密集的全连接层和一个输出层,该输出层将预测 10 个类的概率。

# add new classifier layers
flat1 = Flatten()(model.layers[-1].output)
class1 = Dense(1024, activation='relu')(flat1)
output = Dense(10, activation='softmax')(class1)
# define new model
model = Model(inputs=model.inputs, outputs=output)

添加扁平化层的另一种方法是用平均池层定义 VGG16 模型,然后添加完全连接的层。也许在您的应用程序中尝试这两种方法,看看哪种方法的表现最好。

VGG16 模型的权重和新模型的权重将在新的数据集上一起训练。

下面列出了完整的示例。

# example of tending the vgg16 model
from keras.applications.vgg16 import VGG16
from keras.models import Model
from keras.layers import Dense
from keras.layers import Flatten
# load model without classifier layers
model = VGG16(include_top=False, input_shape=(300, 300, 3))
# add new classifier layers
flat1 = Flatten()(model.layers[-1].output)
class1 = Dense(1024, activation='relu')(flat1)
output = Dense(10, activation='softmax')(class1)
# define new model
model = Model(inputs=model.inputs, outputs=output)
# summarize
model.summary()
# ...

运行该示例定义了准备培训的新模型,并总结了模型架构。

我们可以看到,我们已经展平了最后一个池层的输出,并添加了新的完全连接的层。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 300, 300, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 300, 300, 64)      1792
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 300, 300, 64)      36928
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 150, 150, 64)      0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 150, 150, 128)     73856
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 150, 150, 128)     147584
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 75, 75, 128)       0
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 75, 75, 256)       295168
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 75, 75, 256)       590080
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 75, 75, 256)       590080
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 37, 37, 256)       0
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 37, 37, 512)       1180160
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 37, 37, 512)       2359808
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 37, 37, 512)       2359808
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 18, 18, 512)       0
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 18, 18, 512)       2359808
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 9, 9, 512)         0
_________________________________________________________________
flatten_1 (Flatten)          (None, 41472)             0
_________________________________________________________________
dense_1 (Dense)              (None, 1024)              42468352
_________________________________________________________________
dense_2 (Dense)              (None, 10)                10250
=================================================================
Total params: 57,193,290
Trainable params: 57,193,290
Non-trainable params: 0
_________________________________________________________________

或者,我们可能希望使用 VGG16 模型层,但是在不更新 VGG16 层权重的情况下训练模型的新层。这将允许新的输出层学习解释 VGG16 模型的学习功能。

这可以通过在训练之前将加载的 VGG 模型中的每个层的“可训练”属性设置为假来实现。例如:

# load model without classifier layers
model = VGG16(include_top=False, input_shape=(300, 300, 3))
# mark loaded layers as not trainable
for layer in model.layers:
	layer.trainable = False
...

您可以选择哪些层是可训练的。

例如,也许您想重新训练模型中的一些卷积层,但是模型中没有更早的层。例如:

# load model without classifier layers
model = VGG16(include_top=False, input_shape=(300, 300, 3))
# mark some layers as not trainable
model.get_layer('block1_conv1').trainable = False
model.get_layer('block1_conv2').trainable = False
model.get_layer('block2_conv1').trainable = False
model.get_layer('block2_conv2').trainable = False
...

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

邮件

报纸

蜜蜂

文章

摘要

在这篇文章中,你发现了如何在为计算机视觉应用开发卷积神经网络时使用转移学习。

具体来说,您了解到:

  • 迁移学习包括使用在一个问题上训练的模型作为相关问题的起点。
  • 迁移学习是灵活的,允许使用预先训练的模型直接作为特征提取预处理,并集成到全新的模型中。
  • Keras 可以方便地访问 ImageNet 图像识别任务中的许多顶级模型,如 VGG、Inception 和 ResNet。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

如何在卷积神经网络中可视化过滤器和特征图

原文:machinelearningmastery.com/how-to-visu…

最后更新于 2019 年 7 月 5 日

深度学习神经网络通常是不透明的,这意味着尽管它们可以做出有用和熟练的预测,但不清楚如何或为什么做出给定的预测。

卷积神经网络具有内部结构,其被设计成对二维图像数据进行操作,并且因此保留了模型所学习的内容的空间关系。具体而言,可以检查和可视化模型学习的二维滤波器,以发现模型将检测到的特征类型,并且可以检查卷积层输出的激活图,以准确理解对于给定的输入图像检测到了什么特征。

在本教程中,您将发现如何为卷积神经网络中的过滤器和特征映射开发简单的可视化。

完成本教程后,您将知道:

  • 如何开发卷积神经网络中特定滤波器的可视化?
  • 如何开发卷积神经网络中特定特征图的可视化?
  • 如何在深度卷积神经网络中系统地可视化每个块的特征图?

用我的新书计算机视觉深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

How to Visualize Filters and Feature Maps in Convolutional Neural Networks

如何在卷积神经网络中可视化过滤器和特征图 图片由马克·肯特提供,保留部分权利。

教程概述

本教程分为四个部分;它们是:

  1. 可视化卷积层
  2. 预拟合 VGG 模型
  3. 如何可视化过滤器
  4. 如何可视化要素地图

可视化卷积层

神经网络模型通常被称为不透明的。这意味着他们不善于解释做出特定决定或预测的原因。

卷积神经网络是为处理图像数据而设计的,它们的结构和功能表明,应该比其他类型的神经网络不那么难以理解。

具体来说,模型由小的线性过滤器和应用过滤器的结果组成,这些过滤器被称为激活图,或者更一般地说,特征图。

过滤器和特征图都可以可视化。

例如,我们可以设计和理解小型滤波器,如线检测器。也许在一个学习过的卷积神经网络中可视化过滤器可以提供对模型如何工作的洞察。

通过将过滤器应用于输入图像和由先前层输出的特征图而产生的特征图可以提供对模型中给定点的特定输入的模型内部表示的洞察。

在本教程中,我们将探索这两种方法来可视化卷积神经网络。

预拟合 VGG 模型

我们需要一个模型来可视化。

我们可以使用预拟合的现有最先进的图像分类模型,而不是从零开始拟合模型。

Keras 提供了许多由不同研究小组为 ImageNet 大规模视觉识别挑战(ILSVRC)开发的表现良好的图像分类模型的例子。一个例子是在 2014 年竞赛中取得最高表现的 VGG-16 型。

这是一个很好的可视化模型,因为它有一个简单统一的串行有序卷积和池化层结构,它有 16 个深度学习层,并且表现非常好,这意味着过滤器和生成的特征图将捕获有用的特征。有关该模型的更多信息,请参见 2015 年的论文“用于大规模图像识别的超深度卷积网络

我们只需几行代码就可以加载并汇总 VGG16 模型;例如:

# load vgg model
from keras.applications.vgg16 import VGG16
# load the model
model = VGG16()
# summarize the model
model.summary()

运行该示例会将模型权重加载到内存中,并打印加载模型的摘要。

如果这是您第一次加载模型,重量将从互联网下载并存储在您的主目录中。这些权重大约为 500 兆字节,根据您的互联网连接速度,下载可能需要一些时间。

我们可以看到,这些层命名良好,组织成块,并在每个块中用整数索引命名。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0
_________________________________________________________________
flatten (Flatten)            (None, 25088)             0
_________________________________________________________________
fc1 (Dense)                  (None, 4096)              102764544
_________________________________________________________________
fc2 (Dense)                  (None, 4096)              16781312
_________________________________________________________________
predictions (Dense)          (None, 1000)              4097000
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________

现在我们有了一个预拟合模型,我们可以将其用作可视化的基础。

如何可视化过滤器

也许最简单的可视化是直接绘制学习过的过滤器。

在神经网络术语中,所学习的滤波器仅仅是权重,然而由于滤波器的专用二维结构,权重值彼此具有空间关系,并且将每个滤波器绘制为二维图像是有意义的(或者可能是)。

第一步是查看模型中的过滤器,看看我们需要处理什么。

上一节中打印的模型摘要总结了每个层的输出形状,例如生成的要素图的形状。它没有给出网络中过滤器的形状(权重)的任何概念,只有每层权重的总数。

我们可以通过 model.layers 属性访问模型的所有层。

每一层都有一个层. name 属性,其中卷积层有一个命名卷积,如块#_conv# ,其中“ # ”是一个整数。因此,我们可以检查每个层的名称,跳过任何不包含字符串“ conv ”的层。

# summarize filter shapes
for layer in model.layers:
	# check for convolutional layer
	if 'conv' not in layer.name:
		continue

每个卷积层有两组权重。

一个是滤波器块,另一个是偏差值块。这些可通过层. get_weights() 功能访问。我们可以检索这些权重,然后总结它们的形状。

# get filter weights
filters, biases = layer.get_weights()
print(layer.name, filters.shape)

将这些联系在一起,下面列出了总结模型过滤器的完整示例。

# summarize filters in each convolutional layer
from keras.applications.vgg16 import VGG16
from matplotlib import pyplot
# load the model
model = VGG16()
# summarize filter shapes
for layer in model.layers:
	# check for convolutional layer
	if 'conv' not in layer.name:
		continue
	# get filter weights
	filters, biases = layer.get_weights()
	print(layer.name, filters.shape)

运行该示例会打印层详细信息列表,包括层名称和层中过滤器的形状。

block1_conv1 (3, 3, 3, 64)
block1_conv2 (3, 3, 64, 64)
block2_conv1 (3, 3, 64, 128)
block2_conv2 (3, 3, 128, 128)
block3_conv1 (3, 3, 128, 256)
block3_conv2 (3, 3, 256, 256)
block3_conv3 (3, 3, 256, 256)
block4_conv1 (3, 3, 256, 512)
block4_conv2 (3, 3, 512, 512)
block4_conv3 (3, 3, 512, 512)
block5_conv1 (3, 3, 512, 512)
block5_conv2 (3, 3, 512, 512)
block5_conv3 (3, 3, 512, 512)

我们可以看到,所有卷积层都使用 3×3 滤波器,这些滤波器很小,可能很容易解释。

卷积神经网络的一个结构问题是滤波器的深度必须与滤波器的输入深度(例如通道数)相匹配。

我们可以看到,对于具有红色、绿色和蓝色三个通道的输入图像,每个过滤器都有三个深度(这里我们使用的是通道在后格式)。我们可以把一个滤镜想象成一个有三个图像的图,每个通道一个,或者把三个都压缩成一个彩色图像,或者只看第一个通道,假设其他通道看起来都一样。问题是,我们还有另外 63 个我们可能想要可视化的过滤器。

我们可以从第一层检索过滤器,如下所示:

# retrieve weights from the second hidden layer
filters, biases = model.layers[1].get_weights()

权重值可能是以 0.0 为中心的小正值和负值。

我们可以将它们的值标准化到 0-1 的范围内,以便于可视化。

# normalize filter values to 0-1 so we can visualize them
f_min, f_max = filters.min(), filters.max()
filters = (filters - f_min) / (f_max - f_min)

现在,我们可以列举块中 64 个滤波器中的前 6 个,并绘制每个滤波器的三个通道。

我们使用 matplotlib 库,将每个滤镜绘制为一行新的子情节,将每个滤镜通道或深度绘制为一列新的内容。

# plot first few filters
n_filters, ix = 6, 1
for i in range(n_filters):
	# get the filter
	f = filters[:, :, :, i]
	# plot each channel separately
	for j in range(3):
		# specify subplot and turn of axis
		ax = pyplot.subplot(n_filters, 3, ix)
		ax.set_xticks([])
		ax.set_yticks([])
		# plot filter channel in grayscale
		pyplot.imshow(f[:, :, j], cmap='gray')
		ix += 1
# show the figure
pyplot.show()

将这些联系在一起,下面列出了从 VGG16 模型的第一个隐藏卷积层绘制前六个滤波器的完整示例。

# cannot easily visualize filters lower down
from keras.applications.vgg16 import VGG16
from matplotlib import pyplot
# load the model
model = VGG16()
# retrieve weights from the second hidden layer
filters, biases = model.layers[1].get_weights()
# normalize filter values to 0-1 so we can visualize them
f_min, f_max = filters.min(), filters.max()
filters = (filters - f_min) / (f_max - f_min)
# plot first few filters
n_filters, ix = 6, 1
for i in range(n_filters):
	# get the filter
	f = filters[:, :, :, i]
	# plot each channel separately
	for j in range(3):
		# specify subplot and turn of axis
		ax = pyplot.subplot(n_filters, 3, ix)
		ax.set_xticks([])
		ax.set_yticks([])
		# plot filter channel in grayscale
		pyplot.imshow(f[:, :, j], cmap='gray')
		ix += 1
# show the figure
pyplot.show()

运行该示例会创建一个具有六行三个图像或 18 个图像的图形,每个过滤器一行,每个通道一列

我们可以看到,在某些情况下,通道之间的过滤器是相同的(第一行),而在其他情况下,过滤器是不同的(最后一行)。

深色方块表示小的或抑制性的权重,浅色方块表示大的或兴奋性的权重。利用这种直觉,我们可以看到第一行的过滤器检测到从左上角的亮到右下角的暗的梯度。

Plot of the First 6 Filters From VGG16 With One Subplot per Channel

VGG16 的前 6 个滤波器图,每个通道一个子图

虽然我们有一个可视化,但我们只看到第一个卷积层中 64 个滤波器的前 6 个。在一张图像中可视化所有 64 个过滤器是可行的。

可悲的是,这无法扩展;如果我们希望开始查看第二卷积层中的滤波器,我们可以再次看到,我们有 64 个滤波器,但是每个滤波器都有 64 个通道来匹配输入特征映射。要查看所有 64 个过滤器的所有 64 个通道,需要(64×64) 4,096 个子情节,其中可能很难看到任何细节。

如何可视化要素地图

激活图(称为特征图)捕获将过滤器应用于输入的结果,例如输入图像或另一个特征图。

可视化特定输入图像的特征图的想法是理解在特征图中检测或保留了输入的哪些特征。期望靠近输入的特征映射检测小的或细粒度的细节,而靠近模型输出的特征映射捕获更一般的特征。

为了探索特征图的可视化,我们需要输入可用于创建激活的 VGG16 模型。我们将使用一张简单的鸟的照片。具体来说,是一只知更鸟,由克里斯·希尔德拍摄,在许可许可下发行。

下载照片,并将其放入当前工作目录,文件名为“bird.jpg”。

Robin, by Chris Heald

罗宾,克里斯·希尔德

接下来,我们需要对每个卷积层输出的特征图的形状和层索引号有一个更清楚的了解,以便我们可以检索适当的层输出。

以下示例将枚举模型中的所有层,并打印每个卷积层的输出大小或要素图大小以及模型中的层索引。

# summarize feature map size for each conv layer
from keras.applications.vgg16 import VGG16
from matplotlib import pyplot
# load the model
model = VGG16()
# summarize feature map shapes
for i in range(len(model.layers)):
	layer = model.layers[i]
	# check for convolutional layer
	if 'conv' not in layer.name:
		continue
	# summarize output shape
	print(i, layer.name, layer.output.shape)

运行该示例,我们看到了与我们在模型摘要中看到的相同的输出形状,但在这种情况下,仅针对卷积层。

1 block1_conv1 (?, 224, 224, 64)
2 block1_conv2 (?, 224, 224, 64)
4 block2_conv1 (?, 112, 112, 128)
5 block2_conv2 (?, 112, 112, 128)
7 block3_conv1 (?, 56, 56, 256)
8 block3_conv2 (?, 56, 56, 256)
9 block3_conv3 (?, 56, 56, 256)
11 block4_conv1 (?, 28, 28, 512)
12 block4_conv2 (?, 28, 28, 512)
13 block4_conv3 (?, 28, 28, 512)
15 block5_conv1 (?, 14, 14, 512)
16 block5_conv2 (?, 14, 14, 512)
17 block5_conv3 (?, 14, 14, 512)

我们可以使用这些信息设计一个新模型,它是完整 VGG16 模型中层的子集。该模型将具有与原始模型相同的输入层,但是输出将是给定卷积层的输出,我们知道这将是层或特征图的激活。

例如,在加载 VGG 模型之后,我们可以定义一个新模型,它从第一个卷积层(索引 1)输出一个特征图,如下所示。

# redefine model to output right after the first hidden layer
model = Model(inputs=model.inputs, outputs=model.layers[1].output)

利用该模型进行预测将给出给定输入图像的第一卷积层的特征图。让我们实现它。

定义模型后,我们需要加载模型预期大小的鸟图像,在本例中为 224×224。

# load the image with the required shape
img = load_img('bird.jpg', target_size=(224, 224))

接下来,需要将图像 PIL 对象转换为像素数据的 NumPy 数组,并从 3D 数组扩展为具有[ 个样本、行、列、通道 ]个维度的 4D 数组,其中我们只有一个样本。

# convert the image to an array
img = img_to_array(img)
# expand dimensions so that it represents a single 'sample'
img = expand_dims(img, axis=0)

然后,需要为 VGG 模型适当地缩放像素值。

# prepare the image (e.g. scale pixel values for the vgg)
img = preprocess_input(img)

我们现在准备获取要素地图。我们可以通过调用 model.predict() 函数并传入准备好的单个图像来轻松做到这一点。

# get feature map for first hidden layer
feature_maps = model.predict(img)

我们知道结果将是 224x224x64 的要素地图。我们可以将所有 64 幅二维图像绘制成 8×8 的正方形图像。

# plot all 64 maps in an 8x8 squares
square = 8
ix = 1
for _ in range(square):
	for _ in range(square):
		# specify subplot and turn of axis
		ax = pyplot.subplot(square, square, ix)
		ax.set_xticks([])
		ax.set_yticks([])
		# plot filter channel in grayscale
		pyplot.imshow(feature_maps[0, :, :, ix-1], cmap='gray')
		ix += 1
# show the figure
pyplot.show()

将所有这些结合在一起,下面列出了在鸟类输入图像的 VGG16 模型中可视化第一个卷积层的特征图的完整代码示例。

# plot feature map of first conv layer for given image
from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.models import Model
from matplotlib import pyplot
from numpy import expand_dims
# load the model
model = VGG16()
# redefine model to output right after the first hidden layer
model = Model(inputs=model.inputs, outputs=model.layers[1].output)
model.summary()
# load the image with the required shape
img = load_img('bird.jpg', target_size=(224, 224))
# convert the image to an array
img = img_to_array(img)
# expand dimensions so that it represents a single 'sample'
img = expand_dims(img, axis=0)
# prepare the image (e.g. scale pixel values for the vgg)
img = preprocess_input(img)
# get feature map for first hidden layer
feature_maps = model.predict(img)
# plot all 64 maps in an 8x8 squares
square = 8
ix = 1
for _ in range(square):
	for _ in range(square):
		# specify subplot and turn of axis
		ax = pyplot.subplot(square, square, ix)
		ax.set_xticks([])
		ax.set_yticks([])
		# plot filter channel in grayscale
		pyplot.imshow(feature_maps[0, :, :, ix-1], cmap='gray')
		ix += 1
# show the figure
pyplot.show()

运行示例首先总结了新的较小模型,该模型获取图像并输出要素图。

请记住:该模型比 VGG16 模型小得多,但在第一个卷积层中仍然使用与 VGG16 模型相同的权重(滤波器)。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792
=================================================================
Total params: 1,792
Trainable params: 1,792
Non-trainable params: 0
_________________________________________________________________

接下来,创建一个图形,将所有 64 个特征图显示为子情节。

我们可以看到,在第一个卷积层中应用过滤器的结果是突出显示了许多不同特征的鸟图像版本。

例如,一些高光线,另一些聚焦在背景或前景上。

Visualization of the Feature Maps Extracted From the First Convolutional Layer in the VGG16 Model

VGG16 模型中第一卷积层特征图的可视化

这是一个有趣的结果,总体上符合我们的预期。我们可以更新示例,根据其他特定卷积层的输出绘制特征图。

另一种方法是一次性从模型的每个块中收集要素图输出,然后创建每个要素图的图像。

图像中有五个主要块(例如块 1、块 2 等)。)在池化层结束。每个块中最后一个卷积层的层索引是[2,5,9,13,17]。

我们可以定义一个具有多个输出的新模型,每个块中最后一个卷积层的每个输出一个特征图;例如:

# redefine model to output right after the first hidden layer
ixs = [2, 5, 9, 13, 17]
outputs = [model.layers[i+1].output for i in ixs]
model = Model(inputs=model.inputs, outputs=outputs)
model.summary()

用这种新模型进行预测将产生一个特征地图列表。

我们知道,更深层中的特征图数量(例如深度或通道数量)远远超过 64,例如 256 或 512。然而,为了保持一致性,我们可以将可视化的特征图的数量限制在 64 个。

# plot the output from each block
square = 8
for fmap in feature_maps:
	# plot all 64 maps in an 8x8 squares
	ix = 1
	for _ in range(square):
		for _ in range(square):
			# specify subplot and turn of axis
			ax = pyplot.subplot(square, square, ix)
			ax.set_xticks([])
			ax.set_yticks([])
			# plot filter channel in grayscale
			pyplot.imshow(fmap[0, :, :, ix-1], cmap='gray')
			ix += 1
	# show the figure
	pyplot.show()

将这些变化联系在一起,我们现在可以为 VGG16 模型中的五个区块中的每一个区块创建五个单独的地块,用于我们的鸟类照片。完整列表如下。

# visualize feature maps output from each block in the vgg model
from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.models import Model
from matplotlib import pyplot
from numpy import expand_dims
# load the model
model = VGG16()
# redefine model to output right after the first hidden layer
ixs = [2, 5, 9, 13, 17]
outputs = [model.layers[i].output for i in ixs]
model = Model(inputs=model.inputs, outputs=outputs)
# load the image with the required shape
img = load_img('bird.jpg', target_size=(224, 224))
# convert the image to an array
img = img_to_array(img)
# expand dimensions so that it represents a single 'sample'
img = expand_dims(img, axis=0)
# prepare the image (e.g. scale pixel values for the vgg)
img = preprocess_input(img)
# get feature map for first hidden layer
feature_maps = model.predict(img)
# plot the output from each block
square = 8
for fmap in feature_maps:
	# plot all 64 maps in an 8x8 squares
	ix = 1
	for _ in range(square):
		for _ in range(square):
			# specify subplot and turn of axis
			ax = pyplot.subplot(square, square, ix)
			ax.set_xticks([])
			ax.set_yticks([])
			# plot filter channel in grayscale
			pyplot.imshow(fmap[0, :, :, ix-1], cmap='gray')
			ix += 1
	# show the figure
	pyplot.show()

运行该示例会产生五个图,显示 VGG16 模型的五个主要块的要素图。

我们可以看到,更接近模型输入的特征图捕捉到了图像中的许多细节,随着我们深入模型,特征图显示的细节越来越少。

这种模式是意料之中的,因为模型将图像中的特征抽象成更一般的概念,可以用来进行分类。虽然从最终的图像中不清楚模型是否看到了鸟,但我们通常会失去解释这些更深层次的特征图的能力。

Visualization of the Feature Maps Extracted From Block 1 in the VGG16 Model

VGG16 模型中从区块 1 提取的特征图的可视化

Visualization of the Feature Maps Extracted From Block 2 in the VGG16 Model

VGG16 模型中从区块 2 提取的特征图的可视化

Visualization of the Feature Maps Extracted From Block 3 in the VGG16 Model

VGG16 模型中从区块 3 提取的特征图的可视化

Visualization of the Feature Maps Extracted From Block 4 in the VGG16 Model

VGG16 模型中从区块 4 提取的特征图的可视化

Visualization of the Feature Maps Extracted From Block 5 in the VGG16 Model

VGG16 模型中从第 5 区块提取的特征图可视化

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

应用程序接口

文章

摘要

在本教程中,您发现了如何为卷积神经网络中的过滤器和特征映射开发简单的可视化。

具体来说,您了解到:

  • 如何开发卷积神经网络中特定滤波器的可视化?
  • 如何开发卷积神经网络中特定特征图的可视化?
  • 如何在深度卷积神经网络中系统地可视化每个块的特征图?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

用于管理模型复杂性1×1卷积的温和介绍

原文:machinelearningmastery.com/introductio…

最后更新于 2019 年 7 月 5 日

池化可用于对要素地图的内容进行下采样,减少其宽度和高度,同时保持其显著特征。

深度卷积神经网络的一个问题是特征图的数量通常随着网络的深度而增加。当使用更大的滤波器尺寸(例如 5×5 和 7×7)时,这个问题会导致所需的参数数量和计算量急剧增加。

为了解决这个问题,可以使用 1×1 卷积层,它提供了一个通道池,通常称为要素地图池或投影层。这种简单的技术可以用于降维,减少特征图的数量,同时保留它们的显著特征。它还可以直接用于创建要素地图的一对一投影,以跨通道池化要素或增加要素地图的数量,例如在传统的池化层之后。

在本教程中,您将发现如何使用 1×1 滤波器来控制卷积神经网络中特征映射的数量。

完成本教程后,您将知道:

  • 1×1 滤波器可用于创建一叠要素图的线性投影。
  • 1×1 创建的投影可以像通道池一样工作,并用于降维。
  • 由 1×1 创建的投影也可以直接使用或用于增加模型中的特征地图的数量。

用我的新书计算机视觉深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

A Gentle Introduction to 1x1 Convolutions to Reduce the Complexity of Convolutional Neural Networks

1×1 卷积的温和介绍降低卷积神经网络的复杂性 图片版权,保留部分权利。

教程概述

本教程分为五个部分;它们是:

  1. 通道上的卷积
  2. 要素地图过多的问题
  3. 使用 1×1 过滤器对要素地图进行下采样
  4. 如何使用 1×1 卷积的例子
  5. 有线电视新闻网模型架构中的 1×1 滤波器示例

通道上的卷积

回想一下,卷积运算是将较小的滤波器线性应用于较大的输入,从而产生输出特征图。

应用于输入图像或输入要素图的过滤器总是产生一个数字。过滤器从左至右和从上至下系统地应用于输入,产生二维特征图。一个过滤器创建一个相应的要素地图。

过滤器必须具有与输入相同的深度或通道数,然而,无论输入和过滤器的深度如何,结果输出都是单个数字,一个过滤器创建一个具有单个通道的要素图。

让我们用一些例子来具体说明这一点:

  • 如果输入有一个通道,如灰度图像,则 3×3 滤波器将应用于 3x3x1 块。
  • 如果输入图像有红色、绿色和蓝色三个通道,那么将在 3x3x3 块中应用 3×3 滤波器。
  • 如果输入是来自另一个卷积层或池化层的特征图块,并且深度为 64,则 3×3 滤波器将应用于 3x3x64 块,以创建组成单个输出特征图的单个值。

一个卷积层的输出深度仅由应用于输入的并行滤波器的数量来定义。

要素地图过多的问题

卷积层中使用的输入深度或滤波器数量通常会随着网络深度的增加而增加,从而导致生成的特征图数量增加。这是一种常见的模型设计模式。

此外,一些网络体系结构,例如初始体系结构,也可以连接来自多个卷积层的输出特征图,这也可以显著增加到后续卷积层的输入深度。

卷积神经网络中的大量特征映射会导致问题,因为卷积运算必须向下执行到输入的深度。如果正在执行的卷积运算相对较大,例如 5×5 或 7×7 像素,这是一个特别的问题,因为这可能导致相当多的参数(权重)以及反过来执行卷积运算的计算(大的空间和时间复杂度)。

池化层旨在缩小要素地图的比例,并系统地将网络中要素地图的宽度和高度减半。然而,池化层不会改变模型中的过滤器数量、深度或通道数量。

深度卷积神经网络需要相应的池化类型的层,该层可以下采样或减少特征图的深度或数量。

使用 1×1 过滤器对要素地图进行下采样

解决方案是使用 1×1 滤波器对深度或特征图数量进行下采样。

1×1 滤波器对于输入中的每个通道只有一个参数或权重,就像任何滤波器的应用都会产生一个输出值一样。这种结构允许 1×1 滤波器像单个神经元一样工作,输入来自输入中每个特征映射的相同位置。然后,该单个神经元可以以一个的步幅从左到右和从上到下系统地应用,而不需要任何填充,从而产生与输入具有相同宽度和高度的特征图。

1×1 滤波器非常简单,不涉及输入中的任何相邻像素;它可能不被认为是卷积运算。相反,它是输入的线性加权或投影。此外,非线性被用于其他卷积层,允许投影对输入特征图执行非平凡计算。

这个简单的 1×1 过滤器提供了一种有效总结输入特征图的方法。反过来,多个 1×1 过滤器的使用允许调整要创建的输入要素图的汇总数量,有效地允许根据需要增加或减少要素图的深度。

因此,具有 1×1 滤波器的卷积层可以在卷积神经网络中的任何点使用,以控制特征映射的数量。因此,它通常被称为投影操作或投影层,甚至是要素地图或通道池层。

既然我们知道可以用 1×1 的过滤器控制特征图的数量,那么我们就用一些例子来具体说明一下。

如何使用 1×1 卷积的例子

通过一些例子,我们可以利用 1×1 的过滤混凝土。

考虑我们有一个卷积神经网络,它期望输入的彩色图像具有 256x256x3 像素的正方形形状。

然后,这些图像通过具有 512 个滤镜的第一隐藏层,每个滤镜的大小为 3×3,填充相同,随后是 ReLU 激活功能

下面的例子演示了这个简单的模型。

# example of simple cnn model
from keras.models import Sequential
from keras.layers import Conv2D
# create model
model = Sequential()
model.add(Conv2D(512, (3,3), padding='same', activation='relu', input_shape=(256, 256, 3)))
# summarize model
model.summary()

运行该示例会创建模型并总结模型架构。

没有惊喜;第一个隐藏层的输出是一组三维形状为 256x256x512 的要素图。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 256, 256, 512)     14336
=================================================================
Total params: 14,336
Trainable params: 14,336
Non-trainable params: 0
_________________________________________________________________

投影要素地图示例

1×1 过滤器可用于创建要素地图的投影。

创建的要素地图的数量将是相同的,并且效果可能是对已提取的要素的细化。这通常被称为通道池,与传统的每个通道上的功能池相反。它可以如下实现:

model.add(Conv2D(512, (1,1), activation='relu'))

我们可以看到,我们使用了相同数量的特征,并且仍然遵循具有整流线性激活函数的滤波器的应用。

下面列出了完整的示例。

# example of a 1x1 filter for projection
from keras.models import Sequential
from keras.layers import Conv2D
# create model
model = Sequential()
model.add(Conv2D(512, (3,3), padding='same', activation='relu', input_shape=(256, 256, 3)))
model.add(Conv2D(512, (1,1), activation='relu'))
# summarize model
model.summary()

运行该示例会创建模型并总结体系结构。

我们可以看到,要素地图的宽度或高度没有变化,通过设计,要素地图的数量通过应用简单的投影操作保持不变。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 256, 256, 512)     14336
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 256, 256, 512)     262656
=================================================================
Total params: 276,992
Trainable params: 276,992
Non-trainable params: 0
_________________________________________________________________

减少要素地图的示例

1×1 滤波器可用于减少要素图的数量。

这是此类过滤器最常见的应用,因此,该层通常称为要素地图池层。

在本例中,我们可以将深度(或通道)从 512 减少到 64。如果我们要添加到模型中的后续层是另一个带有 7×7 滤波器的卷积层,这可能会很有用。这些过滤器仅适用于深度 64,而不是 512。

model.add(Conv2D(64, (1,1), activation='relu'))

64 个特征图的组成与原始 512 不相同,但是包含捕获显著特征的降维的有用总结,使得 7×7 操作可能对 64 个特征图具有与原始 512 相似的效果。

此外,具有 64 个滤波器的 7×7 卷积层本身应用于由第一隐藏层输出的 512 个特征图将产生大约一百万个参数(权重)。如果首先使用 1×1 滤波器将特征图的数量减少到 64 个,那么 7×7 层所需的参数数量大约只有 20 万个,这是一个巨大的差异。

下面列出了使用 1×1 滤波器进行降维的完整示例。

# example of a 1x1 filter for dimensionality reduction
from keras.models import Sequential
from keras.layers import Conv2D
# create model
model = Sequential()
model.add(Conv2D(512, (3,3), padding='same', activation='relu', input_shape=(256, 256, 3)))
model.add(Conv2D(64, (1,1), activation='relu'))
# summarize model
model.summary()

运行该示例会创建模型并总结其结构。

我们可以看到,特征地图的宽度和高度没有变化,但特征地图的数量从 512 个减少到 64 个。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 256, 256, 512)     14336
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 256, 256, 64)      32832
=================================================================
Total params: 47,168
Trainable params: 47,168
Non-trainable params: 0
_________________________________________________________________

增加要素地图的示例

1×1 滤波器可用于增加要素图的数量。

这是在应用另一个卷积层之前在池层之后使用的常见操作。

过滤器的投影效果可以根据需要多次应用于输入,允许特征图的数量按比例增加,但仍具有捕捉原始特征的合成。

我们可以将要素地图的数量从第一个隐藏层的 512 个输入增加到 1,024 个要素地图的两倍。

model.add(Conv2D(1024, (1,1), activation='relu'))

下面列出了完整的示例。

# example of a 1x1 filter to increase dimensionality
from keras.models import Sequential
from keras.layers import Conv2D
# create model
model = Sequential()
model.add(Conv2D(512, (3,3), padding='same', activation='relu', input_shape=(256, 256, 3)))
model.add(Conv2D(1024, (1,1), activation='relu'))
# summarize model
model.summary()

运行该示例会创建模型并总结其结构。

我们可以看到,要素地图的宽度和高度保持不变,要素地图的数量从 512 个增加到两倍,达到 1024 个。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 256, 256, 512)     14336
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 256, 256, 1024)    525312
=================================================================
Total params: 539,648
Trainable params: 539,648
Non-trainable params: 0
_________________________________________________________________

现在我们已经熟悉了如何使用 1×1 滤波器,让我们看一些例子,它们已经被用于卷积神经网络模型的体系结构中。

有线电视新闻网模型架构中的 1×1 滤波器示例

在本节中,我们将重点介绍一些重要的例子,其中 1×1 滤波器已被用作现代卷积神经网络模型架构中的关键元素。

网络中的网络

林敏等人在 2013 年的论文《网络中的网络》中首次描述并推广了 1×1 过滤器

在这篇论文中,作者提出需要一个 MLP 卷积层和需要跨信道池来促进跨信道学习。

这种级联的跨信道参数池结构允许跨信道信息的复杂且可学习的交互。

——网络中的网络,2013 年。

他们将 1×1 卷积层描述为跨信道参数池的具体实现,实际上,这正是 1×1 滤波器所实现的。

每个池化层对输入特征图执行加权线性重组,然后通过一个整流线性单元。[……]跨通道参数池层也相当于一个具有 1×1 卷积核的卷积层。

——网络中的网络,2013 年。

初始架构

克里斯蒂安·塞格迪(Christian Szegedy)等人在 2014 年发表的题为“T0”的论文中提到,在初始模块的设计中,1×1 滤波器被明确用于降维和增加特征图的维数

本文描述了一个“初始模块”,其中特征图的输入块由不同的卷积层并行处理,每个卷积层具有不同大小的滤波器,其中 1×1 大小的滤波器是所使用的层之一。

Example of the Naive Inception Module

天真初始模块示例 摘自《用卷积深入》,2014 年。

然后,并行层的输出按信道堆叠,产生非常深的卷积层堆叠,供后续初始模块处理。

池化层的输出与卷积层的输出的合并将不可避免地导致各级输出数量的增加。即使这种架构可能覆盖了最佳的稀疏结构,它也会非常低效地完成,导致几个阶段内的计算爆炸。

——用回旋更深入,2014。

然后重新设计初始模块,在使用 5×5 和 7×7 大小的滤波器的并行卷积层之前,使用 1×1 滤波器来减少特征映射的数量。

这就引出了建议架构的第二个想法:在计算需求增加过多的情况下,明智地应用降维和投影。[……]也就是说,在昂贵的 3×3 和 5×5 卷积之前,使用 1×1 卷积来计算约简。除了用作还原剂,它们还包括整流线性激活的使用,这使得它们具有双重用途

——用回旋更深入,2014。

1×1 过滤器还用于在池化后增加要素地图的数量,从而人为地创建下采样的要素地图内容的更多投影。

Example of the Inception Module with Dimensionality Reduction

降维初始模块的例子 摘自《用卷积深入》,2014 年。

残余建筑

在何等人 2015 年发表的论文《用于图像识别的深度残差学习》中,1×1 滤波器被用作投影技术,以匹配残差网络设计中输入的滤波器数量和残差模块的输出

作者描述了一个由“剩余模块”组成的架构,其中模块的输入被添加到模块的输出,这被称为快捷连接。

因为输入被添加到模块的输出中,所以维度必须在宽度、高度和深度方面匹配。宽度和高度可以通过填充来保持,尽管使用了 1×1 滤波器来根据需要更改输入的深度,以便将其添加到模块的输出中。这种类型的连接称为投影快捷连接。

此外,由于计算效率的原因,剩余模块使用具有 1×1 滤波器的瓶颈设计来减少特征图的数量。

这三层是 1×1、3×3 和 1×1 卷积,其中 1×1 层负责减少然后增加(恢复)维度,留下 3×3 层是输入/输出维度较小的瓶颈。

——图像识别的深度残差学习,2015。

Example of a Normal and Bottleneck Residual Modules with Shortcut Connections

带有快捷连接的正常和瓶颈残差模块示例 摘自《图像识别深度残差学习》,2015 年。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

文章

摘要

在本教程中,您发现了如何使用 1×1 滤波器来控制卷积神经网络中特征映射的数量。

具体来说,您了解到:

  • 1×1 滤波器可用于创建一叠要素图的线性投影。
  • 1×1 创建的投影可以像通道池一样工作,并用于降维。
  • 由 1×1 创建的投影也可以直接使用或用于增加模型中的特征地图的数量。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

面向人脸识别的深度学习入门

原文:machinelearningmastery.com/introductio…

最后更新于 2019 年 7 月 5 日

人脸识别是通过人脸来识别和验证照片中的人的问题。

这是一项由人类轻而易举地完成的任务,即使在不同的光线下,当面部因年龄而改变或被配饰和面部毛发遮挡时也是如此。然而,直到最近几十年,它仍然是一个具有挑战性的计算机视觉问题。

深度学习方法能够利用非常大的人脸数据集,学习丰富而紧凑的人脸表示,允许现代模型首先表现良好,然后超越人类的人脸识别能力。

在这篇文章中,你将发现人脸识别的问题,以及深度学习方法如何达到超人的表现。

看完这篇文章,你会知道:

  • 人脸识别是一个广泛的识别或验证照片和视频中的人的问题。
  • 人脸识别是一个由检测、对齐、特征提取和识别任务组成的过程
  • 深度学习模型首先接近,然后超过了人脸识别任务的人类表现。

用我的新书计算机视觉深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

A Gentle Introduction to Deep Learning for Face Recognition

人脸识别深度学习入门 图片由苏珊·尼尔森提供,版权所有。

概观

本教程分为五个部分;它们是:

  1. 照片中的脸
  2. 自动人脸识别过程
  3. 人脸检测任务
  4. 人脸识别任务
  5. 人脸识别的深度学习

照片中的脸

经常需要自动识别照片中的人。

有很多原因可以让我们自动识别照片中的人。

例如:

  • 我们可能希望将对资源的访问限制在一个人,称为面部认证。
  • 我们可能想确认这个人与他们的身份证匹配,这叫做面部验证。
  • 我们可能想给一张脸起一个名字,叫做脸识别。

一般来说,我们称之为“自动”人脸识别的问题,它可能适用于静态照片或视频流中的人脸。

人类可以非常容易地完成这项任务。

我们可以在图像中找到人脸,并评论这些人是谁,如果他们知道的话。我们可以很好地做到这一点,比如当人们变老了,戴着太阳镜,有不同颜色的头发,看着不同的方向,等等。我们可以做得很好,以至于我们可以在没有人脸的地方找到人脸,比如在云层中。

然而,即使经过 60 年或更长时间的研究,这仍然是软件自动执行的一个难题。直到最近。

例如,识别在光照和/或姿态变化的室外环境中获取的人脸图像仍然是一个很大程度上未解决的问题。换句话说,目前的系统离人类感知系统的能力还很远。

——人脸识别:文献综述,2003 年。

自动人脸识别过程

人脸识别是识别或验证照片中人脸的问题。

人脸机器识别问题的一般陈述可以表述如下:给定场景的静止或视频图像,使用存储的人脸数据库识别或验证场景中的一个或多个人

——人脸识别:文献综述,2003 年。

人脸识别通常被描述为一个过程,首先涉及四个步骤;分别是:人脸检测,人脸对齐,特征提取,最后是人脸识别。

  1. 人脸检测。在图像中找到一个或多个面,并用边界框标记。
  2. 面对齐。标准化面部,使其与数据库保持一致,例如几何和光度测量。
  3. 特征提取。从面部提取可用于识别任务的特征。
  4. 人脸识别。在准备好的数据库中,将人脸与一个或多个已知人脸进行匹配。

一个给定的系统对于每个步骤可以有一个单独的模块或程序,这是传统的情况,或者可以将一些或所有的步骤组合成一个单一的过程。

以下提供的《人脸识别手册》一书中提供了该过程的有用概述:

Overview of the Steps in a Face Recognition Process

人脸识别过程步骤概述。摘自《人脸识别手册》,2011 年。

人脸检测任务

人脸检测是人脸识别不平凡的第一步。

这是一个对象识别的问题,需要识别照片中每个面部的位置(例如位置)和面部的范围(例如用边界框)。对象识别本身是一个具有挑战性的问题,尽管在这种情况下,它是相似的,因为只有一种类型的对象,例如人脸,要被定位,尽管人脸可以变化很大。

人脸是一个动态的对象,其外观具有高度的可变性,这使得人脸检测成为计算机视觉中的一个难题。

——人脸检测:一项调查,2001。

此外,因为这是更广泛的人脸识别系统的第一步,人脸检测必须是鲁棒的。例如,如果无法首先检测到人脸,则无法识别该人脸。这意味着人脸必须通过各种方向、角度、光线水平、发型、帽子、眼镜、面部毛发、妆容、年龄等等来检测。

作为一个视觉前端处理器,人脸检测系统也应该能够实现这一任务,而不管光照、方向和相机距离如何

——人脸检测:一项调查,2001。

2001 年的论文标题为“人脸检测:一项调查”提供了人脸检测方法的分类,可大致分为两大类:

  • 基于特征。
  • 基于图像。

基于特征的人脸检测使用手工制作的过滤器,根据对该领域的深入了解来搜索和定位照片中的人脸。当过滤器匹配时,它们可以非常快速和非常有效,尽管当它们不匹配时,它们会显著失效,例如使它们变得有些脆弱。

……明确利用人脸知识,并遵循经典的检测方法,在基于知识的分析之前,先导出低层特征。在不同的系统级别上利用了面部的外观属性,例如肤色和面部几何形状。

——人脸检测:一项调查,2001。

或者,基于图像的人脸检测是整体性的,并且学习如何从整个图像中自动定位和提取人脸。神经网络适合这类方法。

…将人脸检测作为一般的识别问题。基于图像的人脸表示,例如在 2D 强度阵列中,使用训练算法直接分类为人脸组,而无需特征推导和分析。[……]这些相对较新的技术通过映射和训练方案将人脸知识隐含地结合到系统中。

——人脸检测:一项调查,2001。

也许多年来用于人脸检测的主要方法(在许多相机中使用)在 2004 年的论文中有所描述,该论文的标题为“鲁棒实时对象检测”,“称为检测器级联或简称为“级联”

他们的检测器被称为检测器级联,由一系列简单到复杂的人脸分类器组成,吸引了广泛的研究。此外,探测器级联已被部署在许多商业产品中,如智能手机和数码相机。虽然级联检测器可以准确地找到可见的直立人脸,但它们通常无法从不同的角度检测人脸,例如侧视或部分遮挡的人脸。

——使用深度卷积神经网络的多视角人脸检测,2015。

有关人脸检测深度学习的教程,请参见:

人脸识别任务

人脸识别的任务很广泛,可以根据预测问题的具体需要进行定制。

例如,在 1995 年发表的题为“人脸的人类和机器识别:一项调查”的论文中,作者描述了三项人脸识别任务:

  • 人脸匹配:找到给定人脸的最佳匹配。
  • 人脸相似度:查找与给定人脸最相似的人脸。
  • 人脸变换:生成与给定人脸相似的新人脸。

他们将这三项独立的任务总结如下:

匹配要求候选匹配人脸图像在系统选择的某一组人脸图像中。除了匹配之外,相似性检测还要求找到与召回的人脸相似的人脸图像。这要求识别系统使用的相似性度量与人类使用的相似性度量紧密匹配。变换应用要求系统创建的新图像与人类对人脸的回忆相似。

——人脸的人与机器识别:一项调查,1995。

2011 年出版的名为《人脸识别手册》的人脸识别书籍描述了人脸识别的两种主要模式,如下所示:

  • 人脸验证。给定人脸与已知身份(例如的一对一映射是这个人吗?)。
  • 人脸识别。给定人脸与已知人脸数据库的一对多映射(例如这个人是谁?)。

人脸识别系统有望自动识别图像和视频中的人脸。它可以在两种模式中的一种或两种模式下工作:(1)人脸验证(或认证)和(2)人脸识别(或识别)。

—第 1 页,人脸识别手册。2011.

我们可以将人脸识别问题描述为在具有输入和输出的样本上训练的监督预测建模任务。

在所有任务中,输入是包含至少一张脸的照片,很可能是检测到的也可能已经对齐的脸。

输出因任务所需的预测类型而异;例如:

  • 在人脸验证任务的情况下,它可以是二进制类别标签或二进制类别概率。
  • 它可以是一个分类类别标签或一组人脸识别任务的概率。
  • 在相似性类型任务的情况下,它可以是相似性度量。

人脸识别的深度学习

人脸识别一直是计算机视觉研究的一个活跃领域。

在 1991 年发表的题为“使用特征脸的人脸识别”的论文中,描述了一种更广为人知和采用的人脸识别“机器学习”方法他们的方法,简称为“T2”特征脸“T3”,是一个里程碑,因为它取得了令人印象深刻的结果,并展示了简单的整体方法的能力。

人脸图像被投影到特征空间(“人脸空间”)上,该特征空间对已知人脸图像之间的变化进行最佳编码。人脸空间由“特征脸”定义,特征脸是人脸集合的特征向量;它们不一定对应于孤立的特征,如眼睛、耳朵和鼻子

——使用特征脸的人脸识别,1991 年。

2018 年发表的题为《深度人脸识别:一项调查》的论文对近 30 年来人脸识别的研究状况进行了有益的总结,强调了从整体学习方法(如特征脸),到局部手工特征检测,到浅层学习方法,最后到目前最先进的深度学习方法的广泛趋势。

整体方法在 20 世纪 90 年代主导了人脸识别领域。2000 年代早期,手工制作的局部描述符开始流行,2000 年代后期引入了局部特征学习方法。[……][浅层学习法]表现从 60%左右稳步提升到 90%以上,而深度学习在短短三年内将表现提升到 99.80%。

——深度人脸识别:一项调查,2018。

鉴于 AlexNet 在 2012 年对更简单的图像分类问题的突破,2014 年和 2015 年出现了一系列关于人脸识别深度学习方法的研究和出版物。功能很快达到接近人类水平的表现,然后在三年内超过标准人脸识别数据集的人类水平表现,考虑到之前几十年的努力,这是一个惊人的改进速度。

人脸识别深度学习可能有四个里程碑式的系统推动了这些创新;它们是:DeepFace、DeepID 系列系统、VGGFace 和 FaceNet。让我们简要地讨论一下每一个。

DeepFace 是由 Yaniv Taigman 等人从脸书 AI Research 和特拉维夫描述的基于深度卷积神经网络的系统。这在 2014 年的论文《T2 深度人脸:缩小人脸验证中与人类水平的差距》中有所描述这可能是使用深度学习进行人脸识别的第一次重大飞跃,在标准基准数据集上实现了接近人类水平的表现。

我们的方法在野生(LFW)数据集中的标记人脸上达到 97.35%的准确率,将当前技术水平的误差降低了 27%以上,接近人类水平的表现。

——deep Face:缩小与人脸验证中人层面表现的差距,2014。

DeepID,或“T0”深藏身份特征,是一系列系统(如 DeepID、DeepID2 等)。),最早由孙一等人在 2014 年发表的论文《从预测 10,000 个类中深度学习人脸表征》中描述他们的系统最初被描述得很像 DeepFace,尽管在随后的出版物中被扩展,通过对比损失的训练来支持识别和验证任务。

人脸识别的关键挑战是开发有效的特征表示,以减少个人内部的差异,同时扩大个人之间的差异。[……]人脸识别任务通过将从不同身份提取的 DeepID2 特征拉开来增加人与人之间的差异,而人脸验证任务通过将从同一身份提取的 DeepID2 特征拉到一起来减少人与人之间的差异,这两个特征对于人脸识别都是必不可少的。

——联合识别验证深度学习人脸表示,2014。

DeepID 系统是第一批在任务中获得优于人类的表现的深度学习模型之一,例如 DeepID2 在野生(LFW)数据集中的标记人脸上获得了 99.15%的表现,优于人类的表现为 97.53%。随后的系统,如 FaceNet 和 VGGFace,对这些结果进行了改进。

谷歌的 Florian Schroff 等人在 2015 年发表的题为“ FaceNet:人脸识别和聚类的统一嵌入”的论文中描述了 FaceNet 他们的系统取得了当时最先进的成果,并提出了一种被称为“T2”三重损失“T3”的创新,允许图像被有效地编码为特征向量,从而允许通过距离计算进行快速相似度计算和匹配。

FaceNet,它直接学习从人脸图像到紧致欧氏空间的映射,其中距离直接对应于人脸相似性的度量。[……]我们的方法使用了经过训练的深度卷积网络来直接优化嵌入本身,而不是像以前的深度学习方法那样使用中间瓶颈层。为了进行训练,我们使用一种新的在线三元组挖掘方法生成的粗略对齐的匹配/不匹配人脸面片的三元组

——Face net:人脸识别和聚类的统一嵌入,2015。

有关 FaceNet 的教程,请参见:

VGGFace(因为没有更好的名字)是由牛津大学视觉几何组(VGG)的 Omkar Parkhi 等人开发的,并在他们 2015 年发表的题为“深度人脸识别”的论文中进行了描述除了一个更好调整的模型,他们的工作重点是如何收集一个非常大的训练数据集,并使用它来训练一个非常深的 CNN 人脸识别模型,使他们能够在标准数据集上获得当时最先进的结果。

…我们展示了如何通过自动化和人工的结合来组装一个非常大规模的数据集(2.6M 图像,超过 2.6K 人)

——深度人脸识别,2015 年。

有关 VGGFace 的教程,请参见:

尽管这些可能是计算机视觉深度学习领域的关键早期里程碑,但进展仍在继续,许多创新集中在损失函数上,以有效地训练模型。

优秀的最新总结见 2018 年论文《深度人脸识别:一项调查》

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

人脸识别论文

深度学习人脸识别论文

文章

摘要

在这篇文章中,你发现了人脸识别的问题,以及深度学习方法如何达到超人的表现。

具体来说,您了解到:

  • 人脸识别是一个广泛的识别或验证照片和视频中的人的问题。
  • 人脸识别是一个由检测、对齐、特征提取和识别任务组成的过程
  • 深度学习模型首先接近,然后超过了人脸识别任务的人类表现。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。