项目数据分为带标签和不带标签
带标签: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)
我们将这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')
训练前,我们将全部的图像输入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)
可以看到训练到40步的时候准确率已经变化不大了
准备两张要预测的图片
dog.jpg
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)
随机从测试集中抽取一张图片进行预测
# 随机从测试集中读取文件
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)
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)