人工智能实践,Tensorflow笔记(之卷积神经网路篇)

📅 2026/7/4 20:13:56
人工智能实践,Tensorflow笔记(之卷积神经网路篇)
一、卷积计算过程1、全连接网络全连接网络NN特点每个神经元与前后相邻层的每个神经元都有连接关系全连接网络的参数个数为针对一张分辨率仅为28*28的黑白图像像素值个数为28*28*1784全连接网络的参数总量就有将近40万个。在实际应用中图像分辨率远高于此且大部分都是彩色图像虽然全连接网络一般被认为是分类预测的最佳网络但待优化的参数过多容易导致模型过拟合。针对参数量过大而导致模型过拟合的问题我们一般不会将原始图直接输入而是先对图像进行特征提取再将提取的特征输入全连接网络。例如将汽车图片经过多次特征提取投入全连接网络。2、卷积神经网络卷积的概念(具体可以看知乎上马同学的文章如何通俗易懂地解释卷积以下内容摘抄这个作者的)连续的定义离散的定义在图像处理上原图有很多噪点 属于高频信号就像平地耸立的山峰看起来很明显如果需要平滑这座山峰就要把山峰刨掉一些土填到旁边的山峰周围意思就是把山峰的高度平均一下。卷积可以帮助实现这个平滑算法有噪声点的原图将它转为一个矩阵然后用平均矩阵来平滑图像 把高频信号与周围的数值平均一下就可以平滑山峰。例如需要平滑点就在矩阵中取出点附近的点组成矩阵和进行卷积计算后再填回去。要注意为了运用卷积虽然和同纬度但是下标有点不一样在提取图像特征上一般会用一个正方形的卷积核按指定步长在输入特征图上滑动遍历输入特征图中的每个像素点。每个步长卷积核会与输入特征图出现重合区域重合区域对应的元素相乘、求和再加上偏置项得到输出特征的一个像素点。如图其中利用大小为3*3*1的卷积核对5*5*1的单通道图像做卷积计算的结果对于彩色图像多通道来说卷积核通道数与输入特征一致然后进行后乘加操作。如图利用三通道卷积核对三通道的彩色特征图做卷积计算。用多个卷积核可以实现对同一层输入特征的多次特征提取卷积核的个数决定输出层的通道数channels即输出特征图的深度。输入特征图的深度channel数决定了当前层卷积核的深度二、感受野Receptive Field感受野卷积神经网络各输出特征图中的每个像素点在原始入图片上映射区域的大小当采用尺寸不同的卷积核时最大的区别是感受野大小不同所以到底选用什么哪种卷积核这里有个示例设输入特征图的宽、高均为x 卷积计算的步长为1在参数量上两个3*3卷积核的参数数量为9918一个5*5卷积核的参数数量为25前者的参数量少。输出特征图边长的公式为在计算量上5*5卷积核的输出特征图共有个像素点每个像素点需要进行5*525次乘加运算总共计算量。两个3*3的卷积核来说第一个卷积核输出特征图共有个像素点每个像素点需要进行3*39次乘加运算第二个3*3卷积核输出特征共有个像素点每个像素点同样进行9次乘加运算总的计算量为对比两者的总计算量3*3小于5*5的经过运算得到x22/7orx10所以两个3*3卷积核在特征图边长大于10的情况下是优于一个5*5卷积核的尤其在特征图尺寸特别大的时候两个3*3卷积核的优势会更加明显。三、全零填充padding概念为了保持输出图像尺寸与输入图像尺寸一致经常会在输入图像周围进行全零填充。过程如下用参数paddingsame or paddingvalid表示是否进行全零填充其输出特征图尺寸大小为以下公式same表示对输入特征图进行全零填充valid反之。根据以上知识在Tensorflow框架下利用keras来构建CNN中的卷积层使用的是tf.keras.layers.Conv2D函数具体方法如下tf.keras.layers.Conv2D( input_shape (高宽通道数) #仅在第一层有 filter 卷积核个数 kernel_size 卷积核尺寸 #列如kernel_size(3,3)为3*3的卷积核 strides 卷积步长 #即卷积核在输入图像上滑动的步长纵向步长和横向步长通常是相同的默认值为1 padding SAMEorVALID, #是否全零填充 activation reluorsigmoidortanhorsoftmax等 #采用哪种激活函数如有BN则此处不用写 在利用Tensorflow框架构建卷积网络时一般会利用BatchNormalization函数来构建BN层进行批归一化操作所以在Conv2D函数中经常不写BN。四、Batch Normalization批标准化概念对一小批数据在网络各层的输出做标准化处理其具体实现如下图标准化使数据符合0均值1为标准差的分布Batch Normalization将神经网络每层的输入都调整到均值为0方差为1的标准正态分布其目的是解决神经网络中梯度消失的问题。BN操作的另一个重要步骤是缩放和偏移注意的是缩放因子和偏移因子都是可训练的参数可学习参数起作用如图BN操作通常位于卷积层之后激活层之前通常用tf.keras.layers.BatchNormalization函数来构建BN层。在调用此函数时需要注意的一个参数是training,此参数只在调用时指定在模型进行前向推理前向传播时产生作用当trainingTrue时BN操作采用当前batch的均值和标准差当trainingFalseBN操作采用滑动平均running的均值和标准差在Tensorflow中通常会指定trainingFalse可以更好的反映模型在测试集上的真实效果。滑动平均的解释滑动平均即通过一个个batch历史的叠加最终趋向数据集整体分布的过程在测试集上进行推理时话哦的那个平均的参数也就是最终保存的参数。BN函数中常见参数momentunm动量参数控制滑动平均running statistics的更新速度。用于推理时估算全局均值和方差。默认值不同tf为0.99。可学习参数上面讲过具体可以问ai五、池化pooling池化的作用减少特征数量降维。最大值池化可以提取图片纹理均值池化可以保留背景特征在Tensorflow框架下利用keras来构建池化层使用的是tf.keras.layers.MaxPool2D函数和tf.keras.layers.AveragePooling2D函数具体方法如下tf.keras.layers.MaxPool2D( pool_size 池化核尺寸1, #仅在第一层有 strides 池化步长, padding SAMEorVALID, tf.keras.layers.AveragePooling2D( pool_size 池化核尺寸 strides 池化步长 padding SAMEorVALID, #是否全零填充 六、舍弃Dropout概念在神经网络的训练过程中将一部分神经元按照一定概率从神经网络中暂时舍弃使用时被舍弃的神经元恢复链接。在Tensorflow框架下利用tf.keras.layers.Dropout函数构建Dropout层参数为舍弃的概率大于0小于1。七、构建基本的卷积神经网络CNN核心思路在CNN中利用卷积核kernel提取特征后送入全连接网络卷积神经网路的主要模块卷积是特征提取器就是CBAPDmodel tf.keras.models.Sequential([ C Conv2D(filters6, kernel_size(5,5), paddingsame), #卷积层 B BatchNormalization(), #BN层 A Activation(relu), #激活层 P MaxPool2D(pool_size(2,2), strides2, paddingsame), #池化层 D Dropout(0,2), # dropout层 ])在此基础上可以总结出在Tensorflow框架下利用Keras来搭建神经网络的“八股”套路在主干基础上可以添加其他内容来完善神经网络的功能eg利用自己的图片和标签文件来自制数据集通过旋转、缩放、平移等操作对数据集进行数据增强保存模型文件进行断点续训提取训练后得到的模型参数以及准确率曲线实现可视化。构建神经网络的“八股”套路1、import引入 tensorflow及keras、numpy等所需模块。2、读取数据集从图片和标签文件中读取所需的数据集。3、搭建需要的网络结构当网络结构比较简单时可以利用keras模块中的tf.keras.Sequential来搭建顺序网络模型但当网络结构不再是简单的顺序结构而是有其他的特殊结构出现时便需利用class来定义自己的网络结构。前者方便但是后者在实际应用中更多。4、对搭建好的网络进行编译compile通常在这一步指定所采用的优化器如Adam、sgd、RMSdrop等以及损失函数如交叉熵函数、均方差函数等对这两者的选择往往对训练速度和效果有很大的影响/5、将数据输入到编译好的网络进行训练model.fit在这一步指定训练轮数epochs和batch_size等信息由于神经网络的参数量和计算量一般比较大训练时间较长特别硬件条件受限的情况下所以这一步通常会加入断点训练和模型参数保存等功能使得训练更方便防止程序异常而丢失数据。6、将神经网络模型的具体信息打印出来model.summary包括网络结构、网络各层参数等便于对网络进行浏览和检查。八、CNN经典网络这里对5个经典网络进行介绍1、LeNet1998年借鉴点共享卷积核减少网络参数LeNet即LeNet5如下图是LeNet5网络结构搭建流程以cifar10数据集为例A) 输入图像大小为32*32*3三通道彩色图像输入B进行卷积卷积核大小为5*5个数为6步长为1不进行全零填充C将卷积结果输入sigmoid激活函数非线性函数进行激活D进行最大池化池化核大小为2*2步长为2self.c1 Conv2D(filters6,kernel_size(5,5),paddingvalid,input_shape(32,32,3),activationsigmoid) #进行卷积 self.p1 MaxPool2D(pool_size(2,2),strides2) #最大池化E进行卷积卷积核大小为5*5个数为16步长为1不进行全零填充F将卷积结果输入sigmoid激活函数非线性函数进行激活G进行最大池化池化核大小为2*2步长为2self.c2 Conv2D(filters16,kernel_size(5,5),paddingvalid,activationsigmoid) #进行卷积 self.p2 MaxPool2D(pool_size(2,2),strides2) #最大池化H输入三层全连接网络进行10分类。self.flatten Flatten() self.f1 Dense(120,activationsigmoid) self.f2 Dense(84,activationsigmoid) self.f3 Dense(10,activationsoftmax)总体上看与如今一些主流CNN网络相比其结构可以说相当简单不过它成功地利用“卷积提取特征”到“全连接分类”的经典思路解决了手写数字识别。对神经网络研究的发展有重要意义。2、AlexNet2012年借鉴点激活函数使用Relu提升训练速度Dropout防止过拟合AlexNet的总体结构与LeNet5有相似之处但有一些重要的改进A有5层卷积、3层全连接组成输入图像尺寸为224*224*3网络规模远大于LeNet5B使用Relu激活函数C进行了舍弃Dropout操作以防止模型过拟合提升鲁棒性D增加一些训练技巧包括数据增强、学习率衰减、权重衰减L2正则化等。其网络结构如下搭建AlexNet模型需要对输入图像进行尺寸修改改为32*32*3并且将原来模型中的11*11、7*7、5*5等大尺寸卷积核均替换成3*3的小卷积核。下图是模型的搭建。紫色代表卷积部分卷积操作共进行了5次。红色代表全连接部分共三层该模型与结构类似的LeNet5相比AlexNet模型的参数量有了明显的提升卷积运算的层数也更多了有利于更好地提取特征Relu激活函数的使用加快了模型的训练速度Dropout提升了模型鲁棒性使得AlexNet的性能大大提升。3、VGGNet2014年借鉴点小卷积核减少参数的同时提升识别准确率网络结构规整适合并行加速。其ImageNet Top5错误率减小到了7.3%。该模型的最大改动在网络深度上由AlexNet的8层增加到了16层和19层更深的网络意味着更强的表达能力这得益于强大的运算能力的支持。另一个显著特点是仅使用了单一尺寸的3*3卷积核事实上3*3的小卷积核在很多卷积网络中都被大量使用这是由于感受野相同的情况下小卷积核堆积的效果要优于大卷积核取得了较好的效果 。VGGNet16和VGGNet19并没有本质上的区别只是网络深度不同前者16层13层卷积3层全连接后者19层16层卷积3层全连接。搭建VGGNet16模型需将输入特征尺寸改为32*32*3将VGG16模型分为六部分在每进行一次池化操作时特征图的边长缩小为1/2其余操作均未影响特征图尺寸A第一部分两次卷积64个3*3卷积核、BN、Relu激活函数、最大池化、Dropoutself.c1 Conv2D(filters64, kernel_size(3, 3), paddingsame) # 卷积层1 self.b1 BatchNormalization() # BN层1 self.a1 Activation(relu) # 激活层1 self.c2 Conv2D(filters64, kernel_size(3, 3), paddingsame, ) self.b2 BatchNormalization() # BN层1 self.a2 Activation(relu) # 激活层1 self.p1 MaxPool2D(pool_size(2, 2), strides2, paddingsame) self.d1 Dropout(0.2) # dropout层B第二部分两次卷积128个3*3卷积核、BN、Relu激活、最大池化、Dropoutself.c3 Conv2D(filters128, kernel_size(3, 3), paddingsame) self.b3 BatchNormalization() # BN层1 self.a3 Activation(relu) # 激活层1 self.c4 Conv2D(filters128, kernel_size(3, 3), paddingsame) self.b4 BatchNormalization() # BN层1 self.a4 Activation(relu) # 激活层1 self.p2 MaxPool2D(pool_size(2, 2), strides2, paddingsame) self.d2 Dropout(0.2) # dropout层C第三部分三次卷积256个3*3卷积核、BN、Relu激活、最大池化、Dropoutself.c5 Conv2D(filters256, kernel_size(3, 3), paddingsame) self.b5 BatchNormalization() # BN层1 self.a5 Activation(relu) # 激活层1 self.c6 Conv2D(filters256, kernel_size(3, 3), paddingsame) self.b6 BatchNormalization() # BN层1 self.a6 Activation(relu) # 激活层1 self.c7 Conv2D(filters256, kernel_size(3, 3), paddingsame) self.b7 BatchNormalization() self.a7 Activation(relu) self.p3 MaxPool2D(pool_size(2, 2), strides2, paddingsame) self.d3 Dropout(0.2)D第四部分三次卷积512个3*3卷积核、BN、Relu激活、最大池化、Dropoutself.c8 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b8 BatchNormalization() # BN层1 self.a8 Activation(relu) # 激活层1 self.c9 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b9 BatchNormalization() # BN层1 self.a9 Activation(relu) # 激活层1 self.c10 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b10 BatchNormalization() self.a10 Activation(relu) self.p4 MaxPool2D(pool_size(2, 2), strides2, paddingsame) self.d4 Dropout(0.2)E第五部分三次卷积512个3*3卷积核、BN、Relu激活、最大池化、Dropoutself.c11 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b11 BatchNormalization() # BN层1 self.a11 Activation(relu) # 激活层1 self.c12 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b12 BatchNormalization() # BN层1 self.a12 Activation(relu) # 激活层1 self.c13 Conv2D(filters512, kernel_size(3, 3), paddingsame) self.b13 BatchNormalization() self.a13 Activation(relu) self.p5 MaxPool2D(pool_size(2, 2), strides2, paddingsame) self.d5 Dropout(0.2)F第六部分全连接512个神经元、Dropout、全连接512个神经元、Dropout、全连接10个神经元self.flatten Flatten() self.f1 Dense(512, activationrelu) self.d6 Dropout(0.2) self.f2 Dense(512, activationrelu) self.d7 Dropout(0.2) self.f3 Dense(10, activationsoftmax)总的来看VGG的结构是相当规整的它继承了AlexNet中的Relu激活函数、Dropout操作等有效方法同样采用单一尺寸的3*3小卷积核形成规整的CBAPD结构这一经典结构在卷积神经网络中应用非常广。4、InceptionNet2015年借鉴点一层内使用不同尺寸的卷积核提升感知力通过padding实现输出特面积一致使用1*1卷积核改变输出特征channel数减少网络参数。InceptionNet即GoogLeNet旨在通过增加网络的宽度来提升网络的能力与VGGNet通过卷积层堆叠的方式纵向相比是横向的。显然InceptionNet的模型构建与之前的网络有所不同不再是简单纵向堆叠要理解这个的结构首先要理解它的基本单元根据以上图看到卷积部分是比较统一的C、B、A典型结构即卷积、BN、激活激活均采用Relu激活函数同时包含最大池化操作。构建InceptionNet模型可以将C、B、A结构封装在一起定义一个新的ConvBNRelu类一介绍代码量同时更便于阅读。class ConvBNRelu(Model): def __init__(self, ch, kernelsz3, strides1, paddingsame): super(ConvBNRelu, self).__init__() self.model tf.keras.models.Sequential([ Conv2D(ch, kernelsz, stridesstrides, paddingpadding), BatchNormalization(), Activation(relu) ]) def call(self, x): x self.model(x, trainingFalse) #在trainingFalse时BN通过整个训练集计算均值、方差去做批归一化trainingTrue时通过当前batch的均值、方差去做批归一化。推理时 trainingFalse效果好 return x参数ch代表特征图的通道数也即卷积核个数kernelsz代表卷积核尺寸strides代表卷积步长padding代表是否进行全零填充。完成上面步骤开始构建InceptionNet的基本单元利用class定义的方式定义一个新的InceptionBlk类。class InceptionBlk(Model): def __init__(self, ch, strides1): super(InceptionBlk, self).__init__() self.ch ch self.strides strides self.c1 ConvBNRelu(ch, kernelsz1, stridesstrides) self.c2_1 ConvBNRelu(ch, kernelsz1, stridesstrides) self.c2_2 ConvBNRelu(ch, kernelsz3, strides1) self.c3_1 ConvBNRelu(ch, kernelsz1, stridesstrides) self.c3_2 ConvBNRelu(ch, kernelsz5, strides1) self.p4_1 MaxPool2D(3, strides1, paddingsame) self.c4_2 ConvBNRelu(ch, kernelsz1, stridesstrides) def call(self, x): x1 self.c1(x) x2_1 self.c2_1(x) x2_2 self.c2_2(x2_1) x3_1 self.c3_1(x) x3_2 self.c3_2(x3_1) x4_1 self.p4_1(x) x4_2 self.c4_2(x4_1) # concat along axischannel x tf.concat([x1, x2_2, x3_2, x4_2], axis3) return x其中tf.concat函数将四个输出连接在一起这四个输出对应图中四列输出结合结构图和代码可以看出二者对应关系。并且可以看出该模型大量使用了1*1卷积核然而最原始的模型也不包含1*1卷积vs从最原始的模型设计可以看出最初的思想即通过不同尺寸的卷积层和池化层的横向组合卷积、池化后的尺寸相同通道可以相加来拓宽网络深度可以增加网络对尺寸的适应性。但是会有一个新问题所有卷积核都会在上一层的输出上直接做卷积运算会导致参数量和计算量过大特别是5*5的卷积核因此在做卷积运算前、最大池化后均加入了1*1的卷积层可以降低特征厚度一定程度上避免参数量过大的问题。那1*1的卷积运算是如何降低特征厚度的呢以5*5的卷积运算为例以下代码是以cifar10数据集为例class Inception10(Model): def __init__(self, num_blocks, num_classes, init_ch16, **kwargs): super(Inception10, self).__init__(**kwargs) self.in_channels init_ch # 初始通道数即基本单元的初始卷积核个数 self.out_channels init_ch # 当前阶段的输出通道数会在循环中动态翻倍 self.num_blocks num_blocks # 网络包含的“大阶段”个数每个block包含两个基本单元,每经过一个block特征图尺寸变为1/2通道数变为2倍 self.init_ch init_ch # 初始通道数保存以备后用 # ---- 第 1 层初始卷积块 ---- # ConvBNRelu 是一个自定义层组合了Conv2D BatchNormalization ReLU # 作用将输入图像如 32×32×3映射为 32×32×init_ch通道数16 # 该层不进行下采样默认 strides1保持空间尺寸不变 self.c1 ConvBNRelu(init_ch) # ---- 核心部分构建多个 Inception 块序列 ---- self.blocks tf.keras.models.Sequential() # 外循环遍历 num_blocks 个阶段Stage for block_id in range(num_blocks): # 每个阶段固定包含 2 个 Inception 块 for layer_id in range(2): # 第一个块layer_id 0负责下采样步长 strides2 # 第二个块layer_id 1保持尺寸步长 strides1 if layer_id 0: block InceptionBlk(self.out_channels, strides2) else: block InceptionBlk(self.out_channels, strides1) # 将构建好的块添加到 Sequential 容器中 self.blocks.add(block) # 每个阶段结束后将输出通道数翻倍这是经典设计空间减半通道加倍 # 下一阶段的所有 Inception 块将使用这个新的通道数作为输出通道 self.out_channels * 2 # ---- 分类头 ---- # 全局平均池化将特征图如 4×4×128压缩为 1×1×128 # 相比 Flatten极大减少参数量有效防止过拟合 self.p1 GlobalAveragePooling2D() # 全连接层Dense将 128 维特征映射到 num_classes 个类别 # softmax 激活函数将输出转换为概率分布所有类别概率和为 1 self.f1 Dense(num_classes, activationsoftmax) # ---- 前向传播推理/训练时的数据流 ---- def call(self, x): x self.c1(x) # 输入 → 初始卷积块 → 形状: [batch, H, W, init_ch] x self.blocks(x) # 依次通过所有 Inception 块 → 形状逐阶段变化 x self.p1(x) # 全局池化 → 形状: [batch, 1, 1, out_ch_final] y self.f1(x) # 全连接分类 → 形状: [batch, num_classes] return y全局平均池化用GlobalAveragePooling2D函数实现相比于平均池化在特征图上以窗口的形式滑动取窗口内的平均值为采样值全局平均池化不再以窗口滑动的形式取均值而是直接对特征图取均值即每个特征图输出一个值。通过这种方式每个特征图都与分类概率直接联系起来这替代了全连接层的功能并且不产生额外的训练参数减少了过拟合的可能但使用后会导致网络收敛速度变慢。总体看该模型采用了多尺寸卷积再聚合的方式拓宽网络结构并通过1*1的卷积运算来减小参数量但是问题是当网络深度不断增加时训练会十分困难甚至无法收敛。5、ResNet借鉴点层间残差跳跃引入前方信息减少梯度消失使神经网络训练数百层成为可能。