Keras搭建卷积神经网络,识别小猫小狗


Keras搭建卷积神经网络,识别小猫小狗

Keras是一个高度封装的神经网络API,能快速将自己的Idea识别为结果。我们可以通过Sequential的API迅速实现一个神经网络模型。这篇文章记录用简单的Sequential模型对猫狗图片进行分类。

环境版本


本人环境的配置如下:

版本
Keras1.0.8
numpy1.19.2
pandas1.3.5
Tensorflow-gpu2.1.0
scikit-learn1.0.2
opencv-python4.5.5.62
matplotlib3.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.]]

预测正确!!

声明:奋斗小刘|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Keras搭建卷积神经网络,识别小猫小狗


Make Everyday Count