人工智能的研究领域主要分成三个方向:
- 机器学习(Machine Learning,ML)
- 语音识别和自然语言处理(Natural Language Processing,NLP)
- 计算机视觉(Computer Vision,CV)
其中,机器学习专门研究计算机怎样模拟或实现人类的学习行为,传统上采用样本数据记录-人工设计特征-机器学习的方式实现,但随着神经网络的提出和完善,尤其是深度学习的发展,其实现方式变成了样本数据记录-机器学习特征-端到端学习的方式。深度学习因为应用在人工智能领域的方方面面而备受关注。深度学习是一种基于统计的概率模型,是通过多层非线性变换对某种高复杂度的数据模式进行建模的一种方法;它源于人工神经网络的研究,含多隐层的多层感知器就是一种深度学习结构,它通过组合底层特征形成更抽象的高层表示属性类别或特征,以发现数据的分布式特征表示;实际上深度学习指的就是“深度神经网络”,相对于早期的人工神经网络,它采用了更深层的图模型结构、每层容纳了更多的节点,使得它能进一步接近人类大脑结构
深度学习的核心理念就是通过增加网络层数来使机器自动从数据中进行学习
到目前为止,深度学习经历过三次高潮阶段
- 最初在1957年,感知器被Roseblatt提出,作为首个可以学习的人工神经网络红极一时,但是随着Minskey指出感知器只能做简单的线性分类任务,甚至无法实现稍复杂的异或问题,神经网络的研究陷入冰河期。
- 1974年,Paul Werbos首次提出反向传播训练方法,随后Rumelhart和Hinton等在1986年利用该算法解决了两层神经网络需要的复杂计算量问题,BP算法、Hopfield网络和玻尔兹曼机相继出现,人们致力于研究语音识别、图像识别、自动驾驶等技术,但是由于当时计算机算力有限,神经网络的效果不如传统机器学习,导致了又一次寒冬
- 2006年,Hinton提出深度信念网络和深度学习概念,继而出现了自编码器、深度置信网、卷积神经网络、深度残差网络、图神经网络和生成对抗网络等深度模型,很快出现了各种神经网络模型和新算法
- 监督学习:所有数据都有明确标签,通过建立一个学习训练过程,将模型训练结果和实际数据标签比较,不断调节预测模型,直到模型得到一个理想的结果,主要用于分类和回归任务
- 半监督学习:所有训练集中的数据有一部分有标签,一部分没有标签,往往没有标签的数据比有标签的数据量大
- 无监督学习:模型从大量无标记数据中学习到较好的数据特征,再直接进行分析利用
- 强化学习:在没有数据标签的情况下,网络通过汇报函数来判断输出结果是否接近目标,这种学习方式常常用于游戏、推荐系统、智能控制等方面
深度置信网络DBN、深度卷积神经网络DCNN、深度递归神经网络RNN、生成对抗网络RNN、图神经网络、AutoML等都是典型的深度神经网络
深度学习还常常和图像、音频、无人驾驶、遥感测控、智能安防、自然语言处理等等方面有结合应用——几乎一切工作都可以用一个训练过的深度学习模型来实现
-
GPU
GPU可以实现高效的并行化运算,因为深度神经网络中的运算大多是矩阵的线性运算,涉及大量数值计算,但是控制逻辑简单,因此可以使用GPU高效部署实现
-
FPGA
使用FPGA部署定制化的算法,具有极高的灵活性,并且成本相对较低,在面对需求不大或算法不稳定的情况时,使用FPGA实现半定制AI芯片可以大大降低成本
-
ASIC
直接生产针对特定算法优化的专用芯片可以实现最高的算法效率,但是在小批量生产时往往成本高昂,并且开发成本高、周期长门槛高
-
SoC
使用片上系统可以针对性地解决多方面的AI需求,Zynq、昇腾SoC等都是针对AI计算场景设计的SoC
主流的深度学习开发平台有以下两种:
-
TensorFlow
这是一个将数据流图模型应用于数值计算的开源软件库
严格意义上它并不是一个神经网络的数据库——任何用户都可以通过一个数据流图来表示驱动计算的呢不循环,并帮助用户组装被广泛应用于神经网络的子图,用户也可以编写自己的上层库
TensorFlow常常被用于写论文TensorFlow支持GPU和CPU运行,具有可扩展性和高效性,但由于TF1和TF2的不兼容,以及PyTorch的冲击,现在TensorFlow的使用人数正在下降,不过仍然在学术界和工业部署上有着优势
由于出现时间较早,现在很多嵌入式设备只提供基于TensorFlow的SDK,使用C/C++编程
-
PyTorch
Torch的Python版本,由于入门便捷、NumPy风格、效率高、Python优先、方便易用、可利用GPU加速训练等原因在近些年快速发展,并且它具有c++底层接口,可以嵌入c++来加速计算
深度神经网络常常使用算子这一概念。深度学习中的算子可以类比量子力学的算符——二者都用于描述一种计算过程。常用的算子有以下几种
卷积算子是卷积神经网络CNN的核心计算算子,是深度神经网络的重要组成部分。深度学习中所谓的卷积不是指信号处理里面的那个卷积,这个卷积过程只是反复的矩阵乘法,但是核心思想类似。
援引网友@pdaman的解释,可以如下形象理解CV中卷积这个过程
什么是卷积操作呢?没法讲数学细节,我讲一个通俗的例子。
标准答题卡里的卷积操作
我上学的时候,有一次英语考试我们在下面做题,英语老师用烟头在一张答题卡上烫了一些洞。
考完试后,他把他的标准答題卡往我们的答题卡上一放,很显然哪位同学答题卡漏出的黑色铅笔标记越多,他的选择题的得分就越好。实际我们可以把所有同学的答题卡平摊开来放在教室的地面上,所有的答题卡组成了一副图片。 当我们把老师的标准答题卡滑过学生答题卡组成的图片时,根据重合部分的多寡,就可以得到每个学生的得分。
如果把标准答题卡看做一个向量,学生答题卡也组成向量,之间点积最高的学生得分最高。 这里也可以用图像很像来描述。
卷积操作不再单独的把标准答案的每一项和学生的每项逐一比较,而是把整个考试的答案看做一个整体。
图像处理时,常会使用对输入图像中的一个小区域内的像素加权平均后成为输出图像这种操作,其中权值由一个函数定义,这个函数被称为卷积核(Kernel),一般是一个3*3或5*5的矩阵
卷积核这个概念和传统的支持向量机中核函数的概念很类似,训练深度神经网络的过程也就是让模型“找到”合适的核函数的过程
输入的图像往往分成两部分:算法关心的和算法不关心的。对于算法关心的那部分图像,称为特征图。往往使用填充(Padding)的方法处理特征图边缘:将输入的特征图边缘数据置为某些值(一般设置为0),直到特征图和原图像尺寸一致或达到我们希望的大小。
一个输入图像一般需要经过填充和卷积两个过程才能进入下一层。卷积过程是使用卷积核,按照一定的步长依次遍历输入特征图,遍历过程中,卷积核会与图像上每个像素的数据相乘,最后得到一个输出特征图
步长是指卷积核遍历输入特征图时每次移动的像素数
通过以上描述可以得到卷积算子的运算过程:
-
输入图像本被看作一个数据矩阵,每个像素都是由一些数据构成的(对于常见的三通道图像,每个像素都由3个数据组成,一个3*3像素的图片就由3*3*3个数据构成)
为简单起见,这里使用一个3*3的矩阵为例 $$ T=\left[ \begin{matrix} 1&2&3 \ 4&5&6\ 7&8&9 \end{matrix} \right] $$
-
首先对输入图像边缘进行填充
这里假设填充一圈0,那么就会获得一个5*5的矩阵 $$ T'=\left[ \begin{matrix} 0&0&0&0&0 \ 0&1&2&3&0 \ 0&4&5&6&0 \ 0&7&8&9&0 \ 0&0&0&0&0 \ \end{matrix} \right] $$
-
然后使用一个卷积核对得到的图像进行卷积运算——也就是依次矩阵相乘来获取输出特征图
实际使用中,往往还会使用多个卷积核得到多个输出特征图
实际计算中常常分步执行卷积。假设使用3*3的卷积核,那么对上面矩阵计算就会需要先按照卷积核的顺序切分原图得到9个9元素的列向量,这个过程称为数据向量化
随后将卷积核展开为行向量,对图像矩阵按3*3小块展开成的列向量重组成的矩阵相乘
假设卷积核为 $$ T=\left[ \begin{matrix} -1&0&1 \ 1&0&-1\ -1&0&1 \end{matrix} \right] $$ 那么得到一个9元素的列向量为 $$ T=\left[ \begin{matrix} 3 \ 0\ -3\ 5\ 2\ -5\ -3\ 0\ 3 \end{matrix} \right] $$ 最后需要将得到的列向量进行矩阵转特征图操作,也就是把生成的列向量转成矩阵输出
得到 $$ T=\left[ \begin{matrix} 3&0&-3 \ 5&2&-5\ -3&0&3 \end{matrix} \right] $$ 把矩阵卷积转换成矩阵乘法实现,可以大大提高计算的速率,也为硬件加速设备实现算法提供便利
在数学上的卷积常常被以下面的公式描述: $$ f(x)*g(x)=\int_{- \infin}^{+\infin} f(\tau)g(t-\tau)d\tau $$ 实际上这个式子描述的也是用一个函数g(x)在另一个函数f(x)上滑动造成的影响
将某时刻的f值和向后推迟一个微小时刻的g值相乘,在整个定义域上反复重复无限次这个过程,得到每个点上f(x)被g(x-t)作用过后的结果就是卷积了,这和图像的滑动很类似
反卷积算子是一种上采样算子,它并不是卷积的逆过程,而是一种特殊的正向卷积。事实上,反卷积算子常常被称为转置卷积,它先对向量化的卷积核进行转置,得到一个列向量,再和向量化后特征图进行矩阵乘法,这样可以得到一个比特征图更大的图像。
反卷积是从低分辨率映射到高分辨率的过程,常常用于扩大图像尺寸
对于一般的卷积过程,输出特征图尺寸为 $$ O=1+\frac{I-K+2P}{S} $$ 其中I为输入特征图尺寸,K为卷积核尺寸,P为填充像素数,S为滑动步长
而反卷积操作可等效于步长S>1时的卷积,输出特征图尺寸为 $$ O'=S(I'-1)+K-2P $$ 可见它的输出尺寸比原图尺寸更大
可以把卷积形象理解成“从原图中提取特征”;而反卷积则是“在原图中补充细节”
池化算子是一种下采样算子,常常用于在网络结构中二次提取特征
它通过降低特征图的分片率获得特征图里具有空间不变性的特征
常用的池化算子有
- 平均池化算子:对输入矩阵的每个块中所有数据求平均值,将该值作为结果矩阵中相应位置的数值
- 最大池化算子:对输入矩阵的每个块中所有数据求最大值,将该值作为结果矩阵中相应位置的数值
二者都可以理解成张量(图像数据)上的滤波函数
CNN结构中,多个卷积层和池化层后需要连接一个或多个全连接层,其中每个神经元都会与上一层的所有神经元相连,因此全连接算子可以整合卷积层、池化层中具有类别区别性的局部信息。全连接算子并不是简单的连接神经元并进行传递,而是要对输入进行加权后输出
全连接层的参数W,也就是权值,就是深度神经网络训练中全连接层寻求的最有权值,一般将其表示成T行N列的二维向量(矩阵),其中T表示类别数
输出可表示为$Y=W \times X + bias$
训练时通过经典的BP算法或其他新算法改善权值,其中还需要用到大量求导计算
激活算子一般接在卷积层或全连接层之后,用于激活神经网络中的部分神经元并将激活信息向后传递
在0点附近具有陡峭斜率的Sigmoid函数、双曲正切函数、ReLu函数都可以作为激活函数实现激活算子的功能,这一层本质上是用于耦合前后神经网络层来避免层次之间积累的微小误差逐层放大——传统神经网络中的大多数激活函数都可以用于激活算子
Softmax算子常用于多分类问题,计算公式为 $$ S_j=\frac{e^{a_j}}{\sum_{k=1}^{T}e^{a_k}} $$ 该算子主要用于神经网络最后一层,负责输出样本属于各个类别的概率
其中$a_j$、$a_k$表示上一层输出向量中的j个和第k个值
该算子可类比为将上一层的矩阵单位化,只不过它增加了一层$e^\alpha$函数
经过计算后,得到的输出向量中所有值都在0~1的范围内,输出向量中共有T个值——T是需要分类的类别
于是就解决了样本的多分类问题
批标准化算子即BN算子,可加快模型收敛速度并解决网络中梯度弥散问题
网络训练中前面层训练参数的更新会导致后面层输入的变化,因此上层的网络需要不停适应这些变化,就导致训练过程困难,BN算子用来标准化某些层或所有层的输入,这样就能固定每层输入信号的均值与方差
BN算子一般用在激活函数之前,对输入$y=Wx+b$进行规范化,使得输出结果均值为0,方差为1,这样每层的输入就会有稳定的分布
BN算子的工作方式如下:
-
求一批次训练数据的均值$\mu_B$
-
求对应数据的方差$\sigma^2$
-
根据公式
$\hat{x}_i=\frac{x_i-\mu_B}{\sqrt{\sigma^2+\epsilon}}$ 来计算标准化训练数据
-
对标准化输入进行线性变换,得到对应层的标准化输入
这一步是为了提高网络的表达能力,防止正态分布的$x_i$限制网络
单纯增加网络深度往往会造成梯度弥散或梯度爆炸情况。梯度爆炸导致网络过拟合,但BN算子可以很好的解决这个问题;而随着网络层数增加,梯度弥散情况严重,训练集上的准确率会出现饱和甚至下降情况,这是由于冗余的网络层学习了冗余参数导致的,使用Shortcut算子可以有效解决这个问题
通过在层次间加入具有权重的搬运门(carry gate),可以让梯度下降时总有直接向后传递的数据,从而使训练过程中梯度不会消失,缓解了梯度发散问题
计算平台的计算力决定了计算平台的处理神经网络模型性能的上限,用计算平台每秒所能完成最多浮点运算数计量;而计算平台的带宽决定了平台中内存数据交换速度的上限,考虑到这两个关键因素,需要为一个模型寻找适合其所需计算力和带宽的平台,同时也要保证模型的计算量和访存量在可接受范围内
计算量通常用时间复杂度描述:输入单个样本(CV中是一张图像,NLP中是一个语句,以此类推),模型完成一次前向传播过程中所发生的浮点运算数;访存量则用空间复杂度描述:输入单个样本,模型完成一次前向传播过程中所发生的内存数据交换总量,需要注意:不考虑缓存情况下,模型的访存量总等于模型各层权重参数的内存占用和每层输出特征图的内存占用之和
一般地,CNN中会包含卷积层、池化层、激活层、全连接层等,针对每一层分别有如下计算公式: $$ 卷积层计算量=M^2 \times K^2 \times C_{in} \times C_{out} $$ 其中M表示每个卷积核输出特征图的边长,K表示每个卷积核的边长,$C_{in}$表示每个卷积核的输入通道数,$C_{out}$表示本卷积层具有的卷积核个数(即输出通道数) $$ 全连接层计算量=H \times W $$ H表示当前层权重矩阵的行数,W表示当前层权重矩阵的列数 $$ 池化层计算量=池化算法计算量 \newline 激活层计算量=激活函数算法计算量 $$ 其他的算子和层级都根据对应算法的计算量得到结果,应该按照具体实现进行计算、估算
模型的空间复杂度由三部分决定:
-
输入量:输入特征图数据量的总和 $$ 卷积层输入量=I^2 \times C \newline 全连接层输入量=H $$ I表示特征图边长,C为输入特征图的通道数,H为当前层输入矩阵的行数
-
参数:模型所有带参数的层的权重参数总和
参数量实际上就是模型的参数体积,计算公式如下 $$ 卷积层参数量=K^2 \times C_{in} \times C_{out} \newline 全连接层参数量=H \times W $$ 基本上和前面的计算量公式一样,只不过少了卷积核输出特征图的边长M,因为这里只涉及模型要计算的参数而不涉及模型计算出的结果
-
输出量:输出特征图的数据量总和 $$ 卷积层输出量=O^2 \times C_{out} \newline 全连接层输出量=W $$ 其中O表示输出特征图的边长,$C_{out}$表示本卷积层具有的卷积核个数(即输出通道数),W为当前层输出矩阵的列数
模型的复杂度由计算量和访存量决定,上面提到的这两个因素通常可以具象为理论计算量和参数数量。
理论计算量:对应时间复杂度,指模型推断时需要多少计算次数。一般地,使用FLOPs(Floating Point Operations)描述,一个大模型的单位通常达到GFLOPs也就是十亿次浮点运算,小模型也往往有数MFLOPs的计算量(百万次浮点运算)
和上面所说的计算公式对应,一般只考虑卷积层、池化层、全连接层等需要大量乘加操作的算子,忽略批标准化层(BN层)、激活层等不需要大量计算或者计算能够得到简化的算子
参数数量:对应空间复杂度,指模型含有多少参数。对应模型参数的数据格式,模型大小会是参数数量的4倍(float32)、2倍(float16)抑或是等同于参数数量(int8)
访存量实际上概括了参数数量和输入数据大小,在针对异构运算设备设计模型时往往要考虑到输入数据本身的大小,但针对CPU运算模型或ASIC上运行模型一般不需要考虑输入数据,因为输入数据被以流水线的形式“分摊”到硬件设备的所有寄存器中,这一项的影响会与理论计算量一同考虑
对应于理论计算量和参数数量,执行神经网络算法的硬件设备需要使用算力和带宽来评判性能水平
算力:计算平台倾尽全力每秒钟所能完成的浮点(float32格式)运算数,单位为TFLOPS(Floating Point Of Per Second)
算力的理论运算会针对硬件平台不同而变化,一般来说同等功耗下,设备算力ASIC>FPGA>GPU,但是一般设备主频GPU>ASIC>FPGA,处理器核心数量和数据吞吐量也会影响设备的算力。
带宽:计算平台倾尽全力每秒所能完成的内存交换量,单位为GB/s
带宽计算可以用一个统一的公式描述: $$ 带宽=(内存核心时钟频率 × 2) × (内存位宽 / 8) × 通道数 $$ 带宽决定了模型在单位时间内的访存能力,设备带宽越高就越能更好处理庞大的参数