YOLOv4配置文件深度解析:从参数原理到实战调优
1. 项目概述从配置文件开始真正理解YOLOv4如果你刚接触YOLOv4或者从其他框架比如PyTorch版的YOLO转过来面对Darknet框架下那个密密麻麻的yolov4.cfg文件是不是有点头皮发麻一堆[net]、[convolutional]、[yolo]还有各种看不懂的数字。网上的教程大多直接给一个修改好的配置文件让你用却很少告诉你这些参数到底是什么意思为什么要这么设置。这就像给你一辆顶级跑车却只教你怎么踩油门和刹车至于引擎怎么工作、悬挂如何调校一概不提。结果就是一旦遇到问题比如检测框不准、训练时显存爆炸你根本不知道从哪里下手调试。这份笔记就是来解决这个问题的。我们不跑训练也不做推理就静下心来一行一行地啃yolov4.cfg这个配置文件。我的目标很简单让你看完之后不仅能看懂每一行配置的含义更能理解设计者背后的意图。当你想修改网络结构、适配自己的数据集、或者进行模型剪枝和优化时你能清楚地知道动哪里、怎么动、会有什么后果。这对于想深入计算机视觉特别是目标检测领域的朋友来说是必不可少的一课。无论你是学生、研究员还是工程师吃透这个配置文件都是你从“会用工具”到“理解工具”的关键一步。2. 配置文件整体结构与设计哲学2.1 为什么是.cfg文件Darknet的模块化设计Darknet是一个用C语言写的轻量级深度学习框架它的一个核心设计思想就是高度可配置化。*.cfg文件就是这个思想的体现。它采用了一种类似“乐高积木”的声明式语法来描述整个神经网络的结构。每一个[xxx]块比如[convolutional],[route],[yolo]都代表一个特定的网络层Layer。框架在启动时会读取这个配置文件然后根据里面的描述动态地创建和组装这些层最终形成完整的计算图。这种设计有几个巨大的优势无需重新编译修改网络结构、调整超参数只需要编辑文本格式的.cfg文件然后重新运行程序即可。这比修改C/Python源代码然后重新编译要快捷、安全得多。结构清晰配置文件将网络结构以文本形式固化下来一目了然便于版本管理和分享。灵活性高通过组合不同的层块可以轻松实现各种复杂的网络拓扑如残差连接Residual、特征金字塔FPN、路径聚合网络PANet等这些在YOLOv4中都有体现。yolov4.cfg文件就是遵循这种范式完整定义了从输入图像到最终输出边界框的整个流程。它的结构大致可以分为三大部分网络全局参数部分([net]块)定义训练/测试模式、输入尺寸、优化器参数等影响整个网络的超参数。网络主体结构部分由数十个连续的层块如[convolutional],[shortcut],[route]等构成描述了特征提取和融合的详细路径。检测头部分([yolo]块)定义如何将网络输出的特征图解码成最终的检测框包括锚框anchors配置、损失函数计算等。2.2 YOLOv4.cfg 的宏观脉络CSPDarknet53 SPP PANet YOLOv3 Head在深入每个参数之前我们需要对YOLOv4的整体架构有个俯瞰式的认识。它不是一个全新的发明而是一个精妙的“组装大师”作品集成了当时多种有效的技术骨干网络Backbone: CSPDarknet53这是YOLOv4的特征提取器基于Darknet53改进而来。核心是引入了CSPNetCross Stage Partial Network结构。简单来说CSP结构将特征图分成两部分一部分进行复杂的卷积操作另一部分直接短路shortcut连接最后再将两部分合并。这样做的好处是在保持甚至提升精度的同时大幅减少了计算量并缓解了梯度消失问题。在cfg文件中你会看到大量成对出现的[convolutional]和[shortcut]层它们共同构成了CSP模块。颈部网络Neck: SPP PANet颈部网络负责对骨干网络提取的特征进行再处理和融合以聚合不同尺度的上下文信息。SPPSpatial Pyramid Pooling在骨干网络末端SPP层通过三种不同尺度的最大池化size1,5,9,13将任意尺寸的特征图固定成相同长度的特征向量从而显著增加了感受野有助于检测不同大小的物体。在cfg中对应[maxpool]层的特殊堆叠。PANetPath Aggregation Network这是YOLOv4实现多尺度检测的关键。它包含一个自底向上的特征金字塔类似FPN和一个自顶向下的增强路径。简单说它不仅将深层的语义信息传递到浅层自上而下还将浅层的精确定位信息传递到深层自下而上实现了更好的特征融合。在cfg中这是通过一系列[convolutional],[upsample]和复杂的[route]层来实现的[route]层负责从前面某一层“取”特征图。检测头Head: YOLOv3 HeadYOLOv4的检测头基本沿用了YOLOv3的设计是一种“多尺度预测”头。它在三个不同尺度的特征图上进行预测大尺度特征图检测小物体小尺度特征图检测大物体。每个[yolo]层就对应一个尺度的检测头。它负责将网络输出的张量包含框坐标、置信度、类别概率解码成我们最终看到的边界框。理解了这三部分再看cfg文件你就会发现它不再是杂乱无章的代码而是一张有清晰逻辑的电路图先经过一系列CSP模块Backbone提炼特征然后进入SPP池化扩大感受野接着通过PANetNeck进行多层次的特征搅拌与融合最后交给三个YOLO头Head输出预测结果。3. [net] 部分网络全局超参数详解[net]块通常位于cfg文件的开头它定义的参数对整个网络的训练和测试行为都有全局性影响。我们逐行解析最常见的配置[net] # 测试/训练配置 batch64 subdivisions16 width608 height608 channels3 momentum0.949 decay0.0005 angle0 saturation 1.5 exposure 1.5 hue.1 learning_rate0.0013 burn_in1000 max_batches500500 policysteps steps400000,450000 scales.1,.1batch64与subdivisions16 这是Darknet里最容易混淆的一对参数。batch指的是总批量大小而subdivisions指的是子分批数量。实际每次前向-反向传播的批量大小 batch/subdivisions 64 / 16 4。为什么要这么做因为你的显卡显存VRAM可能不足以一次性加载64张图片进行计算。通过subdivisionsDarknet将一个大批量拆分成16个小份每次只处理4张图累积16次的梯度后再进行一次权重更新。这相当于实现了梯度累积让你在有限显存下也能使用较大的batch而较大的batch通常能使训练更稳定梯度估计更准确。注意batch和subdivisions的值必须是整数倍关系并且调整它们会直接影响训练的动态。增大batch同时可能需调整learning_rate可能提升效果但需要更多显存。subdivisions越大瞬时显存占用越小但权重更新频率变低。width608,height608,channels3 定义了网络输入图像的尺寸和通道数。YOLOv4的默认输入是608x608的RGB图像。这个尺寸必须是32的倍数因为网络中有5次步长为2的下采样2^532最终特征图会缩小32倍。如果你修改这个尺寸比如改成416x416那么网络最终输出的特征图尺寸也会同比变化你需要重新计算对应的[yolo]层里的锚框anchors尺寸否则检测性能会严重下降。momentum0.949与decay0.0005momentum动量参数用于随机梯度下降SGD优化器。它模拟了物理中的动量让参数更新方向不仅依赖于当前梯度还保留一部分之前更新的方向有助于加速收敛并抑制震荡。0.949是一个较高的值意味着历史方向权重很大。decay权重衰减系数即L2正则化项。它通过对大的权重值施加惩罚来防止模型过拟合。0.0005是深度学习中的常用值。数据增强参数angle,saturation,exposure,hue Darknet在加载数据时会在线on-the-fly进行随机数据增强以提升模型鲁棒性。saturation,exposure分别控制饱和度和曝光度的随机缩放因子范围。例如1.5表示在[1/1.5, 1.5]之间随机缩放。hue色调随机偏移的范围。angle随机旋转的角度范围此处为0即不旋转。实操心得对于小数据集强烈建议使用这些数据增强。但对于已经非常多样化的数据集或追求极限精度时可以适当减弱增强强度如将系数调至1.1左右或关闭某些增强设为0因为过强的增强可能引入太多噪声干扰模型学习本质特征。学习率调度参数learning_rate0.0013初始学习率。这是一个相对较大的值因为YOLOv4模型容量大。policysteps学习率调整策略为“阶梯下降”。steps400000,450000在第40万和45万次迭代时触发学习率下降。scales.1,.1每次触发时学习率乘以0.1。因此学习率变化为0.0013 - (第40万次迭代) 0.00013 - (第45万次迭代) 0.000013。burn_in1000在前1000次迭代中使用一种“热身”策略学习率从0.0013 * (current_iter / burn_in)^4逐渐上升到设定的初始学习率这有助于训练初期稳定。max_batches500500最大训练迭代次数。4. 核心层类型参数解析一卷积、短路与路由4.1 [convolutional] 层网络的基本构建块卷积层是CNN的基石。在cfg中一个典型的[convolutional]层如下[convolutional] batch_normalize1 filters32 size3 stride1 pad1 activationmishbatch_normalize1是否使用批量归一化Batch Normalization, BN。在YOLOv4中几乎每一个卷积层后面都跟了BN层1。BN通过对每一批数据进行归一化可以加速训练、允许使用更高的学习率并有一定的正则化效果。如果设为0则不加BN。filters32该卷积层输出特征图的通道数也就是卷积核的数量。size3卷积核的尺寸通常是3x3。有时也会看到1x1的卷积size1用于降维或升维。stride1卷积步长。步长为1时输出特征图尺寸不变步长为2时尺寸减半实现下采样。pad1填充padding。当size3且stride1时设置pad1可以保证输出特征图的空间尺寸宽高与输入相同。这里有个关键公式pad (size - 1) / 2对于size3的卷积核pad1正好满足。activationmish激活函数。YOLOv4用Mish激活函数取代了之前常用的Leaky ReLU。Mish函数是x * tanh(softplus(x))被证明在深度网络中具有更好的平滑性和精度。这也是YOLOv4的一个小改进点。4.2 [shortcut] 层实现残差连接的核心Shortcut层是构建残差网络ResNet和CSPNet的关键它实现了恒等映射identity mapping。[shortcut] from-3 activationlinearfrom-3这是最重要的参数。它指定了要与当前层输入进行相加element-wise add的来源层。“-3”表示向前回溯3层。例如当前层是第50层from-3就表示取第47层的输出与第50层的输入相加。activationlinearShortcut操作本身是加法通常后面会接一个激活函数在下一层指定。这里设为linear表示短路连接本身不进行非线性变换。工作原理假设当前层的输入是xfrom层输出是F(x)可能经过了若干层变换那么shortcut层的输出就是x F(x)。这解决了深层网络梯度消失的问题让网络可以轻松地学习一个残差F(x)而不是直接学习复杂的底层映射。4.3 [route] 层特征图的“接线员”Route层是Darknet实现复杂网络拓扑如FPN, PANet的“瑞士军刀”它负责拼接concatenate或传递来自前面各层的特征图。[route] layers-1, -3, -5, -7layers-1, -3, -5, -7指定要路由的层。数字规则与shortcut层类似负数表示向前回溯。-1就是上一层的输出。操作Route层会将这些指定层的输出在通道维度channel dimension上进行拼接。例如如果层-1输出是256通道层-3输出是128通道那么route层的输出就是256128384通道。高级用法单个值layers-4表示直接取回溯第4层的输出相当于“跳连”不改变特征图。多个值layers-1, 61这里61是绝对索引从0开始表示取上一层的输出和第61层的输出进行拼接。这在PANet结构中非常常见用于将深层语义特征与浅层定位特征融合。注意事项被拼接的各层特征图其宽度和高度必须完全相同否则无法拼接。Darknet会在构建网络时检查这一点。这要求你在设计网络时要仔细规划下采样和上采样的位置。5. 核心层类型参数解析二上采样、池化与YOLO层5.1 [upsample] 层实现特征图放大为了将深层的、语义信息丰富的特征图与浅层的、细节丰富的特征图进行融合如在PANet中需要将深层特征图的空间尺寸放大。这就是上采样层的作用。[upsample] stride2stride2上采样倍数。stride2表示将特征图的宽度和高度都放大为原来的2倍。实现方式在Darknet中[upsample]层默认使用最近邻插值Nearest Neighbor Interpolation。这是一种简单快速的上采样方法对于特征图来说通常效果不错。它没有可学习的参数。5.2 [maxpool] 层与SPP结构标准的池化层用于下采样但在YOLOv4中[maxpool]层被巧妙地用来构建SPP空间金字塔池化模块。# SPP结构中的一部分 [maxpool] stride1 size5 [maxpool] stride1 size9 [maxpool] stride1 size13在SPP模块中你会看到一连串stride1的[maxpool]层。stride1意味着池化窗口在移动时每次只移动1个像素因此输出特征图的尺寸不会改变。size5,9,13定义了不同尺度的池化窗口。这些池化操作是并行的。它们对同一个输入特征图分别用5x5, 9x9, 13x13的窗口进行最大池化。由于stride1且使用了适当的填充padding在Darknet中根据公式自动计算输出的特征图尺寸与输入相同但每个池化操作都聚合了不同大小区域的信息。SPP模块的工作流程输入特征图先经过一个1x1卷积降维然后同时送入上述三个不同尺度的最大池化层以及一个“直连”路径可以看作size1的池化得到四个尺寸相同但感受野不同的特征图最后在通道维度上将它们拼接concat起来。这样就得到了一个融合了多尺度上下文信息的特征图极大地增强了模型对不同尺寸物体的检测能力。5.3 [yolo] 层检测结果的解码器[yolo]层是Darknet框架中目标检测的核心输出层。它不进行实质的计算而是定义了如何将前面卷积层输出的张量解码成我们熟悉的边界框、置信度和类别概率。[yolo] mask 6,7,8 anchors 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401 classes80 num9 jitter.3 ignore_thresh .7 truth_thresh 1 random1 scale_x_y 1.2 iou_thresh0.213 cls_normalizer1.0 iou_normalizer0.07 iou_lossciou nms_kindgreedynms beta_nms0.6这个参数非常多我们分组解读1. 锚框Anchor相关参数anchors 12,16, 19,36, 40,28, ...这是一长串偶数个数字每两个数字一组(w, h)表示预设锚框的宽度和高度相对于网络输入图像608x608的尺寸。例如(12,16)表示一个宽12像素、高16像素的锚框。num9锚框的总数量这里是9组。mask 6,7,8这是关键它指定当前[yolo]层使用哪几个锚框。mask中的索引0-based对应anchors列表中的位置。6,7,8表示使用第7、8、9组锚框即列表中的最后三组。YOLOv4有三个[yolo]层分别负责大、中、小三种尺度的预测每个层分配3个最匹配该尺度物体大小的锚框。为什么这么分配通常anchors列表是按尺寸从小到大排列的。前面的[yolo]层对应大特征图检测小物体使用小的锚框如索引0,1,2中间的用中等锚框最后的[yolo]层对应小特征图检测大物体使用大的锚框如索引6,7,8。这需要通过对你的数据集进行聚类分析如使用Darknet提供的darknet detector calc_anchors命令来确定。2. 检测头结构参数classes80要检测的物体类别数COCO数据集是80类。输出张量维度这是理解[yolo]层的基础。假设输入到[yolo]层的特征图尺寸是[N, C, H, W]其中H, W是特征图的高和宽。那么C (classes 5) * len(mask)。以classes80,mask包含3个锚框为例C (805)*3 255。每个锚框位置预测(805)85个值4个框坐标偏移量tx, ty, tw, th、1个物体置信度to、80个类别概率。3. 训练损失相关参数至关重要ignore_thresh .7忽略阈值。在计算置信度损失时如果某个预测框与所有真实框的最大IoU交并比大于0.7但又没有被分配为正样本即不是与真实框IoU最大的那个预测框则忽略这个预测框的置信度损失。这可以避免模型对“差不多对”的冗余预测进行过度惩罚。truth_thresh 1通常设为1参与正样本匹配的阈值。iou_thresh0.213用于非极大值抑制NMS的IoU阈值。推理时IoU大于此阈值的框会被视为重叠框而抑制掉。iou_lossciou边界框回归的损失函数。YOLOv4使用了CIoU Loss它考虑了重叠面积、中心点距离和长宽比比传统的IoU Loss或GIoU Loss收敛更好精度更高。jitter.3与random1这是YOLOv3/v4一个独特的数据增强技巧——随机缩放图像尺寸。jitter定义了随机缩放的比例范围如0.3表示在[1-0.3, 10.3]即[0.7, 1.3]倍之间随机缩放输入图像尺寸。random1启用此功能。这相当于在训练时让模型适应不同尺度的输入提升了尺度不变性。scale_x_y 1.2YOLOv4的一个改进用于调整预测框中心点的解码公式使中心点偏移范围不再局限于当前网格内可以略微超出增加了预测的灵活性。4. 损失权重与NMS参数cls_normalizer1.0,iou_normalizer0.07分类损失和IoU损失的归一化系数权重。iou_normalizer通常设得较小因为框坐标的数值范围0~1比类别得分经过sigmoid要小需要平衡不同损失项的量级。nms_kindgreedynmsNMS类型默认是贪心NMS。beta_nms0.6某些NMS变体如DIoU-NMS的参数这里未使用。6. 网络结构逐段解析与设计意图现在我们带着对各个层参数的理解来走读一段典型的YOLOv4 cfg文件看看这些“乐高积木”是如何搭建起这座大厦的。我们以CSPDarknet53中的一个CSP块为例# 下采样卷积 [convolutional] batch_normalize1 filters128 size3 stride2 pad1 activationmish # CSP Block 开始 [convolutional] batch_normalize1 filters64 size1 stride1 pad1 activationmish [convolutional] batch_normalize1 filters64 size3 stride1 pad1 activationmish [shortcut] from-3 activationlinear # 继续几个卷积... [convolutional] batch_normalize1 filters64 size1 stride1 pad1 activationmish [route] layers-1,-7 # 将当前层与CSP块开始前的那个卷积层输出拼接 [convolutional] batch_normalize1 filters128 size1 stride1 pad1 activationmish # CSP Block 结束设计意图解析首先一个stride2的3x3卷积将特征图尺寸减半同时通道数翻倍例如从64到128实现下采样和升维。进入CSP块先通过一个1x1卷积将通道数减半128-64这是CSP结构中的“分割”操作的一部分。随后是主要的卷积处理分支可能包含多个残差单元即[convolutional][shortcut]的组合对一半的特征进行深度处理。关键点[route] layers-1,-7。-1是主分支处理完的输出-7是回溯到CSP块开始前、那个1x1卷积之前的特征即未经处理的那一半特征。这一步将“处理过的特征”和“未处理的原始特征”在通道维度上拼接起来。最后再用一个1x1卷积调整通道数。这样做的妙处既让一部分特征通过了复杂的变换以提取高级特征又保留了一部分原始特征以确保梯度流畅通。同时由于只有一半的特征参与了复杂计算整体计算量FLOPs和内存占用都显著降低这是CSPNet的核心思想。再看一个PANet中的特征融合例子# 深层特征图尺寸小通道数多例如76x76 ... [convolutional] # 降维 filters128 size1 ... [upsample] # 上采样放大到和浅层特征图一样大例如152x152 stride2 [route] # 与来自骨干网络浅层的特征图拼接 layers-1, 36 # 假设第36层是某个浅层特征 [convolutional] # 混合后的卷积处理 filters128 size3 ...设计意图解析对深层特征进行1x1卷积降维减少通道数以便与浅层特征融合。通过[upsample]层将深层特征的空间尺寸放大2倍使其与要融合的浅层特征尺寸匹配。[route]层将上采样后的深层特征layers-1与来自前面第36层的浅层特征进行拼接。浅层特征富含细节和位置信息深层特征富含语义信息拼接后实现了信息互补。最后用3x3卷积对融合后的特征进行混合处理。这就是PANet中“自顶向下”和“自底向上”路径聚合的直观体现。7. 根据需求修改配置文件的实战指南理解了所有参数含义后我们就可以动手修改cfg文件来适配自己的任务了。以下是几个常见场景7.1 适配自定义数据集这是最常见的需求。假设你自己的数据集只有3个类别。修改[yolo]层中的classes参数 在文件中搜索classes你会找到三处对应三个检测头。将所有的classes80改为classes3。修改每个[yolo]层之前的卷积层filters参数 这是最容易出错的一步每个[yolo]层正上方紧邻的[convolutional]层其filters数决定了输出通道必须根据公式调整。公式filters (classes 5) * len(mask)以第一个[yolo]层mask0,1,2len(mask)3为例classes3则filters (35)*3 24。 找到这个卷积层通常在其上方不远处有size1的卷积将其filters改为计算出的值24。对三个[yolo]层都进行此操作。重新计算锚框Anchors强烈建议 YOLO默认的锚框是针对COCO数据集聚类得到的对于你的数据集可能不是最优的。使用Darknet工具准备好你的数据集.txt标注文件每行class x_center y_center width height坐标是相对图像宽高的比例运行./darknet detector calc_anchors your_data.data -num_of_clusters 9 -width 608 -height 608它会输出9组新的锚框尺寸。用这9组数字替换cfg文件中anchors 后面的所有数字。注意mask的分配逻辑通常不变前3个小锚框给第一个yolo层中间3个给第二个最后3个大锚框给第三个。如果你聚类的锚框顺序变了可能需要调整mask的值。7.2 在资源受限环境下训练如显存不足调整batch和subdivisions 这是最直接的方法。如果你的显存不足导致Out of memory错误首先增大subdivisions。例如将subdivisions16改为subdivisions32或64。这会让每次迭代处理的图片数减少从而降低瞬时显存占用。同时为了保证等效批量大小你可能需要同比增大batch例如batch64-subdivisions32时等效批量是2如果想保持等效批量4则需设batch128。但增大batch会消耗更多显存所以通常的做法是只增大subdivisions保持batch不变或减小接受等效批量变小的设定并可能需要微调学习率。减小输入图像尺寸width和height 将width608, height608改为width416, height416或320。这会大幅减少计算量和显存占用约与尺寸的平方成正比。但是你必须重新计算锚框因为锚框尺寸是相对于输入图像608x608定义的。改为416后原有的锚框尺寸就太大了。你需要用上面提到的方法基于416的输入尺寸重新聚类锚框或者简单地将原锚框数值按比例缩放416/608 ≈ 0.684。简化网络结构高级操作 可以通过减少某些卷积层的filters数量来降低模型宽度或者删除某些CSP块来降低模型深度。但这会改变模型容量可能影响精度需要谨慎尝试和评估。7.3 冻结骨干网络进行微调Transfer Learning如果你想在一个小数据集上微调YOLOv4冻结骨干网络CSPDarknet53可以防止预训练的特征被破坏加速收敛并可能提升小数据集上的性能。在.cfg文件中指定 在你想冻结的层之前和之后加上[stopbackward]层这是一个非官方的技巧有些Darknet版本支持。更通用的方法是在训练命令中指定。使用训练命令参数推荐 在启动训练时使用-dont_show -map等参数的基础上加上-freeze 137。这里的137是你想冻结的层数。例如CSPDarknet53骨干网络大概在前137层具体需根据你的cfg文件数一下冻结它们意味着在反向传播时这些层的权重不会更新。./darknet detector train your_data.data yolov4-custom.cfg yolov4.conv.137 -freeze 137yolov4.conv.137是Darknet官方提供的、只包含骨干网络权重的预训练文件。7.4 常见错误排查与调试技巧错误CUDA Error: out of memory原因显存不足。排查立即减小batch或增大subdivisions。也可以尝试减小输入图像尺寸。使用nvidia-smi命令监控显存使用情况。错误Error in load_image: Cannot load image ...或Cannot find label file ...原因数据路径配置错误。排查检查你的.data文件中的train和valid路径是否正确以及图片和标签文件是否存在于对应路径且命名是否匹配除了后缀。训练时Loss为nan原因1学习率learning_rate太高。这是最常见原因。解决大幅降低学习率尝试0.0001或0.00001。原因2数据标注有问题。例如边界框的坐标超出了[0,1]的范围或者宽高为0。解决仔细检查你的标注文件确保格式为归一化后的中心坐标和宽高。原因3锚框尺寸与输入图像尺寸严重不匹配。解决重新计算针对你数据集和输入尺寸的锚框。训练后检测效果很差框乱飞原因1没有修改[yolo]层前卷积的filters数。这是新手最高频的错误解决严格按照filters (classes 5) * len(mask)的公式修改。原因2数据集类别不平衡或标注质量差。解决检查数据集进行清洗和增强。原因3训练轮数不够或学习率策略不合适。解决增加max_batches并确保steps和scales设置合理让学习率能充分下降。调试网络结构是否正确修改使用Darknet的test模式加载你的cfg文件但不加载权重它会打印出网络每一层的详细信息包括输出尺寸。这是检查网络结构是否被你意外改崩的最快方法。./darknet detector test your_data.data your_modified.cfg观察打印出的各层输出尺寸是否符合逻辑特别是[route]层拼接时各输入层的尺寸是否一致。