精华内容
下载资源
问答
  • 卷积显然是概率论和计算机图形学中的一个有用工具,但是用卷积来表述卷积神经网络,我们获得了什么? 第一个好处是,我们有了一些非常强大的语言来描述神经网络的层。卷积大大简化了繁琐的计算工作。 其次,卷积非常...

    点上方计算机视觉联盟获取更多干货

    仅作学术分享,不代表本公众号立场,侵权联系删除

    转载于:作者丨CoCoairforce@知乎(已授权)

    来源丨https://zhuanlan.zhihu.com/p/357648179

    AI博士笔记系列推荐

    周志华《机器学习》手推笔记正式开源!可打印版本附pdf下载链接

    抛球实验 -- Ball drop experiment

    想象一下,我们把一个球从某个高度落到地面上,它只有一个运动维度。如果你把球落下,然后再从它的落点上方把它落下,球会走一段距离   的可能性有多大?

    我们来分析一下: 第一次下落后,它将以概率   落在离起点一个单位的地方,其中 是概率分布。现在,在第一次落下之后,我们把球捡起来,从它第一次落地点以上的另一个高度落下。球从新的起点滚动   个单位的概率是  ,如果它是从不同的高度落下的,那么   可能是不同的概率分布。

    如果我们把第一次下落的结果固定下来,使我们知道球走了距离  ,对于球走的总距离  ,第二次下落时走的距离也固定为  ,即  。所以这种情况发生的概率简单来说就是  .。

    我们用一个具体的离散例子来思考这个问题。我们希望总距离   为 3。如果它第一次滚动, ,那么第二次必须滚动  ,才能达到我们的总距离  。这个概率是 

    然而,这并不是我们可以达到总距离3的唯一方法。球可以第一次滚1个单位,第二次滚2个单位。或者第一次滚0个单位,第二次滚3个单位。它可以是任何   和  ,只要他们加起来是 3。

    如上图所示,其概率分别为   和 

    为了求出小球到达总   的总概率,我们不能只考虑到达   的一种可能方式,而是考虑将   分成   和   的所有可能方式,并将每种方式的概率相加。

    我们已经知道,  的每一种情况的概率简单来说就是  .所以,将   的每一个解求和,我们可以将总似然表示为。

     和   的卷积,在   处被定义为。

    如果我们把 代入,我们得到。

    为了使这一点更加具体,我们可以从球可能落地的位置来考虑。在第一次落地后,它将以概率  落在中间位置  。如果它落在   处,它落在   处的概率为 

    为了得到卷积,我们需要考虑所有的中间位置。

    可视化卷积 -- Visualizing Convolutions

    假设一个球落在离原点一定距离   的概率是 。那么,它 从   处返回原点的的概率是

    如果我们知道球在第二次落地后落在  处,那么第一次的位置是   的概率是多少?

    所以,前一个位置是   的概率是  .

    每个中间位置球最终落在   处的概率。我们知道第一个落点把球放到中间位置   的概率是  。我们还知道,如果它落在   处,它在   处的概率是 

    将   的所有可能值相加,我们得到卷积结果。

    通过移动下半部分,当分布对齐时,卷积达到峰值。

    并且随着分布之间的交点越来越小而缩小。

    下图,我们能够直观地看到三角波与方波函数的卷积。

    掌握了这个要点,很多概念变得更加直观。

    在音频处理中有时会用到卷积。例如,人们可能会使用一个有两个尖峰,但其他地方都是零的函数来创建一个回声。当我们的双尖峰函数滑动时,一个尖峰首先击中一个时间点,将该信号添加到输出声音中,之后,另一个尖峰跟随,添加第二个延迟的副本。

    高维卷积--Higher Dimensional Convolutions

    卷积不仅仅适用于1维看空间,也适用于高维空间。

    回顾开头的例子,落下的球。现在,当它落下时,它的位置不仅在一维中移动,而且在二维中移动。

    和前面的卷积一样。

    只是,现在  , 和   都是向量。更明确地说,

    标准定义:

    就像一维卷积一样,我们可以把二维卷积看成是把一个函数滑动到另一个函数之上,进行乘法和加法。

    卷积神经网络--Convolutional Neural Networks

    那么,卷积与卷积神经网络的关系如何呢?

    在一个1维卷积层中,输入  ,输出 

    从信号与系统的角度来描述,

     是输入信号, 是输出信号,  是系统,这个系统由   个神经元组成,可以用输入来描述输出。

    也可以用神经网络的方式来描述

    • 其中  是输入,  是权重。权重描述了神经元与输入的连接方式。

    • 负的权重意味着输入会抑制神经元发射,而正的权重则鼓励它发射。

    • 权重是神经元的心脏,控制着它的行为.如果说2个神经元是相同的,即它们的权重是相同的。

    其中一个常见的应用是图像处理。我们可以把图像看作是二维函数。许多重要的图像变换都是卷积,你用一个非常小的局部函数(称为 "内核")对图像函数进行卷积。

    图像尺寸= 5(高度)x5(宽度)x1(通道数)

    在上面的演示中,绿色部分类似于我们的 5x5x1 输入图像  。在卷积层的第一部分进行卷积操作的元素被称为Kernel/Filter, 用黄色表示。我们选择一个3x3x1矩阵作为Kernel。

    Kernel 以一定的步伐向右移动,直到它解析出整行的宽度。接着,它以相同的步伐值跳到图像的开头(左边),并重复这个过程,直到遍历整个图像。

    在多通道图像的情况下( 如RGB ),Kernel 的深度与输入图像的深度相同。Kernel 与图片   进行矩阵乘法,然后将所有结果与偏置相加,得到一个单通道卷积特征输出。

    卷积操作的目的是从输入图像中提取高级特征,如边缘。传统上,卷积层可以捕捉低级特征,如边缘、颜色、梯度方向等。随着层数的增加,架构也可以捕捉高阶特征,让我们的神经网络对图像有更深刻的理解.

    该卷积有两种结果--一种是卷积特征与输入相比维度减少,有效填充(Valide Padding)。另一种是维度增加或保持不变,相同填充(Same Padding).

    当我们将5x5x1的图像填充为6x6x1的图像,然后在其上应用3x3x1的核,我们发现卷积矩阵变成了5x5x1的尺寸。因此,我们将其命名为--相同填充(Same Padding)。

    另一方面,如果我们在没有填充的情况下执行同样的操作,我们将得到一个具有内核(3x3x1)本身尺寸的矩阵--有效填充(Valide Padding)。

    池化层 -- Pooling Layer

    与卷积层类似,Pooling层负责减少卷积特征的空间大小。这是为了通过降低维度来降低处理数据所需的计算能力。此外,它还有助于提取旋转和位置不变的主导特征,从而保持模型的有效训练过程。下图表示在5x5卷积特征上的3x3池化。

    有两种类型的池化。最大池化和平均池化。最大池化(Max Pooling)返回的是Kernel覆盖的图像部分的最大值。另一方面,平均池化(Average Pooling)返回Kernel覆盖的图像部分的所有值的平均值。

    Max Pooling也是一种噪声抑制器。它完全丢弃了嘈杂的激活,并在降低维度的同时进行去噪。另一方面,Average Pooling只是作为噪声抑制机制进行维度降低。因此,我们可以说Max Pooling的性能比Average Pooling好很多。

    卷积层和池化层,共同构成了卷积神经网络的第 层。根据图像的复杂程度,可以增加这些层的数量,以便进一步捕捉低层次的细节,但代价是增加计算能力。

    在经历了上述过程后,我们已经成功地使模型理解了特征。接下来,我们要将最终的输出结果进行扁平化处理,并将其馈送到普通的神经网络中,以达到分类的目的。

    全连接层(FC层) —- Fully Connected Layer (FC Layer)

    全连接层正在学习该空间中可能的非线性函数。

    现在,我们已经将输入图像转换为适合多级感知器 (Multi-Level Perceptron) 的形式,我们将把图像扁平化(Flatten layer)为列向量。扁平化的输出被送入前馈神经网络,并在每次训练迭代中应用反向传播。在一系列的纪元中,该模型能够区分图像中的主导特征和某些低级特征,并使用Softmax分类方法对其进行分类。

    总结 -- Conclusion

    我们在这篇博文中介绍了很多数学机制,但我们获得的东西可能并不明显。卷积显然是概率论和计算机图形学中的一个有用工具,但是用卷积来表述卷积神经网络,我们获得了什么?

    • 第一个好处是,我们有了一些非常强大的语言来描述神经网络的层。卷积大大简化了繁琐的计算工作。

    • 其次,卷积非常容易实现。现存的许多库都提供了高效的卷积方法。

    • 此外,卷积看起来是一个  操作,但使用一些相当深刻的数学见解,可以创建一个   的实现。

    Tips:

    我们想知道球第一次滚动   单位,第二次滚动   单位的概率。所以 .

    卷积满足交换律,即 

    卷积满足结合律的,即 ,

    end

    我是王博Kings,一名985AI博士,华为云专家/CSDN博客专家,单个AI项目在Github上获得了2000标星,为了方便大家交流,附上了联系方式。

    这是我的私人微信,还有少量坑位,可与相关学者研究人员交流学习 

    目前开设有人工智能、机器学习、计算机视觉、自动驾驶(含SLAM)、Python、求职面经、综合交流群扫描添加CV联盟微信拉你进群,备注:CV联盟

    王博Kings 的公众号,欢迎关注,干货多多

    王博Kings的系列手推笔记(附高清PDF下载):

    博士笔记 | 周志华《机器学习》手推笔记第一章思维导图

    博士笔记 | 周志华《机器学习》手推笔记第二章“模型评估与选择”

    博士笔记 | 周志华《机器学习》手推笔记第三章“线性模型”

    博士笔记 | 周志华《机器学习》手推笔记第四章“决策树”

    博士笔记 | 周志华《机器学习》手推笔记第五章“神经网络”

    博士笔记 | 周志华《机器学习》手推笔记第六章支持向量机(上)

    博士笔记 | 周志华《机器学习》手推笔记第六章支持向量机(下)

    博士笔记 | 周志华《机器学习》手推笔记第七章贝叶斯分类(上)

    博士笔记 | 周志华《机器学习》手推笔记第七章贝叶斯分类(下)

    博士笔记 | 周志华《机器学习》手推笔记第八章集成学习(上)

    博士笔记 | 周志华《机器学习》手推笔记第八章集成学习(下)

    博士笔记 | 周志华《机器学习》手推笔记第九章聚类

    博士笔记 | 周志华《机器学习》手推笔记第十章降维与度量学习

    博士笔记 | 周志华《机器学习》手推笔记第十一章特征选择与稀疏学习

    博士笔记 | 周志华《机器学习》手推笔记第十二章计算学习理论(上)

    博士笔记 | 周志华《机器学习》手推笔记第十二章计算学习理论(下)

    博士笔记 | 周志华《机器学习》手推笔记第十三章半监督学习

    博士笔记 | 周志华《机器学习》手推笔记第十四章概率图模型

    点个在看支持一下吧

    展开全文
  • 卷积神经网络原理与实战卷积神经网络原理与实战前言卷积神经网络的原理卷积运算卷积神经网络的维度运作的本质Keras搭建的卷积神经网络训练前的数据处理一维卷积神经网络时间序列数据与序列数据一维卷积运算:Keras一...

    卷积神经网络原理与实战

    Time:2021-07-16
    Author:雾雨霜星
    个人网站:雾雨霜星 | DA☆ZE (shuangxing.top)
    转载请注明出处!!!

    前言

    开始学习CNN,试着跑了一个分类MNIST手写数据的卷积神经网络模型,跑了一个猫狗图片分类的demo,经过测试与总结后写下笔记。

    内容包括:卷积神经网络的原理、各个卷积神经网络对于数据格式、相应的keras接口、实战全过程源码记录及其分析

    重点记录了使用Keras完成一次卷积神经网络训练的代码思路,其中关键记录了GPU内存分配设置和使用图像数据生成器,以及两个训练中遇到的错误及其解决方法。

    卷积神经网络的原理

    卷积运算

    连续函数间的卷积运算公式如下:
    f ( x ) ∗ g ( x ) = ∫ f ( τ ) g ( x − τ ) d τ f(x) \ast g(x)=\int f(\tau)g(x-\tau)d\tau f(x)g(x)=f(τ)g(xτ)dτ
    矩阵间的卷积运算:首先将卷积核矩阵翻转180度(上下对换,不是倒置),然后卷积核对应在被卷积的矩阵上,每个元素对应相乘,将各个元素对应计算结果相加即得到某次运算的值。然后按照步幅沿着相应维度移动,再次重复上述计算。

    特点:局部数据的总体计算

    卷积神经网络的维度

    卷积神经网络的维度是指卷积核的移动有几个维度。对于一维卷积神经网络,卷积核只沿着一个维度进行移动。二维卷积神经网络中卷积核将会沿着两个不同的维度移动(先沿着行移动再沿着列移动)。

    运作的本质

    通过使用卷积运算,来发掘一个完整的整体数据各个局部区域的细节特征。

    通过使用不同的滤波器(不同的卷积核)来提取得到不同的特征。

    每一层卷积神经网络计算后得到内容的本质是样本特征。比如对于图像,使用二维卷积神经网络就是在发掘图像每个局部区域的特征,得到一些列的特征图,再进行下次的卷积,在每个特征图上进一步划分特征细节,最终得到许多的细节特征图,通过组合细节特征图,来进行决策。

    Keras搭建的卷积神经网络训练前的数据处理

    1. 数据向量化

      即把数据转化为网络输入要求的张量形式。

      一般对数据使用numpy.reshape()方法。不是为了改变大小,而是为了设置深度维度(通常对二维卷积神经网络的数据需要)。因为图片输入时,被看做为(M * N)的二维数据,而实际上应该是M * N * 1 或 M * N * 3的数据。

      (train_data,train_label),(test_data,test_label)=mnist.load_data()
      train_image = train_data.reshape((60000,28,28,1))
      test_image = test_data.reshape((10000,28,28,1))
      
    2. 数据归一化

      • 对于图像数据,转化为float类型并且全部值除以255

        train_image = train_image.astype(np.float32)/255
        test_image = test_image.astype(np.float32)/255
        

        经过测试,如果不除以255,那么训练结果会较早出现过拟合且最终效果明显下降。

      • 对于序列数据,

        还未具体进行相关实践,按照之前的经验,应该是对各个特征分别进行数据的标准化。

    3. 数据集打乱

      对于序列数据集或者图像数据集,容易出现相关性较强的两个数据在附件,为了让验证集和训练集的状况更加接近,应该进行数据集打乱。

      打乱数据集的同时要保证对于目标值/标签值按照相同的顺序打乱。

      可以采用如下方法:

      # 数据集(样本与标签)对应序号打乱
      permutation = np.random.permutation(train_label.shape[0])
      train_image_shuffled = train_image[permutation]
      train_labels_shuffled = train_label[permutation]
      

      np.random.permutation方法,输入一个数字,则生成一个该数字范围内的数字随机排列。

      然后样本和标签分别按照这个顺序进行排列即可。

    4. 标签处理

      比如标签向量化:

      train_label = keras.utils.to_categorical(train_label)
      test_label = keras.utils.to_categorical(test_label)
      

      keras.utils.to_categorical方法即转化为one-hot编码标签,通常对分类任务的标签进行处理。

      或者其他提取标签特征,以更好方式表现标签特征的方法。

    一维卷积神经网络

    时间序列数据与序列数据

    一维卷积神经网络通常是对 时间序列数据 或者 序列数据 进行使用的。

    时间序列数据:每一行代表一个特征,每一列代表一个时刻(看作一帧)。每个矩阵是一个样本。

    时间序列数据的一个样本的shape为:(feature_dimension,total_time_step,batch_size)。

    实例:交易市场某只股票价格数据
    
    数据特征:该时刻股票价格、上一时刻股票最高价格、上一时刻股票最低价格
    单位时间长度:1min(即每一帧之间的时间间隔)
    每个样本总时间长度:1天(24*60min=1440min)
    
    故一个样本有3行,1440列。一个样本集中的样本数就是有几天这样的数据。
    
    数据集的样子:
    每个样本:[[...],[...],[...]]
    

    序列数据:每一行代表一个特征,每一列代表一个单位间隔,每个样本是矩阵。

    时间序列数据是序列数据的一种,只是时间序列数据中每个单位间隔是时间。

    序列输出常有的一种是字符串(文本类数据):每个字符的编码号是一个特征,每个单位间隔是这个字符串(文本)中的每个字符。

    实例:文章内容数据集
    
    数据特征:假设定义字符编码形式500种,即文章中只有这500个字可能出现。
             (即出现该字符则相应该位置为1,其余位置为0)
    单位间隔:文章每个字符
    每个样本的间隔总数:假设限制文章最多1000字,则一篇文章最多只有1000个字符。
    
    故一个样本共有500行,1000列。每个样本代表一篇这样的文章。一个数据集中的样本数就是这样的文章的数量。
    
    数据集的样子:
    每个样本[[...],[...],[...],[...],[...],...]
    

    一维卷积运算:

    卷积核是一个矩阵(features * kernel_size),kernel_size即卷积核的长度(卷积核每次卷积所包括的时间长度),features即样本特征数(样本行数)。其中矩阵的每个元素是一个整数。

    对于被卷积的数据是一个矩阵或者行向量。

    一维卷积运算就是卷积核沿着一个维度进行卷积,一般默认这个维度就是描述“time_step/单位间隔”的维度。

    每个卷积核进行卷积运算的最终的输出结果大小取决于采用“窄卷积”或者“宽卷积”的模式。

    • 注意不是一个行向量每次一行一行地进行卷积,而是一个矩阵,直接对一个时刻的所有特征,沿着时间步骤的维度进行卷积。

    Keras一维卷积神经网络API

    参考:Conv1D layer (keras.io)

    import numpy as np
    import tensrflow as tf
    import tensorflow.keras as keras
    
    model = keras.models.Sequential()
    model.add(keras.layers.Conv1D(filters=100, kernel_size=10, padding="valid", input_shape=(1440,3)))
    model.add(keras.layers.Activation(keras.activations.relu))
    model.add(keras.layers.MaxPooling1D(3))
    model.add(karas.layers.Flatten())
    model.add(karas.layers.Dense(1))
    

    此处需要注意的是Conv1D方法的input_shape,输入的是(time_period, features),time_period代表时间区间,即所输入数据每个样本的时间序列长度(列数)。而features是特征维度,即每个样本有几个特征(行数)。

    滤波器(filter),在给定参数时filters参数即设置滤波器的个数,每个滤波器会使用不同的卷积核进行卷积运算,从而实现不同细节特征的提取。

    每个滤波器使用一个卷积核进行卷积运算得到一个行向量。因此对于一个样本,经过每一层得到的数据是一个2D张量,其shape为(新的时间步骤长度,滤波器的个数),而每一行则代表了一个滤波器的卷积核运算后所得某种特征的时间序列。

    kernel_size参数用于指定窗体的宽度(把样本行数看做窗体的高度),即每次卷积运算中包括数据的时间范围大小。

    如果使用的是keras.Input作为输入尺寸的指定,那么我猜测应是这样的:

    model.add(keras.Input(shape=(1440,3,)))
    

    因为没有试过所以我也不太清楚对不对了。。。建议还是采用上述把input_shape写到卷积层里的官方文档的写法。

    代码中的MaxPooling1D是一维池化层,池化层的作用都是一样的,这里是用一个矩阵沿着一个维度进行池化。

    官方文档中说明了,相应输入下,最终的输出为:

    Input shape
    3+D tensor with shape: batch_shape + (steps, input_dim)
    
    Output shape
    3+D tensor with shape: batch_shape + (new_steps, filters) steps value might have changed due to padding or strides.
    
    Returns
    A tensor of rank 3 representing activation(conv1d(inputs, kernel) + bias).
    

    二维卷积神经网络

    图像数据

    应用:二维卷积神经网络的使用对象通常是图像数据,即每个样本代表了一张图像。

    图像在计算机中的表示:使用矩阵(或者三维矩阵)进行描述的,每个矩阵的点代表一个像素点,矩阵的行列代表了图像的长宽。

    图像的分辨率:图像中存储的信息量,是每英寸图像内有多少个像素点。单位:PPI。即相应的长宽下如何切分图像为等大小的方块。

    图像分辨率_百度百科 (baidu.com)

    (关于dpi与ppi的区别参考:DPI 和 PPI 的区别是什么? - 知乎 (zhihu.com)

    图像的像素(像素值):图像分割为相应的小块,每一小块处计算机赋予的值。用于描述该小块平均亮度信息,大小范围是0~255px。

    灰度图图像的像素值:范围是0~255px,值越大说明亮度越高(白色),值越小则相应越黑。

    RGB图像:在计算机中是一个含有三个矩阵数据元素的数组。每个矩阵记录一个颜色通道的图片各个像素点的像素值。三个通道分别为红绿蓝。

    数据形式:

    1. 灰度图:每个样本是一个矩阵(2D张量),矩阵每个值代表图像像素点的像素值。一个样本集中样本数量图片的数量。
    2. RGB图像:每个样本是一个3D张量,格式为:(height,width,color_depth),即三个颜色通道各自一个矩阵进行描述。一个样本集中样本数量图片的数量。

    二维卷积运算

    卷积核一般是一个矩阵(方阵),即shape为(kernel_size* kernel_size),对图像样本数据进行卷积运算,就是线性代数的矩阵的卷积运算。

    实际上,卷积核是多层的矩阵。如果对象是RGB图像,即输入样本的input_depth=3,那么每个滤波器的卷积核是(kernel_size* kernel_size*3)的。最终的计算结果,一般是采用将滤波器每层进行卷积的结果相加。即对于RGB图像,每个滤波器有三层,分别对三个通道进行卷积运算,再把各层卷积运算结果对应相加,得到该滤波器在这个RGB图像样本上的卷积计算结果。

    对多通道图像的二维卷积,每次卷积运算公式如下:
    f ( x , y , z ) ∗ g ( x , y , z ) = ∫ ( ∫ ∫ f ( u , v , z ) g ( x − u , y − v , z ) d u d v ) d z f(x,y,z)\ast g(x,y,z) = \int(\int\int f(u,v,z)g(x-u,y-v,z)dudv)dz f(x,y,z)g(x,y,z)=(f(u,v,z)g(xu,yv,z)dudv)dz
    可见只是沿着两个方向进行卷积,卷积核只有两个移动维度,第三维度(图片通道数/深度)是直接相加的。

    RGB图像在CNN中如何进行convolution? - 知乎 (zhihu.com)

    注意卷积核会沿着两个维度进行移动,一般默认是,首先沿着行维度进行移动,该行卷积完成后先沿着列维度进行移动到下一行,再继续沿着这一行移动进行卷积运算,如此循环。

    Keras二维卷积神经网络API

    参考:Conv2D layer (keras.io)

    搭建二维卷积层,使用keras的layers接口中提供的Conv2D方法。

    例如:

    model.add(keras.layers.Conv2D(filters=100, kernel_size=10, padding="valid", input_shape=(28,54,3)))
    

    其中input_shape用于指定每个样本的形状,此处即 28*54 大小的RGB图像。其中28是行宽,54是列宽。

    filters同理用于指定滤波器的个数,样本与每个滤波器卷积运算后得到一个2D张量(矩阵),而所有滤波器的结果总体就是一个3D张量,即相应每一层的输出数据形状为:(new_row, new_col, filters)。

    官方文档说明的如下:

    Input shape
    4+D tensor with shape: batch_shape + (channels, rows, cols) if data_format='channels_first' or 4+D tensor with shape: batch_shape + (rows, cols, channels) if data_format='channels_last'.
    
    Output shape
    4+D tensor with shape: batch_shape + (filters, new_rows, new_cols) if data_format='channels_first' or 4+D tensor with shape: batch_shape + (new_rows, new_cols, filters) if data_format='channels_last'. rows and cols values might have changed due to padding.
    
    Returns
    A tensor of rank 4+ representing activation(conv2d(inputs, kernel) + bias).
    

    三维卷积神经网络

    视频数据

    应用:三维卷积神经网络的应用对象通常是视频数据。

    视频数据:通常每个视频数据样本是一个4D张量,可认为许多图片(帧)组合为一个视频,每个图片时3D张量(灰度图则第三维度为1)。

    Keras三维卷积神经网络API

    参考:Conv3D layer (keras.io)

    使用方法keras.layers.Conv3D搭建三维卷积层。

    Keras 卷积神经网络实战记录

    环境

    Windows10

    Anaconda虚拟环境:Python3.7.7、tensorflow2.0.0、tensorflow-gpu2.0.0、Pillow8.3.1

    CPU:intel CORE i7 10thGen

    显卡:NVIDIA RTX2060

    CUDA10.0.0、cudnn7.4.1

    关于GPU开发环境的配置(CUDA与cudnn的配置)

    参考我的文章:

    制作数据集

    1. 下载数据集:到Kaggle官方网站进行注册,然后到:Dogs vs. Cats | Kaggle 验证账户后下载所有的图片集合(不是只下载csv)

    2. 下载得到压缩包,解压即可得到train和test两个文件夹,里面都是关于猫的或者狗的JPG图像

    3. 图片格式:下载得到数据的图片,训练集图片命名格式为:cat/dog.num.jpg ; 测试集图片命名格式为 num.jpg

      其中训练集(train文件夹中)共有猫的图片12500张,狗的图片12500张。测试集(test文件夹中)共有图片12500张。

    4. 使用python的shutil模块进行数据集制作:

      # 确定训练集和测试集图片所在文件位置
      origin_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/train'
      origin_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/test'
      # 设置目标训练集、验证集、测试集保存图片的文件位置
      dataset_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_train'
      dataset_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_test'
      dataset_image_val_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_val'
      
      # 使用Python的os模块设置各个路径变量
      train_dogs_dir = os.path.join(dataset_image_train_dir,'dogs')
      train_cats_dir = os.path.join(dataset_image_train_dir,'cats')
      test_dogs_dir = os.path.join(dataset_image_test_dir,'dogs')
      test_cats_dir = os.path.join(dataset_image_test_dir,'cats')
      val_dogs_dir = os.path.join(dataset_image_val_dir,'dogs')
      val_cats_dir = os.path.join(dataset_image_val_dir,'cats')
      
      # 制作文件夹(如果该文件夹还未存在则制作,否则注释掉该部分)
      # os.mkdir(train_dogs_dir)
      # os.mkdir(train_cats_dir)
      # os.mkdir(test_dogs_dir)
      # os.mkdir(test_cats_dir)
      # os.mkdir(val_dogs_dir)
      # os.mkdir(val_cats_dir)
      
      # 制作训练集
      fnames = {'dog.{}.jpg'.format(i) for i in range(5000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(train_dogs_dir,fname)
          shutil.copyfile(src,dst)
      fnames = {'cat.{}.jpg'.format(i) for i in range(5000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(train_cats_dir,fname)
          shutil.copyfile(src,dst)
      # 制作测试集
      fnames = {'dog.{}.jpg'.format(i) for i in range(5000,8000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(test_dogs_dir,fname)
          shutil.copyfile(src,dst)  
      fnames = {'cat.{}.jpg'.format(i) for i in range(5000,8000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(test_cats_dir,fname)
          shutil.copyfile(src,dst)
      # 制作验证集
      fnames = {'dog.{}.jpg'.format(i) for i in range(8000,10000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(val_dogs_dir,fname)
          shutil.copyfile(src,dst)  
      fnames = {'cat.{}.jpg'.format(i) for i in range(8000,10000)}
      for fname in fnames:
          src = os.path.join(origin_image_train_dir,fname)
          dst = os.path.join(val_cats_dir,fname)
          shutil.copyfile(src,dst)
      

      基本思路就是,确定需要复制的图像的名字列表,按照列表进行迭代,每次迭代就是使用shutil的copyfile方法,确定一个文件所在的具体路径和文件的目标路径。

    使用图像数据生成器

    使用keras的数据预处理API中的preprocessing的image类组件:ImageDataGenerator

    即:tensorflow.keras.preprocessing.image.ImageDataGenerator()

    参考官方文档:Image data preprocessing (keras.io)

    使用该数据生成器主要是可以从数据指定位置按照需要的格式将训练数据和标签输入,而不用事先将全部训练数据输入到变量中,从而不用消耗大量的内存。同时,该数据生成器可以使用“数据增强”,从而增加样本数。

    使用方法:

    1. 构建ImageDataGenerator实例,设置数据增强和数据输入是的处理方法
    2. 使用ImageDataGenerator类的输入流方法,获取数据输入的迭代器实例
    3. 调用迭代器获取数据

    参考代码如下:

    # 构建数据生成器的实例
    ImageDataGenerator = tensorflow.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)
    # 使用目录输入流的方法,获取相应的迭代器
    # 训练集的数据生成器迭代器
    TrainGen = ImageDataGenerator.flow_from_directory(dataset_image_train_dir,
                                                     target_size=(150,150),
                                                     batch_size=64,
                                                     class_mode='binary')
    # 测试集的数据生成器迭代器
    TestGen = ImageDataGenerator.flow_from_directory(dataset_image_test_dir,
                                                    target_size=(150,150),
                                                    batch_size=64,
                                                    class_mode='binary')
    # 验证集的数据生成器迭代器
    ValGen = ImageDataGenerator.flow_from_directory(dataset_image_val_dir,
                                                   target_size=(150,150),
                                                   batch_size=64,
                                                   class_mode='binary')
    

    其中设置ImageDataGenerator的rescale参数是用于图像数据归一化,该指定值会被乘到每个输入图像数据矩阵中。

    flow_from_directory方法,返回字典迭代器的实例对象,该对象可用于从指定目录下的数据集中按batch_size输入数据。官方解释为:

    Returns
    A DirectoryIterator yielding tuples of (x, y) where x is a numpy array containing a batch of images with shape (batch_size, *target_size, channels) and y is a numpy array of corresponding labels.
    

    可见该迭代器每次返回一组数据和标签,标签的形式按照class_mode参数决定。

    该输入流方法一般提供四个参数:directory、target size、batch size、class mode。分别用于:指定数据所在目录路径、输入图像调整尺寸、每次迭代输入批量、标签类型。其中该方法提供shuffle参数,默认为true,从目录输入时打乱数据集。

    • 注意,目录路径应该按照官方所确定的如下格式:

      main_directory/
      ...class_a/
      ......a_image_1.jpg
      ......a_image_2.jpg
      ...class_b/
      ......b_image_1.jpg
      ......b_image_2.jpg
      

      也就是说flow_from_directory方法中给定的目录路径下应该是相应所分类别的文件夹,文件夹内才是图片数据。
      例如我给的目录路径为:dataset_image_train_dir = ‘E:/MLTrainingData/dogs-vs-cats/dataset_train’,该路径下只有dogs和cats两个文件夹,这两个文件夹中分别是狗的图片和猫的图片。

      相应的标签会按照该路径下文件夹状况进行生成:

      # 检查各个数据集的标签分类是否一致
      print(TrainGen.class_indices)
      print(TrainGen.class_indices)
      print(TrainGen.class_indices)
      # 输出结果
      # {'cats': 0, 'dogs': 1}
      # {'cats': 0, 'dogs': 1}
      # {'cats': 0, 'dogs': 1}
      

      通过对迭代器实例对象调用class_indices属性可以检查其分类状况。

    搭建神经网络模型

    使用KerasAPI,按照堆叠式的结构进行搭建:

    model = models.Sequential()
    model.add(keras.Input(shape=(150,150,3)))
    model.add(layers.Conv2D(32,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(64,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(128,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(128,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Flatten())
    model.add(layers.Dropout(0.5))
    model.add(layers.Dense(512,activation='relu'))
    model.add(layers.Dense(1,activation='sigmoid'))
    

    对于卷积神经网络,基本结构就是: 卷积层->池化层->…->展平层->Dropout层->全连接层->全连接输出层

    卷积层的滤波器个数逐渐增多,而因为这里是分类问题,所以使用一个神经元进行输出,因为是二分类,所以对输出神经元使用sigmoid激活,把输出值映射到0~1的区间。

    模型编译:

    model.compile(optimizer=keras.optimizers.Adam(lr=1e-4),
                 loss='binary_crossentropy',
                 metrics=['acc'])
    

    采用Adam优化器:结合Momentum和RMSprop的优点,采用动量的方法:每次更新都考虑前一次梯度计算结果;采用RMSprop的思路:计算衰减率用于改变每一次的学习率,Adam正是使用了动量计算和RMSprop中学习率衰减的计算方法。

    对于二分类问题,损失函数可以使用二分类交叉熵,binary_crossentropy。

    选择准确率用于评估模型。

    GPU显存分配设置初始化

    这一个步骤对于使用GPU进行训练应是必要的,缺少这个步骤,会因为对GPU内存不能保证全部数据输入而报错。

    只需添加如下代码:

    from tensorflow.compat.v1 import ConfigProto
    from tensorflow.compat.v1 import InteractiveSession
    
    config = ConfigProto()
    config.gpu_options.allow_growth = True
    session = InteractiveSession(config=config)
    

    tensorflow.compat.v1具体内容参考官方文档:Module: tf.compat.v1 | TensorFlow Core v2.5.0 (google.cn)

    tensorflow.compat模块中提供了TF1与TF2各种API的完整副本,主要是给用户对接TF1与TF2的项目,以实现TF两个版本(版本1和版本2)前后兼容。官方文档还说到,tensorflow.compat里面还包含一组帮助函数,用于编写在以下两种情况下都能工作的代码。

    tensorflow.compat.v1即包括了TF1的各种API。

    ConfigProto类:在创建Tensorflow的会话session时,用来对session进行参数配置。给出参数配置的实例。

    session:Tensorflow架构中,每次训练都会需要会话,会话是程序与GPU打交道的方法,即通过会话告知GPU相应的任务。(很久以前学Tensorflow时似乎看到过相关内容,不记得自己这样的理解对不对了)

    config.gpu_options.allow_growth = True 即设置了GPU可以进行动态显存申请。可能原本是给定一定的空间给GPU,而后就去用这部分显存进行训练。这里的设置应该是如果GPU在训练时需要更大的显存,可以动态申请使用。这样在通常状况下,可以有效避免训练时显存不足的问题。

    InteractiveSession类:可以用于加载自身默认构建的会话。不需要对session使用with语句再形成结构。即InteractiveSession可看作为带有with结构的session。

    官方源码注解中写道,对于session需要使用with语句形式形成默认会话:

    Note that a regular session installs itself as the default session when it is created in a `with` statement.  
    来源:https://github.com/tensorflow/tensorflow/blob/v2.5.0/tensorflow/python/client/session.py#L1689-L1794
    其中的1714行
    

    而InteractiveSession自身即带有了with结构的session,因此以下语句等效:

    a = tf.constant(4)
    b = tf.constant(7)
    c = a + b
    with tf.Session() as sess:
        print(c.eval())
    ##############################
    a = tf.constant(4)
    b = tf.constant(7)
    c = a + b
    sess = tf.InteractiveSession()
    print(c.eval())
    

    因此在配置GPU的时候,使用session = InteractiveSession(config=config)对会话进行设置。将参数配置实例输入到相应session的设置中。因为这里使用的是InteractiveSession,因此不需要使用with语句形式来写。

    参考:

    模型训练

    与以往不同,我们不是直接给出存储所有数据集合标签的变量用于训练,而是给出可以产生数据集合标签的迭代器,因此,在使用数据生成器的状况下,一般训练函数采用fit_generator,而测试集用于评估模型时则使用evaluate_generator。

    fit_generator

    输入训练集迭代器,给出总轮数,确定每轮执行迭代器获取数据多少次,同时可以输入验证集迭代器,那么还需要会给出每次验证从验证集迭代器获取数据的次数。例如:

    steps_per_epoch=5000//64
    validation_steps=2000//64
    his = model.fit_generator(TrainGen,epochs=100,steps_per_epoch=steps_per_epoch,
                              validation_data=ValGen,validation_steps=validation_steps)
    

    其中steps_per_epoch即每轮训练需要从迭代器获取数据的次数,而validation_steps是每一轮验证时从验证集迭代去获取数据的次数。

    使用steps_per_epoch与validation_steps的意义

    因为数据生成器提供的迭代器,可以无限迭代生成给出数据,如果不设置每次迭代器进行数据获取的次数,那么就会无限一直迭代获取。使用普通的fit方法中不使用这个参数是因为那些数据集是有限的,默认提取数据到全部用完就停止。而数据生成器可以按照数据增强的规则无限生成数据。

    因此每一轮训练需要指定、每轮验证也需要指定。

    一般是当获取数据总数为整个样本的总数,即整个样本的长度除以每次提取数据的batch size,经过这么多轮后实际已经向模型输入了整个数据集数据数量的数据。

    evaluate_generator

    输入测试集数据生成器的迭代器实例和指定获取数据次数即可。

    获取训练过程数据绘制图表

    对训练方法返回值,调用history对象,获取数据字典,使用matplotlib的pyplot绘图。

    获取数据字典:

    # 获取训练方法返回对象
    his = model.fit_generator(TrainGen,epochs=100,steps_per_epoch=steps_per_epoch,
                              validation_data=ValGen,validation_steps=validation_steps)
    # 获取训练过程数据记录字典
    h = his.history
    # 检查字典有什么数据记录
    print(h.keys())
    # 获取具体数据,得到相应数据每轮的值,以列表形式记录
    Loss = h['loss']
    Acc = h['acc']
    Val_Loss = h['val_loss']
    Val_Acc = h['val_acc']
    # 设置绘图横坐标的数据(轮数),得到一个整数列表
    Epochs = range(1,len(Loss)+1)
    

    绘图:

    plt.figure(figsize=(12,4))
    plt.subplot(1,2,1)
    plt.plot(Epochs,Loss,'-',label='train loss')
    plt.plot(Epochs,Acc,'--',label='train acc')
    plt.xlabel('Epoch')
    plt.ylabel('Value')
    plt.title('Train Loss and Accuracy')
    plt.legend()
    plt.subplot(1,2,2)
    plt.plot(Epochs,Val_Loss,'-',label='validation loss')
    plt.plot(Epochs,Val_Acc,'--',label='validation acc')
    plt.xlabel('Epoch')
    plt.ylabel('Value')
    plt.title('Validation Loss and Accuracy')
    plt.legend()
    plt.show()
    

    plt.figure(figsize=(12,4))创建图表对象,设置size,在Jupyter中该size可以较为美观的在一行显示两个图。

    plt.subplot(1,2,1)设置子图相应编号和总体。

    plt.legend()添加曲线符号指示对于曲线含义。

    模型测试

    使用Keras的model API中的方法:model.evaluate_generator

    result = model.evaluate_generator(TestGen,steps=50)
    print(result)
    

    模型的保存与调用

    参考:Model saving & serialization APIs (keras.io)

    错误记录

    • ImportError: Could not import PIL.Image. The use of load_img requires PIL.

      这个错误的原因在于环境中没有安装Pillow库或者Pillow库的版本在7.0以下。

      虽然不需要引用Pillow库,但是使用fit_generator方法训练时,是要提供Pillow库中部分功能才能实现的。

      我在Anaconda中,找到相应虚拟环境,pip安装Pillow,然后重启Anaconda或者重新进入该虚拟环境后生效,错误解决。

    • UnknowError:Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above. [Op: Conv2D]

      大概意思是在执行Conv2D需要的运算时,无法加载卷积运算算法,可能是cuDNN初始化失败导致。

      但是实际上,并不是我的cuDNN有问题,或者环境驱动什么的没有配置好。而是GPU内存(显存)分配的问题,可能是这里的卷积算法需要载入足够数据才能执行,即给定了数据长度但是还没载入完毕,所以这个算法加载使用失败,没能加载这么多数据是因为GPU没有设置动态分配内存,导致运算中发生内存不足的问题。

      以上理解,不知对不对,仅供参考。

      解决的方法是在整个工程前几行加入显存动态分配的设置:

      from tensorflow.compat.v1 import ConfigProto
      from tensorflow.compat.v1 import InteractiveSession
      config = ConfigProto()
      config.gpu_options.allow_growth = True
      session = InteractiveSession(config=config)
      

      这样就不会再报错了,可以正常训练模型了。

      小插曲

      一开始我百度找到了这个错误的相关说法,然后也添加了以上代码,但是,我没有重新启动这个项目,而是直接加入后run了这段,然后再去run训练部分的代码,报错没有变化,所以我以为自己不是这个问题,就跑去研究驱动了…终于到了第二天早上,闲着试试,加入这段代码后,关掉了整个Anaconda(我用的是Window系统上Anaconda的图形化界面),然后重新打开Anaconda来跑这个工程,然后就发现没有报错了…

      修改配置前必须停下这个GPU在这部分程序已经使用的部分,gpu的配置要在其使用之前进行,才能有效。

      怎么说,Anaconda里面的Jupyter,程序报错不意味着这个项目已经停止运作,需要自己关掉,但是关掉这个页面,会发现Jupyter目录中对应的这个程序那本书的小图片还是绿色的,这是否意味着整个工程已经停止?不太懂了。

      原来是要到Jupyter的Running区(图标下方的导航栏第二个选项卡),找到相应工程点击关闭,以此在Jupyter里面关闭一个工程。

    使用数据增强

    对于数据生成器ImageDataGenerator,创建实例时指定相应规则可以进行数据增强。

    常见的数据增强具体方法:

    • rotation_range:设置图像随机旋转的角度范围
    • width_shift_range:水平方向上的平移范围(输入的值是所占水平尺寸的比例)
    • height_shift_range:垂直方向上的平移范围(输入的值是所占垂直尺寸的比例)
    • shear_range:随机错切变换的角度
    • zoom_range:随机缩放方范围
    • fill_mode:用于指定填充原图像生成新的素材的方法

    更多具体的规则参考Keras文档中ImageDataGenerator类部分即可。

    • 一般验证集和测试集不采用数据增强。

    实战部分的完整代码

    一下为猫狗图片分类的完整代码。其实Keras的书中应该也有很多这样的例子,不过还是有一些细节不同。所有细节均已在上文分析了。

    代码如下:

    import os
    import shutil
    import numpy as np
    import tensorflow as tf
    import tensorflow.keras as keras
    import tensorflow.keras.models as models
    import tensorflow.keras.layers as layers
    import tensorflow.keras.preprocessing.image as imagepreprocess
    from matplotlib import pyplot as plt
    
    from tensorflow.compat.v1 import ConfigProto
    from tensorflow.compat.v1 import InteractiveSession
    
    config = ConfigProto()
    config.gpu_options.allow_growth = True
    session = InteractiveSession(config=config)
    
    origin_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/train'
    origin_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/test'
    dataset_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_train'
    dataset_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_test'
    dataset_image_val_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_val'
    
    train_dogs_dir = os.path.join(dataset_image_train_dir,'dogs')
    train_cats_dir = os.path.join(dataset_image_train_dir,'cats')
    test_dogs_dir = os.path.join(dataset_image_test_dir,'dogs')
    test_cats_dir = os.path.join(dataset_image_test_dir,'cats')
    val_dogs_dir = os.path.join(dataset_image_val_dir,'dogs')
    val_cats_dir = os.path.join(dataset_image_val_dir,'cats')
    
    # 若未创建相应的文件夹,就去掉注释
    # os.mkdir(train_dogs_dir)
    # os.mkdir(train_cats_dir)
    # os.mkdir(test_dogs_dir)
    # os.mkdir(test_cats_dir)
    # os.mkdir(val_dogs_dir)
    # os.mkdir(val_cats_dir)
    
    # 制作训练集
    fnames = {'dog.{}.jpg'.format(i) for i in range(5000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(train_dogs_dir,fname)
        shutil.copyfile(src,dst)
    fnames = {'cat.{}.jpg'.format(i) for i in range(5000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(train_cats_dir,fname)
        shutil.copyfile(src,dst)
    # 制作测试集
    fnames = {'dog.{}.jpg'.format(i) for i in range(5000,8000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(test_dogs_dir,fname)
        shutil.copyfile(src,dst)  
    fnames = {'cat.{}.jpg'.format(i) for i in range(5000,8000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(test_cats_dir,fname)
        shutil.copyfile(src,dst)
    # 制作验证集
    fnames = {'dog.{}.jpg'.format(i) for i in range(8000,10000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(val_dogs_dir,fname)
        shutil.copyfile(src,dst)  
    fnames = {'cat.{}.jpg'.format(i) for i in range(8000,10000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(val_cats_dir,fname)
        shutil.copyfile(src,dst)
        
    ImageDataGenerator = imagepreprocess.ImageDataGenerator(rescale=1/255)
    
    TrainGen = ImageDataGenerator.flow_from_directory(dataset_image_train_dir,
                                                     target_size=(150,150),
                                                     batch_size=64,
                                                     class_mode='binary')
    TestGen = ImageDataGenerator.flow_from_directory(dataset_image_test_dir,
                                                    target_size=(150,150),
                                                     batch_size=64,
                                                     class_mode='binary')
    ValGen = ImageDataGenerator.flow_from_directory(dataset_image_val_dir,
                                                   target_size=(150,150),
                                                     batch_size=64,
                                                     class_mode='binary')
    
    # 检查各个数据集的标签分类是否一致
    print(TrainGen.class_indices)
    print(TrainGen.class_indices)
    print(TrainGen.class_indices)
    
    model = models.Sequential()
    model.add(keras.Input(shape=(150,150,3)))
    model.add(layers.Conv2D(32,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(64,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(128,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Conv2D(128,(3,3),activation='relu'))
    model.add(layers.MaxPooling2D((2,2)))
    model.add(layers.Flatten())
    model.add(layers.Dropout(0.5))
    model.add(layers.Dense(512,activation='relu'))
    model.add(layers.Dense(1,activation='sigmoid'))
    
    model.compile(optimizer=keras.optimizers.Adam(lr=1e-4),
                 loss='binary_crossentropy',
                 metrics=['acc'])
    
    steps_per_epoch=5000//64
    validation_steps=2000//64
    his = model.fit_generator(TrainGen,epochs=100,validation_data=ValGen, steps_per_epoch=steps_per_epoch,validation_steps=validation_steps)
    
    h = his.history
    print(h.keys())
    
    plt.figure(figsize=(12,4))
    plt.subplot(1,2,1)
    plt.plot(Epochs,Loss,'-',label='train loss')
    plt.plot(Epochs,Acc,'--',label='train acc')
    plt.xlabel('Epoch')
    plt.ylabel('Value')
    plt.title('Train Loss and Accuracy')
    plt.legend()
    plt.subplot(1,2,2)
    plt.plot(Epochs,Val_Loss,'-',label='validation loss')
    plt.plot(Epochs,Val_Acc,'--',label='validation acc')
    plt.xlabel('Epoch')
    plt.ylabel('Value')
    plt.title('Validation Loss and Accuracy')
    plt.legend()
    plt.show()
    
    # 测试
    result = model.evaluate_generator(TestGen,steps=50)
    print(result)
    

    参考学习书籍:Python深度学习 (豆瓣) (douban.com)

    转载请注明出处!!!

    Author:雾雨霜星
    欢迎来我的网站学习:
    https://www.shuangxing.top

    Thanks!

    PS: 毕竟,霜星酱水平有限,如果发现任何错误还请及时邮箱告知我,我会去改哦!

    展开全文
  • 高斯核卷积去噪滤波原理

    千次阅读 2020-05-21 19:20:43
    1.卷积应用原理: 用一个模板和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,然后各点的积相加,就得到了该点的卷积值。对图像上的每个点都这样处理。由于...

    1.卷积应用原理:
    用一个模板和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,然后各点的积相加,就得到了该点的卷积值。对图像上的每个点都这样处理。由于大多数模板都是对称的,所以模板不旋转。卷积是一种积分运算,用来求两个曲线重叠区域面积。可以看作加权求和,可以用来消除噪声、特征增强。

    把一个点的像素值用它周围的点的像素值的加权平均代替。

    卷积是一种线性运算,图像处理中常见的mask运算都是卷积,广泛应用于图像滤波。

    卷积在数据处理中用来平滑,卷积有平滑效应和展宽效应.

    2.平滑过程
    有噪点的原图,可以把它转为一个矩阵:
    在这里插入图片描述
    比如我要平滑 a_{1,1} 点,就在矩阵中,取出 a_{1,1} 点附近的点组成矩阵 f ,和 g 进行卷积计算后,再填回去
    在这里插入图片描述
    用一个动图来说明下计算过程:
    在这里插入图片描述
    写成卷积公式就是:
    在这里插入图片描述
    要求 c_{4,5} ,一样可以套用上面的卷积公式。

    这样相当于实现了 g 这个矩阵在原来图像上的划动
    在这里插入图片描述
    在图像平滑处理时,函数g通常取高斯核函数

    3.高斯核
    二维高斯分布:
    在这里插入图片描述
    在图形上,正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。
    计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。例如:通常,图像处理软件会提供"模糊"(blur)滤镜,使图片产生模糊的效果。

    高斯模糊的原理:
    所谓"模糊",可以理解成每一个像素都取周边像素的加权平均值。
    以高斯分布取g矩阵的值得到的模糊方法即为高斯模糊。

    权重矩阵
    假定中心点的坐标是(0,0),那么距离它最近的8个点的坐标如下:
    在这里插入图片描述
    更远的点以此类推。
    为了计算权重矩阵,需要设定σ的值。假定σ=1.5,则模糊半径为1的权重矩阵如下
    在这里插入图片描述
    这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
    在这里插入图片描述
    计算高斯模糊
    有了权重矩阵,就可以计算高斯模糊的值了。
    假设现有9个像素点,灰度值(0-255)如下:
    在这里插入图片描述
    每个点乘以自己的权重值(高斯核函数关于中心对称,所以直接对应相乘与旋转180°相乘效果一样):
    在这里插入图片描述
    得到
    在这里插入图片描述
    将这9个值加起来,就是中心点的高斯模糊的值。

    对所有点重复这个过程,就得到了高斯模糊后的图像。如果原图是彩色图片,可以对RGB三个通道分别做高斯模糊。

    参考:https://blog.csdn.net/weixin_39124778/article/details/78411314

    展开全文
  • 1 官方文档 2 函数原型及参数介绍 3 关于二维卷积: (1)单通道卷积 (2)多通道卷积 (3)多通道输入,多通道输出 4 卷积操作后图形尺寸的变化公式: 5 关于参数 padding与padding_mode: (1)padding的理解: (2)...

    参考:

    https://pytorch.org/docs/master/generated/torch.nn.Conv2d.html#torch.nn.Conv2d

    https://zhuanlan.zhihu.com/p/251068800

    https://www.cnblogs.com/mrlonely2018/p/13946730.html

    https://www.jb51.net/article/177739.htm

    https://www.jianshu.com/p/a6da4ad8e8e7

    https://blog.csdn.net/HappinessSourceL/article/details/106207022

    1 官方文档

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

    在这里插入图片描述

    2 函数原型及参数介绍

    函数原型:

    class torch.nn.Conv2d(in_channels, 
    					  out_channels,
    				       kernel_size,
    						stride=1, 
    						padding=0, 
    						dilation=1, 
    						groups=1, 
    						bias=True)
    

    函数作用:定义一个卷积核,方便后面进行二维卷积操作
    我们会发现一个现象:pytorch的conv2d没有要求输入卷积核的信息,设置卷积核的权重可以在后面采用:

    conv_zeros = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='zeros',bias=False)
    conv_zeros.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
    

    Parameters:

    参数数据类型
    in_channelsintNumber of channels in the input image输入图像通道数
    out_channelsintNumber of channels produced by the convolution卷积产生的通道数
    kernel_size(int or tuple)Size of the convolving kernel卷积核尺寸,可以设为1个int型数或者一个(int, int)型的元组。例如(2,3)是高2宽3卷积核
    stride(int or tuple, optional)Stride of the convolution. Default: 1卷积步长,默认为1。可以设为1个int型数或者一个(int, int)型的元组。
    padding(int or tuple, optional)Zero-padding added to both sides of the input. Default: 0填充操作,控制padding_mode的数目。简言之,就是决定图像边沿填充的方式
    padding_mode(string, optional)optional) ‘zeros’, ‘reflect’, ‘replicate’ or ‘circular’. Default: ‘zeros’padding模式,默认为Zero-padding 。
    dilation(int or tuple, optional)Spacing between kernel elements. Default: 1扩张操作:控制kernel点(卷积核点)的间距,默认值:1。
    groups(int, optional)Number of blocked connections from input channels to output channels. Default: 1group参数的作用是控制分组卷积,默认不分组,为1组。输入图像通道数
    bias(bool, optional)If True, adds a learnable bias to the output. Default: True为真,则在输出中添加一个可学习的偏差。默认:True。

    3 关于二维卷积:

    首先我们介绍以下基本的卷积运算:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以看出,上图的卷积运算实质上就是两个相同尺寸的矩阵进行了一次矩阵内积运算。

    实际上,一次卷积操作要经过很多次上述的矩阵内积运算,最终也会获得一个新的矩阵。

    (1)单通道卷积

    下图可以看作是一次常规的卷积操作:
    这是用一个卷积核在一个二维图像上进行卷积操作后得到一个二维的特征映射。
    在这里插入图片描述

    可以看出卷积核尺寸为3*3kernel_size=3,卷积步长为1stride=1
    输入通道in_channels和输出通道out_channels不得而知,假设都为1。
    关于padding后面介绍,暂定为padding=0

    代码:

     nn.Conv2d(in_channels=1,out_channels=1,kernel_size=3,stride=1,padding=0)
    

    (2)多通道卷积

    若是出现in_channels>1,out_channels=1,则可以参考下图的卷积运算:

    在这里插入图片描述卷积过程如下,每一个通道的像素值与对应的卷积核通道的数值进行卷积,因此每一个通道会对应一个输出卷积结果,三个卷积结果对应位置累加求和,得到最终的卷积结果(这里卷积输出结果通道只有1个,因为卷积核只有1个。卷积多输出通道下面会继续讲到)。
    可以这么理解:最终得到的卷积结果是原始图像各个通道上的综合信息结果。

    代码:

     nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,stride=1,padding=0)
    

    上述过程中,每一个卷积核的通道数量,必须要求与输入通道数量一致,因为要对每一个通道的像素值要进行卷积运算,所以每一个卷积核的通道数量必须要与输入通道数量保持一致

    在这里插入图片描述

    (3)多通道输入,多通道输出

    在这里插入图片描述
    代码:

     nn.Conv2d(in_channels=3,out_channels=3,kernel_size=1,stride=1,padding=0)
    

    4 卷积操作后图形尺寸的变化公式:

    input_size: 输入图形的尺寸
    output_size: 输出图形的尺寸
    kernel_size: 卷积核的尺寸
    stride: 步长
    padding: 边缘扩充

    output_size = ((input_size - dilation*(kernel_size-1) + 2* padding)-1)/stride + 1
    注:整除
    

    常见举例:

    input_size=(512,512)
    
    conv2d(in_channels,out_channels,kernel_size=3,stride=2, padding=1)
    # 求output_size:
    output_size = (input_size - kernel_size + 2* padding)/stride + 1
    output = (512 - 3 +2)/2+1 = 255+1 = 256
    
    
    input_size=(128,128)
    
    conv2d(in_channels,out_channels,kernel_size=3,stride=1, padding=1)
    # 求output_size:
    output_size = (input_size - kernel_size + 2* padding)/stride + 1
    output = (128 - 3 +2)/1+1 = 127+1 = 128
    
    

    5 关于参数 padding与padding_mode:

    (1)padding的理解:

    padding参数作用:控制zero-padding的数目。
    用容易理解的语言来说:padding就是在图像四周加格子。

    原因:如果不对图像边缘进行填充,卷积核将无法到达图像边缘的像素,而且卷积前后图像的尺寸也会发生变化,这会造成许多麻烦。
    因此现在各大深度学习框架的卷积层实现上基本都配备了padding操作,以保证图像输入输出前后的尺寸大小不变。

    例如,若卷积核大小为3x3,那么就应该设定padding=1,即填充1层边缘像素;若卷积核大小为7x7,那么就应该设定padding=3,填充3层边缘像素;也就是padding大小一般设定为核大小的一半。在pytorch的卷积层定义中,默认的padding为零填充。

    (2) PyTorch Conv2d中的padding_mode四种填充模式解析

    padding,即边缘填充,可以分为四类:零填充,常数填充,镜像填充,重复填充。
    padding_mode参数,可选项有4种:

    (1) zeros,代表零填充。padding_mode默认选项为zeros

    即在矩阵的高、宽两个维度上用0进行填充,填充时将在一个维度的两边都进行填充:

    torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='zeros',bias=False)
    

    (2) reflect,代表镜像填充。
    reflect是以矩阵边缘为对称轴,将矩阵中的元素对称的填充到最外围。

    torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='reflect',bias=False)
    

    (3) replicate,代表重复填充。
    对图像或者张量的边缘进行重复填充,就是说直接用边缘的像素值来填充。示例如下:

    torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='replicate',bias=False)
    

    (4) circular,代表循环填充。
    示例如下:

    # 定义一个1*1卷积,设置填充模式为'circular',在高和宽维度上两边各填充1个单位
    In [64]: conv_reflect = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='circular',bias=False)
    
    # 将卷积核的权重设置为1,这样可使卷积后的输出即为填充后的输入矩阵
    In [65]: conv_reflect.weight = torch.nn.Parameter(torch.ones(1,1,1,1))
    
    # 进行卷积计算,并输出结果
    In [66]: conv_circular(x)
    Out[66]:
    tensor([[[[16., 13., 14., 15., 16., 13.],
              [ 4.,  1.,  2.,  3.,  4.,  1.],
              [ 8.,  5.,  6.,  7.,  8.,  5.],
              [12.,  9., 10., 11., 12.,  9.],
              [16., 13., 14., 15., 16., 13.],
              [ 4.,  1.,  2.,  3.,  4.,  1.]]]], grad_fn=<ThnnConv2DBackward>)
    
    

    6 关于参数dilation:

    dilation:控制kernel点(卷积核点)的间距,默认值:1。

    (1)dilation=0的话,效果如图:
    在这里插入图片描述
    蓝色(下面)为输入,绿色(上面)为输出,卷积核为3*3,此时为密集连接。

    (2)dilation=1,那么效果如图:
    称为扩张卷积(也叫空洞卷积)

    在这里插入图片描述
    优点:

    这样单次计算时覆盖的面积(即感受域)由dilation=0时的3*3=9变为了dilation=1时的5*5=25
    在增加了感受域的同时却没有增加计算量,保留了更多的细节信息,对图像还原的精度有明显的提升。

    7 关于参数groups——分组卷积:

    Group Convolution顾名思义,则是对输入feature map进行分组,然后每组分别卷积。

    在这里插入图片描述
    当group=1时,该卷积层需要6611=36个参数,即需要6个61*1的卷积核

    计算时就是6H_inW_in的输入整个乘以一个611的卷积核,得到输出的一个channel的值,即1H_outW_out。这样经过6次与6个卷积核计算就能够得到6H_outW_out的结果了

    如果将group=3时,卷积核大小为torch.Size([6, 2, 1, 1]),即6个211的卷积核,只需要需要621*1=12个参数

    那么每组计算就只被in_channels/groups=2个channels的卷积核计算,当然这也会将输入分为三份大小为2H_inW_in的小输入,分别与211大小的卷积核进行三次运算,然后将得到的3个2H_outW_out的小输出concat起来得到最后的6H_outW_out输出

    在实际实验中,同样的网络结构下,这种分组的卷积效果是好于未分组的卷积的效果的。

    为什么要设置groups参数,有什么优点?

    为了在GPU上并行计算:

    标准卷积的计算如下图:

    举个例子,假设有一个3×3大小的卷积层,其输入通道为16、输出通道为32。
    那么一般的操作就是用32个3×3的卷积核来分别同输入数据卷积,这样每个卷积核需要3×3×16个参数,得到的输出是只有一个通道的数据。之所以会得到一通道的数据,是因为刚开始3×3×16的卷积核的每个通道会在输入数据的每个对应通道上做卷积,然后叠加每一个通道对应位置的值,使之变成了单通道,那么32个卷积核一共需要(3×3×16)×32 =4068个参数。
    在这里插入图片描述

    分组卷积的计算:

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

    9 标准卷积的详细计算图可表示为:

    在这里插入图片描述

    10 案例代码:

    >>> # With square kernels and equal stride
    >>> m = nn.Conv2d(16, 33, 3, stride=2)
    >>> # non-square kernels and unequal stride and with padding
    >>> m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
    >>> # non-square kernels and unequal stride and with padding and dilation
    >>> m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
    >>> input = torch.randn(20, 16, 50, 100)
    >>> output = m(input)
    
    
    展开全文
  • 卷积神经网络(CNN)中的卷积核 概念 原理

    万次阅读 多人点赞 2018-12-11 09:23:06
    作者:Tim Dettmers(Understanding...有太多的公开课、教程在反复传颂卷积神经网络的好,却都没有讲什么是“卷积”,似乎默认所有读者都有相关基础。这篇外文既友好又深入,所以翻译了过来。文章高级部分通过流体力...
  • 计算机视觉之卷积神经网络原理

    千次阅读 2020-11-10 22:54:21
    一个图片做卷积,结果也可以理解为另一张图片,这里的卷积和数字信号处理中的卷积千差万别。在信号类教材中,做元素乘积求和之前将卷积核沿对角线翻转也就是卷起来(不是旋转),之后再进行乘积求和,但在计算机视觉中...
  • CNN卷积神经网络原理

    2017-08-06 19:03:02
    理解CNN卷积神经网络原理   1 前言  2012年我在北京组织过8期machine learning读书会,那时“机器学习”非常火,很多人都对其抱有巨大的热情。当我2013年再次来到北京时,有一个词似乎...
  • 卷积神经网络原理

    千次阅读 2017-05-23 12:59:34
    卷积神经网络原理   Convolutional Neural Networks卷积神经网络  卷积神经网络是人工神经网络的一种,已成为当前语音分析和图像识别领域的研究热点。它的权值共享网络结构使之更类似于生物神经网络,...
  • 人工智能中卷积神经网络基本原理综述

    万次阅读 多人点赞 2017-12-11 16:10:00
    人工智能Artificial Intelligence中卷积神经网络Convolutional Neural Network基本原理综述人工智能(Artificial Intelligence,简称AI)的Deep Learning(深度学习)通过机器学习,把某一层的输出output当做下一层...
  • 根据cs231n的卷积动图依次截取了18张图,然后用一gif 制图工具制作了一gif 动态卷积图。如下gif 图所示  可以看到: 两个神经元,即depth=2,意味着有两个滤波器。 数据窗口 每次...
  • 卷积神经网络提取特征原理

    千次阅读 2021-03-01 18:31:46
    众所周知,卷积神经网络是深度学习中非常有代表性的学习领域,而深度学习又是机器学习的主要分支,因此卷积神经网络就是用来让机器学习的过程途径,而机器要学的就是图像中的特征,“卷积”就是用来提取特征的。...
  • 卷积神经网络CNN总结 从神经网络到卷积神经网络(CNN) 我们知道神经网络的结构是这样的: 那卷积神经网络跟它是什么关系呢? 其实卷积神经网络依旧是层级网络,只是层的功能和形式做了变化,可以说是传统神经网络...
  • 目录(?)[+] 卷积 ...图示两个方形脉冲波的卷积。... "t" 处的卷积,其中横坐标代表待积变量 以及新...图示方形脉冲波和指数衰退的脉冲波的卷积(后者可能出现于 RC电路中),同样地重叠部份面积就相当于 "t" 处的卷
  • 来自|知乎 作者丨CoCoairforce链接丨https://zhuanlan.zhihu.com/p/357648179编辑丨极市平台作者以抛球实验为例讲解了许多卷积的数学原理和机...
  • 本只是想把重点放在其卷积计算具体是怎么计算怎么操作的,但后面不断补充,故写成了关于卷积神经网络的通俗导论性的文章。有何问题,欢迎不吝指正。 2 人工神经网络 2.1 神经元  神经网络由大量的节点(或称“神经...
  • 卷积

    2020-11-22 18:45:32
    文章目录卷积的定义 以下回答摘自知乎回答“如何通俗易懂地理解卷积”。 卷积的定义 连续形式的卷积: 离散形式的卷积: 它们都有一个共同的特征: 直观上来看,函数g(n−τ)g(n-\tau)g(n−τ)的含义是将...
  • 卷积神经网络在图像识别领域无限风光,通过一张图片,算法可以知道图片上的物体是什么,着实令人震惊,但是很多人和我一样,对于其背后的原理,都非常好奇,卷积神经网络是如何进行图像识别的呢? 如果你的...
  • 图形学滤波器 激活函数 tfnnrelu tfnndropout tfnnbias_add 正则化 损失函数 分类器 TensorFlow实现简易神经网络模型 MNIST数据集下的基础CNN CIFAR-10数据集下的进阶CNN CIFAR...
  • 作者:Michael Bronstein翻译:陈之炎校对:郑滋本文约3200字,建议阅读9分钟本文介绍了第一原理中推导出卷积,并展示它的平移对称性。标签:卷积TLDR:你有没有想过卷积有...
  • 从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。...本篇文章详细讲解了卷积神经网络CNN原理,并通过Keras编写CNN实现了MNIST分类学习案例。基础性文章,希望对您有所帮助!
  • 本文来源1:... ... 卷积神经网络是人工神经网络的一种,已成为当前语音分析和图像识别领域的研究热点。它的权值共享网络结构使之更类似于生物神经网络,降低了网络模...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,624
精华内容 3,049
关键字:

卷积图形原理