【使用卷积神经网络进行猫狗识别】------机器学习(附完整代码和数据集)

最近报名了官方的新星计划Python赛道,开营时看到很多优秀的前辈分享自己的博主经历,从普通小白蜕变成万粉博主,大大鼓舞了我,业精于勤,砥砺前行。于是我决定从最近感兴趣的人工智能------卷积神经网络进行图像识别开始,一边学习,一边将学习过程记录下来,方便自己回顾总结,也希望给后面向我一样的萌新提供一点点微薄的帮助,所有的参考博文、文献、视频、代码都会在文末附上链接。

本文的目录如下:

一、通俗易懂理解卷积

提到卷积,有很多太过专业冗杂的解释,经常一个头两个大,这里不用“反转/翻转/反褶/对称”等解释卷积。
参考知乎以及博文 卷积

1、卷积的物理意义

首先明确:
卷积的重要的物理意义是:一个函数(如:单位响应)在另一个函数(如:输入信号)上的加权叠加。
为什么这样说呢?请看
已知x[0] = a, x[1] = b,x[2]=c ,
这里写图片描述
已知y[0] = i, y[1] = j, y[2]=k ,
这里写图片描述
下面通过演示求x[n] * y[n]的过程,揭示卷积的物理意义。

第一步,x[n]乘以y[0]并平移到位置0:
这里写图片描述

第二步,x[n]乘以y[1]并平移到位置1
在这里插入图片描述
第三步,x[n]乘以y[2]并平移到位置2:
这里写图片描述
最后,把上面三个图叠加,就得到了x[n] * y[n]:
这里写图片描述
没错,卷积无非是平移、叠加。

2 、卷积的定义

教科书中卷积定义是这样的,我们可以把它理解为是一种运算。我们都知道小学先学加法,接着学乘法,乘法的本质就是加法,所以这也是上面为什么我们说 卷积的物理意义是:一个函数(如:单位响应)在另一个函数(如:输入信号)上的加权叠加。
我们看回教科书的定义,卷积分为连续和离散。

在这里插入图片描述

1)离散卷积

对于离散信号,我们可以理解为多个信号的加权叠加。

丢骰子的例子

我有两枚骰子,把这两枚骰子都抛出去,求:两枚骰子点数加起来为4的概率是多少?
这里问题的关键是,两个骰子加起来要等于4,这正是卷积的应用场景。

我们把骰子各个点数出现的概率表示出来:
在这里插入图片描述
那么,两枚骰子点数加起来为4的情况有:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因此,两枚骰子点数加起来为4的概率为:

f ( 1 ) g ( 3 ) + f ( 2 ) g ( 2 ) + f ( 3 ) g ( 1 ) f(1)g(3)+f(2)g(2)+f(3)g(1) f(1)g(3)+f(2)g(2)+f(3)g(1)

符合卷积的定义,把它写成标准的形式就是:

( f ∗ g ) ( 4 ) = ∑ m = 1 3 f ( 4 − m ) g ( m ) (f∗g)(4)=∑_{m=1}^3f(4−m)g(m) (fg)(4)=m=13f(4m)g(m)

2)图像处理里的卷积

在这里插入图片描述)
2D卷积是一个相当简单的操作:
我们先从一个小小的权重矩阵,也就是 卷积核(kernel) 开始,让它逐步在二维输入数据上“扫描”。卷积核“滑动”的同时,计算权重矩阵和扫描所得的数据矩阵(来自input image)的乘积,然后把结果汇总成一个输出像素。

二、神经网络

人工神经元及神经网络模拟了大脑的神经元及其连接。
正向传播求损失,反向传播回传误差,根据误差信号修正每层的权重,多次迭代更新,直到满足条件。

在这里插入图片描述
1 .神经网络分为三种类型的层:
输入层:神经网络最左边的一层,通过这些神经元输入需要训练观察的样本,即初始输入数据的一层。
隐藏层:介于输入输出之间所有节点组成的一层。帮助神经网络学习数据间的复杂关系,即对数据进行处理的层。
输出层:由前两层得到神经网络最后一层,即最后结果输出的一层。

2 .传递函数/激活函数 前面每一层输入经过线性变换 w x + b wx+b wx+b后还用到了sigmoid函数,在神经网络的结构中被称为传递函数或者激活函数。除了sigmoid,还有tanh、relu 等激活函数。激活函数使线性的结果非线性化。

3 .为什么需要传递函数 简单理解上,如果不加激活函数,无论多少层隐层,最终的结果还是原始输入的线性变化,这样一层隐层就可以达到结果,就没有多层感知器的意义了。所以每个隐层都会配一个激活函数,提供非线性变化。

三、CNN卷积神经网络

类比一般的神经网络,CNN的输入是图片像素信息。计算机看到的图片是一个个代表明暗的数字。彩色图片是由RGB三色组成的。
在这里插入图片描述
CNN卷积神经网络需要训练来得到最佳的模型参数。
在这里插入图片描述

1、输入数据:

假设一只猫咪的输入像素信息是 100 ∗ 100 ∗ 3 100*100*3 1001003 ,其中3是RGB三色通道,任何一张彩色图片都可以看成是三个色的叠加。

在这里插入图片描述
在这里插入图片描述
假如将输入像素信息全部作为输入,在有两层网络的情况下,其中第一层有 3 ∗ 1 0 4 3*10^4 3104个节点,第二层有1000个,考虑全连接的情况,那么计算量已经达到 3 ∗ 1 0 7 3*10^7 3107个。如果有多层网络,那计算量呈指数形式进行增长。

我们考虑到:
一些模式比整张图片小得多,不需要看到整个图像去发现模式,通过较少的参数连接到小区域即可。
比如猫的典型特征就是尖尖的耳朵。我们只需做 “猫耳”检测器。
在这里插入图片描述
同时,
在这里插入图片描述而且,
在这里插入图片描述
综上所述,我们可以设计CNN模型。

2、模型

在这里插入图片描述)

在这里插入图片描述)

3、卷积层

1) 卷积核:

卷积层中的卷积核是需要学习的参数,不同的卷积核会有不同的效果。如下:边缘检测或锐化等。
在这里插入图片描述

在这里插入图片描述
如上所示,原始图片是 8 ∗ 8 8*8 88像素,卷积结果是 6 ∗ 6 6*6 66像素。那么想要获得与原始图片一样的像素,我们需要对边界进行一下处理。
具体方法就是将待扫描的数据矩阵周围加上0,具体加几圈要视不同的情况而定。或者是改变卷积移动的步长。

2)Padding : 处理边界

在这里插入图片描述

3)Stride :卷积核移动的步长

在这里插入图片描述

4、最大池化层

除了上面的改变边界和步长的方法,也可以在池化层部分想办法。我们可以最大池化层,顾名思义,将每个小部分中的最大值作为输出。
在这里插入图片描述

5、Flatten

将卷积后的新图像数据平摊开,输入全连接网络。
在这里插入图片描述

总结:

1.卷积神经网络主要的设计思想是更好的利用图片的性质。

●图片中的模式比图片小的多

●图片中的模式出现在图像的不同区域

●缩放不影响图片中的物体

2.卷积层就是在图片中扫描特征。

3.最大池化层就是在缩放图片,减小参数。

4.多次的卷积和池化后,再经过flatten连接一个全连接层

四、猫狗识别代码实现

代码分为两部分,1)训练模型 ;2)测试分类效果。

1、代码

1)训练模型

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@File :CNN_train.py
"""
import sys
from matplotlib import pyplot
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPool2D
from keras.layers import Dense
from keras.layers import Flatten
from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
from keras.models import load_model
import tensorflow as tf
tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)
 
def define_cnn_model():
    # 使用Sequential序列模型
    model = Sequential()
    # 卷积层
    model.add(Conv2D(32,(3,3),activation="relu",padding="same",input_shape=(200,200,3))) 
    # 添加一个卷积层,第一个参数32是卷积核的数量,第二个是卷积核的规格,(3,3)即为3*3的,3是颜色通道个数。第三个参数relu是激活函数类型,第四个是same边缘的处理办法,第五个因为第一层即为卷积层,要定义输入图片的规格(200,200,3)即为200*200,3说明是彩色图片。 
    # 最大池化层
    model.add(MaxPool2D((2,2)))  # 池化窗格  (2,2)说明每2*2化作一个窗格。
    # Flatten层
    model.add(Flatten())   # 再添加一个Flatten层,将池化后的结果展开。
    # 全连接层
    model.add(Dense(128,activation="relu"))  # 再添加一个全连接层,第一个参数是神经元个数,第二个参数是激活函数的类型;
    model.add(Dense(1,activation="sigmoid"))  # 最后再添加一个全连接层输出结果,注意我们的结果需判断猫狗就行,因此一个神经元就行。二分类,所以只需要一个神经元就够了。
    # 编译模型
    opt = SGD(lr= 0.001,momentum=0.9)  # 随机梯度,最后用随机梯度编译模型。
    model.compile(optimizer=opt,loss="binary_crossentropy",metrics=["accuracy"])
    return model

def train_cnn_model():
    # 实例化模型
    model = define_cnn_model()
    # 创建图片生成器
    datagen = ImageDataGenerator(rescale=1.0/255.0)
    train_it = datagen.flow_from_directory(
        "./ma1ogo3ushu4ju4ji2/dogs_cats/data/train/",
        class_mode="binary",
        batch_size=64,
        target_size=(200, 200))  # batch_size:一次拿出多少张照片 targe_size:将图片缩放到一定比例
    # 训练模型
    model.fit_generator(train_it,
                        steps_per_epoch=len(train_it),
                        epochs=20,                   #  !!!!!!!!!!!!20次结果较准确
                        verbose=1)
    model.save("my_model.h5")
train_cnn_model()
#首先调用define_cnn_model(),紧接着创建图片生成器:这个作用就是把文件夹中的图片传入模型中训练。里面的参数batch_size是规定一次只能传入64张图片,这样可以有效地避免内存的问题。训练模型中一个重要参数epochs,这里设置为20,说明传入的图片他要学习20次。比如,这里我总共传入了2500张图片,它学习了20次,也就是50000张图片。这样的重复学习,可以有效提高进度,但是当你值调整比较大时,会非常耗时。最后将训练好的模型保存到项目文件夹下。

2)测试分类效果

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@File    :CNN_test.py
"""
 
import os,random
import matplotlib.pyplot as plt
from keras.models import load_model
from matplotlib.pyplot import imshow
import numpy as np
from PIL import Image
model_path = "my_model.h5"
model = load_model(model_path)
import pylab
plt.rcParams['font.sans-serif']=['SimHei']
def read_random_image():
    folder = r"./ma1ogo3ushu4ju4ji2/dogs_cats/data/test/"
    file_path = folder + random.choice(os.listdir(folder))
    pil_im = Image.open(file_path, 'r')
    return pil_im
 
def get_predict(pil_im,model):
    # 首先更改图片的大小
    name = ''
    pil_im = pil_im.resize((200,200))
    # 将格式转为numpy array格式
    array_im = np.asarray(pil_im)
    # array_im = array_im.resize((4,4))
    array_im = array_im[np.newaxis,:]    # 注意上一行的array_im 是一个三维数组,不符合运行规范,这里要将其转化为四位数组,否则会报错!
    #对图像检测
    result = model.predict([[array_im]])
    if result[0][0]>0.5:
        name = "它是狗!"
        print("预测结果是:狗")
    else:
        name = "它是猫!"
        print("预测结果是:猫")
    return name
pil_im =read_random_image()
imshow(np.asarray(pil_im))
plt.title(get_predict(pil_im,model))
pylab.show()

2、代码解释

1)keras

在这里插入图片描述

2) sequential模型

在这里插入图片描述 在这里插入图片描述

import keras
from keras import layers
model = keras.Sequential()  #建立模型
model.add(layers.Dense(20,activation="relu",input_shape=(10,))) # 加了一个全连接层 (神经元数量,激活函数,输入的参数值数量:10个参数)
model.add(layers.Dense(20,activation="relu"))  # 再加一个全连接层
model.add(layers.Dense(10,activation="softmax")) # 同上
model.fit(x,y,epochs=10,batch_size=32)  #模型训练: x是图片,y是图形标签 epochs:每张图片看、训练10遍 batch_size:一次只传入32张图片
keras. Sequential() 建立函数

model.add() 添加层

model.fit() 训练模型

3) Conv2D

keras.layers.Conv2D(filters,kernel_size,strides=(1,1),padding="valid",data_formt=None))
filters:整数,输出空间的维度,卷积核的数量

kernel_size:一个整数,或者2个整数代表的元组或列表,指明2D卷积窗口的宽度和高度,可以是一个整数,为所有空间维度指定相同的值。

strides:一个整数,或者2个整数代表的元组或列表,指明卷积沿宽度和高度方向的步长。可以是一个整数,为所有空间维度指定相同的值。

padding:"valid"或者"same",大小写敏感,用于边缘处理部分。

4) MaxPooling2D

keras.layers.MaxPooling2D(pool_size=(2,2),strides=None,padding="valid",data_format =None)
pool_size:整数,或者2个整数表示的元组,沿(垂直,水平)方向缩小比例的因数。(2,2)会把输入张量的两个维度都缩小一半。如果只使用一个整数,那么两个维度都会使用同样的窗口长度。

strides:整数,2个整数表示的元组,或者是None。表示步长值。如果是None,那么默认值是pool_size。

padding:"valid"或者“same"

3、结果分析

可以看到,大部分情况还是可以准确识别的。
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

但也有个别情况,识别错误,可以肯定的是,增大epochs 重复次数, 即 训练的 CNN_test.py 倒数第四行 ,可以提高准确率。
比如:

在这里插入图片描述在这里插入图片描述在这里插入图片描述

数据集来源:

猫狗的数据集
在这里插入图片描述

参考文章:

基于卷积神经网络(CNN)的猫狗识别
卷积(convolution)最容易理解的解释

参考视频:

教学视频

最后,感谢各位前辈的分享,本文先写到这里,欢迎大家批评指正!

猜你喜欢

转载自blog.csdn.net/weixin_47296493/article/details/129868596