引入

全连接的问题

  • 空间结构特征消失,被展平了

  • 参数太多了,难以训练

解决方案:

  1. 视角层面:全连接,什么都看 => 卷积,只看局部 

  2. 同一组参数,可以共享

  3. 多个卷积 = 多个观察特征的角度

效果:        空间特征得以保存   +  参数量大大减少 

softmax:多分类激活函数,sigmoid:二分类激活函数

经典分类模型

CNN进化简史:四大经典架构

这条脉络的主线是:从“证明有效”到“走向深度”,再到 “解决深度带来的问题”。

LeNet-5

1. 起源与验证:LeNet-5(1998年)

  • 核心贡献首次成功将卷积、池化、全连接堆叠,构成了现代CNN的雏形。它证明了通过梯度下降能够有效训练卷积神经网络。

  • 关键结构

    • 卷积层 -> 池化层 -> 卷积层 -> 池化层 -> 全连接层

    • 使用 Sigmoid 激活函数。

  • 历史意义:成功用于手写数字识别(MNIST数据集),但因数据量和算力限制,其思想沉寂了近十年。

  • 记忆点CNN的“祖父”,奠定了基本组件。


AlexNet-8

2. 复兴与崛起:AlexNet(2012年)

  • 核心贡献:在大数据(ImageNet)和大算力(GPU) 时代,证明了深度CNN的惊人能力,点燃了深度学习的热潮。

  • 关键创新

    1. 引入ReLU激活函数:代替Sigmoid,极大缓解梯度消失问题,训练速度大幅加快。

    2. 使用Dropout正则化:在全连接层随机丢弃部分神经元,有效防止模型过拟合。

    3. 使用GPU并行训练。

  • 历史意义:以压倒性优势赢得ImageNet图像分类竞赛(Top-5错误率从26%降至16%),标志着深度学习时代的正式来临。

  • 记忆点深度CNN的“引爆点”“ReLU + Dropout + GPU” 三件套。


VGG-16

总层数:16层(13个卷积层 + 3个全连接层)

特征提取部分 (features)

cfg = [
    64,  64,  "M",      # 阶段1:2个卷积层 + 1个池化层
    128, 128, "M",      # 阶段2:2个卷积层 + 1个池化层
    256, 256, 256, "M", # 阶段3:3个卷积层 + 1个池化层
    512, 512, 512, "M", # 阶段4:3个卷积层 + 1个池化层
    512, 512, 512, "M", # 阶段5:3个卷积层 + 1个池化层
]
# make_layers: make_features_receive_layers
def make_layers(cfg, batch_norm=False):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == "M":
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v
    return nn.Sequential(*layers)  

分类器部分 (classifier)

self.classifier = nn.Sequential(
    nn.Linear(512 * 7 * 7, 4096),  # 将7×7×512=25088展平,连接4096个神经元
    nn.ReLU(True),                 # ReLU激活
    nn.Dropout(),                  # Dropout正则化(防止过拟合)
    nn.Linear(4096, 4096),         # 第二全连接层:4096个神经元
    nn.ReLU(True),                 # ReLU激活
    nn.Dropout(),                  # Dropout正则化
    nn.Linear(4096, num_classes),  # 输出层:1000个类别(ImageNet)
)
输入图像 (224×224×3)
    ↓
[特征提取部分]
    ↓ 13个卷积层 + 5个池化层
特征图 (7×7×512)
    ↓
自适应平均池化 (AdaptiveAvgPool2d) → 固定为7×7
    ↓
展平 (Flatten) → 512×7×7 = 25088维向量
    ↓
[分类器部分]
    ↓
全连接层1:25088 → 4096
    ↓ ReLU + Dropout
全连接层2:4096 → 4096
    ↓ ReLU + Dropout
全连接层3:4096 → 1000(分类结果)

def forward(self, x)
	x = self.features(x)	
    avgpool = nn.AdaptiveAvgPool2d((7, 7))  # 应该在 __init__中
    x = avgpool(x)
    x = x.view(x.size(0), -1)
    x = self.classifier(x)
    return x
输入(224,224,3)
├─ 2×[Conv3×3(64)] → MaxPool
├─ 2×[Conv3×3(128)] → MaxPool  
├─ 3×[Conv3×3(256)] → MaxPool
├─ 3×[Conv3×3(512)] → MaxPool
├─ 3×[Conv3×3(512)] → MaxPool
├─ AdaptiveAvgPool(7×7)
├─ Flatten(512×7×7=25088)
├─ FC(25088→4096) → ReLU → Dropout
├─ FC(4096→4096) → ReLU → Dropout
└─ FC(4096→1000) → 输出

3. 深化与统一:VGGNet(2014年)

  • 核心贡献探索了网络“深度”的重要性,并证明通过堆叠更小的卷积核(3x3)是构建更深、更强网络的有效途径。

  • 关键结构

    • 规律化设计:全部使用3x3卷积2x2最大池化,不断加深网络(VGG-16, VGG-19)。

    • 小卷积核优势:多个3x3卷积串联可达到大卷积核的感受野,但参数更少,非线性更强。

  • 历史意义:提供了简洁、统一、模块化的设计范式,其结构清晰,易于迁移学习,至今仍是常用的骨干网络之一。

  • 记忆点“更小、更深、更规整”3x3卷积的教科书


ResNet-50

4. 突破与升华:ResNet(2015年)

  • 核心贡献:提出了残差学习,通过跳跃连接解决了超深网络中的梯度消失/爆炸网络退化问题。

  • 关键创新残差块

    • 不是让网络直接拟合目标映射 H(x),而是拟合残差 F(x) = H(x) - x

    • 通过 跳跃连接 实现恒等映射:H(x) = F(x) + x

    • 这使得信息(包括梯度)可以无损地穿越很多层。

  • 历史意义:让网络深度突破千层大关,在多项任务上达到人类水平。其思想深刻影响了后续几乎所有网络设计。

  • 记忆点“跳跃连接,大道至简”“让网络学会恒等变换”,解决了深度网络的核心瓶颈

ResNet50架构详解

  1. 头部(Head):初始特征提取

  2. 主体(Body):多个残差块堆叠

  3. 尾部(Tail):分类器

Bottleneck设计

# 参数量对比示例

传统结构 = 256×256×3×3×2 = 1,179,648

Bottleneck = 256×64×1×1 + 64×64×3×3 + 64×256×1×1 = 69,632

参数减少约94%!

PyTorch实现精解

核心组件

class ConvBlock(nn.Module):
    """ 卷积三件套:nn.Conv2d + BN + ReLU """
    def __init__(self, in_c, out_c, ks, stride, pad):
        super().__init__()
        self.conv = nn.Conv2d(inc, out_c, ks, stride, pad) #ks: kernal_sz
        self.bn = nn.BatchNorm2d(out_c)   # BN:
        self.relu = nn.ReLU()

残差块实现

# 实现Bottlenet残差结构(nn.Module)
class BodyBlock(nn.Module):  
    """
    残差块模块
    
    实现了ResNet中的残差连接结构,包含多个卷积层和跳跃连接
    
    Args:
        in_channels (int): 输入通道数
        out_channels (int): 输出通道数
        copy_cnt (int): 卷积层重复次数
        specical_stride (int, optional): 特殊步长,默认为1
    """
    def __init__(self, in_channels, out_channels, copy_cnt, specical_stride=1):
        super(BodyBlock, self).__init__()
        self.copy_cnt = copy_cnt
        # 标准Bottleneck结构中间通道数为输出通道数的1/4
        mid_channels = out_channels // 4
        
        # 第一个残差块的主路径
        self.conv1 = nn.Sequential(
            ConvBlock(in_channels, mid_channels, 1, 1, 0),  # 降维
            ConvBlock(mid_channels, mid_channels, 3, specical_stride, 1),  # 保持维度
            ConvBlock(mid_channels, out_channels, 1, 1, 0)  # 升维
        )
        
        # 第一个残差块的捷径连接,当输入输出通道不一致时需要调整
        self.conv2 = ConvBlock(in_channels, out_channels, 1, specical_stride, 0)
​
        # 后续残差块的主路径
        self.conv3 = nn.Sequential(
            ConvBlock(out_channels, mid_channels, 1, 1, 0),  # 降维
            ConvBlock(mid_channels, mid_channels, 3, 1, 1),  # 保持维度
            ConvBlock(mid_channels, out_channels, 1, 1, 0)  # 升维
        )
​
    def forward():
        # 第一个块:主路径 + 调整捷径
        x = self.conv1(x) + self.conv2(x)
​
        # 后续块: 主路径 + 恒等映射
        for _ in range(self.copy_cnt):  # copy_cnt: 重复次数
            x = self.conv3(x) + x   # 残差连接
        return x

网络整体构建

net = nn.Sequential(
    # head
    nn.Sequential(
        ConvBlock(in_channel=3, out_channel=64, kernel_size=7, stride=2, padding=3),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        ),
    # body
    nn.Sequential(
        BodyBlock(in_channels=64, out_channels=256, copy_cnt=3, specical_stride=1),
        BodyBlock(in_channels=256, out_channels=512, copy_cnt=4, specical_stride=2),
        BodyBlock(in_channels=512, out_channels=1024, copy_cnt=6, specical_stride=2),
        BodyBlock(in_channels=1024, out_channels=2048, copy_cnt=3, specical_stride=2)
        ),
    # tail
    nn.Sequential(
        nn.AdaptiveAvgPool2d((1,1)),
        nn.Flatten(),
        nn.Linear(2048, 1000)
        )
)

优势:

参数效率与总参数量

  • ResNet50采用Bottleneck设计,通过1×1卷积进行通道降维和升维,大大减少了参数量和计算量,同时保持了模型的表达能力。根据我们的实现,ResNet50的总参数量约为25.5M(2550万),这个数字相对于其50层的深度来说是相当高效的。

    相比之下,VGG16虽然只有16层,但参数量高达138M,ResNet50在深度增加的同时,通过巧妙的结构设计将参数量控制在了更低的水平。

这种参数效率主要得益于以下几点:

  • Bottleneck结构:通过1×1卷积进行通道降维和升维,大幅减少参数量

  • 共享权重:残差连接允许网络重用特征,减少了冗余参数

  • 全局平均池化:在网络末端使用全局平均池化代替多个全连接层,显著减少了参数量

梯度流动

  • 残差连接使得梯度可以直接流过捷径,有效缓解了深层网络中的梯度消失问题,使得训练更加稳定和高效。

总结

你可以想象一个故事:

有一位科学家(LeNet)造出了一个概念车(CNN雏形),但当时路况(数据)和燃料(算力)不好,被搁置了。 十几年后,另一位极客(AlexNet)搞来了大量优质燃料(大数据+GPU),改进了引擎(ReLU)和刹车系统(Dropout),开着改装车一举赢得了世界赛车大奖(ImageNet冠军),震惊世界! 接着,一家大公司(VGG)决定工业化生产这款车。他们发现,用一堆小齿轮(3x3卷积)组合,比用一个大齿轮更高效、更灵活,于是造出了又长又稳的加长豪华车(16-19层)。 但是,车造得太长(网络太深)后,车头和车尾的通信出现了问题(梯度消失/网络退化)。这时,一位天才工程师(ResNet)想出了一个绝妙主意:在车里直接修建一些“传送门”或“高速公路”(跳跃连接),让信息可以不经过复杂的机械结构直接传递。从此,车想造多长就造多长(1000+层),性能还越来越好。

目标检测模型

Faster-RCNN

目标检测过程:框出目标、判断分类

Faster-RCNN

Anchor Box

Anchor Box来源:源于滑动窗口缺点处理

将原图映射到特征图,并且每个特征图像素均预制多个锚框,不同形状的预制框、更为方便的进行框选不同形状的物体

IOU定义:锚框与标注框的交集情况

Anchor 和 Ground Trust 做IOU时的 单位统一问题: 

一个是特征图像素为单位,另一个则是原始图片像素为单位

Selective Search

Selective Search 是一种目标候选区域生成算法,核心目的是:在没有先验框的情况下,从一张图片里自动找出所有可能包含目标的区域,为后续的分类和定位任务提供候选;

它是解决传统目标检测 “如何找目标位置” 的经典方案,主要用在 R-CNN 系列模型(R-CNN、Fast R-CNN)中。

核心原理(通俗版)

  1. 初始分割:先用图像分割算法把图片分成很多小区域(比如基于颜色、纹理的超像素分割)。

  2. 区域合并:计算相邻小区域的相似度(颜色、纹理、大小、形状),把相似的区域不断合并成更大的区域。

  3. 生成候选框:把所有合并过程中产生的区域,都转换成对应的矩形框,这些框就是候选区域

通俗例子

就像你找一张照片里的猫:先把照片分成 “猫耳朵”“猫脸”“背景草地” 等小区域,再把 “猫耳朵” 和 “猫脸” 合并成一个大区域,最后给这个大区域画个框,作为 “可能有猫” 的候选框。

缺点(关键痛点)

  • 速度慢:合并过程是纯 CPU 计算,一张图要生成 1000~2000 个候选框,耗时几秒到十几秒,完全无法实时。

  • 候选框冗余:很多候选框是重复或无效的,后续分类时会做大量无用功。

  • 无法端到端训练:候选区域生成和后续的分类、回归是分开的两个步骤,不能一起优化模型参数。

  • 区域合并算法细节,用直方图交叉计算来计算相似性?然后由相似性进行合并相邻相似区域

RPN&Anchor Box

RPN(Region Proposal Network)和 Anchor Box 是为了解决 Selective Search 的痛点,实现 “候选区域生成 + 分类 + 回归” 端到端训练,同时大幅提升检测速度,是 Faster R-CNN 模型的核心创新

1. 核心需求:用神经网络替代手工生成候选框

Selective Search 是手工设计的规则算法,速度慢且不可学习。而深度学习的核心是 “让模型自己学”,因此研究者希望:

用一个 卷积神经网络(RPN)直接从图像特征图上 生成候选区域替代 Selective Search

但这里有个关键问题:卷积神经网络输出的是特征图(理解Anchor Box技术诞生的关键),怎么从特征图上确定候选框的位置和大小? 解决特征图和候选框的映射问题

Anchor Box(锚框)就是为解决这个问题而生的。

2. Anchor Box:给特征图 “预设参考框” (不同类别目标的预制框,对应到原图上)

eg:人:长条框,人脸:方正框,车辆:方正框,木棍:长条框

Anchor Box一组预设的、不同大小和长宽比的矩形框,提前定义好,然后 “铺” 在特征图的每个像素点上。

  • 作用 :为每个特征点提供 参考基准,让 RPN 可以基于这些基准框,预测 “这个位置有没有目标” “框需要往哪个方向调整” 。

  • 通俗理解

    就像你找目标时,提前准备了一堆不同尺寸的 “模板框”(比如小框、大框、竖框、横框),然后把这些模板框一个个放在图片的每个位置,再判断 “哪个模板框和目标最像,需要怎么微调”。

  • 举例

    通常预设 3 种尺寸(比如 128×128、256×256、512×512)和 3 种长宽比(1:1、1:2、2:1),总共 9 个 Anchor Box,每个特征点都会对应这 9 个框。

3. RPN:用神经网络高效生成候选框

RPN(区域提议网络) 是一个轻量级的卷积网络,输入是图像的特征图,输出是两部分:

  • 分类分支:预测每个 Anchor Box 里 “ 是前景(有目标)还是背景(无目标)”。

  • 回归分支:预测每个前景 Anchor Box 需要调整的偏移量(x、y 坐标偏移,宽高缩放),把 Anchor Box 修正成更贴合目标的候选框。

整体流程图解+代码

prorosals:输入RPN结果+anchorbox,输出真正的候选区域,目标框选区域

RPN:输入feature maps,输出分类器 和 BoundingBox偏移量

 #每个batch调用一次,batch_size=1
    def forward(self, images, targets=None):
        feature_map = self.backbone(images)     # 先经过主干网络获取feature_map
        batch_size, _, h, w = feature_map.shape # 获取feature_map的形状
     
        # 执行rpn分类,获取分类分数 + bbox 偏移预测
        rpn_cls_scores, rpn_bbox_preds = self.rpn(feature_map)  
        
        # 生成锚框
        anchors = generate_anchors((h, w), stride=self.stride).to(images.device)   

        if self.training:
            assert targets is not None, "Targets must be provided during training"
            losses = {}
            for i in range(batch_size):
                rpn_loss_cls, rpn_loss_bbox = self.compute_rpn_loss(
                    rpn_cls_scores[i], rpn_bbox_preds[i], anchors, targets[i]
                )
                
                proposals = self.generate_proposals(
                    rpn_cls_scores[i:i+1], 
                    rpn_bbox_preds[i:i+1], 
                    anchors)   # 生成候选区域
              
                #然后将新创建的tensor与proposals进行按列拼接,得到rois,其形状为:(n,5)
                #其中n是proposals的数量,5是[batch_index, x1, y1, x2, y2]
                rois = torch.cat(
                    [torch.full((proposals.shape[0], 1), 
                                i, device=proposals.device), proposals]
                    , dim=1)

                # ROI Pooling
                roi_features = self.roi_align(feature_map, rois)
                
                
                # roi_features.size(0)表示获得roi_features的行数,也就是roi的数量
                # 将roi_features的形状从(N, 256, 7, 7)变成(N, 256 * 7 * 7)
                x = roi_features.view(roi_features.size(0), -1)
                x = F.relu(self.fc1(x))
                x = F.relu(self.fc2(x))
                cls_scores = self.cls_score(x)
                bbox_deltas = self.bbox_pred(x)
                det_loss_cls, det_loss_bbox = self.compute_detection_loss(
                    cls_scores, bbox_deltas, targets[i], proposals)
                
                losses[f"rpn_loss_cls_{i}"] = rpn_loss_cls
                losses[f"rpn_loss_bbox_{i}"] = rpn_loss_bbox
                losses[f"det_loss_cls_{i}"] = det_loss_cls
                losses[f"det_loss_bbox_{i}"] = det_loss_bbox
            return losses
   

RPN代码

    # RPN(区域提议网络)
    # 生产智能渔网的工厂
    class RPN(nn.Module):
        def __init__(self, in_channels, mid_channels=256, num_anchors=9):
            super(RPN, self).__init__()
            self.conv = nn.Conv2d(in_channels, mid_channels, kernel_size=3, stride=1, padding=1) # 转换为纹理、边缘、结构 信息
            self.cls_layer = nn.Conv2d(mid_channels, num_anchors * 2, kernel_size=1)
            # 判断anchor种有没有物体: 分数1:背景概率、分数2: 前景概率
            self.reg_layer = nn.Conv2d(mid_channels, num_anchors * 4, kernel_size=1)
            # 调整建议层:dx、dy、dw、dh  (平移、拉升)

        def forward(self, x):
            x = F.relu(self.conv(x))
            cls_scores = self.cls_layer(x)
            bbox_preds = self.reg_layer(x)
            return cls_scores, bbox_preds

Anchor生成代码

    # 生成 Anchor Box (撒下多张大小形状不同的渔网)   224/4 = 56个特征像素点  
    def generate_anchors(feature_map_size, stride=4, scales=[8, 16, 32], 
                         ratios=[0.5, 1, 2]):
        anchors = []
        h, w = feature_map_size
        base_size = stride
        for i in range(h):
            for j in range(w):
                cx = j * stride + stride / 2 # 中心点x坐标(原图上)
                cy = i * stride + stride / 2
                for scale in scales:         # 3
                    for ratio in ratios:     # 3*3 = 9种不同的网
                        w_box = base_size * scale * (ratio ** 0.5)
                        h_box = base_size * scale / (ratio ** 0.5)
                        anchors.append([cx - w_box / 2, cy - h_box / 2, 
                                        cx + w_box / 2, cy + h_box / 2])
        return torch.tensor(anchors, dtype=torch.float32)

Proposal生成代码

  def apply_bbox_deltas(self, proposals, deltas, labels=None):
        #proposals中的数据格式是[x1, y1, x2, y2],
        # deltas中的数据格式是[dx, dy, dw, dh]。
        proposals_w = proposals[:, 2] - proposals[:, 0]
        proposals_h = proposals[:, 3] - proposals[:, 1]
        proposals_cx = proposals[:, 0] + proposals_w / 2   # Anchor box的中心点横坐标cx
        proposals_cy = proposals[:, 1] + proposals_h / 2   # Anchor box的中心点纵坐标cy

        if labels is not None:   
            #labels是一个一维张量,其长度等于proposals的长度。
            #labels中的每个元素表示对应的proposal的类别标签。
            batch_size = proposals.shape[0]
            indices = torch.arange(batch_size, device=deltas.device) * self.cls_score.out_features + labels
            dx = deltas.view(-1, 4)[indices, 0]
            dy = deltas.view(-1, 4)[indices, 1]
            dw = deltas.view(-1, 4)[indices, 2]
            dh = deltas.view(-1, 4)[indices, 3]
        else:  # 用同一的调整参数
            dx = deltas[:, 0]  
            dy = deltas[:, 1]
            dw = deltas[:, 2]
            dh = deltas[:, 3]

        #dx 表示预测的候选框的偏移比例,比如0.1表示向右移动10%的宽度
        pred_cx = dx * proposals_w + proposals_cx       # 横坐标 + 偏移 
        pred_cy = dy * proposals_h + proposals_cy       # 纵坐标 + 偏移
        pred_w = torch.exp(dw) * proposals_w			# 新宽度	
        pred_h = torch.exp(dh) * proposals_h            # 新高度
		
		# 从新计算矩形框 左上角/右下角 坐标
        return torch.stack([pred_cx - pred_w / 2, pred_cy - pred_h / 2, pred_cx + pred_w / 2, pred_cy + pred_h / 2], dim=1)
		
		
		
		
    # 筛选收网 (判断网里有没有🐟) cls_probs = 每个网是 "有🐟" 的概率(0-1之间) 只取 “前景概率”
    def generate_proposals(self, cls_scores, bbox_preds, anchors):
        #cls_scores原始形状是(batch_size, 9 * 2, 224/4, 224/4)
        #cls_scores.view(-1, 2)相当于将一个tensor reshape 成二维tensor, 即转成n行2列
        #dim=-1表示按前面参数的最后一个维度计算softmax, 最后一个维度是2
        #[:, 1]表示取c前面参数ls_scores的第二列
        cls_probs = torch.softmax(cls_scores.view(-1, 2), dim=-1)[:, 1] 
        bbox_preds = bbox_preds.view(-1, 4)
        
        # 根据预测的调整值(bbox_preds)微调网的位置
        proposals = self.apply_bbox_deltas(anchors, bbox_preds)    
        scores = cls_probs

        keep = ops.nms(proposals, scores, iou_threshold=0.7)      
        # 去掉重复的网(按照"有🐟概率"排序, 保留最高分的网, 删掉重叠度>70%的其他网)
        proposals = proposals[keep]
        scores = scores[keep]

        #proposals是候选区,共有4列,其中第0列表示cx, 1列表示cy, 2列表示w, 3列表示h
        #为了不让预测的框超出图像范围,所以对proposals进行裁剪
        # clamp(0,224)  限制坐标在图像范围内
        proposals[:, 0] = torch.clamp(proposals[:, 0], min=0, max=224)   
        proposals[:, 1] = torch.clamp(proposals[:, 1], min=0, max=224)
        proposals[:, 2] = torch.clamp(proposals[:, 2], min=0, max=224)
        proposals[:, 3] = torch.clamp(proposals[:, 3], min=0, max=224)

        #numel函数的作用是返回张量中元素的总数
        if scores.numel() == 0:
            return torch.empty((0, 4), device=anchors.device)
       
        #取值最大的10个值的索引值
        top_n = torch.topk(scores, min(10, scores.shape[0])).indices   # 只收最好的10张网
        return proposals[top_n]

from torchvision.ops import roi_align as tv_roi_align

    def roi_align(self, feature_map, rois, output_size=(7,7)):
        """
        封装ROI Align调用(兼容手动/官方实现)
        Args:
            feature_map: 特征图 [batch_size, channels, h, w]
            rois: [num_rois, 5] 格式 [batch_idx, x1, y1, x2, y2]
            output_size: 输出特征尺寸,默认7x7
        Returns:
            对齐后的ROI特征 [num_rois, channels, 7, 7]
        """
        # 推荐使用torchvision官方实现(效率更高、精度更准)
        return tv_roi_align(
            input=feature_map,
            boxes=rois,
            output_size=output_size,
            spatial_scale=1/self.stride,  # 特征图相对于原图的缩放比例
            sampling_ratio=2  # 每个bin采样2x2个点(双线性插值)
        )

两阶段检测发展

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐