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