虽然有多种方法可以对狗的品种进行分类,但本文将使用预训练的堆叠模型来实现这一目的。在数据集中,包含120种狗的品种,需要对它们进行分类。通过堆叠Resnet50V2和Densenet121,在瓶颈特征提取后得到了良好的结果。这些预训练模型都是在ImageNet开放数据集上训练的。
在进一步讨论之前,先来看一下任务的工作流程。首先,将使用堆叠的预训练模型提取瓶颈特征,然后将训练一个简单的密集神经网络来对提取的特征进行分类。在测试或预测部分,将输入提取的特征,并将其作为输入传递给简单的DNN模型以进行最终预测。预测将是类别编号,然后将该类别编号转换为相应的狗品种标签。
模型堆叠的概念非常简单,只需使用concatenate层连接多个模型的输出即可。将使用堆叠模型进行瓶颈特征提取。
from keras.applications.densenet import DenseNet121, preprocess_input as preprocess_densenet
from keras.applications.resnet_v2 import ResNet50V2 , preprocess_input as preprocess_resnet
from keras.layers.merge import concatenate
input_shape = (331,331,3)
input_layer = Input(shape=input_shape)
# 第一个特征提取器
preprocessor_resnet = Lambda(resnet_preprocess)(input_layer)
resnet50v2 = ResNet50V2(weights = 'imagenet', include_top = False, input_shape = input_shape, pooling ='avg')(preprocessor_resnet)
preprocessor_densenet = Lambda(densenet_preprocess)(input_layer)
densenet = DenseNet121(weights = 'imagenet', include_top = False, input_shape = input_shape, pooling ='avg')(preprocessor_densenet)
merge = concatenate([resnet50v2,densenet])
stacked_model_final = Model(inputs = input_layer, outputs = merge)
stacked_model_final.summary()
已经堆叠了Densenet121和resnet50v2。通过concatenate层,连接了两个预训练模型提取的特征。
将使用Keras和其他库来实现狗品种分类器。在下一篇文章中,将讨论如何将训练好的模型部署到网站上。
1. 加载数据
将使用的数据集可以在Kaggle上找到,下载链接。建议使用Kaggle笔记本而不是将数据集下载到本地机器上。
# 数据路径
train_dir = '/kaggle/input/dog-breed-identification/train/'
labels_dataframe = pd.read_csv('/kaggle/input/dog-breed-identification/labels.csv')
dog_breeds = sorted(list(set(labels_dataframe['breed'])))
n_classes = len(dog_breeds)
class_to_num = dict(zip(dog_breeds, range(n_classes)))
labels_dataframe['file_path'] = labels_dataframe['id'].apply(lambda x:train_dir+f"{x}.jpg")
labels_dataframe['breed'] = labels_dataframe.breed.map(class_to_num)
2. 提取瓶颈特征
这一步非常关键且耗时,将使用堆叠模型提取所有图像的特征。为了避免内存不足的问题,将分批提取特征。提取的特征将作为新X_train,类别编号将作为y_train。
def bottleneck_feature_extractor(df):
img_size = (331,331,3)
data_size = len(df)
batch_size = 20
X = np.zeros([data_size,3072], dtype=np.uint8)
datagen = ImageDataGenerator()
generator = datagen.flow_from_dataframe(df, x_col = 'file_path', class_mode = None, batch_size=20, shuffle = False,target_size = (img_size[:2]),color_mode = 'rgb')
i = 0
for batch_input in tqdm(generator):
batch_input = stacked_model_final.predict(batch_input)
X[i * batch_size : (i + 1) * batch_size] = batch_input
i += 1
if i * batch_size >= data_size:
break
return X
X_train = bottleneck_feature_extractor(labels_dataframe)
X_train包含了所有提取的特征,这些特征将作为最终预测模型的输入。
3. 最终预测模型:
有X_train(提取的特征)和y_train(类别编号的分类值),是时候构建一个最终的预测模型,该模型将在X_train和y_train上进行训练,并返回最终预测的类别编号。
import keras
final_model = keras.models.Sequential([
InputLayer(X.shape[1:]),
Dropout(0.7),
Dense(n_classes, activation='softmax')
])
final_model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
为了高效训练,可以利用回调函数。
from keras.callbacks import EarlyStopping,ModelCheckpoint, ReduceLROnPlateau
# 回调函数
EarlyStop_callback = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
checkpoint = ModelCheckpoint('/kaggle/working/checkpoing', monitor = 'val_loss',mode = 'min',save_best_only= True)
# 基于改进减少学习率
lr = ReduceLROnPlateau(factor = 0.5,patience = 3,monitor = 'val_loss',min_lr = 0.00001)
my_callback=[EarlyStop_callback,checkpoint]
有一个早期停止器,如果训练没有进一步改进,学习率减少器将减少学习率。
4. 训练阶段
在X_train和y_train以及X_test和y_test上训练final_model,并借助validation_split。
# 在提取的特征上训练final_model。
history_graph = final_model.fit(X_train , y_train, batch_size=128, epochs=60, validation_split=0.1 , callbacks = my_callback)
使用validation_split = 0.1,数据集将被分成(90%)和(10%)。
5. 绘制结果
绘制训练历史可以让了解性能和训练情况。这里的history_graph是包含每个epoch所有训练图的历史对象。
import matplotlib.pyplot as plt
fig, (ax_1, ax_2) = plt.subplots(2, 1, figsize=(14, 14))
ax1.plot(history_graph.history['loss'],color = 'b',label = 'loss')
ax1.plot(history_graph.history['val_loss'],color = 'r',label = 'val_loss')
ax_1.set_xticks(np.arange(1, 60, 1))
ax_1.set_yticks(np.arange(0, 1, 0.1))
ax_1.legend(['loss','val_loss'],shadow = True)
ax_2.plot(history_graph.history['accuracy'],color = 'green',label = 'accuracy')
ax_2.plot(history_graph.history['val_accuracy'],color = 'red',label = 'val_accuracy')
ax_2.legend(['accuracy','val_accuracy'],shadow = True)
plt.show()
哇!最后,已经训练了模型,并获得了85%的验证准确率,这是一个好的开始。虽然这不是结束,可以通过一些微调进一步提高这个准确率。
6. 模型保存
保存训练好的模型以供进一步开发。使用保存的模型,可以在网站上部署模型,可以创建一个能够分类狗品种的Android应用。
final_model.save('/kaggle/working/dogbreed.h5')
stacked_model_final.save('/kaggle/working/feature_extractor.h5')
7. 测试和预测
需要遵循在训练过程中遵循的相同工作流程。首先使用stacked_model_final提取单个图像的瓶颈特征,然后将它们转换为张量并传递给最终模型作为输入。最终模型将返回狗品种的类别编号。
img = load_img(img_path, target_size=(331,331))
img = img_to_array(img)
img = np.expand_dims(img,axis = 0) # 这是创建张量(4维)
extracted_features = stacked_model_final.predict(img)
y_pred = predictor_model.predict(extracted_features)
y_pred是包含所有120个狗品种类别编号概率的预测数组。需要挑选具有最高概率的类别编号。获得类别编号后,将类别编号转换回类别标签,使用字典class_to_num。
def get_key(val):
for key, value in class_to_num.items():
if val == value:
return key
pred_codes = np.argmax(y_pred, axis = 1)
predicted_dog_breed = get_key(pred_codes)