Kaggle_Cat&Dog

1. 前言

  • 本项目是kaggle中的入门级项目,本篇博文采用Keras+CNN网络模型完成该项目。
  • kaggle的项目地址:链接
  • GitHub地址:链接

2. 数据集介绍

  • 数据包含两部分,训练集和测试集,训练集有25000张图片,测试集有12500张图片。
  • 在训练集的图片名称中包含了图片的标签信息,而测试集的图片名称代表的图片的id,提交结果的时候要求提交图片的id和预测对应图片的标签(1表示dog,0表示cat)。

  • 训练集图片信息大致如下:

  • 训练集中的Cat和Dog的图片数量大致相同,在图片的名字中,含有该图片的类别。

3. 项目流程

  • 准备训练数据:对文件名的cat和dog进行截取,并用0和1代替,存储至pandas的DataFrame结构中
  • 搭建网络模型:这里搭建的是一个普通的CNN模型,具体参数可见下文。
  • Callbacks回调函数:Keras框架的回调应用,作用是监视loss等指标,减小学习率和停止训练等操作
  • 划分训练、验证集:按照4:1的比例划分训练数据和验证数据。
  • 训练数据生成器:数据增强、构建训练数据生成器
  • 验证数据生成器:无数据增强,构建验证数据生成器
  • fit model:投入数据,开始训练
  • 测试数据部分:保存训练好的模型进行测试集上测试
  • 提交结果至kaggle平台

4.流程代码详解

4.1 导入相关包及设定超参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import pandas as pd
# 数据生成器,数据增强
from keras.preprocessing.image import ImageDataGenerator, load_img
from keras.utils import to_categorical
from sklearn.model_selection import train_test_split # 随机划分工具,训练集验证集
import matplotlib.pyplot as plt
import random
import os

FAST_RUN = False
IMAGE_WIDTH=128
IMAGE_HEIGHT=128
IMAGE_SIZE=(IMAGE_WIDTH, IMAGE_HEIGHT)
IMAGE_CHANNELS=3

4.2 准备训练数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 准备训练数据
filenames = os.listdir("/kaggle/input/dogs-vs-cats/train/train")
categories = [] # 存放类别
for filename in filenames:
category = filename.split('.')[0] # 取出cat or dog
if category == 'dog':
categories.append(1)
else:
categories.append(0)
# 将文件与分好类别相对应
df = pd.DataFrame({
'filename': filenames,
'category': categories
})

df.head()
  • df.head()会显示DataFrame中的前5条信息。

4.3 搭建网络模型

  • 模型代码如下:
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
from keras.models import Sequential
from keras.layers import *

model = Sequential()

model.add(Conv2D(32,(3,3),activation = 'relu',input_shape=(IMAGE_WIDTH,IMAGE_HEIGHT,IMAGE_CHANNELS))) #卷积层
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

# 全连接层
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax')) # 2 猫狗二分类问题

model.compile(loss='categorical_crossentropy',optimizer='rmsprop',metrics=['accuracy'])

model.summary() # 输出模型各层的具体参数情况
  • 模型结构可视化如下:

4.4 Callbacks回调函数

  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from keras.callbacks import EarlyStopping, ReduceLROnPlateau

# 如发现loss相比上一个epoch训练没有下降,则经过patience个epoch后停止训练。
earlystop = EarlyStopping(patience=10)

learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', # 被监控的量
# 当patience个epoch过去而模型性能不提升时,学习率减少的动作会被触发
patience=2,
verbose=1, # 日志打印格式: 进度条
# 每次减少学习率的因子,学习率将以lr = lr*factor的形式被减少
factor=0.5,
min_lr=0.00001) # min_lr:学习率的下限

callbacks = [earlystop, learning_rate_reduction]

4.5 训练数据划分

  • 因为之后的数据生成器要求传入字符串类型的类别,所以这里先将0和1转为cat,dog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将0,1替换为字符串,后面的image genaretor用得到
df["category"] = df["category"].replace({0: 'cat', 1: 'dog'})

# train_test_split函数将矩阵随机划分为训练子集和验证子集,返回划分好的训练集测试集样本和验证集测试集标签。
# test_size 为测试集样本数目与原始样本数目之比;
# random_state 随机数种子
train_df, validate_df = train_test_split(df, test_size=0.20, random_state=42)

# reset_index:重置索引列
train_df = train_df.reset_index(drop=True)
validate_df = validate_df.reset_index(drop=True)

train_df['category'].value_counts().plot.bar() # 查看训练集10000cat10000dog
validate_df['category'].value_counts().plot.bar() # 查看验证集2500cat和dog
  • 可视化结果如下:

4.6 训练集数据生成器

  • 生成器作用:此部分对图像进行数据增强,及生成器为了fit model
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
total_train = train_df.shape[0] #20000条训练数据
total_validate = validate_df.shape[0]
batch_size=15

# 对训练集
# 数据增强操作:ImageDataGenerator()是keras.preprocessing.image模块中的图片生成器,同时也可以在batch中对数据进行增强,扩充数据集大小,增强模型的泛化能力。
train_datagen = ImageDataGenerator(
rotation_range=15, # 旋转范围
rescale=1./255, # rescale参数指定将图像张量的数字缩放
shear_range=0.1, # float, 透视变换的范围
zoom_range=0.2, # 缩放范围
horizontal_flip=True, # 水平反转
width_shift_range=0.1, # 水平平移范围
height_shift_range=0.1 # 垂直平移范围
)

# 返回值:一个生成 (x, y) 元组的 DataFrameIterator, 其中:
# x 是一个包含一批尺寸为 (batch_size, *target_size, channels) 的图像样本的 numpy 数组,
# y 是对应的标签的 numpy 数组。
train_generator = train_datagen.flow_from_dataframe(
train_df, # Pandas dataframe
"/kaggle/input/dogs-vs-cats/train/train", # 目标目录的路径,包含在 dataframe 中的所有图像
x_col='filename', # 字符串,dataframe 中包含目标图像文件夹的目录的列
y_col='category', # 字符串或字符串列表,dataframe中作为目标数据的列
target_size=IMAGE_SIZE, # 整数元组 (height, width)图都会调整到这个维度。
class_mode='categorical', # "categorical" 将是 2D one-hot 编码标签
batch_size=batch_size # 批量数据的尺寸
)

4.7 验证集数据生成器

  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 对验证集
# 验证集不用数据增强,只需缩放
validation_datagen = ImageDataGenerator(rescale=1./255)
# 生成对应的图像与标签的(x,y)的数组
validation_generator = validation_datagen.flow_from_dataframe(
validate_df,
"/kaggle/input/dogs-vs-cats/train/train",
x_col='filename',
y_col='category',
target_size=IMAGE_SIZE,
class_mode='categorical',
batch_size=batch_size
)

4.8 fit model

  • 由于kaggle的kernel会掉线,于是将epochs设置成了20次,有环境的可以多训练
  • Keras Model 上的 fit()fit_generator()方法返回一个 History 对象。History.history 属性是一个记录了连续迭代的训练/验证(如果存在)损失值和评估值的字典。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 将数据fit model
# Keras中的fit()函数传入的x_train和y_train是被完整的加载进内存的,当然用起来很方便.
# 但是如果我们数据量很大,那么是不可能将所有数据载入内存的,必将导致内存泄漏,这时候我们可以用fit_generator函数来进行训练
epochs=3 if FAST_RUN else 20 # 训练轮次
history = model.fit_generator(
train_generator, # 训练数据生成器,20000个(x,y)元组
epochs=epochs, # 轮次
validation_data=validation_generator, # 验证集生成器
# 仅当 validation_data 是一个生成器时才可用。 在停止前 generator 生成的总步数(样本批数)
validation_steps=total_validate//batch_size,
# 一个 epoch 完成并开始下一个 epoch 之前从 generator 产生的总步数(批次样本)
steps_per_epoch=total_train//batch_size,
callbacks=callbacks # 在训练时调用的一系列回调函数
)

model.save_weights("model.h5") # 保存模型
  • 训练结果:这里放一张之前的一个训练截图,新的训练截图没来及截kernel就中断了。

4.9 描绘history存储值的变化

  • History.history 属性是一个记录了连续迭代的训练/验证(如果存在)损失值和评估值的字典。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# plt.subplots()返回一个包含figure和axes对象的元组,2和1为子图的行列数
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12))
ax1.plot(history.history['loss'], color='b', label="Training loss")
ax1.plot(history.history['val_loss'], color='r', label="validation loss")
ax1.set_xticks(np.arange(1, epochs, 1)) # 设置x轴的起始点(不包括终点)及步长
ax1.set_yticks(np.arange(0, 1, 0.1)) # 设置y轴的起始点及步长

ax2.plot(history.history['acc'], color='b', label="Training accuracy")
ax2.plot(history.history['val_acc'], color='r',label="Validation accuracy")
ax2.set_xticks(np.arange(1, epochs, 1))

legend = plt.legend(loc='best', shadow=True)
plt.tight_layout()
plt.show()
  • 运行结果:

4.10 准备测试数据

  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test_filenames = os.listdir("/kaggle/input/dogs-vs-cats/test1/test1")
test_df = pd.DataFrame({
'filename': test_filenames
})
nb_samples = test_df.shape[0] # 测试数据的数量

test_gen = ImageDataGenerator(rescale=1./255)
test_generator = test_gen.flow_from_dataframe( # 测试集数据生成器
test_df,
"/kaggle/input/dogs-vs-cats/test1/test1",
x_col='filename',
#classmode=None, 不返回任何标签(生成器只会产生批量的图像数据,这对使用 model.predict_generator(), model.evaluate_generator() 等很有用)
y_col=None,
class_mode=None,
target_size=IMAGE_SIZE,
batch_size=batch_size,
shuffle=False
)

4.11 预测测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用测试数据生成器进行预测,np.ceil取大于等于该值的最小整数
# 返回一个numpy的array数组,有每个类别的概率,n行2列,每行对应两个概率
predict = model.predict_generator(test_generator, steps=np.ceil(nb_samples/batch_size))
# 分类问题,-1代表取最后一维最大的那个概率对应的索引;
test_df['category'] = np.argmax(predict, axis=-1)

# train_generator.class_indices:{'cat':0,'dog':1}
# 获取训练数据集中cat&dog与0和1的对应关系,然后翻转过来,来使预测结果中的索引转变为真的标签
# 建立代码标签与真实标签的对应关系,这里预测得到的索引index对应反转过来的真实标签索引,如下:
# 相对应的具体原因:训练时采用了2D的one-hot编码,所以训练,预测得到的都是2D的
label_map = dict((v,k) for k,v in train_generator.class_indices.items())
test_df['category'] = test_df['category'].replace(label_map)

test_df['category'] = test_df['category'].replace({ 'dog': 1, 'cat': 0 })
  • 输出predict截图

  • axis=0,1,-1实例:0表示第一维,1是第二维,-1是最后一维。
  • 更多可看博文:链接

4.12 可视化预测结果与真实图片对比

  • 将test_df中的图片找出并展示,在文件名后面添加预测的类别(1,0),查看准确率。
  • 选取了开头的6张图片,
1
2
3
4
5
6
7
8
9
10
11
12
13
sample_test = test_df.head(6)
sample_test.head()
plt.figure(figsize=(12, 24))
for index, row in sample_test.iterrows():
filename = row['filename']
category = row['category']
img = load_img("/kaggle/input/dogs-vs-cats/test1/test1/"+filename, target_size=IMAGE_SIZE)
plt.subplot(2, 3, index+1) # 2行3列个子图
plt.imshow(img)
# "{}".format(category)将数字转为str类型,以便拼接
plt.xlabel(filename + '(' + "{}".format(category) + ')' )
plt.tight_layout()
plt.show()
  • 运行结果:

4.13 Submission

  • 这里要提交测试数据集预测之后对应的结果,结果为csv类型,包含id和label
1
2
3
4
5
6
submission_df = test_df.copy()
submission_df['id'] = submission_df['filename'].str.split('.').str[0]
submission_df['label'] = submission_df['category']
# drop是删除函数,默认删除列,当指定axis=1,删除对应的列,inplace=True说明在df上直接删除
submission_df.drop(['filename', 'category'], axis=1, inplace=True)
submission_df.to_csv('submission.csv', index=False)
-------------The End-------------
谢谢大锅请我喝杯阔乐~