猫狗识别-CNN与VGG实现

2022-10-14,,,

本次项目首先使用CNN卷积神经网络模型进行训练,最终训练效果不太理想,出现了过拟合的情况。准确率达到0.72,loss达到0.54。使用预训练的VGG模型后,在测试集上准确率达到0.91,取得了不错的改进效果。

数据集

本次项目使用The Asirra 数据集,Asirra(Animal Species Image Recognition for Restricting Access)是一套人机交互证明系统(Human Interactive Proof),它使用猫和狗的图片来验证网站访问者是真人还是机器人。
Asirra使用的猫狗图片来自于世界上最大的流浪动物救助网站petfinder.com,图片被数千个流浪动物救助者进行手动分类和标准。它也为微软研究院提供了超过300万张猫狗图片。
我们使用的数据集包含25000张已标注的猫狗图片,和12500张未标注的测试图片。

数据处理

由于测试数据集的图片没有标签,所以我们从训练集中划出一部分作为测试集。

import numpy as np
import pandas as pd
from keras.preprocessing.image import ImageDataGenerator, load_img
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import random
import os filenames= os.listdir("./train") categories=[] for filename in filenames:
#从文件名中分离标签,0代表猫,1代表狗
category=filename.split(".")[0]
if category=="dog":
categories.append(1)
else:
categories.append(0) df=pd.DataFrame({'filename':filenames,
'category':categories
})
df.shape #(25000,2)
df.head()

随机展示一张图片。

random_Img=random.choice(filenames)
image=load_img("./train/"+random_Img)
plt.imshow(image)

我们也可以发现数据集中的图片尺寸并不一致,有些图片中的猫狗太小不足以识别,或者只露了部分部位,这些都是属于质量不高的数据。

卷积神经网络

我们尝试使用卷积神经网络来进行模型训练

from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, Activation, BatchNormalization model1 = Sequential() model1.add(Conv2D(128, (3, 3), activation='relu', input_shape=(128,128,3)))
model1.add(BatchNormalization())
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(Dropout(0.25)) model1.add(Conv2D(64, (3, 3), activation='relu'))
model1.add(BatchNormalization())
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(Dropout(0.25)) model1.add(Conv2D(64, (3, 3), activation='relu'))
model1.add(BatchNormalization())
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(Dropout(0.25)) model1.add(Flatten())
model1.add(Dense(512, activation='relu'))
model1.add(BatchNormalization())
model1.add(Dropout(0.2))
model1.add(Dense(2, activation='softmax')) # 2 because we have cat and dog classes model1.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

使用sklearn的train_test_split把数据集划分为训练集和测试集,测试集的比例test_size设置为0.20,随机数种子random_state设置为任意整数,这样不管重复多少次划分结果都是一致的。该函数默认参数 shuffle为True,对数据集进行随机打散。

train_df,validate_df=train_test_split(df,test_size=.20,random_state=4)
train_df=train_df.reset_index(drop=True)
validate_df=validate_df.reset_index(drop=True) total_train=train_df.shape[0]
total_validate=validate_df.shape[0] #5000
batch_size=16

数据增强

为了避免过拟合,增加我们的训练数据集大小,通过对图片进行旋转、移位、缩放、亮度改变、色调改变、增加噪音等转换成一张与已有图片类似但却稍有不同的新图片。对于人眼来说这些图片很相似,但是对于一个未训练完成的机器学习模型来说却是不同的。卷积神经网络可以学习图片的细节特征,对于旋转、移位等具有不变性。通过数据增强可以强化我们训练出的模型的鲁棒性。
keras中提供了ImageDataGenerator类来进行图像数据增强,可以在训练过程中进行图像的随机变化,增加训练数据;还附带赠送了获取数据batch生成器对象的功能,省去了手工再去获取batch数据的部分。该类可以对图像进行多种变换操作,默认的初始化参数如下:

# keras/preprocessing/image.py
class ImageDataGenerator(image.ImageDataGenerator):
def __init__(self,
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
zca_epsilon=1e-6,
rotation_range=0,
width_shift_range=0.,
height_shift_range=0.,
brightness_range=None,
shear_range=0.,
zoom_range=0.,
channel_shift_range=0.,
fill_mode='nearest',
cval=0.,
horizontal_flip=False,
vertical_flip=False,
rescale=None,
preprocessing_function=None,
data_format=None,
validation_split=0.0,
dtype=None):

在这里我们对训练数据进行旋转、标准化、剪切范围、缩放范围、水平翻转、宽度变换、高度变换。对测试集的图片就无需进行数据增强了,只需要按照训练集一样进行rescaling标准化。
ImageDataGenerator的flow_from_dataframe用于从pandas的DataFrame中载入图片数据,同时自动进行数据增强和生成批数据。我们之前定义的tran_df,一列为图片文件名,一列为图片对于的分类。传入flow_from_dataframe后,会通过我们定义的dataframe、文件路径读取图片,target_size设置目标图像尺寸,batch_size则是批大小,x_col和y_col为dataframe中的图片名和标签列名称。对于class_mode的设置,‘categorical’为one-hot编码。该方法shuffle参数默认为True,即也默认打散数据。

train_datagen=ImageDataGenerator(
rotation_range=30,
rescale=1./255,
shear_range=0.4,
zoom_range=0.4,
horizontal_flip=True,
width_shift_range=0.4,
height_shift_range=0.4
) train_generator=train_datagen.flow_from_dataframe( train_df,
"./train",
x_col='filename',
y_col='category',
target_size=(128,128),
class_mode='categorical',
batch_size=batch_size ) validate_datagen=ImageDataGenerator(
rescale=1./255
) validate_generator=validate_datagen.flow_from_dataframe(
validate_df,
"./train",
x_col='filename',
y_col='category',
target_size=(128,128),
class_mode='categorical',
batch_size=batch_size )

回调函数callback

回调函数是一个函数的合集,会在训练的阶段中所使用。你可以使用回调函数来查看训练模型的内在状态和统计。你可以传递一个列表的回调函数(作为 callbacks 关键字参数)到 Sequential 或 Model 类型的 .fit() 方法。在训练时,相应的回调函数的方法就会被在各自的阶段被调用。通过定义回调函数,我们可以检测训练过程并在符合条件时执行我们的回调函数。
EarlyStop用于监测定义的指标,当它不再提升时就停止训练。用于在训练中监测loss指标,防止过拟合。

EarlyStopping
keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=0, verbose=0, mode='auto', baseline=None, restore_best_weights=False)
当被监测的数量不再提升,则停止训练。 参数 monitor: 被监测的数据。
min_delta: 在被监测的数据中被认为是提升的最小变化, 例如,小于 min_delta 的绝对变化会被认为没有提升。
patience: 没有进步的训练轮数,在这之后训练就会被停止。
verbose: 详细信息模式。
mode: {auto, min, max} 其中之一。 在 min 模式中, 当被监测的数据停止下降,训练就会停止;在 max 模式中,当被监测的数据停止上升,训练就会停止;在 auto 模式中,方向会自动从被监测的数据的名字中判断出来。
baseline: 要监控的数量的基准值。 如果模型没有显示基准的改善,训练将停止。
restore_best_weights: 是否从具有监测数量的最佳值的时期恢复模型权重。 如果为 False,则使用在训练的最后一步获得的模型权重。

ReduceLROnPlateau用于评估当模型停止提升时降低学习率。

keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10, verbose=0, mode='auto', min_delta=0.0001, cooldown=0, min_lr=0)
当标准评估停止提升时,降低学习速率。 当学习停止时,模型总是会受益于降低 2-10 倍的学习速率。 这个回调函数监测一个数据并且当这个数据在一定「有耐心」的训练轮之后还没有进步, 那么学习速率就会被降低。 例子 reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2,
patience=5, min_lr=0.001)
model.fit(X_train, Y_train, callbacks=[reduce_lr])
参数 monitor: 被监测的数据。
factor: 学习速率被降低的因数。新的学习速率 = 学习速率 * 因数
patience: 没有进步的训练轮数,在这之后训练速率会被降低。
verbose: 整数。0:安静,1:更新信息。
mode: {auto, min, max} 其中之一。如果是 min 模式,学习速率会被降低如果被监测的数据已经停止下降; 在 max 模式,学习塑料会被降低如果被监测的数据已经停止上升; 在 auto 模式,方向会被从被监测的数据中自动推断出来。
min_delta: 对于测量新的最优化的阀值,只关注巨大的改变。
cooldown: 在学习速率被降低之后,重新恢复正常操作之前等待的训练轮数量。
min_lr: 学习速率的下边界。

在这里我们定义Early Stopping的patience为10,即val_loss十轮训练后没有下降则训练停止。定义ReduceLROnPlateau的监测参数monitor为'val_acc'即验证集的accuracy,patience为2,减小因子为0.5(学习率每次更次为上次的0.5),学习率下边界为0.00001(学习率降到0.00001后不再下降)。

from keras.callbacks import EarlyStopping, ReduceLROnPlateau

earlystop = EarlyStopping(patience=10)
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc',
patience=2,
verbose=1,
factor=0.5,
min_lr=0.00001)
callbacks = [earlystop, learning_rate_reduction]

训练卷积神经网络模型

history1=model1.fit_generator(
train_generator,
steps_per_epoch=total_train//batch_size,
epochs=10,
verbose=1,
callbacks=callbacks,
validation_data=validate_generator,
validation_steps=total_validate//batch_size,
)

训练过程如下:

加载预训练VGG模型

TensorFlow为我们提供了预训练的VGG模型,我们可以很方便的调用它。VGG预训练模型在大规模图像数据集上训练完成后,在图像识别和分类任务上表现良好。我们可以使用预训练模型进行迁移学习(transfer learning),在类似的图片识别任务上取得良好的效果。

from tensorflow.keras.applications import VGG16
from keras.models import Model
from keras.layers import Dense,Dropout
from keras.backend import pool2d
from keras.layers.pooling import GlobalAveragePooling2D base_model = VGG16(input_shape = (128, 128, 3), # Shape of our images
include_top = False, # Leave out the last fully connected layer
weights = 'imagenet')

我们不需要训练VGG模型,可以以它为基础,添加更多的隐藏层。只有后面被添加的层才会被训练。我们在VGG模型的基础上添加了输出为512维的全连接层,一个平均池化层,一个rate为0.5的Dropout层,一个softmax全连接层。

base_model.trainable=False

model=base_model.output

model=Dense(512, activation='relu')(model)
model= GlobalAveragePooling2D()(model)
model=Dropout(rate=0.5)(model)
model=Dense(2, activation='softmax')(model)
model=Model(inputs=base_model.inputs, outputs=model) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary()

定义好后的模型结构如下:

Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 128, 128, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 128, 128, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 128, 128, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 64, 64, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 64, 64, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 64, 64, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 32, 32, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 32, 32, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 32, 32, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 32, 32, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 16, 16, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 16, 16, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 16, 16, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 16, 16, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 8, 8, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 8, 8, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 8, 8, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 8, 8, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 4, 4, 512) 0
_________________________________________________________________
dense_2 (Dense) (None, 4, 4, 512) 262656
_________________________________________________________________
global_average_pooling2d (Gl (None, 512) 0
_________________________________________________________________
dropout_4 (Dropout) (None, 512) 0
_________________________________________________________________
dense_3 (Dense) (None, 2) 1026
=================================================================
Total params: 14,978,370
Trainable params: 263,682
Non-trainable params: 14,714,688
_________________________________________________________________

VGG模型训练

与之前CNN网络模型训练一样,我们进行数据集的载入和处理,打散之后生成批训练数据。

from sklearn.model_selection import train_test_split
from tqdm import tqdm
import cv2
from sklearn.utils import shuffle df["category"] = df["category"].replace({0: 'cat', 1: 'dog'}) #进行数据集划分交叉验证
train_df, validate_df = train_test_split(df, test_size=0.20, random_state=42)
train_df = train_df.reset_index(drop=True)
validate_df = validate_df.reset_index(drop=True) total_train = train_df.shape[0]
total_validate = validate_df.shape[0]
batch_size=4 #生成训练数据
train_datagen=ImageDataGenerator(
rescale=1/255
) train_generator=train_datagen.flow_from_dataframe(train_df,
'./train',
x_col='filename',
y_col='category',
weight_col=None,
target_size=(128, 128),
class_mode='categorical',
batch_size=batch_size,
)
#生成测试数据
validation_datagen = ImageDataGenerator(rescale=1./255)
validation_generator = validation_datagen.flow_from_dataframe(
validate_df,
"./train",
x_col='filename',
y_col='category',
target_size=(128,128),
class_mode='categorical',
batch_size=batch_size
)
#定义回调函数
earlystop = EarlyStopping(patience=10)
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc',
patience=2,
verbose=1,
factor=0.5,
min_lr=0.00001)
callbacks = [earlystop, learning_rate_reduction]
#进行模型训练
history = model.fit_generator(
train_generator,
epochs=12,
validation_data=validation_generator,
validation_steps=total_validate//batch_size,
steps_per_epoch=total_train//batch_size,
callbacks=callbacks,
verbose=1
)

训练的过程如下,最终在验证集上accuracy达到0.91。

Epoch 1/12
5000/5000 [==============================] - 99s 20ms/step - loss: 0.3466 - accuracy: 0.8461 - val_loss: 0.2575 - val_accuracy: 0.8920
Epoch 2/12
5000/5000 [==============================] - 90s 18ms/step - loss: 0.2886 - accuracy: 0.8752 - val_loss: 0.2365 - val_accuracy: 0.8992
Epoch 3/12
5000/5000 [==============================] - 89s 18ms/step - loss: 0.2625 - accuracy: 0.8877 - val_loss: 0.2296 - val_accuracy: 0.9016
Epoch 4/12
5000/5000 [==============================] - 90s 18ms/step - loss: 0.2415 - accuracy: 0.8970 - val_loss: 0.2215 - val_accuracy: 0.9076
Epoch 5/12
5000/5000 [==============================] - 90s 18ms/step - loss: 0.2358 - accuracy: 0.9004 - val_loss: 0.2331 - val_accuracy: 0.8982
Epoch 6/12
5000/5000 [==============================] - 90s 18ms/step - loss: 0.2195 - accuracy: 0.9074 - val_loss: 0.2387 - val_accuracy: 0.9002
Epoch 7/12
5000/5000 [==============================] - 89s 18ms/step - loss: 0.2106 - accuracy: 0.9096 - val_loss: 0.2237 - val_accuracy: 0.9094
Epoch 8/12
5000/5000 [==============================] - 90s 18ms/step - loss: 0.2028 - accuracy: 0.9155 - val_loss: 0.2173 - val_accuracy: 0.9126
Epoch 9/12
5000/5000 [==============================] - 95s 19ms/step - loss: 0.1908 - accuracy: 0.9204 - val_loss: 0.2285 - val_accuracy: 0.9076
Epoch 10/12
5000/5000 [==============================] - 94s 19ms/step - loss: 0.1821 - accuracy: 0.9247 - val_loss: 0.2242 - val_accuracy: 0.9112
Epoch 11/12
5000/5000 [==============================] - 94s 19ms/step - loss: 0.1740 - accuracy: 0.9278 - val_loss: 0.2364 - val_accuracy: 0.9044
Epoch 12/12
5000/5000 [==============================] - 94s 19ms/step - loss: 0.1644 - accuracy: 0.9322 - val_loss: 0.2335 - val_accuracy: 0.9106

猫狗识别-CNN与VGG实现的相关教程结束。

《猫狗识别-CNN与VGG实现.doc》

下载本文的Word格式文档,以方便收藏与打印。