写点什么

深度学习基础入门篇 [8]::计算机视觉与卷积神经网络、卷积模型 CNN 综述、池化讲解、CNN 参数计算

作者:汀丶
  • 2023-05-21
    浙江
  • 本文字数:9976 字

    阅读完需:约 33 分钟

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型 CNN 综述、池化讲解、CNN 参数计算

1.计算机视觉与卷积神经网络

1.1 计算机视觉综述

计算机视觉作为一门让机器学会如何去“看”的学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统。


计算机视觉技术经过几十年的发展,已经在交通(车牌识别、道路违章抓拍)、安防(人脸闸机、小区监控)、金融(刷脸支付、柜台的自动票据识别)、医疗(医疗影像诊断)、工业生产(产品缺陷自动检测)等多个领域应用,影响或正在改变人们的日常生活和工业生产方式。未来,随着技术的不断演进,必将涌现出更多的产品和应用,为我们的生活创造更大的便利和更广阔的机会。



图 1 计算机视觉技术在各领域的应用

1.2 计算机视觉的发展历程

计算机视觉的发展历程要从生物视觉讲起。对于生物视觉的起源,目前学术界尚没有形成定论。有研究者认为最早的生物视觉形成于距今约 7 亿年前的水母之中,也有研究者认为生物视觉产生于距今约 5 亿年前寒武纪。寒武纪生物大爆发的原因一直是个未解之谜,不过可以肯定的是在寒武纪动物具有了视觉能力,捕食者可以更容易地发现猎物,被捕食者也可以更早的发现天敌的位置。视觉系统的形成有力地推动了食物链的演化,加速了生物进化过程,是生物发展史上重要的里程碑。经过几亿年的演化,目前人类的视觉系统已经具备非常高的复杂度和强大的功能,人脑中神经元数目达到了 1000 亿个,这些神经元通过网络互相连接,这样庞大的视觉神经网络使得我们可以很轻松的观察周围的世界,如 图 2 所示。



图 2 人类视觉感知


对人类来说,识别猫和狗是件非常容易的事。但对计算机来说,即使是一个精通编程的高手,也很难轻松写出具有通用性的程序(比如:假设程序认为体型大的是狗,体型小的是猫,但由于拍摄角度不同,可能一张图片上猫占据的像素比狗还多)。那么,如何让计算机也能像人一样看懂周围的世界呢?研究者尝试着从不同的角度去解决这个问题,由此也发展出一系列的子任务,如 图 3 所示。



图 3 计算机视觉子任务示意图


  • (a) Image Classification: 图像分类,用于识别图像中物体的类别(如:bottle、cup、cube)。

  • (b) Object Localization: 目标检测,用于检测图像中每个物体的类别,并准确标出它们的位置。

  • (c) Semantic Segmentation: 语义分割,用于标出图像中每个像素点所属的类别,属于同一类别的像素点用一个颜色标识。

  • (d) Instance Segmentation: 实例分割,值得注意的是,目标检测任务只需要标注出物体位置,而实例分割任务不仅要标注出物体位置,还需要标注出物体的外形轮廓。


这里以图像分类任务为例,为大家介绍计算机视觉技术的发展历程。在早期的图像分类任务中,通常是先人工提取图像特征,再用机器学习算法对这些特征进行分类,分类的结果强依赖于特征提取方法,往往只有经验丰富的研究者才能完成,如 图 4 所示。



图 4 早期的图像分类任务


在这种背景下,基于神经网络的特征提取方法应运而生。Yann LeCun 是最早将卷积神经网络应用到图像识别领域的,其主要逻辑是使用卷积神经网络提取图像特征,并对图像所属类别进行预测,通过训练数据不断调整网络参数,最终形成一套能自动提取图像特征并对这些特征进行分类的网络,如 图 5 所示。



图 5 早期的卷积神经网络处理图像任务示意


这一方法在手写数字识别任务上取得了极大的成功,但在接下来的时间里,却没有得到很好的发展。其主要原因一方面是数据集不完善,只能处理简单任务,在大尺寸的数据上容易发生过拟合;另一方面是硬件瓶颈,网络模型复杂时,计算速度会特别慢。


目前,随着互联网技术的不断进步,数据量呈现大规模的增长,越来越丰富的数据集不断涌现。另外,得益于硬件能力的提升,计算机的算力也越来越强大。不断有研究者将新的模型和算法应用到计算机视觉领域。由此催生了越来越丰富的模型结构和更加准确的精度,同时计算机视觉所处理的问题也越来越丰富,包括分类、检测、分割、场景描述、图像生成和风格变换等,甚至还不仅仅局限于 2 维图片,包括视频处理技术和 3D 视觉等。

1.3 卷积神经网络

卷积神经网络是目前计算机视觉中使用最普遍的模型结构。图 6 是一个典型的卷积神经网络结构,多层卷积和池化层组合作用在输入图片上,在网络的最后通常会加入一系列全连接层,ReLU 激活函数一般加在卷积或者全连接层的输出上,网络中通常还会加入 Dropout 来防止过拟合。



图 6 卷积神经网络经典结构


  • 卷积层:卷积层用于对输入的图像进行特征提取。卷积的计算范围是在像素点的空间邻域内进行的,因此可以利用输入图像的空间信息。卷积核本身与输入图片大小无关,它代表了对空间邻域内某种特征模式的提取。比如,有些卷积核提取物体边缘特征,有些卷积核提取物体拐角处的特征,图像上不同区域共享同一个卷积核。当输入图片大小不一样时,仍然可以使用同一个卷积核进行操作。

  • 池化层:池化层通过对卷积层输出的特征图进行约减,实现了下采样。同时对感受域内的特征进行筛选,提取区域内最具代表性的特征,保留特征图中最主要的信息。

  • 激活函数:激活函数给神经元引入了非线性因素,对输入信息进行非线性变换,从而使得神经网络可以任意逼近任何非线性函数,然后将变换后的输出信息作为输入信息传给下一层神经元。

  • 全连接层:全连接层用于对卷积神经网络提取到的特征进行汇总,将多维的特征映射为二维的输出。其中,高维代表样本批次大小,低维代表分类或回归结果。

2. 池化

2.1 基础概念(平均池化和最大池化)

在图像处理中,由于图像中存在较多冗余信息,可用某一区域子块的统计信息(如最大值或均值等)来刻画该区域中所有像素点呈现的空间分布模式,以替代区域子块中所有像素点取值,这就是卷积神经网络中池化(pooling)操作。


池化操作对卷积结果特征图进行约减,实现了下采样,同时保留了特征图中主要信息。比如:当识别一张图像是否是人脸时,我们需要知道人脸左边有一只眼睛,右边也有一只眼睛,而不需要知道眼睛的精确位置,这时候通过池化某一片区域的像素点来得到总体统计特征会显得很有用。


池化的几种常见方法包括:平均池化、最大池化、K-max 池化。其中平均池化和最大池化如 图 1 所示,K-max 池化如 图 2 所示。



图 1 平均池化和最大池化


  • 平均池化: 计算区域子块所包含所有像素点的均值,将均值作为平均池化结果。如 **图 1(a)**,这里使用大小为的池化窗口,每次移动的步幅为 2,对池化窗口覆盖区域内的像素取平均值,得到相应的输出特征图的像素值。池化窗口的大小也称为池化大小,用表示。在卷积神经网络中用的比较多的是窗口大小为,步幅为 2 的池化。

  • 最大池化: 从输入特征图的某个区域子块中选择值最大的像素点作为最大池化结果。如 **图 1(b)**,对池化窗口覆盖区域内的像素取最大值,得到输出特征图的像素值。当池化窗口在图片上滑动时,会得到整张输出特征图。



图 2 K-max 池化


  • K-max 池化: 对输入特征图的区域子块中像素点取前 K 个最大值,常用于自然语言处理中的文本特征提取。如图 2,从包含了 4 个取值的每一列中选取前 2 个最大值就得到了 K 最大池化结果。

2.2 池化特点

  1. 当输入数据做出少量平移时,经过池化后的大多数输出还能保持不变,因此,池化对微小的位置变化具有鲁棒性。例如 图 3 中,输入矩阵向右平移一个像素值,使用最大池化后,结果与平移前依旧能保持不变。



图 3 微小位置变化时的最大池化结果


  1. 由于池化之后特征图会变小,如果后面连接的是全连接层,能有效的减小神经元的个数,节省存储空间并提高计算效率

2.3 池化中填充的方式

在飞桨中,各种 Pooling API 中的 Padding 参数, 接收值类型都包括 int、list、tuple 和 string。下面用代码和公式来介绍一下这些方式吧。

2.3.1 int 输入

int 输入即接收一个 int 类型的数字 n,对图片的四周包裹 n 行 n 列的 0 来填充图片。如果要保持图片尺寸不变,n 的值和池化窗口的大小是有关的。假如 为图片输入的大小, 为池化窗口的大小, 为结果图的大小的话,他们之间有着这样的关系。 $$H_{out} = \frac{H_{in} + 2p_h - k_h}{s_h} + 1 \ W_{out} = \frac{W_{out} + 2p_w -k_w}{s_w} + 1在使用 3×3 的池化窗口且步长为 1 的情况下,还要保持图片大小不变,则需要使用 padding=1 的填充。 那么,公式就变为了H_{out} = \frac{6 - 3 + 21}{1} + 1 \ W_{out} = \frac{6 - 3 + 21}{1} + 1另外,在 stride 不为 1 且不能被整除的情况下,整体结果向下取整。 关于 Padding 和 K 的公式如下Padding = \frac{(k-1)}{2} \quad \quad (k % 2 != 0)


关于上面的讲解,下面用飞桨的 API 来看看吧。


import paddle # No paddingx = paddle.rand((1, 1, 6, 6))avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=0)y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 4, 4]
复制代码


这是池化中不 padding,stride 为 1 的结果,可以根据公式填入,,因此池化后的结果为 4。如果填充呢?


import paddle # Padding 1x = paddle.rand((1, 1, 6, 6))avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=1)y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]
复制代码


正如我们上面的公式,, 填充为 1 的时候图像保持为原大小。

2.3.2 list 和 tuple 输入

因为图像有宽和高,所以 list 和 tuple 的长度应该为 2,里面的两个值分别对应了高和宽,计算方式和上面 int 输入的一样,单独计算。一般用作输入图片宽高不一致,或者池化窗口大小不一的情况。我们直接用飞桨的 API 来看看吧。


import paddle # No padding and different kernel sizex = paddle.rand((1, 1, 12, 6)) # 12为高H, 6为宽Wavgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=0)y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 10, 2]
复制代码


这里我们带入公式推理一下,.与结果相符。下面是有填充的情况,且 3 的滑动窗口大小我们需要填充 1,5 的话则需要填充 2 了。下面来看看吧。


import paddle # No padding and different kernel sizex = paddle.rand((1, 1, 12, 6)) # 12为高H, 6为宽Wavgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=(1, 2))y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 12, 6]
复制代码


这里的结果与我们的期望一致,大家试着带入公式算一下吧。

2.3.3 string 输入

string 输入有两个值,一个是 SAME,一个是 VALID。这两个的计算公式如下:


SAME:,


VALID:,


可以看到,VALID 方式就是默认采用的不填充的方式,与上面不 Padding 的公式一样。而 SAME 则与池化窗口的大小无关,若为 1,无论池化窗口的大小,输出的特征图的大小都与原图保持一致。当任意一个大于 1 时,如果能整除,输出的尺寸就是整除的结果,如果不能整除,则通过 padding 的方式继续向上取整。理论过于难懂,我们直接用飞桨的 API 来看看吧。


import paddle # Padding SAME kernel_size 2x = paddle.rand((1, 1, 6, 6))avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='SAME')y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]
复制代码


代码的结果出来了,我们来直接带入公式来计算吧,,结果一致。


import paddle # Padding SAME kernel_size 1x = paddle.rand((1, 1, 6, 6))avgpool = paddle.nn.AvgPool2D(kernel_size=1, padding='SAME')y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]
复制代码


这个呢,就和我们上面说的一致。下面来看看 VALID 填充方式吧。


import paddle # Padding VALIDx = paddle.rand((1, 1, 6, 6))avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='VALID')y = avgpool(x)print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]
复制代码


这就是 VALID 的填充方式的结果啦。大家自己按照公式算算,看看你的答案和程序输出的对不对哦。

2.4 应用示例

与卷积核类似,池化窗口在图片上滑动时,每次移动的步长称为步幅,当宽和高方向的移动大小不一样时,分别用表示。也可以对需要进行池化的图片进行填充,填充方式与卷积类似,假设在第一行之前填充行,在最后一行后面填充行。在第一列之前填充列,在最后一列之后填充列,则池化层的输出特征图大小为:




在卷积神经网络中,通常使用大小的池化窗口,步幅也使用 2,填充为 0,则输出特征图的尺寸为:




通过这种方式的池化,输出特征图的高和宽都减半,但通道数不会改变。


这里以 图 1 中的 2 个池化运算为例,此时,输入大小是 ,使用大小为 的池化窗口进行运算,步幅为 2。此时,输出尺寸的计算方式为:




图 1(a) 中,使用平均池化进行运算,则输出中的每一个像素均为池化窗口对应的 区域求均值得到。计算步骤如下:


  1. 池化窗口的初始位置为左上角,对应粉色区域,此时输出为

  2. 由于步幅为 2,所以池化窗口向右移动两个像素,对应蓝色区域,此时输出为

  3. 遍历完第一行后,再从第三行开始遍历,对应绿色区域,此时输出为

  4. 池化窗口向右移动两个像素,对应黄色区域,此时输出为


图 1(b) 中,使用最大池化进行运算,将上述过程的求均值改为求最大值即为最终结果。

3.CNN 中模型的参数量与 FLOPs 计算

一个卷积神经网络的基本构成一般有卷积层、归一化层、激活层和线性层。这里我们就通过逐步计算这些层来计算一个 CNN 模型所需要的参数量和 FLOPs 吧. 另外,FLOPs 的全程为 floating point operations 的缩写(小写 s 表复数),意指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。

3.1 卷积层

卷积层,最常用的是 2D 卷积,因此我们以飞桨中的 Conv2D 来表示。

3.1.1 卷积层参数量计算

Conv2D 的参数量计算较为简单,先看下列的代码,如果定义一个 Conv2D,卷积层中的参数会随机初始化,如果打印其 shape,就可以知道一个 Conv2D 里大致包含的参数量了,Conv2D 的参数包括两部分,一个是用于卷积的 weight,一个是用于调节网络拟合输入特征的 bias。如下


import paddleimport numpy as npcv2d   = paddle.nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=(1, 1))params = cv2d.parameters()print("shape of weight: ", np.array(params[0]).shape)print("shape of bias: ", np.array(params[1]).shape)shape of weight:  (4, 2, 3, 3)shape of bias:  (4,)
复制代码


这里解释一下上面的代码,我们先定义了一个卷积层 cv2d,然后输出了这个卷积层的参数的形状,参数包含两部分,分别是 weight 和 bias,这两部分相加才是整个卷积的参数量。因此,可以看到,我们定义的 cv2d 的参数量为:$423*3+4 = 76 C_{in} C_{out} K_h K_w C_{out}$即可。

3.1.2 卷积层 FLOPs 计算

参数量会计算了,那么 FLOPs 其实也是很简单的,就一个公式: $ M_{outh}M_{outw}$ 为输出的特征图的高和宽,而不是输入的,这里需要注意一下。

3.1.3 卷积层参数计算示例

Paddle 有提供计算 FLOPs 和参数量的 API,paddle.flops, 这里我们用我们的方法和这个 API 的方法来测一下,看看一不一致吧。代码如下:


import paddlefrom paddle import nnfrom paddle.nn import functional as F

class TestNet(nn.Layer): def __init__(self): super(TestNet, self).__init__() self.conv2d = nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=1)
def forward(self, x): x = self.conv2d(x) return x
if __name__ == "__main__": net = TestNet() paddle.flops(net, input_size=[1, 2, 320, 320]) Total GFlops: 0.00778 Total Params: 76.00
复制代码


API 得出的参数量为 76,GFLOPs 为 0.00778,这里的 GFLOPs 就是 FLOPs 的 10倍,我们的参数量求得的也是 76,那么 FLOPs 呢?我们来算一下,输入的尺寸为 320 * 320, 卷积核为 3 * 3, 且 padding 为 1,那么图片输入的大小和输出的大小一致,即输出也是 320 * 320, 那么根据我们的公式可得: , 与 API 的一致!因此大家计算卷积层的参数和 FLOPs 的时候就可以用上面的公式。

3.2 归一化层

最常用的归一化层为 BatchNorm2D 啦,我们这里就用 BatchNorm2D 来做例子介绍。在算参数量和 FLOPs,先看看 BatchNorm 的算法流程吧!


输入为:Values of over a mini-batch:,


Params to be learned: ,


输出为:{=BN,}


流程如下:


$\quad\quad\quad\hat{x}i\gets\frac{x_i-\mu_B}{\sqrt{\sigma{B}^2+\epsilon}}$


$\quad\quad\quad y_i\gets\gamma\hat{x}i+\beta\equiv BN{\gamma}\beta(x_i)$


在这个公式中, 为一个 Batch 的数据, 为可学习的参数, 为均值和方差,由输入的数据的值求得。该算法先求出整体数据集的均值和方差,然后根据第三行的更新公式求出新的 x,最后根据可学习的调整数据。第三行中的 在飞桨中默认为 1e-5, 用于处理除法中的极端情况。

3.2.1 归一化层参数量计算

由于归一化层较为简单,这里直接写出公式: $ $ 其中 4 表示四个参数值,每个特征图对应一组四个元素的参数组合;


beta_initializer 权重的初始值设定项。


gamma_initializer 伽马权重的初始值设定项。


moving_mean_initializer 移动均值的初始值设定项。


moving_variance_initializer 移动方差的初始值设定项。

3.2.2 归一化层 FLOPs 计算

因为只有两个可以学习的权重,,所以 FLOPs 只需要 2 乘以输出通道数和输入的尺寸即可。 归一化的 FLOPs 计算公式则为: FLOP_{bn2d} = 2 * C_{out} * M_{outh} * M_{outw} 与 1.3 相似,欢迎大家使用上面的代码进行验证。

3.3 线性层



线性层也是常用的分类层了,我们以飞桨的 Linear 为例来介绍。

3.3.1 线性层参数量计算

其实线性层是比较简单的,它就是相当于卷积核为 1 的卷积层,线性层的每一个参数与对应的数据进行矩阵相乘,再加上偏置项 bias,线性层没有类似于卷积层的“卷”的操作的,所以计算公式如下: $ $。我们这里打印一下线性层参数的形状看看。


import paddleimport numpy as nplinear = paddle.nn.Linear(in_features=2, out_features=4)params = linear.parameters()print("shape of weight: ", np.array(params[0]).shape)print("shape of bias: ", np.array(params[1]).shape)
shape of weight: (2, 4)shape of bias: (4,)
复制代码


可以看到,线性层相较于卷积层还是简单的,这里我们直接计算这个定义的线性层的参数量为 。具体对不对,我们在下面的实例演示中检查。

3.3.2 线性层 FLOPs 计算

与卷积层不同的是,线性层没有”卷“的过程,所以线性层的 FLOPs 计算公式为: FLOP_{linear} = C_{in} * C_{out}$$

3.4 实例演示



这里我们就以 LeNet 为例子,计算出 LeNet 的所有参数量和计算量。LeNet 的结构如下。输入的图片大小为 28 * 28


LeNet(  (features): Sequential(    (0): Conv2D(1, 6, kernel_size=[3, 3], padding=1, data_format=NCHW)    (1): ReLU()    (2): MaxPool2D(kernel_size=2, stride=2, padding=0)    (3): Conv2D(6, 16, kernel_size=[5, 5], data_format=NCHW)    (4): ReLU()    (5): MaxPool2D(kernel_size=2, stride=2, padding=0)  )  (fc): Sequential(    (0): Linear(in_features=400, out_features=120, dtype=float32)    (1): Linear(in_features=120, out_features=84, dtype=float32)    (2): Linear(in_features=84, out_features=10, dtype=float32)  ))我们先来手动算一下参数量和FLOPs。

复制代码


features[0] 参数量: , FLOPs :


features[1] 参数量和 FLOPs 均为 0


features[2] 参数量和 FLOPs 均为 0, 输出尺寸变为 14 * 14


features[3] 参数量: , FLOPs : , 需要注意的是,这个卷积没有 padding,所以输出特征图大小变为 10 * 10


features[4] 参数量和 FLOPs 均为 0


features[5] 参数量和 FLOPs 均为 0,输出尺寸变为 5 * 5, 然后整个被拉伸为[1, 400]的尺寸,其中 400 为 5 * 5 * 16。


fc[0] 参数量: , FLOPs : (输出尺寸变为[1, 120])


fc[1] 参数量: , FLOPs : (输出尺寸变为[1, 84])


fc[2] 参数量: , FLOPs : (输出尺寸变为[1, 10])。


总参数量为:


总 FLOPs 为:


下面我们用代码验证以下:


from paddle.vision.models import LeNetnet = LeNet()print(net)paddle.flops(net, input_size=[1, 1, 28, 28], print_detail=True)
+--------------+-----------------+-----------------+--------+--------+| Layer Name | Input Shape | Output Shape | Params | Flops |+--------------+-----------------+-----------------+--------+--------+| conv2d_0 | [1, 1, 28, 28] | [1, 6, 28, 28] | 60 | 47040 || re_lu_0 | [1, 6, 28, 28] | [1, 6, 28, 28] | 0 | 0 || max_pool2d_0 | [1, 6, 28, 28] | [1, 6, 14, 14] | 0 | 0 || conv2d_1 | [1, 6, 14, 14] | [1, 16, 10, 10] | 2416 | 241600 || re_lu_1 | [1, 16, 10, 10] | [1, 16, 10, 10] | 0 | 0 || max_pool2d_1 | [1, 16, 10, 10] | [1, 16, 5, 5] | 0 | 0 || linear_0 | [1, 400] | [1, 120] | 48120 | 48000 || linear_1 | [1, 120] | [1, 84] | 10164 | 10080 || linear_2 | [1, 84] | [1, 10] | 850 | 840 |+--------------+-----------------+-----------------+--------+--------+Total GFlops: 0.00034756 Total Params: 61610.00

复制代码


可以看到,与我们的计算是一致的,大家可以自己把 VGG-16 的模型算一下参数量 FLOPs,相较于 LeNet, VGG-16 只是模型深了点,并没有其余额外的结构。


发布于: 13 小时前阅读数: 2
用户头像

汀丶

关注

本博客将不定期更新关于NLP等领域相关知识 2022-01-06 加入

本博客将不定期更新关于机器学习、强化学习、数据挖掘以及NLP等领域相关知识,以及分享自己学习到的知识技能,感谢大家关注!

评论

发布
暂无评论
深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算_人工智能_汀丶_InfoQ写作社区