猫狗图片识别(卷积神经网络(CNN)详解)

目录

一、模型设计

(1)卷积神经网络认识

(3)卷积神经网络设置

①导入顺序模型

②输入层设置

③隐藏层设置

④维度变化

⑤输出层

二、代码详解

(1)图片文件匹配

(2)图形裁剪

(3)函数参数设置与回调

(4)结果可视化

三、参数调优

四、结果分析


一、模型设计

(1)卷积神经网络认识

1,输入层(必须)。输入层是整个神经网络的输入,在处理图像的卷积神经网络中,它一般代表了一张图片的像素矩阵。比如在上图中最左侧的三维矩阵就是可以代表一张图片。其中三维矩阵的长和宽代表了图像的大小,而三维矩阵的深度代表了图像的色彩通道(channel)。比如黑白图片的深度为1,而在RGB色彩模式下,图像的深度为3。从输入层开始,卷积神经网络通过不同的神经网络结构将上一层的三维矩阵转化为下一层的三维矩阵,直到最后的全连接层。

2,卷积层(视情况而定)。从名字就可以看出,卷积层是一个卷积神经网络中最为重要的部分,和传统全连接层不同,卷积层是一个卷积神经网络中最为重要的部分,和传统全连接层不同,卷积层中每一个节点的输入只是上一层神经网络的一小块,这个小块常用的大小有3*3或者5*5.卷积层试图将神经网络中的每一小块进行更加深入的分析从而得到抽象程度更高的特征。一般来说,通过卷积层处理过的节点矩阵会变得更深,所以上图可以看到经过卷积层之后的节点矩阵的深度会增加。

3,池化层(Pooling)。池化层神经网络不会改变三维矩阵的深度,但是它可以缩小矩阵的大小。池化操作可以认为是将一张分辨率较高的图片转化为分辨率较低的图片。通过池化层,可以进一步缩小最后全连接层中节点的个数,从而达到减少整个神经网络中参数的目的。

4,全连接层(必须),经过多轮卷积层和池化层的处理之后,在卷积神经网络的最后一般会是由1到2个全连接层来给出最后的分类结果。经过几轮卷积层和池化层的处理之后,可以认为图像中的信息以及抽象成了信息含量更高的特征。我们可以将卷积层和池化层看出自动图像特征提取的过程。在特征提取完成之后,让然需要使用全连接层来完成分类任务。

5,Softmax层(必须),Softmax层主要用于分类问题,通过Softmax层,可以得到当前样例属于不同种类的概率分布情况。

(3)卷积神经网络设置

①导入顺序模型

顺序模型,用于一层层神经网络连接构建深度神经网络,相当于一个模型框架,在后面运用sunmmary()方法可以查看模型的构建情况。

#顺序模型
model=Sequential()
#查看搭建模型框架
model.summary()

②输入层设置

# 输入层:第一层
# 添加第一个卷积层/最大池化层(必选)
model.add(Conv2D(filters=32,  # 32 个过滤器
                 kernel_size=(3,3),  # 卷积核大小 3 x 3
                 input_shape=input_shape,  # 图像输入维度
                 activation='relu'))  # 'relu' 激活函数
model.add(MaxPooling2D(pool_size=(2, 2)))  # 池化核大小 2 x 2

第一层设置32个过滤器,卷积核大小3x3,化池核大小设置为2x2

激活函数的作用

如果我们不使用激活函数,那么正向运算时,val=np.dot(w,x)计算完就结束了,同样反向计算时,计算到 error=y(实际值)-y(计算值)后也结束了,这时,虽然最后也会有效果,但中间层完全没有作用。激活函数在深度学习中扮演着非常重要的角色,它给网络赋予了非线性,从而使得神经网络能够拟合任意复杂的函数。如果没有激活函数,无论多复杂的网络,都等价于单一的线性变换,无法对非线性函数进行拟合。

③隐藏层设置

参数设置和前面都是一样。

隐藏层作用:

隐藏层在神经网络中的作用:中间的黑盒子,可以认为是很其他的不同功能层的一个总称每个图层都可以应用您想要的任何函数到前一层(通常是线性变换,然后是压缩非线性)。 隐藏层的工作是将输入转换为输出层可以使用的东西。 输出层将隐藏层激活转换为您希望输出所在的任何比例。 单个隐藏层的意义 隐藏层的意义就是把输入数据的特征,抽象到另一个维度空间,来展现其更抽象化的特征,这些特征能更好的进行线性划分。

隐藏层借点如何选择?

输入层结点数和输出层结点数随之而确定,首先遇到的一个十分重要而又困难的问题是如何优化隐层结点数和隐层数。实验表明,如果隐层结点数过少,网络不能具有必要的学习能力和信息处理能力。反之,若过多,不仅会大大增加网络结构的复杂性(这一点对硬件实现的网络尤其重要),网络在学习过程中更易陷入局部极小点,而且会使网络的学习速度变得很慢。隐层结点数的选择问题一直受到神经网络研究工作者的高度重视。

隐藏层计算方法如下:

方法一: fangfaGorman指出隐层结点数s与模式数N的关系是:s=log2N;  

方法二: Kolmogorov定理表明,隐层结点数s=2n+1(n为输入层结点数);

 方法三: s=sqrt(0.43mn+0.12nn+2.54m+0.77n+0.35)+0.51 (m是输入层的个数,n是输出层的个数)

# 隐藏层:介于第一层和最后一层之间
# 添加第二个卷积层/最大池化层(可选)
model.add(Conv2D(filters=32, kernel_size=(3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# 添加第三个卷积层/最大池化层(可选)
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# 添加第三个卷积层/最大池化层(可选)
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# 由于卷积层是 2D 空间,训练时需要将数据展平为 1D 空间
model.add(Flatten())  # 添加展平层(必选)
model.add(Dense(units=64, activation='relu'))  # 添加全连接层(必选) 64 个神经元
model.add(Dropout(0.5))  # 添加丢弃层,防止过拟合

④维度变化

由于卷积层是 2D 空间,训练时需要将数据展平为 1D 空间,同时添加最后的平展层,最后添加丢弃层,丢弃层存在的意义是为了防止过拟合,降低了模型的准确性。

model.add(Flatten())  # 添加展平层(必选)
model.add(Dense(units=64, activation='relu'))  # 添加全连接层(必选) 64 个神经元
model.add(Dropout(0.5))  # 添加丢弃层,防止过拟合

⑤输出层

激活函数在深度学习中扮演着非常重要的角色,它给网络赋予了非线性,从而使得神经网络能够拟合任意复杂的函数。

如果没有激活函数,无论多复杂的网络,都等价于单一的线性变换,无法对非线性函数进行拟合。

目前,深度学习中最流行的激活函数为 relu, 但也有些新推出的激活函数,例如 swish、GELU
如果我们不使用激活函数,那么正向运算时,val=np.dot(w,x)计算完就结束了,同样反向计算时,计算到 error=y(实际值)-y(计算值)后也结束了,这时,虽然最后也会有效果,但中间层完全没有作用。数理上也简单,如果不使用激活函数,那么可以假设这个激活函数为f(x)=x 同样,导数f'(x)=1,在反向调整误差时,中间层没发生变化。

同时这里我们选择激活函数sigmoid还因为他的如下优点:

1、实现简单,导数易获得。

2、输出在[0,1],所以可以用作输出层,表示概率。

3、最大熵模型,受噪声数据影响较小:

# 输出层:最后一层,神经元控制输出的维度,并指定分类激活函数
model.add(Dense(units=2, activation='sigmoid'))  # 指定分类激活函数

model.summary()

model.compile(loss='binary_crossentropy',  # 指定损失函数类型
              optimizer='rmsprop',  # 优化器
              metrics=['accuracy'])  # 评价指标

最后model.compile()是设置优化器、损失函数和准确率评测标准。

我们常会用到的参数有optimizer,用来配置训练时使用什么优化器、loss配置训练时使用什么损失函数。metrics的解释比较详细,它使用来指定深度学习模型的评价指标是什么,比较简单常用的有metrics=['accuracy']。官方文档还指出,如果是多输出的模型,可以使用一个字典来传给metrics,如metrics={'output_a': 'accuracy', 'output_b': ['accuracy', 'mse']}这样用来给多输出模型的不同输出,指定不同的评价指标。言外之意(我猜的),就是对多输出采用同一评价指标,可以使用metrics=['accuracy']较为简单,当然谨慎一点还是用metrics={'output_a': 'accuracy', 'output_b': 'accuracy'}。

当然也会见到loss_weights这个参数,比较有意思。官方给出的解释是,这个参数可以是一个列表或是一个字典(以列表为例)。列表中存放的都是常数(0.7,0.3)之类(以双输出的模型为例)。模型优化过程中,将损失函数最小化,是指对两个损失值求和之后最小化,那么这个loss_weights就是给这两个输出的损失值加权用的。

二、代码详解

(1)图片文件匹配

本次实验运用了os、glob、load_img, img_to_array, array_to_img等库,其中os.path.join(path,name):连接目录与文件名或目录。glob.glob():返回符合匹配条件的所有文件的路径。最后通过for循环遍历文件夹中的每个文件,并将图像转换为数组,同时用matplotlib库做出图像的比例图,为后面图形选择提供参数依据。

path = 'data'
os.path.join(path, '*/*/*.*')
# 使用 glob 模块批量匹配图像, * 代表匹配所有东西
img_list = glob.glob(os.path.join(path, '*/*/*.*'))
print('>>>图像数量:', len(img_list))
print(img_list[:5])

for i, img_path in enumerate(img_list[:6]):
    img_plot = load_img(img_path)  # 加载图像
    arr = img_to_array(img_plot)  # 将图像转换成数组
    print(arr.shape)  # 图像形状
    plt.subplot(2, 3, i + 1)
    plt.imshow(img_plot)

(2)图形裁剪

通过定义数据的长高即像素,更好的用以后面的图片特征提取,根据上文中加载数据初始时生成的图像比例图形,来确定宽高值。同时使用.image_data_format()进行图像维度确认,返回图像维度顺序(“channels_first”或“channels_last”),最后定义生成器以及生成器的使用,重放缩因子,是对图像进行缩放供后面裁剪进行图片特征的提取,定义的图像生成器说的简单一点就是为更好的对图形进行特征进行提取,此处看到我们使用了两个图像生成器,这分别用于训练集和测试集,能更好的提取出特征值。

# 统一定义图像像素的宽度和高度
img_width, img_height = 200, 200
# 定义训练集、验证集的图形路径(文件夹路径即可)
train_data_dir = 'data/train/'
validation_data_dir = 'data/validation/'

# 图像输入维度设置
if K.image_data_format() == 'channels_first':
    input_shape = (3, img_width, img_height)
else:
    input_shape = (img_width, img_height, 3)
# 定义图像生成器
train_datagen = ImageDataGenerator(rescale=1. / 255,  # 重缩放因子
                                   shear_range=0.2,  # 剪切强度(以弧度逆时针方向剪切角度)
                                   zoom_range=0.2,  # 随机缩放范围
                                   horizontal_flip=True,  # 随机水平翻转
                                   rotation_range=360  # 360度范围内随机旋转
                                   )
# 使用图像生成器,从train_data_dir目录中读取图片,生成训练集(X_train图片数据, y_train图片所在的目录名称)
train_generator = train_datagen.flow_from_directory(train_data_dir,  # 训练数据的文件夹路径
                                                    target_size=(img_width, img_height),  # 统一像素大小
                                                    batch_size=batch_size,  # 每一批次的观测数
                                                    class_mode='categorical'  # 指定分类模式,指定二分类
                                                    )
test_datagen = ImageDataGenerator(rescale=1. / 255,
                                  shear_range=0.2,  # 剪切强度(以弧度逆时针方向剪切角度)
                                  zoom_range=0.2,  # 随机缩放范围
                                  horizontal_flip=True)  # 随机水平翻转

validation_generator = test_datagen.flow_from_directory(validation_data_dir,  # 验证集文件夹路径
                                                        target_size=(img_width, img_height),
                                                        batch_size=batch_size,
                                                        class_mode='categorical'  # 二分类
                                                        )

(3)函数参数设置与回调

前一部分是设置运行结果的存储路径以及回调函数训练结果存储路径,model.fit_generator(),它一般是一个生成器函数,主要作用是利用生成器,分批次向模型送入数据的方式,可以有效节省单次内存的消耗。

回调函数作用:

回调函数是一组在训练的特定阶段被调用的函数集,你可以使用回调函数来观察训练过程中网络内部的状态和统计信息。通过传递回调函数列表到模型的.fit()中,即可在给定的训练阶段调用该函数集中的函数。

# tensorboard回调函数
logs = os.path.join("logs")
if not os.path.exists(logs):
    os.mkdir(logs)
train_callbacks = [
    TensorBoard(
        log_dir=r'./logs',
        histogram_freq=1,
    )
]

tensorboard_dir = os.path.join(r'.logspluginsprofile')
history = model.fit_generator(train_generator,
                              steps_per_epoch=nb_train_samples,
                              epochs=epochs,
                              validation_data=validation_generator,
                              validation_steps=nb_validation_samples,
                              callbacks=train_callbacks
                              )

(4)结果可视化

最后的结果可视化和前面选择的model.compile()有一定联系,为了成图的美观以及更好地观察的训练结果的变化形式,我们选择将数据结果可是化,数据结果可视化更容易观察出实验的结果变化,能更好的反应训练与验证的结果准确性。

#现在将训练后的结果可视化。
acc = history.history['acc']
val_acc = history.history['val_acc']

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

epochs_range = range(epochs)

plt.figure(figsize=(20, 10))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

三、参数调优

在模型框架都搭建好了,我们需要确定的就是这个模型训练的参数。以下代码的参数是影响训练结果的重要因素。其中步长是每次训练训练集和验证集选用数据数量。就是对于整个训练数据集,generator要在多少步内完成一轮遍历(epoch),从而也就规定了每步要加载多少数据(batch_size)。steps_per_epoch是通过把训练图像的数量除以批次大小得出的。

参数迭代次数(epoch)决定网络中所有图像的训练次数,一般选用50最佳。

因此,我们在训练一千张图片时可以选择多迭代几次,训练步长根据需求而定

我的电脑运行的迭代次数50次,步长选择100,50的配比,能训练出来达到百分之80几的效果,但是这是根据个人电脑情况。

注:有时候一次迭代,所输入的数据过大,训练次数过久会对电脑造成一定的负担,有的电脑会受不了,切记,实验需谨慎。

# 模型训练的参数设置
nb_train_samples = 60#步长
nb_validation_samples = 30
epochs = 30  # 迭代次数
batch_size = 32  # 每个批量观测数

# 统一定义图像像素的宽度和高度
img_width, img_height = 200, 200

四、结果分析

最后作出结果变化图,其中的参数为loss(训练集整体的损失值),val_loss(验证集(测试集)整体的损失值),两者之间存在如下关系:

  • 当loss下降,val_loss下降:训练正常,最好情况。
  • 当loss下降,val_loss稳定:网络过拟合化。这时候可以添加Dropout和Max pooling。
  • 当loss稳定,val_loss下降:说明数据集有严重问题,可以查看标签文件是否有注释错误,或者是数据集质量太差。建议重新选择。
  • 当loss稳定,val_loss稳定:学习过程遇到瓶颈,需要减小学习率(自适应网络效果不大)或batch数量。
  • 当loss上升,val_loss上升:网络结构设计问题,训练超参数设置不当,数据集需要清洗等问题,最差情况。

我们根据可视化结果可以依据以上方法了解到自己训练的结果是否良好,同时通过做出的Training and Validation Accuracy图看出本次训练的准确率的大小,通过此两处方法观察自己训练结果

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>