Keras搭建卷积神经网络,识别小猫小狗
Keras是一个高度封装的神经网络API,能快速将自己的Idea识别为结果。我们可以通过Sequential的API迅速实现一个神经网络模型。这篇文章记录用简单的Sequential模型对猫狗图片进行分类。
环境版本
本人环境的配置如下:
包 | 版本 |
---|---|
Keras | 1.0.8 |
numpy | 1.19.2 |
pandas | 1.3.5 |
Tensorflow-gpu | 2.1.0 |
scikit-learn | 1.0.2 |
opencv-python | 4.5.5.62 |
matplotlib | 3.5.1 |
数据集介绍与下载
Kaggle在2013年举办了一场比赛,让我们编写程序识别猫和狗的图片,数据集由微软研究院提供,包括24946张由动物收容所手动分类的猫狗图片。大家可以在这个网址下载数据集:https://www.microsoft.com/en-us/download/details.aspx?id=54765
2013年是神经网络刚刚重获新生的年代,最先进的分类器能在这个数据集达到82.7%的分类准确率。现在我们可以轻松地达到这个水平。
数据预处理
本实验的数据预处理所需的步骤:
- 将数据集乱序(shuffle)
- 划分训练集和测试集
- 调整图像大小
# 前期准备:GPU的相关配置
import tensorflow as tf
physical_devices = tf.config.experimental.list_physical_devices('GPU')
assert len(physical_devices) > 0, "Not enough GPU hardware devices available"
tf.config.experimental.set_memory_growth(physical_devices[0], True)
# 导包
import numpy as np
import os
import cv2
import matplotlib as mpl
import matplotlib.pyplot as plt
from tensorflow import keras
from functools import partial
import random
#指定数据及路径和包含的类别(标签)
data_path = 'C:/Users/galaxer/Pictures/datasets/dog and cat/PetImages/'
categories = ['Dog', 'Cat']
用OpenCV修改图片大小,修改size为$50\times50$
# 定义图像为50*50的大小
img_size = 50
train_data = []
def create_train_data():
for category in categories:
path = os.path.join(data_path, category) # 用于拼接路径
class_num = categories.index(category)
for img in os.listdir(path):
try:
# 以灰度模式读取图片
img_array = cv2.imread(os.path.join(path, img), cv2.IMREAD_GRAYSCALE)
# 修改图片大小为50*50
new_array = cv2.resize(img_array, (img_size, img_size))
# 以修后图片+标签对应索引的列表为单位,添加进数据集中
train_data.append([new_array, class_num])
except Exception as e:
pass
# 运行我们自己写的函数,得到了一个包含特征(图片)与标签(索引0 or 1)的训练集
create_train_data()
关于拼接文件路径的建议
我们在拼接路径时,最好使用os.path.join()
方法进行拼接,不要使用字符串的简单拼接如path+='category'
。因为python在Windows,Mac和Linux平台中的路径编码格式有细微的差别,使用字符串拼接可能会引起一些非常隐蔽难以排查的Bug(心酸血泪史)。
# 对训练集进行乱序
random.shuffle(train_data)
# 然后以机器学习的标准来命名,保存训练集
X=[] # 特征集
y=[] # 标签
for feature, label in train_data:
X.append(feature)
y.append(label)
# 最后的1代表grayscale,因为是灰度只有一个通道
X = np.array(X).reshape(-1, img_size, img_size, 1)
用pickle保存一下数据集
import pickle
# 保存数据
pickle_out = open('X.pickle','wb')
pickle.dump(X, pickle_out)
pickle_out.close()
pickle_out = open('y.pickle','wb')
pickle.dump(y, pickle_out)
pickle_out.close()
# 读取数据示例:
# pickle_in = open('X.pickle','rb')
# X = pickle.load(pickle_in)
实现卷积神经网络
接下来让我们动手搭一个卷积神经网络!
# 全连接层 卷积层 池化层
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, MaxPooling2D
import pickle
# 加载特征集和标签集数据
X = pickle.load(open("X.pickle","rb"))
y = pickle.load(open("y.pickle","rb"))
# 对数据进行简单的归一化,将每像素的取值范围缩小至[0,1]
X = X / 255
len(X)
我们用最简单的Sequential模型来实现卷积网络,约定命名格式如下:
(x为层数下标)
- $C_X$:卷积层
- $C_X$:池化层
- $C_X$:全连接层
#1和2以后直接复制就可!!
# 1.模型命名
import time
model_name = "kaggle_cat_dog-cnn-64x2-{}".format(time.strftime("run_%Y_%m_%d-%H_%M_%S"))
# 2.写tensorboard的一个回调
#这个函数时用来生成日志文件的名称及目录的
def get_run_logdir(dir_path):
# 先判断有没有存放日志的文件夹
folder = os.path.exists(dir_path)
if not folder:
os.makedirs(path)
print("--- Create folder successfully ---")
final_path = os.path.join(dir_path,'{}'.format(model_name))
return final_path
from tensorflow.keras.callbacks import TensorBoard
tensorboard_cb = TensorBoard(log_dir=get_run_logdir('my_logs'))
#3. 搭模型
from tensorflow.keras import Sequential
model = Sequential()
model.add(Conv2D(64, (3, 3), input_shape = X.shape[1:]))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(64, (3, 3)))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten()) #Conv Layer是2D, DenseLayer是1D的 所以需要将ConvLayer压平
#第二次:去掉一次全连接层
model.add(Activation("relu"))
model.add(Dense(1))
model.add(Activation("sigmoid"))
model.compile(loss="binary_crossentropy",
optimizer="adam",
metrics=["accuracy"]) # 可以使用categorical_crossentropy作为损失函数
model.fit(X, np.array(y), batch_size =4, epochs=10, validation_split=0.1, callbacks=[tensorboard_cb])
关于tensorboard
上面代码的涵盖了Tensorboard的接口回调。
TensorBoard是一个机器学习领域的可视化工具,能够有效地展示Tensorflow在运行过程中的计算图、各种指标随着时间的变化趋势以及训练中使用到的数据信息。
通过使用TensorBoard,可以将训练过程中评价指标与训练次数绘制成折线图,从而观察诸如准确率提高的过程或损失下降的过程。同时,当我们更改模型的超参数(LearningRate等)时,TensorBoard还可以将不同的超参数对应的折线图分类,可以更加直观地观察超参数的选取对模型训练的影响。
而Keras和Tensorboard是通过Keras内置的回调函数实现连接的。具体详见keras官方文档。
我们唯一需要注意的参数是log_dir
,即“日志路径”,指定模型训练过程生成的日志的保存路径。
接下来让我们查看一下TensorBoard扩展:
%load_ext tensorboard
%tensorboard --logdir=./my_logs --port=6006 --host=127.0.0.1
自己的Tensorboard打不开了,其实打开TensorBoard看损失函数就会发现训练的并不好,曲线尚未收敛。
而后保存模型
def save_model(model, model_path):
folder = os.path.exists(model_path)
if not folder:
os.makedirs(path)
print("--- Create folder successfully ---")
mpath = os.path.join(model_path,model_name)
model.save(mpath)
save_model(model,'saved_models')
优化模型:暴力枚举每一种超参数的组合
- 全连接层的数量有0,1,2三种
- 每一层神经元的个数可以有32,64,128三种
- 卷积层的个数可以有1,2,3三种
排列组合,一共有27种,让我们暴力枚举一下
# 模型命名,生成日志
# 2.写tensorboard的一个回调
#这个函数时用来生成日志文件的名称及目录的
def get_run_logdir(dir_path):
# 先判断有没有存放日志的文件夹
folder = os.path.exists(dir_path)
if not folder:
os.makedirs(dir_path)
print("--- Create folder successfully ---")
final_path = os.path.join(dir_path,'{}'.format(model_name))
return final_path
from tensorflow.keras.callbacks import TensorBoard
tensorboard_cb = TensorBoard(log_dir=get_run_logdir('my_logs'))
# train multiple models
dense_layers = [0, 1, 2]
layer_sizes = [32, 64, 128]
conv_layers = [1, 2, 3]
for dense_layer in dense_layers:
for layer_size in layer_sizes:
for conv_layer in conv_layers:
model_name = "{}-conv-{}-nodes-{}-dense-{}".format(conv_layer, layer_size, dense_layer, int(time.time()))
tensorboard_cb = TensorBoard(log_dir=get_run_logdir('my_logs'))
print(model_name)
model = Sequential()
model.add(Conv2D(layer_size, (3, 3), input_shape = X.shape[1:]))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
for l in range(conv_layer-1):
model.add(Conv2D(layer_size, (3, 3)))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten()) #Conv Layer是2D, DenseLayer是1D的 所以需要将ConvLayer压平
for l in range(dense_layer):
model.add(Dense(layer_size))
model.add(Activation("relu"))
model.add(Dense(1))
model.add(Activation("sigmoid"))
model.compile(loss="binary_crossentropy",
optimizer="adam",
metrics=["accuracy"]) # 可以使用categorical_crossentropy作为损失函数
model.fit(X, y, batch_size =32, epochs=20, validation_split=0.1, callbacks=[tensorboard_cb])
打开Tensorboard面板后我们可以看到,3个卷积层-32个节点-0个全连接层的模型效果是最好的。
使用模型进行预测
在网上找了两张猫猫和狗狗的照片,放到了test文件夹里

狗勾!!

猫猫!!
使用模型进行预测的代码如下:
categories=['Dog', 'Cat'] # 下标为0则为狗狗,下标为1则为猫猫
import cv2
import tensorflow as tf
def prepare(path):
# 跟训练集一样,对读入的数据需要进行预处理
img_size = 50
img_array = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
new_array = cv2.resize(img_array, (img_size, img_size))
return new_array.reshape(-1, img_size, img_size, 1)
model = tf.keras.models.load_model('saved_models/kaggle_cat_dog-cnn-64x2-run_2021_12_07-19_21_38')
prediction = model.predict([prepare('tests/dog.jpg')])
print(prediction)
输出结果:
[[0.]]
预测正确!!
Comments | NOTHING