前言
最近的失业潮引起很多打工人的恐慌,特别是ChatGPT之类的生成式AI的推出,其智能程度和自主学习能力远程人们想象.加剧了大家的恐慌情绪,在裁员潮这个当口,纷纷引发各行各业的猜测--人工智能替代大部分岗位,而且有些行业甚至已经使用上了ChatGPT类的生成式AI进行替代人类的部分工作,比如前阵子传的沸沸扬扬的AI制图替代人工设计,而且相比人类设计师它还有出图快的优势.但是在于个性化的定制方面,以及情感的理解和多学科的融合方面,AI制图跟人类设计师相比目前还是有一些差距的.作为IT打工人,感受到IT行业中目前同样弥漫着这些焦虑.但是个人觉得再厉害的技术都是基于基础理论,基础技术的支撑,而且再牛的产品也是需要人类去驱动和开发的,再加上人类情感共鸣的优势,短时间内AI暂时是无法取代人类大部分岗位.特别是个性化,定制化的方面.
圆规正传,人工智能领域的深度学习中的图像识别这门让机器学会'看'的技术.当年提出的时候也算是拥有划时代意义的,让人工智能有了更快更大的发展,甚至可以说是跨越式发展.这不,这么厉害的技术还得基于今天说的'卷积'这个基础,所以作为人工智能的创造者,推动者,我们应该大胆去了解它,改造它.让人工智能成为我们的辅助工具,使得我们更好地完成工作,而不是担忧它的存在.下面我们接着唠唠今天的主题:卷积运算
前置知识:Python基础知识,因为本文主要以Python的角度来介绍卷积运算
对卷积的理解
在学习卷积运算之前,我们先来了解什么是卷积运算?
卷积运算 (Convolution) 是信号处理和图像处理领域中的重要知识,更是当前DL算法中最核心的组件之一。 我们不要从字面意思理解卷积运算,尤其不要把卷积运算中的“卷”和大饼卷一切的“卷”联系起来,这样只会造成干扰或者理解误差。关于卷积,会涉及到滑动窗口这个概念。
对卷积这个名词的理解:所谓两个函数的卷积,本质上就是先将一个函数翻转,然后进行滑动叠加。 在连续情况下,叠加指的是对两个函数的乘积求积分,在离散情况下就是加权求和,为了方便理解统一称之为叠加。整体过程大致如下图:
如上图多次滑动得到的一系列叠加值,构成了卷积函数。
卷积的“卷”,指的的函数的翻转,从 g(t) 变成 g(-t) 的这个过程;同时,“卷”还有滑动的意思在里面。如果把卷积翻译为“褶积”,那么这个“褶”字就只有翻转的含义。
卷积的“积”,指的是积分/加权求和。
对卷积的意义的理解:
- 从“积”的过程可以看到,我们得到的叠加值,是个全局的概念。以信号分析为例,卷积的结果是不仅跟当前时刻输入信号的响应值有关,也跟过去所有时刻输入信号的响应都有关系,考虑了对过去的所有输入的效果的累积。在图像处理的中,卷积处理的结果,其实就是把每个像素周边的,甚至是整个图像的像素都考虑进来,对当前像素进行某种加权处理。所以说,“积”是全局概念,或者说是一种“混合”,把两个函数在时间或者空间上进行混合。
- 那为什么要进行“卷”?直接相乘不好吗?进行“卷”(即是翻转)的目的其实是施加一种约束,它指定了在“积”的时候以什么为参照。在信号分析的场景,它指定了在哪个特定时间点的前后进行“积”,在空间分析的场景,它指定了在哪个位置的周边进行累积处理。
卷积运算涉及到的知识点
从上面的介绍中我们简单了解卷积的相关概念。在实际的卷积的运算过程中会涉及到维度和向量这两个概念。在python中我们从list或者数组中可以了解到这两个相关的知识点,特别是我们常用的numpy(支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库)
数组的形状
比如我们常说的excel数据中有几行几列,这就是数组的形状,也就是数组的排列方式,shape本身的意思就是形状的意思. numpy中提供了shape()方法来获取数组的形状, 比如下面的代码:
创建数组
import numpy as np arr = np.array([[0,0,0],[1,1,1]]) print(arr)
执行结果如下:
获取数组shape
arr_shape = np.shape(arr) print(arr_shape)
执行结果如下:
我们还可以通过以下方式获取数组的shape:
print(arr.shape)
注意:shape的值是一个元组(tuple)
数组的维度
上面我们通过实例了解了数组的形状,接着我们来了解数组的维度,数组的维度的获取和获取形状一样简单,同样是numpy当中数组的属性,下面我们通过例子来了解一下:我们通过ndim这个属性来获取数组的维度
从上面的例子可以看出数组的维度和数组元素个数无关.数组的一个重要属性是维度,一个向量可以看作是一个一维度数组,n行m列的数组是一个1二维数组,这个数组的ndim属性值为2,一个空数组的维度至少是1,甚至可以是更高维度.
向量的理解
在数学中,向量(也称为欧几里得向量、几何向量、矢量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。与向量对应的只有大小,没有方向的量叫做数量(物理学中称标量)。学习人工智能始终逃不开数学的魔抓,但是简单的入门也没有那么恐怖,很多人买了花书,看到枯燥乏味的数学公式,直接放弃了.为了能轻松入门,我们从简单的向量开始,我们先从加减法开始理解,比如甲乙两个人的体重是150斤,他们同时开始减肥,进过半年努力,甲从150减到135,很遗憾乙减肥失败从150变成160斤.两个人的体重就可以使用向量表示如下:
上图中的150、135与数值list、array、tuple中的单个元素一样,都是在普通不过的数,这样的数就是标量(scalar)。将这些数值组合起来,比如下图中的x组合,这样的组合称为向量(vector).wold是一个向量,wnew也是一个向量.向量中的每个数如150,135,x1都称为向量的元素,也叫向量的分量(components),向量的每个元素都是标量.向量中的元素右下角的下标是元素的索引,x1表示向量x的第一个元素. 以上就是从数学的领域中去理解的向量,而在编程实践中,一个向量通常用一个数组来表示,如下面代码:
在数学中,向量w的第一元素(135)是w1,在python中则是w[0]. 例子中用于表示向量的数组w_new是一个一维数组,在数学上是一列, 也就是说在numpy数组表示数学中的向量,一个向量对应一个一维数组.数学上,向量的第一个元素通常以x1表示,numpy中一个数组的第一个元素的索引为0.
向量的运算
我们通过实例来计算上面提到的关于体重的两个向量的运算
上面代码就是两个变量的减法,抛去数学概念,非常简单,如同我们小学学的自然的减法.理解起来非常的轻松和高效.
从上面结果可以看出,向量的加减乘除其实是对向量中每个对应元素分别进行加减乘除,对应的意思就是两个向量相同索引的元素.
一维卷积运算
滑动窗口简单实操
前面我们体验了一维array实现了向量乘法,用代码可以写成以下形式:
x*w = [x[0]*w[0], x[1]*w[1]]
如果两个array的shape不同,应该怎么做呢? 以最简单体验原则很容易就能得到这样的思路:先体验一维数组,再体验更高维数的array。两个一维array的shape不同,其实就是元素个数不同,直接对两个array使用multiply()方法会报错, 解决方法很简单,是解决复杂问题的常用套路,即,分解、分步。
具体到shape不同的array相乘的问题上,就是将相乘的过程分解为对应元素一一相乘,代码如下:
x = np.array([1,2,3]) w = np.array([3,1]) p_00 = w[0]*x[0] p_11 = w[1]*x[1] arr = np.array([p_00, p_11]) print(arr)
执行结果如下:
x、w是两个一维数组,对应元素是指x中index为0的元素与w中index为0的元素相对应,其乘积存储在变量p_00中。 x中index为1的元素与w中index为1的元素相对应,其乘积存储在变量p_11中。 以两个乘积构成一个新的数组存储变量arr中,就是第一步的运算结果了。 显然,x、w的相乘并没有完成,仅仅是完成了第一步,在进行下一步计算之前,我们先通过下图加深理解第一步的计算过程。下图3个方格中字号较大的数字表示数组x的3个元素。 图中阴影处字号较小且处于下标位置的数字表示数组w的2个元素。 w中每个元素都位于x中对应元素的右下角,以下标(subscript)的形式呈现,下图中箭头所示下标是此时的w[1]。 w、x相乘的第一步可以直观地看作阴影处方格中的每个元素与其下标相乘。 有了这个基础,就可以便捷地进行w、x相乘的第二步了,即将阴影部分右移一格(向右滑动一步),如下图的第二个图示。
此时,x中index为1的元素与w中index为0的元素相对应,x中index为2的元素与w中index为1的元素相对应,如图6-19中箭头所示。 通过阴影部分的移动(滑动),可以实现shape不同的array相乘,同时这个阴影看起来像一个窗口,因此称为滑动窗口。 上图展示的过程表示窗口由x[0]、x[1]的位置滑动到了x[1]、x[2]的位置。 窗口的长度等于w的长度,本例中数组w的长度为2,因此在阴影始终覆盖2个方格。 很多领域中都有滑动窗口这个术语,但它们的含义不尽相同。目前我们只需要掌握其字面意思即可,即滑动的窗口。 数组w是窗口,在数组x之上滑动,由于x的长度为3,w的长度为2,所以只需要滑动一次(也称滑动一步,one step)。
前面我们将概念、流程、数据以图形图像的形式进行表达,从上面的流程可看出参与乘法运算的两个数组中元素的对应位置由窗口决定,窗口覆盖区域内对应位置的元素分别运算。 通过窗口的滑动,可以将两个不同长度的数组进行乘法运算,上面代码我们只做了第一步的运算,第二步操作(即窗口滑动到右侧后的对应元素相乘)的代码我接下来继续唠一唠.
一维卷积运算完善
卷积运算是深度学习算法中最核心、最基础的概念,参与运算的通常为高维数组(如四维)。但是对于初学者来说,很难直接轻松理解高维数组的卷积运算,因此我们先从卷积运算的最简单的一维开始体验,再逐步增加维度,最终完全掌握。 一维数组的卷积运算,得到的结果也是一维数组,因此称为一维卷积。在进行卷积运算之前我们先将第二步操作完成,代码如下:
p_01 = w[0] * x[1] p_12 = w[1] * x[2] arr = np.array([p_01, p_12]) print(arr)
执行结果如下:
我们现在需要将arr1与arr2组合在一起。具体的组合分为以下两步:
- 对arr1、arr2分别求和。
- 以2个数组的和构成一个新的数组。
先来完成第一步,代码如下:
print('arr1.sum() = p_00 + p_11 = ', p_00, '+' , p_11, '=', np.sum(arr1)) print('arr2.sum() = p_01 + p_12 = ', p_01, '+' , p_12, '=', np.sum(arr2))
执行结果如下:
上面的代码中我们将前面两步中几个变量的关系联系在了一起,并整体呈现。 以arr1为例,数组中有两个元素 p_00与p_11。对该数组使用sum()方法,就是将p_00与p_11相加,运算结果是一个标量5。 对arr2进行同样的操作,运算结果为9。
在充分理解前面内容的基础上,接着我们可以轻松平滑地进行下面的操作了,即卷积运算。完整代码与如下:
import numpy as np x = np.array([1,2,3]) w = np.array([3,1]) p_00 = w[0]*x[0] p_11 = w[1]*x[1] arr1 = np.array([p_00, p_11]) print(arr1) p_01 = w[0] * x[1] p_12 = w[1] * x[2] arr2 = np.array([p_01, p_12]) print(arr2) print('arr1.sum() = p_00 + p_11 = ', p_00, '+' , p_11, '=', np.sum(arr1)) print('arr2.sum() = p_01 + p_12 = ', p_01, '+' , p_12, '=', np.sum(arr2)) convol_arr = np.array([arr1.sum(), arr2.sum()]) print(convol_arr) corel_arr = np.correlate(x, w) print(corel_arr)
执行结果如下:
总结
以arr1、arr2各自的和构成一个新的数组,就是x、w这两个数组进行卷积运算的最终结果,array([5,9])。 在DL中的卷积运算(Convolution)对应的是数学中的Correlation,因此,NumPy提供的用于完成这种运算的方法就叫correlate() 。
至此,我们简单体验了一把完成一维卷积的完整计算过程。以x、w这两个数组为例,这个过程可以总结成以下3步:
- w作为窗口,在初始位置(w[0]对应x[0])进行元素乘法运算(element-wise multiplication),对得到新的数组后求和。
- w向右滑动一步,在新的窗口位置上进行元素乘法运算,对结果求和。
- 以上述步骤中得到的结果作为元素,组成新的数组,就是x、w这两个数组的卷积运算结果。
以上步骤仅仅是一维卷积,更高维数的卷积(如二维卷积)的计算过程会多一些步骤,但是基本原理相同,步骤大差不差。