迁移学习之 _ 猫狗大战

#图形图像 #算法实战

1. 迁移学习

  迁移学习(transfer learning)是指将已经学习的知识应用到其它领域,在图像识别问题中,是将训练好的模型通过简单调整来解决新的问题。从图像中提取特征,不一定需要算力强大的 GPU,训练上百层的神经网络。

  卷积神经网络中卷积层和池化层可以抽取图片的几何特征,比如浅层的卷积用于抽取出一些直线,角点等简单的抽象信息,深层的卷积层用于抽取人脸等复杂的抽象信息,最后的全连接层是对图片分类的处理。因此,我们可以使用网络的前 N-1 层提取特征。

  例如,利用在 ImageNet 数据集上训练好的 ResNet50 模型来解决一个自定义的图像分类问题:保留训练好的 ResNet50 模型中卷积层的参数,只去掉最后一个全连接层,将新图像输入训练好的神经网络,利用前 N-1 层的输出作为图片的特征,将 ResNet50 模型作为图片特征提取器,提取得到的特征向量作为输入训练新的单层全连接网络来处理新的分类问题,或者将这些特征代入 SVM,LR 等其它机器学习模型进行训练和预测。

  在数据量足够的情况下,迁移学习的效果往往不如完全重新训练,但是迁移学习所需要的训练时间和训练样本要远远小于训练完整的模型。大多数情况下,更加实用。

2. 比赛介绍

  猫狗大战是 2013 年 Kaggle 上的比赛,它使用 25000 张(约 543M)猫狗图片作为训练集,12500 张 (约 271M) 图片作为测试集,数据都是分辨率 400x400 左右的小图片,目标是识别测试集中的图片是猫还是狗。赛题网址:https://www.kaggle.com/c/dogs-vs-cats。

  对于图像识别,在数据量足量大的情况下,一般使用深度学习中的卷积神经网络(Convolutional Neural Networks, CNN),而本篇将从迁移学习的角度,看看如何应用现有的深度学习模型,从图片中提取特征,供分类器使用。使用此方法,即无需大量学习和训练模型的时间成本,又能解决图片识别相关的大多数问题。

3. 代码分析

(1) 数据及代码位置

  数据及代码位置如下:cat_vs_dog.ipynb 中存放了所有代码,train 目录中存放所有训练数据,注意将猫和狗的图片分开目录存放,test 目录存放测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── cat_vs_dog.ipynb
├── README.md
├── test
│ └── test1
│ ├── 1.jpg
│ ├── 2.jpg
│ └── …
└── train
├── cat
├── cat.1.jpg
├── cat.2.jpg
└── …
└── dog ├── dog.1.jpg
├── dog.2.jpg
└── …

(2) 提取特征

  本例中使用了 InceptionV3,Xception,ResNet50 三种模型,分别提取图片特征,H5 是一种文件存储格式,使用库 h5py 库存取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from keras.models import *
from keras.layers import *
from keras.applications import *
from keras.preprocessing.image import *
import h5py
import warnings
warnings.filterwarnings('ignore')

def get_features(MODEL, width, height, lambda_func=None):
input_tensor = Input((height, width, 3))
x = input_tensor
if lambda_func:

x = Lambda(lambda_func)(x)
base_model = MODEL(input_tensor=x, weights='imagenet', include_top=False)
model = Model(base_model.input, GlobalAveragePooling2D()(base_model.output))
gen = ImageDataGenerator()

# 注意 train 和 test 是图片存储路径
train_generator = gen.flow_from_directory("train", (width, height), shuffle=False,
batch_size=16)
test_generator = gen.flow_from_directory("test", (width, height), shuffle=False,
batch_size=16, class_mode=None)
train = model.predict_generator(train_generator, train_generator.nb_sample)
test = model.predict_generator(test_generator, test_generator.nb_sample)
with h5py.File("data_%s.h5"%MODEL.func_name) as h:
h.create_dataset("train", data=train)
h.create_dataset("test", data=test)
h.create_dataset("label", data=train_generator.classes)

get_features(ResNet50, 224, 224)
get_features(InceptionV3, 299, 299, inception_v3.preprocess_input)
get_features(Xception, 299, 299, xception.preprocess_input)

(3) 训练模型和预测

  特征提取完成后,训练了简单的全连接神经网络,迭代次数为 8 次,并对测试集 test 进行预测,预测结果保存在 y_pred 之中,训练过程保存在 history 之后,此后分析其迭代效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import h5py
import numpy as np
from sklearn.utils import shuffle
from keras.models import *
from keras.layers import *

np.random.seed(12345678)
X_train = []
X_test = []

for filename in ["data_ResNet50.h5", "data_Xception.h5", "data_InceptionV3.h5"]:
with h5py.File(filename, 'r') as h:
X_train.append(np.array(h['train']))
X_test.append(np.array(h['test']))
y_train = np.array(h['label'])

X_train = np.concatenate(X_train, axis=1)
X_test = np.concatenate(X_test, axis=1)
X_train, y_train = shuffle(X_train, y_train)
input_tensor = Input(X_train.shape[1:])
x = Dropout(0.5)(input_tensor)
x = Dense(1, activation='sigmoid')(x)
model = Model(input_tensor, x)
model.compile(optimizer='adadelta',
loss='binary_crossentropy',
metrics=['accuracy'])

history = model.fit(X_train, y_train, batch_size=128, nb_epoch=8, validation_split=0.2)
y_pred = model.predict(X_test, verbose=1)
y_pred = y_pred.clip(min=0.005, max=0.995)

(4) 训练结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import matplotlib.pyplot as plt
%matplotlib inline

def plot_training(history):
acc = history.history['acc']
val_acc = history.history['val_acc']
epochs = range(len(acc))
plt.plot(epochs, acc, 'b')
plt.plot(epochs, val_acc, 'r')
plt.legend(["acc", "val_acc"], loc='best')
plt.title('Training and validation accuracy')
plt.show()

loss = history.history['loss']
val_loss = history.history['val_loss']
plt.plot(epochs, loss, 'b')
plt.plot(epochs, val_loss, 'r')

plt.legend(["loss", "val_loss"], loc='best')
plt.title('Training and validation loss')
plt.show()

plot_training(history)

  使用 matplotlib 库,分别对 8 次迭代的准确率作图比较,从结果可以看出迭代两次之后,精确率就稳定下来。本例中使用了全部图片 25000 张训练模型,正确率相对较高。

(5) 代码下载

  本例中的代码以及少量图片可从 git 下载:https://github.com/xieyan0811/cat_vs_dog 由于整体图片有几百 M,占空间大,特征提取时间长,所以只上传了几百张图片,如果想训练出上图展示的效果,请下载 kaggle 赛题中的所有数据,替换 train 和 test 目录即可,注意,需要把猫和猫的图片存放不同目录下。