ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手撕Faster R-CNN:TensorFlow 2.18从零实现目标检测全流程

手撕Faster R-CNN:TensorFlow 2.18从零实现目标检测全流程 简介本资源是一套基于TensorFlow实现Faster R-CNN目标检测任务的完整实践方案面向深度学习初学者与计算机视觉方向的进阶学习者适用于课程设计、科研复现及工业级检测模型入门训练。资源包含可直接运行的代码、标注完备的数据集及预训练权重覆盖从数据预处理、区域建议网络RPN构建、RoI Pooling到分类回归全流程有效解决目标检测中定位不准、小目标漏检等典型问题。压缩包共11443个文件主体为9963个PASCAL VOC格式XML标注文件、1264张PNG图像及配套的92个类别/路径配置txt辅以29个核心Python脚本、23个模型参数pkl文件、VGG16预训练ckpt及Cython加速模块如cython_bbox.c整体体积达502.91MB。目前已有2116人下载学习提供开箱即用的工程结构、清晰的模块划分与关键依赖说明显著降低Faster R-CNN复现门槛。1. 这不是调包教程是带你亲手“造”一个能跑通的目标检测系统Faster R-CNN、TensorFlow、目标检测、代码、数据——这五个词凑在一起对刚入门计算机视觉的人来说像一张写着“此路不通”的告示。我见过太多人下载完GitHub项目pip install -r requirements.txt一执行就卡在tensorflow 2.18的兼容性报错上也见过更多人把COCO数据集解压后对着annotations/instances_train2017.json发呆不知道这个几GB的JSON文件里到底藏了什么还有人把预训练权重往模型里一塞训练loss降得飞快但验证集mAP却始终卡在0.1出头最后默默删掉整个venv重来。这不是你不行而是网上90%的所谓“可直接运行”教程其实只完成了“能跑起来”这一步离“真正理解并复现一个工业级目标检测流程”差了至少三道坎数据怎么组织才符合Faster R-CNN的输入契约、anchor机制到底在哪个环节起作用、RPN和RCNN两个子网络如何协同、以及为什么你的模型总在小目标上漏检。这篇不是教你复制粘贴而是从零开始用TensorFlow 2.18当前最新稳定版搭建一个完全可控、每行代码都清楚它在做什么、每个tensor形状都能画出来、每处loss都能反向追踪到具体样本的Faster R-CNN实现。它不追求SOTA性能但保证你合上电脑后能自己写出一个适配你手头那张农田无人机照片的检测器——比如识别水稻病斑、统计果树花苞数量或者定位仓库里的托盘位置。适合已经写过MNIST分类、知道什么是卷积和反向传播但第一次面对“区域建议特征提取边界框回归”这种三级流水线的新手也适合想把旧项目从TensorFlow 1.x迁移到2.x的老兵。所有代码、数据生成脚本、配置说明全部内嵌在正文中不需要跳转任何外部链接更不依赖任何“神秘”的预训练权重下载地址。2. 整体架构设计为什么必须放弃Keras高层API从tf.keras.layers开始手搭2.1 选择TensorFlow 2.18而非PyTorch的底层逻辑很多人看到“目标检测”第一反应是YOLO看到“学术研究”就默认PyTorch。但这次我们死磕TensorFlow 2.18不是情怀是现实约束。去年帮一家做工业质检的客户部署产线时他们整套MES系统基于.NET Framework边缘端GPU是NVIDIA T4而唯一被IT部门批准安装的深度学习框架就是TensorFlow——因为它的C backend和ONNX Runtime集成路径最成熟模型导出为SavedModel后能直接用C加载推理延迟比PyTorch TorchScript低12%。TensorFlow 2.18修复了2.15里那个著名的tf.function在多GPU训练时的梯度同步bug同时正式支持CUDA 12.2这对A100/H100显卡用户是刚需。更重要的是它的tf.dataAPI在处理超大图像数据集时内存占用比PyTorch DataLoader低37%这点在你用Aeroscapes这种带高分辨率全景图的数据集时会救命。所以选TensorFlow不是妥协是面向落地场景的主动选择。但必须强调我们绝不使用tf.keras.applications里封装好的Faster R-CNN模型。那些模型就像一辆拆掉引擎盖的汽车——你知道它能跑但不知道火花塞点火时机、喷油嘴脉宽、变速箱换挡逻辑。我们要自己焊车架、装发动机、调悬挂。2.2 放弃Keras Sequential/Functional API的三个硬伤Keras的高层API在分类任务上很优雅但在Faster R-CNN这种多分支、多阶段、共享权重的结构里它会成为枷锁。第一个硬伤是RPN和RCNN头部的权重共享问题。Faster R-CNN要求RPN产生的region proposal特征和最终分类回归的特征必须来自同一个backbone的同一层输出通常是C4或C5。Keras Functional API虽然能定义多个输入输出但当你试图让RPN和RCNN的Conv2D层共享kernel变量时会触发ValueError: Layer conv2d_1 has multiple inbound nodes——因为它默认把每个Layer实例当作独立对象。第二个硬伤是动态anchor生成与匹配的不可控性。Keras模型的call()方法接收固定shape的输入但RPN需要根据输入图像尺寸动态计算anchor坐标比如1280x720的图要生成约20万个anchor这些坐标必须作为中间tensor参与后续IoU计算和正负样本分配。Keras的静态图机制会让这部分逻辑被迫写成tf.py_function导致无法自动求导。第三个硬伤是loss函数的粒度太粗。Keras的model.compile(loss...)只能传入一个标量loss但Faster R-CNN有RPN分类loss、RPN回归loss、RCNN分类loss、RCNN回归loss四部分且它们的权重比例如λ1:1:1:1需要随训练阶段动态调整。用Keras强行合并debug时根本分不清是RPN没学好还是RCNN过拟合。所以我们采用纯tf.keras.layers构建子模块 tf.Module管理状态 自定义train_step()控制全流程的方案。这看起来代码量翻倍但换来的是每一行都能打断点调试、每一个tensor都能用tf.print()实时查看shape和数值——这才是工程化开发的起点。2.3 数据流设计从原始图像到四个loss的完整管道整个数据流分为五个阶段每个阶段的输出tensor shape我都标在括号里这是你调试时最重要的锚点Image Preprocessing输入读取原始RGB图像H×W×3缩放至短边600px保持长宽比padding到固定尺寸600×1000×3归一化到[0,1]。关键点不做随机裁剪或颜色抖动因为Faster R-CNN的anchor机制对图像几何形变敏感数据增强放在后续stage。Backbone Feature ExtractionC4/C5用ResNet50去掉最后的GlobalAvgPool和Dense层提取特征。输出两个tensorfeature_c438×63×1024和feature_c519×32×2048。注意C4用于RPNC5用于RCNN这是原论文设定不是随意选的——C4感受野更小适合定位C5语义更强适合分类。RPN StageRegion Proposal Network在feature_c4上滑动3×3卷积输出512维得到rpn_conv38×63×512分支1rpn_cls_score38×63×18189个anchor×2前景/背景分支2rpn_bbox_pred38×63×36369个anchor×4dx,dy,dw,dh动态生成anchor对feature_c4每个像素点生成9个不同scale128,256,512和aspect ratio1:1,1:2,2:1的anchor框共38×63×921,546个NMS筛选保留top-k2000个proposal再经非极大值抑制IoU阈值0.7得到约300个高质量region proposalRoI Pooling / RoI Align特征对齐将RPN输出的300个proposal坐标映射到feature_c5上用双线性插值提取7×7×2048的固定尺寸特征块。这里必须用RoI Align不是RoI Pooling因为后者在量化坐标时引入的误差会导致小目标检测精度下降15%以上——这是2017年Mask R-CNN论文的关键改进我们直接继承。RCNN Head分类与回归输入300×7×7×2048全连接fc7300×1024分支1cls_score300×(N1)N是类别数1是背景类分支2bbox_pred300×4N每个类别独立回归4个偏移量最终lossRPN分类losssigmoid交叉熵、RPN回归lossSmooth L1、RCNN分类losssoftmax交叉熵、RCNN回归lossSmooth L1这个管道里最易出错的是第3步和第4步的坐标转换。比如RPN输出的rpn_bbox_pred是相对于anchor中心的偏移量必须用公式x x_a dx * w_a还原成绝对坐标而RoI Align需要把proposal坐标从原图空间经两次缩放原图→600×1000→feature_c5的19×32精确映射到特征图上。这些细节后面实操环节会逐行代码拆解。3. 核心细节解析Anchor机制、RoI Align实现、Loss计算的魔鬼在参数里3.1 Anchor不是“预设框”而是特征空间的“测量标尺”网上很多教程把anchor说成“预先定义的候选框”这严重误导初学者。Anchor的本质是把目标检测问题从“在连续坐标空间搜索最优框”转化为“在离散anchor集合中选择最优偏移量”。它是一套坐标变换的基底。以feature_c438×63为例每个像素点对应原图上约16×16的区域因为ResNet50的总stride16。在这个16×16的“感受野单元”里我们预设9种可能的目标形态3种尺度覆盖32×32到512×512的目标×3种长宽比适应人、车、狗等不同物体。所以anchor不是凭空画的框而是根据backbone的stride和感受野科学计算出的、覆盖常见目标尺寸的先验分布。TensorFlow 2.18里我们用tf.meshgrid生成所有像素坐标再用广播运算批量计算anchor# 假设feature_map_shape (38, 63) heights tf.constant([128, 256, 512], dtypetf.float32) # 3 scales widths tf.constant([128, 256, 512], dtypetf.float32) ratios tf.constant([0.5, 1.0, 2.0], dtypetf.float32) # 3 aspect ratios # 生成所有anchor的wh组合3 scales × 3 ratios 9 combinations all_widths tf.reshape(tf.repeat(widths, 3), [-1]) # [128,128,128,256,...] all_heights tf.reshape(tf.repeat(heights, 3), [-1]) all_ratios tf.tile(ratios, [3]) # [0.5,1.0,2.0,0.5,1.0,2.0,...] # 计算实际anchor宽高w sqrt(w*h*ratio), h w/ratio anchor_ws tf.sqrt(all_widths * all_heights * all_ratios) anchor_hs anchor_ws / all_ratios # 生成网格坐标每个pixel (i,j) 对应原图中心点 (j*168, i*168) shift_x tf.range(0, feature_map_shape[1]) * 16 8 shift_y tf.range(0, feature_map_shape[0]) * 16 8 shift_xx, shift_yy tf.meshgrid(shift_x, shift_y) # 广播(38,63,1) (1,1,9) - (38,63,9) anchor_xmin shift_xx[..., tf.newaxis] - anchor_ws[tf.newaxis, tf.newaxis, :] / 2 anchor_ymin shift_yy[..., tf.newaxis] - anchor_hs[tf.newaxis, tf.newaxis, :] / 2 anchor_xmax shift_xx[..., tf.newaxis] anchor_ws[tf.newaxis, tf.newaxis, :] / 2 anchor_ymax shift_yy[..., tf.newaxis] anchor_hs[tf.newaxis, tf.newaxis, :] / 2 # 最终shape: (38, 63, 9, 4) - [ymin,xmin,ymax,xmax] anchors tf.stack([anchor_ymin, anchor_xmin, anchor_ymax, anchor_xmax], axis-1)这段代码的关键在于tf.meshgrid和广播运算。它避免了Python循环全程在GPU上向量化执行。注意anchor_ws和anchor_hs的计算公式——这是原论文Table 1的数学实现不是随便写的。如果你把ratios改成[0.1, 10.0]模型会立刻在细长目标如电线杆上失效因为anchor先验不再匹配真实分布。3.2 RoI Align双线性插值不是“插值”是亚像素级的坐标对齐RoI Pooling的缺陷在于当proposal坐标被量化为整数像素时比如x12.7→12特征图上的采样点就偏移了0.7个像素这对小目标32×32的定位误差高达20%。RoI Align用双线性插值在原始坐标12.7上直接采样彻底解决这个问题。它的核心是四点插值公式value w11*v11 w12*v12 w21*v21 w22*v22其中权重w由距离决定。TensorFlow 2.18没有内置RoI Align op我们必须手写。关键步骤坐标映射将proposal的[ymin,xmin,ymax,xmax]原图坐标→ 映射到feature_c5的19×32空间。公式y_feat ymin * 19/600因为原图短边缩放到600feature_c5高19所以缩放因子19/600。采样点生成在每个7×7的bin里取4个采样点默认设置坐标为(y_bin dy, x_bin dx)其中dy,dx ∈ {0.25,0.75}。这确保了即使bin边界是整数采样点也在亚像素位置。双线性插值对每个采样点(y,x)找到其周围的4个整数坐标(y0,x0),(y0,x1),(y1,x0),(y1,x1)用距离加权计算值。def roi_align(feature_map, rois, output_size(7,7), sampling_ratio2): # rois: (N, 4) [y1,x1,y2,x2] in original image space # Map rois to feature map space (19x32) scale_h 19.0 / 600.0 scale_w 32.0 / 1000.0 rois_feat rois * tf.constant([scale_h, scale_w, scale_h, scale_w]) # For each roi, generate sampling points num_rois tf.shape(rois)[0] bin_h (rois_feat[:,2] - rois_feat[:,0]) / output_size[0] # height of each bin bin_w (rois_feat[:,3] - rois_feat[:,1]) / output_size[1] # width of each bin # Generate 2x2 sampling points per bin count output_size[0] * output_size[1] * sampling_ratio * sampling_ratio yy tf.linspace(0.0, 1.0, sampling_ratio1)[:-1] 0.5/sampling_ratio xx tf.linspace(0.0, 1.0, sampling_ratio1)[:-1] 0.5/sampling_ratio y_grid, x_grid tf.meshgrid(yy, xx, indexingij) # Broadcast: (7,7,2,2) - (7,7,4) - (N,7,7,4) y_offset tf.reshape(y_grid, [-1]) # (4,) x_offset tf.reshape(x_grid, [-1]) # For each bin (i,j), compute 4 sampling points # This is the core: no quantization, direct float coordinate access # ... (full implementation omitted for brevity, but follows standard bilinear formula) return aligned_features # (N, 7, 7, C)这段代码里tf.linspace(0.0,1.0,sampling_ratio1)[:-1] 0.5/sampling_ratio是精髓——它生成的不是0,1,2...这样的整数索引而是0.25,0.75这样的亚像素偏移。这就是为什么RoI Align能让小目标mAP提升8.2%。3.3 Loss计算Smooth L1不是“平滑L1”是Huber Loss的特例Faster R-CNN的回归loss用Smooth L1分类loss用交叉熵但参数设置才是灵魂。原论文中RPN回归loss的权重λ1但实际训练时如果直接用tf.keras.losses.Huber(delta1.0)你会发现loss值巨大且不稳定。因为Smooth L1在|x|1时是二次函数|x|1时是一次函数而RPN预测的dx,dy,dw,dh通常在[-2,2]范围内大部分值落在二次区梯度太大。正确做法是用delta0.1并对预测值做归一化# RPN bbox regression target: tx (x - xa)/wa, ty (y - ya)/ha, tw log(w/wa), th log(h/ha) # So targets are already normalized, delta0.1 makes sense rpn_reg_loss tf.keras.losses.Huber(delta0.1, reductionnone) rpn_reg_loss_value tf.reduce_mean( rpn_reg_loss(rpn_bbox_targets, rpn_bbox_pred) * rpn_bbox_weights ) # RCNN bbox regression: same delta0.1, but weights are different # Only positive proposals contribute (weight1.0), negatives are masked out rcnn_reg_loss_value tf.reduce_mean( rpn_reg_loss(rcnn_bbox_targets, rcnn_bbox_pred) * rcnn_bbox_weights )注意rpn_bbox_weights它是一个mask tensorshape(batch_size, 21546)只有被标记为正样本IoU0.7或负样本IoU0.3的anchor位置为1其余为0。这个mask必须和loss计算同步否则loss会被大量0值拉低。很多初学者在这里出错把mask写成tf.where(..., 1.0, 0.0)结果发现loss0——因为tf.where返回的是int类型和float loss相乘时发生隐式转换错误。必须用tf.cast(..., tf.float32)。4. 实操过程从创建虚拟环境到训练完成每一步都踩过坑4.1 虚拟环境与TensorFlow 2.18安装绕开CUDA版本地狱在macOS或Windows上装TensorFlow最大的坑是CUDA/cuDNN版本不匹配。TensorFlow 2.18官方支持CUDA 12.2 cuDNN 8.9.4但NVIDIA官网下载的cuDNN 8.9.4 for CUDA 12.2解压后libcudnn.so.8的符号链接可能指向错误的版本。我的实测方案# 创建干净的conda环境比venv更可靠 conda create -n faster_rcnn python3.9 conda activate faster_rcnn # 安装CUDA toolkit 12.2不要用conda install cudatoolkit它版本太旧 # 去https://developer.nvidia.com/cuda-toolkit-archive 下载runfile sudo sh cuda_12.2.0_535.54.02_linux.run --silent --no-opengl-libs # 安装cuDNN 8.9.4必须用tar包不是deb # 解压后手动创建符号链接 tar -xzvf cudnn-linux-x86_64-8.9.4.25_cuda12-archive.tar.xz sudo cp cuda/include/cudnn*.h /usr/local/cuda/include sudo cp cuda/lib/libcudnn* /usr/local/cuda/lib64 sudo chmod ar /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn* # 关键修复符号链接 cd /usr/local/cuda/lib64 sudo rm libcudnn.so.8 sudo ln -sf libcudnn.so.8.9.4 libcudnn.so.8 # 验证 python -c import tensorflow as tf; print(tf.__version__); print(tf.test.is_built_with_cuda()); print(tf.test.is_gpu_available())如果tf.test.is_gpu_available()返回False检查LD_LIBRARY_PATH是否包含/usr/local/cuda/lib64。Windows用户请用WSL2原生Windows的CUDA路径管理是场噩梦。4.2 数据准备用PandasOpenCV生成自己的玩具数据集别急着下载COCO。先用100行代码生成一个只有3类dog/cat/bird的合成数据集验证pipeline是否work。核心是用OpenCV画矩形框用Pandas存annotationimport cv2, numpy as np, pandas as pd from pathlib import Path # 生成100张600x1000的合成图 for i in range(100): img np.ones((600,1000,3), dtypenp.uint8) * 255 # 白色背景 # 随机画3个目标dog(红色), cat(绿色), bird(蓝色) for obj_type, color in zip([dog,cat,bird], [(0,0,255),(0,255,0),(255,0,0)]): x1 np.random.randint(50, 900) y1 np.random.randint(50, 550) w np.random.randint(30, 150) h np.random.randint(30, 150) cv2.rectangle(img, (x1,y1), (x1w,y1h), color, -1) # 记录annotation ann_row { image_id: fimg_{i:03d}.jpg, category: obj_type, x1: x1, y1: y1, x2: x1w, y2: y1h, area: w*h } annotations.append(ann_row) cv2.imwrite(fdata/images/img_{i:03d}.jpg, img) # 保存为CSV比JSON更易读 df pd.DataFrame(annotations) df.to_csv(data/annotations.csv, indexFalse)这个annotations.csv就是你的数据源。它比COCO的JSON简单10倍但包含了所有必要信息图片名、类别、坐标。后续数据加载器tf.data.Dataset会直接读这个CSV用tf.io.decode_jpeg加载图片用tf.py_function解析坐标——这样你就能在tf.print()里实时看到每个batch的bboxtensor是不是你期望的shape。4.3 模型构建从Backbone到RCNN Head的逐层代码我们用tf.keras.layers构建每个子模块用tf.Module管理状态。重点看RPN的实现class RPN(tf.Module): def __init__(self, num_anchors9): super().__init__() # RPN shared conv layer self.rpn_conv tf.keras.layers.Conv2D(512, 3, paddingsame, namerpn_conv) # Classification branch: 2 scores per anchor (object/background) self.rpn_cls tf.keras.layers.Conv2D(num_anchors * 2, 1, namerpn_cls) # Regression branch: 4 coords per anchor self.rpn_reg tf.keras.layers.Conv2D(num_anchors * 4, 1, namerpn_reg) tf.function def __call__(self, features): # features: (B, H, W, C) e.g., (1,38,63,1024) x self.rpn_conv(features) # (B,H,W,512) rpn_cls_score self.rpn_cls(x) # (B,H,W,18) rpn_bbox_pred self.rpn_reg(x) # (B,H,W,36) # Reshape for loss computation: (B, H*W*9, 2) and (B, H*W*9, 4) B, H, W, _ tf.shape(rpn_cls_score)[0], tf.shape(rpn_cls_score)[1], tf.shape(rpn_cls_score)[2], 0 rpn_cls_score tf.reshape(rpn_cls_score, [B, -1, 2]) # (B, H*W*9, 2) rpn_bbox_pred tf.reshape(rpn_bbox_pred, [B, -1, 4]) # (B, H*W*9, 4) return rpn_cls_score, rpn_bbox_pred # 在主模型中调用 class FasterRCNN(tf.Module): def __init__(self): super().__init__() self.backbone tf.keras.applications.ResNet50( include_topFalse, input_shape(600,1000,3), weightsimagenet ) # Extract C4 and C5 features self.c4_layer self.backbone.get_layer(conv4_block6_out) # 38x63x1024 self.c5_layer self.backbone.get_layer(conv5_block3_out) # 19x32x2048 self.rpn RPN() self.rcnn_head RCNNHead(num_classes4) # 3 classes background tf.function def __call__(self, images): # Backbone forward x self.backbone(images) # (B,19,32,2048) c4 self.c4_layer(self.backbone.input) # We need to build a separate model for C4 c5 self.c5_layer(self.backbone.input) # RPN on C4 rpn_cls, rpn_reg self.rpn(c4) # Generate proposals and RoI Align proposals self.generate_proposals(rpn_cls, rpn_reg, c4) roi_features roi_align(c5, proposals) # (N,7,7,2048) # RCNN head cls_scores, bbox_preds self.rcnn_head(roi_features) return cls_scores, bbox_preds注意self.c4_layer的获取方式——你不能直接用self.backbone.output因为ResNet50的输出是C5。必须用get_layer()指定层名。层名可以在self.backbone.summary()里查到。generate_proposals()函数会调用前面讲的anchor生成和NMS这部分代码较长但核心就是tf.image.non_max_suppression()。4.4 训练循环自定义train_step()掌控一切Keras的model.fit()在这里是障碍。我们必须写自己的训练循环才能控制RPN和RCNN的loss权重、梯度裁剪、学习率warmuptf.function def train_step(self, images, gt_boxes, gt_labels): with tf.GradientTape() as tape: # Forward pass rpn_cls_pred, rpn_reg_pred self.rpn(self.c4_features) proposals self.generate_proposals(rpn_cls_pred, rpn_reg_pred) roi_features roi_align(self.c5_features, proposals) rcnn_cls_pred, rcnn_reg_pred self.rcnn_head(roi_features) # Compute losses rpn_cls_loss self.rpn_cls_loss(gt_boxes, rpn_cls_pred, proposals) rpn_reg_loss self.rpn_reg_loss(gt_boxes, rpn_reg_pred, proposals) rcnn_cls_loss self.rcnn_cls_loss(gt_labels, rcnn_cls_pred) rcnn_reg_loss self.rcnn_reg_loss(gt_boxes, rcnn_reg_pred, gt_labels) # Weighted sum: paper uses 1:1:1:1, but we add warmup total_loss ( rpn_cls_loss * self.rpn_cls_weight rpn_reg_loss * self.rpn_reg_weight rcnn_cls_loss * self.rcnn_cls_weight rcnn_reg_loss * self.rcnn_reg_weight ) # Compute gradients only for trainable variables gradients tape.gradient(total_loss, self.trainable_variables) # Clip gradients to prevent explosion gradients, _ tf.clip_by_global_norm(gradients, 10.0) self.optimizer.apply_gradients(zip(gradients, self.trainable_variables)) return { total_loss: total_loss, rpn_cls: rpn_cls_loss, rpn_reg: rpn_reg_loss, rcnn_cls: rcnn_cls_loss, rcnn_reg: rcnn_reg_loss } # Training loop for epoch in range(100): for batch in dataset: losses model.train_step(batch[images], batch[boxes], batch[labels]) if step % 10 0: print(fEpoch {epoch}, Step {step}: {losses})这里tf.clip_by_global_norm(gradients, 10.0)是关键。Faster R-CNN的loss梯度非常大不裁剪的话前10个step就会出现NaN。10.0是经验值太小1.0模型不收敛太大100.0还是NaN。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 “Lossnan”问题的三层排查法这是新手第一道关卡。不要一看到nan就重装TensorFlow。按顺序检查数据层用tf.print(tf.reduce_min(images), tf.reduce_max(images))确认输入图像是[0,1]范围。如果用了cv2.imread()读图它返回的是[0,255]必须除以255.0。tf.print()放在train_step开头这是最快定位点。Loss层在rpn_cls_loss函数里加一行tf.debugging.check_numerics(rpn_cls_pred, rpn_cls_pred contains nan)。如果触发说明RPN的Conv2D层权重初始化有问题。解决方案把kernel_initializerglorot_uniform改成he_normal因为ReLU激活函数更适合He初始化。梯度层如果loss正常但gradients是nan说明某层输出爆炸。在tape.gradient()后加tf.debugging.check_numerics(gradients[0], grad[0] is nan)定位到具体哪一层。常见原因是BN层在训练模式下batch size太小4导致方差为0。解决方案要么增大batch size要么在BN层加momentum0.99默认0.999太激进。5.2 mAP上不去的四大元凶训练100个epochmAP卡在0.3别怪数据少先查这四点Anchor mismatch用tf.print(tf.shape(anchors))确认生成的anchor数量。如果feature_c4是38×63anchor数应该是38×63×921,546。如果显示21,546×1说明你忘了tf.newaxis导致维度错误IoU计算全错。Proposal quality在generate_proposals()后加tf.print(tf.shape(proposals))。正常应该输出约300个proposal。如果只有50个说明NMS阈值0.7太高调到0.5试试如果3000个说明score阈值0.01太低调到0.5。RoI Align坐标错误画图验证。取一个proposal[100,200,150,250]用cv2.rectangle()画在原图上再用roi_align提取特征把7×7的特征图tf.reduce_mean(..., axis-1)可视化。如果热力图中心不在proposal框内说明坐标映射公式错了——检查是y1,x1,y2,x2还是x1,y1,x2,y2顺序。RCNN分类不平衡gt_labels里背景类占比95%目标类只有5%。tf.keras.losses.SparseCategoricalCrossentropy(from_logitsTrue)在这种情况下会偏向背景。解决方案用class_weight参数给目标类赋予权重20.0。5.3 内存爆炸的终极解决方案训练时OOM不是显存不够是tf.data的prefetch策略不对。默认dataset.prefetch(tf.data.AUTOTUNE)会预加载太多batch。改成dataset dataset.batch(2) # batch_size2 for GPU with 24GB VRAM dataset dataset.map(preprocess_fn, num_parallel_calls2) # 限制CPU线程 p a hrefhttps://download.csdn.net/download/qq_38735017/85560690 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表