基于卷积神经网络CNN的猫狗大战图片二分类(tf2.1 py3.6)

前言:卷积神经网络在生活中的适用范围越来越广,并且也能真正的解决一些问题。比如:水果分类、男女分类等。并且平时我们接触的信息大多是以图像的方式,所以我们就想通过神经网络来进行对图片的二分类和识别,由于之前未接触过深度学习,所以也是一次尝试和学习。对于此神经网络,不仅可以用来进行猫狗的二分类,只要有相应的训练图片,就可进行相应的二分类训练,比如用人脸数据库的图片训练,就可以通过照片对性别进行分类,具有一定的普适性。

本例程使用 kaggle猫狗大战数据集如下:猫狗比赛数据集下载链接

第一个卷积神经网络是1987年由Alexander Waibel等提出的时间延迟网络(Time Delay Neural Network, TDNN),TDNN是一个应用于语音识别问题的卷积神经网络。

接着卷积神经网络有了一些发展,在2006年深度学习理论被提出后,卷积神经网络的表征学习能力得到了关注,并随着计算设备的更新得到发展。自2012年的AlexNet开始,得到GPU计算集群支持的复杂卷积神经网络多次成为ImageNet大规模视觉识别竞赛(ImageNet Large Scale Visual Recognition Challenge, ILSVRC)的优胜算法。

在2015年,ResNet获得了ILSVRC 2015的冠军。ResNet最大的贡献在于解决了深层网络难以训练的问题(反向传播梯度弥散),它的网络深度达到了152层!

随着计算机计算能力的提升,神经网络的复杂度和准确性都在提升。

截至2017年,典型CNN网络的准确度和复杂度:

本次训练使用的数据集为kaggle比赛猫狗的中的部分数据,训练集大概2000张,验证集大概200张。这个比赛的Top2%的正确率达到了99%以上,Top1的Tog Loss达到了0.03302。

当然了受限于硬件、算法等限制,本教程仅为介绍。

卷积网络(ConvNets)是一种特殊的神经网络类型,其特别适合计算机视觉应用,因为它们对于局部操作有很强的抽象表征能力。

 

神经网络大致的流程如下:

训练的数据和检验的数据存储在下图示例的文件夹中。

Data中的cat和dog分别存储着训练用的图片,每种大概1000张。

 

Validation中的cat和dog分别存储检验用的照片,每种大概100张。

Remove为清洗数据程序;

Test为判断图片分类程序;

Training为训练神经网络程序。

训练历程(一)

首先运行第一版程序得到如下的神经网络输出:

 

左图为训练集和验证集的准确率(点为训练集,直线为验证集);

右图为训练集和验证集的loss(点为训练集,直线为验证集)。

从曲线中可以看出:模型基本没有变化,可以看出是模型的结构出现了问题。分析可以神经网络默认的激活函数对此不适用,处理的问题是二分类问题,输出为0和1,那么输出层选择sigmoid函数,其他神经元选择ReLU函数。

解决方案:设置输出层的激活函数为“sigmoid”型,即: tf.keras.layers.Dense(1, activation ='sigmoid'),用于二分类。其余的激活函数均采用relu激活函数。

训练历程(二)

接着运行第二版程序得到如下的神经网络输出:

 

从曲线中可以看出:模型的训练集准确率一直都比验证集高,太过贴近于训练数据的特征了,在训练集上表现更优秀,但是在验证集中的正确率却不够,不具泛化性,出现了过拟合的现象。

解决方法是可以加大数据集来进行训练。比如在图像领域可以通过拉伸旋转变换剪裁等等方式来增加训练数据集,然后通过Dropout随机清零参数来避免过拟合现象。所以我们两种方法都采用了,首先利用 tf.keras提供的ImageDataGenerator类对图像进行随机处理(缩放、旋转、镜像等),这样由一张图片即可衍生出较多的图片;再利用随机失活层Dropout,将隐含层的部分权重或输出随机归零。tf.keras.layers.Dropout(x),其中的x取0.3左右,即可解决过拟合问题。

训练历程(三)

然后运行第三版程序得到如下的神经网络输出:

 

在解决了过拟合的问题之后,发现训练集的正确率几乎一直比训练集要高,分析其原因可能是因为训练集数据不纯净,需要进行清洗。

解决方案:编写remove程序,加载之前保存的神经网络,对训练集进行清洗,清除其中与神经网络预测差别较大的训练数据。通过以下代码,使训练集中与预测结果相比差别较大的,移动到其他文件夹中去。

if abs(predict[0]) < 0.05:

        shutil.move(image_path, 'D:/Python/AI/catvsdog/remove/cat')

    

以上是清洗出的部分照片,可以看出照片被清洗出的原因有以下几点:

1.狗或猫为黑色,或者光线暗

2.照片背景复杂,经常有人在其中

3.照片模糊等

训练历程(四)

然后运行第四版程序得到如下的神经网络输出:

 

最终经过我们对参数不断地进行调整(比如失活层的参数、迭代次数、每次喂入数据数量、学习率),使模型准确率接近90%,达到了我们的要求。

通过model_new.save('the_save_model.h5'),以h5格式完整保存最后训练得到的模型,供后面的模型检验加载使用。

 

训练界面部分截图,使用GPU加速,训练集共2041张照片,测试集共201张照片数据。

 

训练迭代30次,最后一次正确率在85以上,并保存训练得到的神经网络模型。

神经网络效果展示

首先使用tf.keras.models.load_model(‘’)加载训练都保存的模型,再读取要判断的图片,对图片进行处理,改变其数据的格式、维度,然后送入神经网络,通过predict = new_model.predict(),来获取输出结果。

由于输出层采用Sigmoid函数进行二分类,故在模型检验中要对Sigmoid函数的输出进行判断,输出为0-1之间。

 

以0.5为分界线,越接近1看作是狗的可能性越大,越接近0看作是猫的可能性越大。

考虑到实际可能输入的不是猫也不是狗的照片,所以我们在0.5的附近设置一阈值,当预测值减去0.5的绝对值小于0.05时,认为既不属于猫,也不属于狗。

判断的核心代码如下:

if abs(predict[0]-0.5) < 0.05:

    print("can not predict")

else:

    if predict[0]>0.5:

        print("it is a dog")

    else:

        print("it is a cat")

实拍样张检验展示

卷积神经网络训练代码如下:

import os
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.optimizers import RMSprop

base_dir = 'D:/Python/AI/catvsdog/'
train_dir = os.path.join(base_dir, 'data/')
validation_dir = os.path.join(base_dir, 'validation/')
train_cats_dir = os.path.join(train_dir, 'cat')  # directory with our training cat pictures
train_dogs_dir = os.path.join(train_dir, 'dog')  # directory with our training dog pictures
validation_cats_dir = os.path.join(validation_dir, 'cat')  # directory with our validation cat pictures
validation_dogs_dir = os.path.join(validation_dir, 'dog')  # directory with our validation dog pictures

batch_size = 64 # 64
epochs = 40 # 40
IMG_HEIGHT = 128
IMG_WIDTH = 128
 
num_cats_tr = len(os.listdir(train_cats_dir))  
num_dogs_tr = len(os.listdir(train_dogs_dir)) 
 
num_cats_val = len(os.listdir(validation_cats_dir)) 
num_dogs_val = len(os.listdir(validation_dogs_dir)) 
total_train = num_cats_tr + num_dogs_tr  
total_val = num_cats_val + num_dogs_val  
def plotImages(images_arr):
    fig, axes = plt.subplots(1, 5, figsize=(20, 20))
    axes = axes.flatten()
    for img, ax in zip(images_arr, axes):
        ax.imshow(img)
        ax.axis('off')
    plt.tight_layout()
    plt.show()
  
image_gen_train = tf.keras.preprocessing.image.ImageDataGenerator(
    rescale = 1. / 255,
    rotation_range=5,
    width_shift_range=.1,
    height_shift_range=.1,
    horizontal_flip=True,
    zoom_range=0.1
)

train_data_gen = image_gen_train.flow_from_directory(batch_size=batch_size,
                                                     directory=train_dir,
                                                     shuffle=True,
                                                     target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                     class_mode='binary')
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
 
# 创建验证集数据生成器
image_gen_val = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1. / 255)
 
val_data_gen = image_gen_val.flow_from_directory(batch_size=batch_size,
                                                 directory=validation_dir,
                                                 target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                 class_mode='binary')

# 创建模型
model_new = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu',
                           input_shape=(IMG_HEIGHT, IMG_WIDTH, 3)),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Dropout(0.4),# 0.4
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Conv2D(64, 1, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.Dropout(0.3),# 0.3
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(256, activation='relu'),
    tf.keras.layers.Dense(1, activation ='sigmoid')
])
#
# # 编译模型
# # 这边选择ADAM优化器和二进制交叉熵损失函数。要查看每个训练时期的训练和验证准确性,请传递metrics参数。
model_new.compile(optimizer=RMSprop(lr = 0.0005),
                    loss='binary_crossentropy',
                    metrics=['acc'])
 
model_new.summary()

print(model_new.trainable_variables)

# 在成功地将数据扩充引入训练样例并向网络中添加Dropout后,训练此新网络:
history = model_new.fit_generator(
    train_data_gen,
    steps_per_epoch=total_train // batch_size,
    epochs=epochs,
    validation_data=val_data_gen,
    validation_steps=total_val // batch_size
)

model_new.save('the_save_model.h5') # 保存模型
print("model save")

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

卷积神经网络数据清洗代码:

import tensorflow as tf
import numpy as np
import glob
import shutil
import os
from PIL import Image
import cv2
from pathlib import Path
from tensorflow.keras.preprocessing.image import ImageDataGenerator
new_model = tf.keras.models.load_model('path') # 加载网络
new_model.summary()
for image_path in glob.glob(r'path /*.jpg'): # 读取所有的图片
    image = cv2.imread(image_path)
    test = cv2.resize(np.array(image),(128,128)) # 调整图片大小
    test = test.astype('float32')

    test = test/255.0  # 归一化
    test = test[tf.newaxis, ...]
    test = np.array(test)
    predict = new_model.predict(test) # 进行预测
    print(predict[0])
    if abs(predict[0]) < 0.05: # 不符合要求
        shutil.move(image_path, 'path') # 移动到其他文件夹

神经网络预测代码:

import tensorflow as tf
import numpy as np
import os
from PIL import Image
import cv2
from pathlib import Path
from tensorflow.keras.preprocessing.image import ImageDataGenerator

new_model = tf.keras.models.load_model('D:/Python/AI/catvsdog/__pycache__/87the_save_model.h5') # 加载模型
new_model.summary()

image = cv2.imread('d:/Python/AI/catvsdog/pre/predict/cat1.jpg') # 读取图片
test_img = cv2.resize(np.array(image),(128,128)) # 调整大小
test = test_img.astype('float32') # 调整格式
 
test = test/255.0 # 归一化
test = test[tf.newaxis, ...] # 增加维度
test = np.array(test)
test.shape
predict = new_model.predict(test) # 进行预测
print(predict)
if abs(predict[0]-0.5) < 0.05: # 进行分类
    print("can not predict")
    title = "can not predict"
else:
    if predict[0]>0.5:
        print("it is a dog")
        title = "it is a dog"
    else:
        print("it is a cat")
        title = "it is a cat"
cv2.imshow(title, image) # 显示图片
cv2.waitKey(0) 
cv2.destroyAllWindows() # 销毁窗口

结论和展望

此次搭建的神经网络用来进行二分类的效果还是可以的,并且适用于大部分的二分类,但是对于多分类问题,其能力就显得不足了,就需要加以改进。

作为一个简单的入门的卷积神经网络,使我对神经网络的搭建过程以及参数设置等,有了一定的了解,可以为以后的学习做铺垫。

未来可以把此神经网络用在其他地方,比如男女照片分类、桃树梨树分类等;增加训练量,即增加训练的图片数量和迭代次数以达到更好的效果;引进断点续训等,提高运行效率;优化网络结构以达到更高的准确率。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值