卷积 Convolution 原理及可视化

文章抄自 知乎-王贵波



本文主要介绍了卷积 Convolution 的背景、基本原理、特点、与全连接的区别与联系、不同的卷积模式,进行了卷积可视化以及代码实现了一个简单的 2 维卷积操作,并针对卷积操作进行了计算优化。

目录

  1. 卷积背景及原理
  2. 卷积的特点(与全连接的区别与联系)
  3. 卷积的三种模式
  4. 卷积操作 Numpy 简单实现
  5. 卷积优化实现

卷积背景及原理

卷积操作历史上来发展于信号处理领域,在信号处理中原始信号通常会被混入噪音,假设传感器在每个时刻 \(t\) 会输出一个信号 \(f(t)\),这个信号通常混入了一些噪声,我们可以通过过个测量点进行加权平均来抵消掉噪声,并且离当前时间点 \(t\) 越近的测量点权重应该越高,我们可以用下面的公式表示

\[h(t)=\sum_{t‘}g(t-t‘)f(t‘)
\]

上式中\(g\)是一个权重函数,参数是时间点\(t‘\)距离当前时间 的\(t\)距离,输出 \(t‘\) 时间点测量的权重;\(f\) 是信号测量函数。在这个例子中,\(t‘\)的采样是离散的,因此采用了加和的形式,同时 \(g\)还应该是一个概率密度函数,因为在这个例子中表示了一种权重。下图就是这个例子的可视化,灰色是 \(f(t)\),红色的部分就是经过翻转的 \(g\),绿色部分是生成的 \(h\) 。

这个例子实际上就是卷积操作的一种特例,进一步扩展成连续函数,并且对\(g\) 函数没有限制,我们就得到了卷积操作的定义。根据维基百科定义,卷积运算(Convolution)g是一种通过两个函数 \(f\) 和\(g\) 生成第三个函数 \(f\) 的一种数学算子,公式表示如下。通常将函数 称为输入(input),函数 称为卷积核(kernel),函数 称为特征图谱(feature map

我们考虑离散多维卷积的情况,这个也是深度学习领域最常见的情况,即输入是一个多维数组,卷积核也是一个多维的数组,时间上是离散的,因此无限的积分变成有限的数组有限元素的加和:

上式表明的操作在直观上理解是先对卷积核翻转,然后与输入点乘、求和得到输出。在机器学习领域尤其是深度学习中,卷积的实现通常省去了卷积核翻转这一步,因为深度学习中的卷积核参数是不断学习更新,因此有没有翻转并没有性质上的影响。严格定义上说,深度学习中的卷积实际上是另一种操作:互相关 Cross-Correlation。公式表示如下

下文中我们忽略卷积核是否被翻转,将严格意义上的互相关和卷积都称作卷积。二维卷积的可视化如下图


卷积的特点(与全连接的区别与联系)

卷积神经网络(Convotional Neural Network, CNN)是深度学习领域中重要的一个领域,可以说 CNN 近年在计算机视觉领域取得的成功直接推动了深度学习的复兴。 CNN 是一种基于卷积操作的神经网络,可以粗略地理解为 CNN 就是将全连接网络中的矩阵相乘换成卷积操作(说粗略是因为通常 CNN 中还带有 pooling 等 CNN 专有的操作)。那么卷积操作有什么区别于全连接的特点呢?

我们以一个例子来看卷积的具体

不考虑 padding,以 stride 为 1,那么我们进行卷积计算得到的结果为

我们对 input 和 kernel 分别进行下图的转换,input 展平为一位向量,kernel 在合适的地方填零

经过这样处理实际上左边的 kernel matrix 乘上右边的 flatten input 向量后得到结果与上面的结果是等效的,即向量的卷积操作可以表示为上图的一个核矩阵乘一个向量得到。有没有觉得很眼熟?这种一个参数矩阵乘一个输入向量得到一个输出向量的形式与全连接是完全一致的

因此卷积和全连接实质上都是一组线性转换,但是卷积相比全连接而言,其参数矩阵更加稀疏,kernel matrix 中很多为零(sparse connectivity),同时非零部分的参数实际上是共享的(parameter sharing)。这两个特点让卷积可以大大减少参数的数量,同时同一套参数(卷积核)在多个地方复用更有利于捕捉局部的特征。相比之下全连接的参数更多,每个参数只负责唯一的连接,计算量、内存需求增大的同时也更加难以训练。

更本质来说,卷积相比全连接实际上是对参数矩阵做了一种先验的限制(矩阵是稀疏的、同时参数复用),这种先验是建立在在高维空间中相邻的数据点存在一定的关系的基础上,比如图像中一个局部之间可能构成一个形状或者一个组件,因此卷积这种操作特别适合应用于图像数据。虽然加入这种先验会让模型损失一定的拟合能力,但是从最终效果上看带来的收益是远远大于拟合能力的损失的。


卷积的三种模式

深度学习框架中通常会实现三种不同的卷积模式,分别是 SAME、VALID、FULL。这三种模式的核心区别在于卷积核进行卷积操作的移动区域不同,进而导致输出的尺寸不同。我们以一个例子来看这三种模式的区别,输入图片的尺寸是 ,卷积核尺寸是 ,stride 取 1。

  • FULL 模式

FULL 模式下卷积核从与输入有一个点的相交的地方就开始卷积。如下图所示,蓝框的位置就是卷积核第一个卷积的地方,灰色部分是为了卷积能够正常进行的 padding(一般填 0)。因此 FULL 模式下卷积核移动区域最大,卷积后输出的尺寸也最大。

  • VALID 模式

VALID 模式与 FULL 模式相反,在整个卷积核与输入重叠的地方才开始卷积操作,因此不需要 padding,输出的尺寸也最小

  • SAME 模式

SAME 模式是最常用的一种模式,SAME 的意思是卷积后输出的尺寸与输入尺寸保持一致(假定 stride 为 1)。通过将卷积核的中心与输入的第一个点进行对齐确定卷积核起始位置,然后补齐对应 padding 即可。如下图所示,可以看到卷积输出的尺寸与出入保持一致。

SAME 模式下当卷积核边长为偶数时,可以通过在其中一边增加多一行(列)padding,即不对称的 padding 实现输出尺寸与输入尺寸保持一致,如下图所示(卷积核尺寸为

以上三种模式区别在于卷积核进行卷积操作的移动区域不同,其实是确定了所需的 padding。各种模式 padding 计算如下

def get_padding(inputs, ks, mode="SAME"):
    """
    Return padding list in different modes.
    params: inputs (input array)
    params: ks (kernel size) [p, q]
    return: padding list [n,m,j,k]
    """
    pad = None
    if mode == "FULL":
        pad = [ks[0] - 1, ks[1] - 1, ks[0] - 1, ks[1] - 1]
    elif mode == "VALID":
        pad = [0, 0, 0, 0]
    elif mode == "SAME":
        pad = [(ks[0] - 1) // 2, (ks[1] - 1) // 2,
               (ks[0] - 1) // 2, (ks[1] - 1) // 2]
        if ks[0] % 2 == 0:
            pad[2] += 1
        if ks[1] % 2 == 0:
            pad[3] += 1
    else:
        print("Invalid mode")
    return pad

确定了输入尺寸、卷积核尺寸、padding 以及 stride,输出的尺寸就被确定下来,可以由以下公式计算。其中 分别是输出、输入尺寸, 是卷积核尺寸, 分别是两侧的 padding。


卷积操作 Numpy 简单实现

解卷积操作的原理后,其实实现起来非常简单,我们可以用代码来实现 2 维卷积操作,代码如下

def conv(inputs, kernel, stride, mode="SAME"):
    ks = kernel.shape[:2]
    # get_padding
    pad = get_padding(inputs, ks, mode="SAME")
    padded_inputs = np.pad(inputs, pad_width=((pad[0], pad[2]), (pad[1], pad[3]), (0, 0)), mode="constant")

    height, width, channels = inputs.shape
    out_width = int((width + pad[0] + pad[2] - ks[0]) / stride + 1)
    out_height = int((height + pad[1] + pad[3] - ks[1]) / stride + 1)

    outputs = np.empty(shape=(out_height, out_width))
    for r, y in enumerate(range(0, padded_inputs.shape[0]-ks[1]+1, stride)):
        for c, x in enumerate(range(0, padded_inputs.shape[1]-ks[0]+1, stride)):
            outputs[r][c] = np.sum(padded_inputs[y:y+ks[1], x:x+ks[0], :] * kernel)
    return outputs

使用一幅图像测试一下

inputs = from_image("./Lenna_test_image.png")
to_image(inputs, save_path="./plots/conv/lenna_origin.png")

#  Embossing Filter
kernel_one_channel = np.array([[0.1, 0.1, 0.1], [0.1, -0.8, 0.1], [0.1, 0.1, 0.1]])
kernel = np.stack([kernel_one_channel] * 3, axis=2)
stride = 1
output = conv(inputs, kernel, stride)
to_image(output, grey=True, save_path="./plots/conv/lenna_conv.png")

输入图像和经过卷积的效果如下。

卷积操作 Numpy 简单实现

上面我们是严格按照卷积的流程实现的(点乘 -> 求和 -> 移动卷积核),在上面讨论卷积与全连接的区别时我们讲到只要对卷积核和输入做一定的变化,卷积可以等价转化为矩阵和向量相乘,这样可以提升卷积计算的效率。这里有两种转换方法,一种是对卷积核扩展填充,对输入展平,另一种则是对卷积核展平,对输入扩展填充。这两种方法最后的结果是一直的,我们选择第二种方案进行实现。下图是经过展平的卷积向量与经过扩展的输入矩阵。

ptyhon 代码实现如下

def conv_matmul(inputs, kernel, stride, mode="SAME"):
    ks = kernel.shape[:2]

    pad = get_padding(inputs, ks, mode=mode)
    padded_inputs = np.pad(inputs, pad_width=((pad[0], pad[2]), (pad[1], pad[3]), (0, 0)), mode="constant")

    height, width, channels = inputs.shape
    out_width = int((width + pad[0] + pad[2] - ks[0]) / stride + 1)
    out_height = int((height + pad[1] + pad[3] - ks[1]) / stride + 1)

    rearrange = []
    for y in range(0, padded_inputs.shape[0]-ks[1]+1, stride):
        for x in range(0, padded_inputs.shape[1]-ks[0]+1, stride):
            patch = padded_inputs[y:y+ks[1], x:x+ks[0], :]
            rearrange.append(patch.ravel())
    rearrange = np.asarray(rearrange).T
    kernel = kernel.reshape(1, -1)
    return np.matmul(kernel, rearrange).reshape(out_height, out_width)

先验证效果

inputs = from_image("./Lenna_test_image.png")
to_image(inputs, save_path="./plots/conv/lenna_origin.png")

#  Embossing Filter
kernel_one_channel = np.array([[0.1, 0.1, 0.1], [0.1, -0.8, 0.1], [0.1, 0.1, 0.1]])
kernel = np.stack([kernel_one_channel] * 3, axis=2)
stride = 1
output = conv_matmul(inputs, kernel, stride)
to_image(output, grey=True, save_path="./plots/conv/lenna_conv.png")

对比下原始实现与优化后的运行时间

n = 5

start = time.time()
for _ in range(n):
    output = conv(inputs, kernel, stride=1)
cost1 = float((time.time() - start) / n)
print("raw time cost: %.4fs" % cost1)

start = time.time()
for _ in range(n):
    output = conv_matmul(inputs, kernel, stride=1)
cost2 = float((time.time() - start) / n)
print("optimized time cost: %.4fs" % cost2)

reduce = 100 * (cost1 - cost2) / cost1
print("reduce %.2f%% time cost" % reduce)
raw time cost: 0.7281s
optimized time cost: 0.1511s
reduce 79.25% time cost

第一种实现方式在测试图像上平均耗时 0.7281s,优化后的实现方式平均耗时 0.1511s,通过优化的矩阵运算大约能带来 80% 的运算速度提升

最后本文相关的代码见 这里

原文地址:https://www.cnblogs.com/wenshinlee/p/12588753.html

时间: 2024-07-30 21:40:36

卷积 Convolution 原理及可视化的相关文章

图像处理之基础---二维卷积运算原理剖析

卷积运算(Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表示函数f 与经过翻转和平移与g 的重叠部分的累积.如果将参加卷积的一个函数看作区间的指示函数,卷积还可以被看作是“滑动平均”的推广.假设: f(x),g(x)是R1上的两个可积函数,并且积分是存在的.这样,随着 x 的不同取值,这个积分就定义了一个新函数h(x),称为函数f 与g 的卷积,记为h(x)=(f*g)(x). 两个向量卷积,说白了就是多项式乘法.下面用个矩阵例子说明其工作原理: a和d的卷积就是

DeepLearning tutorial(4)CNN卷积神经网络原理简介+代码详解

DeepLearning tutorial(4)CNN卷积神经网络原理简介+代码详解 @author:wepon @blog:http://blog.csdn.net/u012162613/article/details/43225445 本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Convolutional Neural Networks (LeNet).经详细注释的代码和原始代码:放在我的github地址上,可下载. 一.CNN卷积神经网络原理

【机器学习笔记之七】PCA 的数学原理和可视化效果

PCA 的数学原理和可视化效果 本文结构: 什么是 PCA 数学原理 可视化效果 1. 什么是 PCA PCA (principal component analysis, 主成分分析) 是机器学习中对数据进行降维的一种方法. 例如,我们有这样的交易数据,它有这几个特征:(日期, 浏览量, 访客数, 下单数, 成交数, 成交金额),从经验可知,“浏览量”和“访客数”,“下单数”和“成交数”之间会具有较强的相关关系.这种情况下,我们保留其中的两个维度就可以保证原有的信息完整. 但是当我们在做降维的

可对Python运行原理进行可视化分析的工具

Python Tutor Python Tutor是一个对Python运行原理进行可视化分析的工具,网址为: http://www.pythontutor.com 使用范例 参考链接 原文地址:https://www.cnblogs.com/jiangling500/p/8353607.html

《卷积神经网络的Python实现》PDF代码+《解析深度学习卷积神经网络原理与视觉实践》PDF分析

CNN正在革新几个应用领域,如视觉识别系统.自动驾驶汽车.医学发现.创新电子商务等.需要在专业项目或个人方案中利用复杂的图像和视频数据集来实现先进.有效和高效的CNN模型. 深度卷积网络DCNN是目前十分流行的深度神经网络架构,它的构造清晰直观,效果引人入胜,在图像.视频.语音.语言领域都有广泛应用. 深度学习,特别是深度卷积神经网络是人工智能的重要分支领域,卷积神经网络技术也被广泛应用于各种现实场景,在许多问题上都取得了超越人类智能的结果. <卷积神经网络的Python实现>作为深度学习领域

GeoHash原理和可视化显示

最近在做附近定位功能的产品,geohash是一个非常不错的实现方式.查询资料,发现阿里的这篇文章讲解的很好.但文中并没有给出geohash显示的工具.无奈,也没有查到类似的.只好自己简单显示一下,方便自己理解. 项目地址:  https://github.com/Ryan-Miao/geohash-visualization geohash可视化显示 经纬度获取9宫格覆盖: https://ryan-miao.github.io/geohash-visualization/index.html

深度卷积网络原理—非线性激活的必要性

假设有如上图所示的神经网络实例图 网络描述如下: 1)有2个输入,即图中的x1.x2 2)有3个神经元,即途中的b1.b2.b3 3)共2层网络.第1层的神经元是b1.b2,第2层的神经元是b3.可将中间层称为隐层.例如途中的b1.b2就属于隐层 4)有6个权重(w11到w23).最终输出为OUT. 神经元b1的输出是: x1w11+x2w21+b1 神经元b2的输出是:x1w12+x2w22+b2 如果没有非线性激活,那么最终的输出OUT的计算公式是: OUT=(x1w11 + x2w21 +

从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (二)

本文属于图神经网络的系列文章,文章目录如下: 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (一) 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (二) 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (三) 在上一篇博客中,我们简单介绍了基于循环图神经网络的两种重要模型,在本篇中,我们将着大量笔墨介绍图卷积神经网络中的卷积操作.接下来,我们将首先介绍一下图卷积神经网络的大概框架

[透析] 卷积神经网络CNN究竟是怎样一步一步工作的?(转)

视频地址:https://www.youtube.com/embed/FmpDIaiMIeA    转载:http://www.jianshu.com/p/fe428f0b32c1 文档参阅:pdf [2MB] & ppt [6MB] & Web View & GitBook 补充知识:深度学习 - 反向传播理论推导 当你听到说深度学习打破了某项新技术障碍,那么十有八九就会涉及到卷积神经网络.它们也被称作CNNs或着ConvNets,是深层神经网络领域的主力.它们已经学会对图像进行