大家好,希望已经使用过TensorFlow或其他CNN预训练模型进行过图像分类,并且对如何分类图像有一定的了解。但是,当需要对细节丰富的对象(如狗的品种、猫的品种、叶子疾病)进行分类时,传统方法可能无法给出满意的结果。在这种情况下,更倾向于使用模型堆叠来捕捉更多的细节。让直接进入技术细节。
在数据集中,有120种狗的品种,将使用堆叠预训练模型(TensorFlow,Densenet121)对它们进行分类,这些模型是在Imagenet上训练的。将堆叠这些模型提取的瓶颈特征,以获得更高的准确性,这将取决于堆叠在一起的模型。
堆叠预训练模型是如何工作的?当处理分类问题时,倾向于使用一个主要关注最大池化特征的分类器,这意味着它在训练时不考虑细小或小对象。这就是使用堆叠模型的原因,它们可以帮助根据突出和细节对象特征轻松分类图像。
为了创建堆叠模型,需要使用两个或更多的分类架构,如Resnet、Vgg、Densenet等。这些分类器以图像为输入,并根据它们的架构生成特征矩阵。通常,每个分类器会按照以下阶段进行,以创建一个特征向量:
1. 卷积:这是生成特征图的过程,这些特征图描绘了图像特定的特征,如边缘、锐度等。
2. 最大池化:在此过程中,从使用卷积过程生成的特征图中提取突出特征。
3. 展平:在此过程中,最终的特征图被转换为特征向量。
在从不同模型获得特征向量后,将它们堆叠在一起,以形成一个特征矩阵,然后用作最终神经网络模型的输入,其工作是将这些矩阵分类为最终的数据类别。
现在知道了堆叠模型的工作原理,让使用Vgg16和Resnet架构设计一个堆叠模型。
在继续之前,让看看如何堆叠模型以获得瓶颈特征。
from keras.applications.resnet_v2 import ResNet50V2, preprocess_input as resnet_preprocess
from keras.applications.densenet import DenseNet121, preprocess_input as densenet_preprocess
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 = Model(inputs = input_layer, outputs = merge)
stacked_model.summary()
在这里,堆叠了两个模型(Densenet121和resnet50V2),两者都有include_top = False,这意味着只提取瓶颈特征,然后使用concatenate层将它们合并。
现在知道如何创建堆叠模型,可以开始创建狗品种分类器。
上述图表显示了训练和推理分类器的工作流程。将在图像上训练堆叠模型,这将生成一个特征矩阵,然后用作另一个神经网络的输入特征以对狗品种进行分类。
现在得到了一个高层次的方法图。让看看训练和推理的逐步程序。
加载数据集。将使用Kaggle上的数据集,可以在这里找到(https://www.kaggle.com/c/dog-breed-identification)。将加载数据到一个名为labels_dataframe的pandas dataframe中,并将每个y标签转换为数值。
# 数据路径
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)
现在需要将breed列转换为y_train,使用to_categorical。
from keras.utils import to_categorical
y_train = to_categorical(labels_dataframe.breed)
在这一步中,将使用刚刚设计的stacked_model进行瓶颈特征提取,这些提取的特征将成为X_train,用于训练。使用批次,以便不会有任何OOM(内存不足)问题。
def 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 input_batch in tqdm(generator):
input_batch = stacked_model.predict(input_batch)
X[i * batch_size : (i + 1) * batch_size] = input_batch
i += 1
if i * batch_size >= data_size:
break
return X
X_train = feature_extractor(labels_dataframe)
这里X_train(包括预训练模型提取的所有特征)是新X_train,将用于最终模型。
现在将创建一个简单的模型,它将接受X_train(由堆叠模型提取的特征)和y_train(分类值),这将是最终的预测模型。
import keras
predictor_model = keras.models.Sequential([
InputLayer(X.shape[1:]),
Dropout(0.7),
Dense(n_classes, activation='softmax')
])
predictor_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(monitor = 'val_loss',factor = 0.5,patience = 3,min_lr = 0.00001)
my_callback=[EarlyStop_callback,checkpoint]
这段代码将自动保存最佳时期,早期停止,如果训练中没有进一步的改进,它将降低学习率。
现在将在X_train和y_train上训练predictor_model,X_test,y_test将自动由validation_split获取。
# 在提取的特征上训练简单的DNN。
history_graph = predictor_model.fit(X_train , y_train,
batch_size=128,
epochs=60,
validation_split=0.1 ,
callbacks = my_callback)
使用validation_split = 0.1,将数据集分为训练(90%)和测试(10%)。
将绘制训练的历史,并计算表现。这里history_graph是将用来绘制每个时期的历史的History对象。
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12))
ax1.plot(history_graph.history['val_loss'],color = 'r',label = 'val_loss')
ax1.set_xticks(np.arange(1, 60, 1))
ax1.set_yticks(np.arange(0, 1, 0.1))
ax1.legend(['loss','val_loss'],shadow = True)
ax2.plot(history_graph.history['accuracy'],color = 'green',label = 'accuracy')
ax2.plot(history_graph.history['val_accuracy'],color = 'red',label = 'val_accuracy')
ax2.legend(['accuracy','val_accuracy'],shadow = True)
plt.show()
已经有效地训练了模型,并获得了大约85%的验证准确率。当然,可以进一步提高它,将在最后讨论。
最后,将保存预训练模型,以便以后用于推理。
dnn.save('/kaggle/working/dogbreed.h5')
stacked_model.save('/kaggle/working/feature_extractor.h5')
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.predict(img)
y_pred = predictor_model.predict(extracted_features)
y_pred
是预测数组的形状(1,120)。y_pred是一个数组,具有每个类别的概率。现在需要找到具有最高概率的类别标签,然后使用已经定义的字典class_to_num将类别编号转换为类别标签。