Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络

猫狗识别

项目数据分为带标签和不带标签
带标签:25000张
不带标签:12500张

文章目录

  • 猫狗识别
    • 数据分类处理
    • 图像增强预处理
    • 编写神经网络结构
    • 设置模型保存路径
    • 输入样本进入模型进行训练
    • 加载模型进行预测
    • 完整代码

数据分类处理

数据集可以从kaggle下载
https://www.kaggle.com/c/dogs-vs-cats
也可以从我网盘下载
链接:https://pan.baidu.com/s/1_A3H4nsL8xX4cmMEMtpACg?pwd=mpvi
提取码:mpvi
–来自百度网盘超级会员V7的分享
下载的数据存放在data文件夹下

创建用于创建文件夹的函数

# 如果文件夹不存在就创建
def mkdir(path):
    if not os.path.exists(path):
        os.makedirs(path)
# 获取当前路径
runPath = os.getcwd()
data_dir =runPath + '/data/'
mkdir(data_dir)

Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第1张图片

  • train是25000张带标签的猫狗图片
  • test1是12500张无标签的猫狗图片

我们将这25000张带标签的猫狗图片进行分类
分为训练集和验证集
训练集 20000张 验证集 5000张

猫的训练数据保存在 train/cat下 一共是10000张
猫的校验数据保存在valid/cat下一共是2500张
狗的训练数据保存在 train/dog下 一共是10000张
狗的校验数据保存在valid/dog下一共是2500张

# 训练素材路径
train_dir = data_dir + 'train/'
mkdir(train_dir)

# 校验素材路径
valid_dir = data_dir + 'valid/'
mkdir(valid_dir)

# 猫训练素材文件夹
cat_dir = train_dir + 'cat/'
mkdir(cat_dir)
# 猫校验素材文件夹
cat_val_dir = valid_dir + 'cat/'
mkdir(cat_val_dir)

# 狗训练素材文件夹
dog_dir = train_dir + 'dog/'
mkdir(dog_dir)
# 狗校验素材文件夹
dog_val_dir = valid_dir + 'dog/'
mkdir(dog_val_dir)

使用代码进行将这25000张带标签的猫狗图片进行分类

# 移动文件到指定文件夹
def move_file(file_path, target_path):
    # 路径分离出文件名
    fileName = os.path.split(file_path)[1]
    # 拼接目标路径名
    target_path+='/'+fileName
    print(target_path)
    if os.path.exists(file_path):
        shutil.move(file_path, target_path)
    else:
        print('文件不存在')

# 分离图片到指定文件夹
def changeFile(filetype):
    test_file = []
    for i in range(0,12500,1):
        filename = train_dir+filetype+"."+str(i)+'.jpg'
        print(filename)
        # 训练数据10000
        if i < 10000:
            move_file(filename,train_dir +filetype)
        # 验证数据2500
        else:
            move_file(filename,valid_dir +filetype)
            
# 分离猫
changeFile('cat')
# 分离狗
changeFile('dog')

Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第2张图片

检查分类情况
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第3张图片
目录情况
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第4张图片

图像增强预处理

训练前,我们将全部的图像输入ImageDataGenerator进行图像增强预处理
参数可以自行修改

# 图像增强数据预处理
# 返回训练集和校验集预处理模板
def img_transforms():
    # 创建训练数据预处理模板
    train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
            rescale=1. / 255, # 所有数据集将乘以该数值(归一化,数据范围控制在0到1之间)
            rotation_range=40, # 随机旋转的范围
            width_shift_range=0.2, # 随机宽度偏移量
            height_shift_range=0.2, # 随机高度偏移量
            shear_range=0.2, # 随机错切变换
            zoom_range=0.2, # 随机缩放范围
            horizontal_flip=True, # 随机将一半图像水平翻转
            fill_mode='nearest', # 填充模式为最近点填充
    )

    # 从文件夹导入训练集
    train_generator = train_datagen.flow_from_directory(
        train_dir,# 训练数据文件夹
        target_size=(128, 128), # 处理后的图片大小128*128
        batch_size=64, # 每次训练导入多少张图片
        seed=7,# 随机数种子
        shuffle=True, # 随机打乱数据
        class_mode='categorical' # 返回2D的one-hot编码标签
    )

    # 创建校验数据预处理模板
    valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
        rescale=1. / 255, # 数据范围控制在0到1之间
    )

    # # 从文件夹导入校验集
    valid_generator = valid_datagen.flow_from_directory(
        valid_dir,# 校验数据文件夹
        target_size=(128, 128), # 处理后的图片大小128*128
        batch_size=64, # 每次训练导入多少张图片
        seed=7,# 随机数种子
        shuffle=False,# 随机打乱数据
        class_mode="categorical" # 返回2D的one-hot编码标签
    )
    return train_generator,valid_generator

# 图像预处理
train_generator,valid_generator = img_transforms()

编写神经网络结构

构建一个8层的神经网络,卷积层6层,全连接层2层,每两个卷积层添加一个池化层防止过拟合

# 编写神经网络
# 参数1:样本宽
# 参数2:样本高
# 参数3:样本深度
# 参数4:输出个数
def cnn(width,height,depth,outputNum):
    # 按顺序添加神经层
    # 神经网络的层数指的是有权重计算的层,池化层不算层数
    # 该模型卷积层6层 全连接层2层 共8层
    model = tf.keras.models.Sequential([
        # Conv2D 向两个维度进行卷积https://blog.csdn.net/qq_37774098/article/details/111997250
        # 卷积层 输入样本为宽:128 高:128 深度:3
        tf.keras.layers.Conv2D(filters=32, # 32个过滤器
                            kernel_size=3, # 核大小3x3
                            padding='same', # same:边缘用0填充 valid:边缘不填充
                            activation='relu', # 激活函数:relu
                            input_shape=[width, height, depth]), # 输入形状 宽:128 高:128 深度:3 不定义那就是默认输入的形状

        # 卷积层
        tf.keras.layers.Conv2D(filters=32,# 32个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same', # same:边缘用0填充 valid:边缘不填充
                            activation='relu'),# 激活函数:relu

        # 池化层 https://blog.csdn.net/Chen_Swan/article/details/105486854
        # 池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合
        tf.keras.layers.MaxPool2D(pool_size=2), # 池化核大小2x2

        # 卷积层
        tf.keras.layers.Conv2D(filters=64,# 64个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# same:边缘用0填充 valid:边缘不填充
                            activation='relu'),# 激活函数:relu

        # 卷积层
        tf.keras.layers.Conv2D(filters=64,# 64个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# same:边缘用0填充 valid:边缘不填充 
                            activation='relu'),# 激活函数:relu

        # 池化层
        tf.keras.layers.MaxPool2D(pool_size=2),# 池化核大小2x2

        # 卷积层
        tf.keras.layers.Conv2D(filters=128,# 128个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# 边缘用0填充
                            activation='relu'),# 激活函数:relu

        # 卷积层
        tf.keras.layers.Conv2D(filters=128,# 128个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# 边缘用0填充
                            activation='relu'),# 激活函数:relu

        # 池化层
        tf.keras.layers.MaxPool2D(pool_size=2),# 池化核大小2x2

        # 输入层的数据压成一维的数据
        tf.keras.layers.Flatten(),

        # 全连接层
        tf.keras.layers.Dense(128, activation='relu'),# 输出的维度大小128个特征点 激活函数:relu
        tf.keras.layers.Dense(outputNum, activation='softmax') # 输出猫狗类型 2类 猫或者狗 激活函数:softmax 用于逻辑回归
    ])
    
    # 模型处理
    # compile的用法:https://blog.csdn.net/yunfeather/article/details/106461754
    model.compile(loss='categorical_crossentropy', # 损失函数 https://blog.csdn.net/qq_40661327/article/details/107034575
                  optimizer='adam',# 优化器 
                  metrics=['accuracy']) # 准确率

    # 打印模型每层的情况
    model.summary()
    return model

model = cnn(128,128,3,2)

设置模型保存路径

将模型保存在model文件夹下的catvsdog_weights.h5中

# 设置模型保存路径
modelPath = './model'
mkdir(modelPath)

output_model_file = os.path.join(modelPath,"catvsdog_weights.h5")

输入样本进入模型进行训练

设置为回调模式,使用model.fit进行训练
使用EarlyStopping,当准确率没有提高超过一定幅度的话就会停止训练

# 打印学习信息
def plot_learning_curves(history, label, epochs, min_value, max_value):
    data = {}
    data[label] = history.history[label]
    data['val_' + label] = history.history['val_' + label]
    pd.DataFrame(data).plot(figsize=(8, 5))
    plt.grid(True)
    plt.axis([0, epochs, min_value, max_value])
    plt.show()

# 定义训练步数
TRAIN_STEP = 100

# 设置回调模式
callbacks = [
            tf.keras.callbacks.TensorBoard(modelPath),
            tf.keras.callbacks.ModelCheckpoint(output_model_file,
                                            save_best_only=True,
                                            save_weights_only=True),
            tf.keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)
        ]

# 开始训练
history = model.fit(
        train_generator,
        epochs=TRAIN_STEP,
        validation_data = valid_generator,
        callbacks = callbacks
    )

# 显示训练曲线
plot_learning_curves(history, 'accuracy', TRAIN_STEP, 0, 1)
plot_learning_curves(history, 'loss', TRAIN_STEP, 0, 5)

Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第5张图片

Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第6张图片
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第7张图片

可以看到训练到40步的时候准确率已经变化不大了

加载模型进行预测

准备两张要预测的图片
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第8张图片
dog.jpg
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第9张图片
cat.jpg
也可以直接使用test1下的图片进行预测,这里只是觉得这两张图可爱

# 预测
# 参数1:要预测的图像cv2格式
# 参数1:模型
# 参数2:模型路径
# 参数3:模型名称
# 参数4:特征宽
# 参数5:特征高
# 参数6:特征深度
# 参数7:结果1名称
# 参数8:结果2名称
def predict(cvImg,model,modelPath,modelName,width,height,depth,result1,result2):
    model_file = os.path.join(modelPath,modelName)

    # 加载模型
    model.load_weights(modelName)

    # 缩放图像减少检测时间
    img = cv2.resize(cvImg, (width, height))
    # 归一化
    img_arr = img / 255.0
    # 重构成模型需要输入的格式
    img_arr = img_arr.reshape((1, width, height, depth))
    # 输入模型进行预测
    pre = model.predict(img_arr)
    # 打印预测结果
    if pre[0][0] > pre[0][1]:
        print("识别结果是:"+result1+"\n概率:"+str(pre[0][0]))
    else:
        print("识别结果是:"+result2+"\n概率:"+str(pre[0][1]))
img = cv2.imread("cat.jpg")
cv2.imshow("img",img)
predict(img,model,modelPath,"catvsdog_weights.h5",128,128,3,"猫","狗")
cv2.waitKey(0)

识别结果为猫
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第10张图片
识别结果为狗
Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第11张图片

随机从测试集中抽取一张图片进行预测

    # 随机从测试集中读取文件
    test_dir = data_dir + "test1/"
    readImg = test_dir + str(random.randint(0,12499)) + ".jpg"
    img = cv2.imread(readImg)
    predict(img,model,modelPath,modelName,simpleWight,simpleHeight,simpleDepth,result1,result2)
    cv2.imshow("img",img)
    cv2.waitKey(0)

Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络_第12张图片

完整代码

import os
# 设置LOG等级为 1 警告 2 错误
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 0为使用CPU 1为使用GPU
CPU = 1
if CPU == 0 :
    os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
else :
    os.environ['CUDA_VISIBLE_DEVICES'] = '0'

import random
import shutil
import cv2
import tensorflow as tf
import pandas as pd
import matplotlib.pyplot as plt

# 模式选择:0 训练模型 1 预测图片
mode = 1
# 移动文件到指定文件夹
def move_file(file_path, target_path):
    # 路径分离出文件名
    fileName = os.path.split(file_path)[1]
    # 拼接目标路径名
    target_path+='/'+fileName
    print(target_path)
    if os.path.exists(file_path):
        shutil.move(file_path, target_path)
    else:
        print('文件不存在')
        
# 如果文件夹不存在就创建
def mkdir(path):
    if not os.path.exists(path):
        os.makedirs(path)


# 分离图片到指定文件夹
def changeFile(filetype):
    test_file = []
    for i in range(0,12500,1):
        filename = train_dir+filetype+"."+str(i)+'.jpg'
        print(filename)
        # 训练数据10000
        if i < 10000:
            move_file(filename,train_dir +filetype)
        # 验证数据2500
        else:
            move_file(filename,valid_dir +filetype)


# 图像增强数据预处理
# 参数解释参考:
# https://blog.csdn.net/fioletfly/article/details/101345584
# https://wenku.baidu.com/view/8f05e708ac45b307e87101f69e3143323968f5c6.html
# 返回训练集和校验集预处理模板
# 参数1:训练集文件夹路径
# 参数2:校验集文件夹路径
def img_transforms(train_dir,valid_dir):
    # 创建训练数据预处理模板
    train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
            rescale=1. / 255, # 所有数据集将乘以该数值(归一化,数据范围控制在0到1之间)
            rotation_range=40, # 随机旋转的范围
            width_shift_range=0.2, # 随机宽度偏移量
            height_shift_range=0.2, # 随机高度偏移量
            shear_range=0.2, # 随机错切变换
            zoom_range=0.2, # 随机缩放范围
            horizontal_flip=True, # 随机将一半图像水平翻转
            fill_mode='nearest', # 填充模式为最近点填充
    )

    # 从文件夹导入训练集
    train_generator = train_datagen.flow_from_directory(
        train_dir,# 训练数据文件夹
        target_size=(128, 128), # 处理后的图片大小128*128
        batch_size=64, # 每次训练导入多少张图片
        seed=7,# 随机数种子
        shuffle=True, # 随机打乱数据
        class_mode='categorical' # 返回2D的one-hot编码标签
    )

    # 创建校验数据预处理模板
    valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
        rescale=1. / 255, # 数据范围控制在0到1之间
    )

    # # 从文件夹导入校验集
    valid_generator = valid_datagen.flow_from_directory(
        valid_dir,# 校验数据文件夹
        target_size=(128, 128), # 处理后的图片大小128*128
        batch_size=64, # 每次训练导入多少张图片
        seed=7,# 随机数种子
        shuffle=False,# 随机打乱数据
        class_mode="categorical" # 返回2D的one-hot编码标签
    )
    return train_generator,valid_generator

# 编写神经网络
# 参数1:样本宽
# 参数2:样本高
# 参数3:样本深度
# 参数4:输出个数
def cnn(width,height,depth,outputNum):
    # 按顺序添加神经层
    # 神经网络的层数指的是有权重计算的层,池化层不算层数
    # 该模型卷积层6层 全连接层2层 共8层
    model = tf.keras.models.Sequential([
        # Conv2D 向两个维度进行卷积https://blog.csdn.net/qq_37774098/article/details/111997250
        # 卷积层 输入样本为宽:128 高:128 深度:3
        tf.keras.layers.Conv2D(filters=32, # 32个过滤器
                            kernel_size=3, # 核大小3x3
                            padding='same', # same:边缘用0填充 valid:边缘不填充
                            activation='relu', # 激活函数:relu
                            input_shape=[width, height, depth]), # 输入形状 宽:128 高:128 深度:3 不定义那就是默认输入的形状

        # 卷积层
        tf.keras.layers.Conv2D(filters=32,# 32个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same', # same:边缘用0填充 valid:边缘不填充
                            activation='relu'),# 激活函数:relu

        # 池化层 https://blog.csdn.net/Chen_Swan/article/details/105486854
        # 池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合
        tf.keras.layers.MaxPool2D(pool_size=2), # 池化核大小2x2

        # 卷积层
        tf.keras.layers.Conv2D(filters=64,# 64个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# same:边缘用0填充 valid:边缘不填充
                            activation='relu'),# 激活函数:relu

        # 卷积层
        tf.keras.layers.Conv2D(filters=64,# 64个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# same:边缘用0填充 valid:边缘不填充 
                            activation='relu'),# 激活函数:relu

        # 池化层
        tf.keras.layers.MaxPool2D(pool_size=2),# 池化核大小2x2

        # 卷积层
        tf.keras.layers.Conv2D(filters=128,# 128个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# 边缘用0填充
                            activation='relu'),# 激活函数:relu

        # 卷积层
        tf.keras.layers.Conv2D(filters=128,# 128个过滤器
                            kernel_size=3,# 核大小3x3
                            padding='same',# 边缘用0填充
                            activation='relu'),# 激活函数:relu

        # 池化层
        tf.keras.layers.MaxPool2D(pool_size=2),# 池化核大小2x2

        # 输入层的数据压成一维的数据
        tf.keras.layers.Flatten(),

        # 全连接层
        tf.keras.layers.Dense(128, activation='relu'),# 输出的维度大小128个特征点 激活函数:relu
        tf.keras.layers.Dense(outputNum, activation='softmax') # 输出猫狗类型 2类 猫或者狗 激活函数:softmax 用于逻辑回归
    ])
    
    # 模型处理
    # compile的用法:https://blog.csdn.net/yunfeather/article/details/106461754
    model.compile(loss='categorical_crossentropy', # 损失函数 https://blog.csdn.net/qq_40661327/article/details/107034575
                  optimizer='adam',# 优化器 
                  metrics=['accuracy']) # 准确率

    # 打印模型每层的情况
    model.summary()
    return model


# 训练
# 参数1:模型
# 参数2:模型路径
# 参数3:模型名称
# 参数4:训练步数
# 参数5:训练集
# 参数6:校验集
# 参数5:是否打印训练曲线
def train(model,modelPath,modelName,step,train_generator,valid_generator,isplot=False):
    # 打印学习信息
    def plot_learning_curves(history, label, epochs, min_value, max_value):
        data = {}
        data[label] = history.history[label]
        data['val_' + label] = history.history['val_' + label]
        pd.DataFrame(data).plot(figsize=(8, 5))
        plt.grid(True)
        plt.axis([0, epochs, min_value, max_value])
        plt.show()

    output_model_file = os.path.join(modelPath,modelName)
    # 设置回调模式
    callbacks = [
                tf.keras.callbacks.TensorBoard(modelPath),
                tf.keras.callbacks.ModelCheckpoint(output_model_file,
                                                save_best_only=True,
                                                save_weights_only=True),
                tf.keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)
            ]

    # 开始训练
    history = model.fit(
            train_generator,
            epochs=step,
            validation_data = valid_generator,
            callbacks = callbacks
        )
    if isplot:
        # 显示训练曲线
        plot_learning_curves(history, 'accuracy', TRAIN_STEP, 0, 1)
        plot_learning_curves(history, 'loss', TRAIN_STEP, 0, 5)

# 预测
# 参数1:要预测的图像cv2格式
# 参数1:模型
# 参数2:模型路径
# 参数3:模型名称
# 参数4:特征宽
# 参数5:特征高
# 参数6:特征深度
# 参数7:结果1名称
# 参数8:结果2名称
def predict(cvImg,model,modelPath,modelName,width,height,depth,result1,result2):

    model_file = os.path.join(modelPath,modelName)
    # 加载模型
    model.load_weights(model_file)

    # 缩放图像减少检测时间
    img = cv2.resize(cvImg, (width, height))
    # 归一化
    img_arr = img / 255.0
    # 重构成模型需要输入的格式
    img_arr = img_arr.reshape((1, width, height, depth))
    # 输入模型进行预测
    pre = model.predict(img_arr)
    # 打印预测结果
    if pre[0][0] > pre[0][1]:
        print("识别结果是:"+result1+"\n概率:"+str(pre[0][0]))
    else:
        print("识别结果是:"+result2+"\n概率:"+str(pre[0][1]))

        

# 获取当前路径
runPath = os.getcwd()
data_dir =runPath + '/data/'
mkdir(data_dir)
 
# 训练素材路径
train_dir = data_dir + 'train/'
mkdir(train_dir)

# 校验素材路径
valid_dir = data_dir + 'valid/'
mkdir(valid_dir)

# 猫训练素材文件夹
cat_dir = train_dir + 'cat/'
mkdir(cat_dir)
# 猫校验素材文件夹
cat_val_dir = valid_dir + 'cat/'
mkdir(cat_val_dir)

# 狗训练素材文件夹
dog_dir = train_dir + 'dog/'
mkdir(dog_dir)
# 狗校验素材文件夹
dog_val_dir = valid_dir + 'dog/'
mkdir(dog_val_dir)

# 设置模型保存路径
modelPath = './model'
mkdir(modelPath)

# 测试模型名称
modelName = "catvsdog_weights.h5"

# 设置样本参数
simpleWight = 128
simpleHeight = 128
simpleDepth = 3

# 设置输出类型
outputNum = 2
result1 = "猫"
result2 = "狗"

# 定义训练步数
TRAIN_STEP = 100



# 搭建神经网络
model = cnn(simpleWight,simpleHeight,simpleDepth,outputNum)

if mode == 0:
    # 分离猫
    changeFile('cat')
    # 分离狗
    changeFile('dog')
    # 图像预处理
    train_generator,valid_generator = img_transforms(train_dir,valid_dir)
    # 训练模型
    train(model,modelPath,modelName,TRAIN_STEP,train_generator,valid_generator)
elif mode == 1:
    #img = cv2.imread("dog.jpg")
    #predict(img,model,modelPath,modelName,simpleWight,simpleHeight,simpleDepth,result1,result2)
    #cv2.imshow("img",img)
    #cv2.waitKey(0)
    # 随机从测试集中读取文件
    test_dir = data_dir + "test1/"
    readImg = test_dir + str(random.randint(0,12499)) + ".jpg"
    img = cv2.imread(readImg)
    predict(img,model,modelPath,modelName,simpleWight,simpleHeight,simpleDepth,result1,result2)
    cv2.imshow("img",img)
    cv2.waitKey(0)

你可能感兴趣的:(Python,python,tensorflow,神经网络)