YOLOv10 超详细解析 | 网络结构、训练策略、论文解读

news2024/12/23 12:10:01

网络结构

1. Backbone

在这里插入图片描述

2. Head

在这里插入图片描述

3. 说明

  • 网络结构按 YOLOv10m 绘制,不同 scale 的模型在结构上略有不同,而不是像 YOLOv8 一样仅调整 depth 和 width。
  • Head 有部分后续计算与 YOLOv8 完全相同,上图省略,具体请看此文。
  • YOLOv10 整体的网络结构与 YOLOv8 相同,在一些细节模块上有所改进,查看具体的模块计算方式、优化策略跳转模型优化部分。

4. Predict Postprocess

  首先需要说明的是,网络实际包含两个 Head(结构相同、参数不同),分别对应了 One-to-many Head 和 One-to-one Head,在训练时两个 Head 同时参与,而推理预测时只需要 One-to-one Head。

class v10Detect(Detect):
    def __init__(self, nc=80, ch=()):
        super().__init__(nc, ch)
        c3 = max(ch[0], min(self.nc, 100))  # channels
        self.cv3 = ...
        self.one2one_cv2 = copy.deepcopy(self.cv2)
        self.one2one_cv3 = copy.deepcopy(self.cv3)

后处理具体流程:

  1. one2one [1, 84, 6300] → \to boxes [1, 6300, 4] + scores [1, 6300, 80]
  2. 根据每个 Anchor 最高的分类得分 max_scores [1,6300] 排序,取前 max_det=300 个 Anchor
  3. 将这 300 个 Anchor 的 80 类分类得分再做一次排序,取前 max_det=300

  此过程可以看作直接将所有 scores 中得分较高的 300 个结果作为检测结果,代码中使用两次 topk 可能是效率更高。 由于 scores 计算使用的是 Sigmoid 而非 Softmax,这个后处理过程就可能会得到两个 Anchor 相同的框(即框的位置大小也都相同),但类别不同的结果。

  由于后处理的不同,作者建议在预测中可以自由调节置信度阈值,或者设较小的阈值来提升对小目标、远处目标的检测效果,并且可以看出调节阈值对于后处理速度并无影响,只是从这 300 个结果中用不同的阈值筛选出最终结果而已。

# step 1
preds = preds["one2one"][0]  	# [1,84,6300]
preds.transpose(-1, -2)  		# [1,6300,84]
boxes, scores = preds.split([4, nc], dim=-1)  # [1,6300,4], [1,6300,80]
# step 2
max_scores = scores.amax(dim=-1)  # [1,6300]
max_scores, index = torch.topk(max_scores, max_det, axis=-1)  # [1,300], [1,300]
index = index.unsqueeze(-1)
boxes = torch.gather(boxes, dim=1, index=index.repeat(1, 1, boxes.shape[-1]))  		# [1,300,4]
scores = torch.gather(scores, dim=1, index=index.repeat(1, 1, scores.shape[-1]))	# [1,300,80]
# step 3
scores, index = torch.topk(scores.flatten(1), max_det, axis=-1)
labels = index % nc
index = index // nc
boxes = boxes.gather(dim=1, index=index.unsqueeze(-1).repeat(1, 1, boxes.shape[-1]))
return boxes, scores, labels

Train

  YOLOv10 最大的特点就是在预测阶段不需要 NMS,而这个功能正是通过训练来实现的。
  YOLOv10 在训练中会对 One-to-many Head 和 One-to-one Head 两个头的输出都计算损失,而损失函数与 YOLOv8 相同。具体看下面代码,仅仅是 tal_topk 参数设置不同,也就是在参与损失计算的正样本的选择上有所不同。

class v10DetectLoss:
    def __init__(self, model):
        self.one2many = v8DetectionLoss(model, tal_topk=10)
        self.one2one = v8DetectionLoss(model, tal_topk=1)
    
    def __call__(self, preds, batch):
        one2many = preds["one2many"]
        loss_one2many = self.one2many(one2many, batch)
        one2one = preds["one2one"]
        loss_one2one = self.one2one(one2one, batch)
        return loss_one2many[0] + loss_one2one[0], torch.cat((loss_one2many[1], loss_one2one[1]))

  YOLOv8 的损失计算代码较多,关于代码逐句解析请看此文。本文将以尽可能简洁明了的方式说明损失的计算流程。

1. 前期准备

  在训练阶段使用的网络输出为 Head 的输出 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3,并将其分离为分类得分 pred_scores [b, a, 80]、原始检测框 pred_dist [b, a, 16] 以及解码后的检测框 pred_bboxes [b, a, 4]。对于标签也会做简单处理,得到分类标签 gt_labels [b, n, 1] 和目标框标签 gt_bboxes [b, n, 4]
b:BatchSize
a:Anchors
80:分类数量
4:xyxy
n:该 Batch 中的最大实例数,实例数不足的图像会填充0,方便后续矩阵运算

2. 正样本选择与指标计算

  选择 Anchor 坐标在 gt_bboxes 内部的输出来计算指标:

overlaps = CIoU(gt_boxes, pd_boxes)
align_metric = bbox_scores.pow(self.alpha) * overlaps.pow(self.beta)

此处 bbox_scores 仅保留 Anchor 所在 gt_bboxes 对应类别的得分,alpha=0.5, beta=6。对应 YOLOv10 论文中的公式( s s s 代表 Anchor 是否在实例内部):
m ( α , β ) = s ⋅ p α ⋅ I o U ( b ^ , b ) β m(\alpha,\beta) = s·p^{\alpha} · \mathrm{IoU}(\hat{b}, b)^\beta m(α,β)=spαIoU(b^,b)β

  选出每个 gt 目标中 align_metric 前 10 / 1 的输出,如果同一个 Anchor 的输出入选多个 gt 目标的 Top10 / 1,选择 overlaps 即 IoU 较高的。

  通俗来说对于每个实例,都会选择一些与其匹配得分较高的输出作为正样本,但对于一个 Anchor 的输出同时只能作为一个目标的正样本。而 One-to-many Head 和 One-to-one Head 的不同之处就在于一个会选择多个(Top 10)得分较高的作为正样本,一个只选择得分最高的作为正样本。

3. target scores

t j = u ∗ ⋅ m j m ∗ t_j = u^*·\frac{m_j}{m^*} tj=ummj
  其中 t j t_j tj 代表某个实例的 j j j 号正样本的 target_score u ∗ u^* u 代表所有正样本中 IoU 的最大值, m j m_j mj 代表该正样本的 align_metric m ∗ m^* m 同理为最大值。

4. 计算损失

4.1 Class

self.bce = nn.BCEWithLogitsLoss(reduction="none")
target_scores_sum = max(target_scores.sum(), 1)
loss[1] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum

l n = − w n [ y n ⋅ log ⁡ σ ( x n ) + ( 1 − y n ) ⋅ log ⁡ ( 1 − σ ( x n ) ) ] l_{n}=-w_{n}\left[y_{n} \cdot \log \sigma\left(x_{n}\right)+\left(1-y_{n}\right) \cdot \log \left(1-\sigma\left(x_{n}\right)\right)\right] ln=wn[ynlogσ(xn)+(1yn)log(1σ(xn))]

l n l_n ln 表示某个 Anchor 在某个类别上的分类损失,一共有 b ∗ a ∗ 80 b*a*80 ba80 l l l
w n w_n wn 统一取 1;
y n y_n yn 在正样本处的值为对应的 target_score,其余负样本处的值为 0;

L c l s = ∑ l ∑ t L_{cls}=\frac{\sum{l}}{\sum{t}} Lcls=tl

4.2 Box

weight = target_scores.sum(-1)[fg_mask].unsqueeze(-1)
iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, CIoU=True)
loss_iou = ((1.0 - iou) * weight).sum() / target_scores_sum

L b o x = ∑ t j ( 1 − C I o U j ) ∑ t L_{box}=\frac{\sum{t_j(1 - \mathrm{CIoU}_j)}} {\sum{t}} Lbox=ttj(1CIoUj)

I o U \mathrm{IoU} IoU 仅计算正样本,并且会以 target_score 作为权重 weight
注:一个 Anchor 输出仅作为一个实例的正样本,仅在实例的类别上存在 target_score,可用以下代码作为验证

(target_scores.sum(-1) == target_scores.max(-1)[0]).all()
> True

4.3 DFL

target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
loss_dfl = self._df_loss(pred_dist[fg_mask].view(-1, self.reg_max + 1), target_ltrb[fg_mask]) * weight
loss_dfl = loss_dfl.sum() / target_scores_sum

def _df_loss(pred_dist, target):
    tl = target.long()  # target left
    tr = tl + 1  # target right
    wl = tr - target  # weight left
    wr = 1 - wl  # weight right
    return (
        F.cross_entropy(pred_dist, tl.view(-1), reduction="none").view(tl.shape) * wl
        + F.cross_entropy(pred_dist, tr.view(-1), reduction="none").view(tl.shape) * wr
    ).mean(-1, keepdim=True)

注:这里计算损失时使用的 pred_dist 是网络对于 Box 的原始输出,未经过 DFL 解码成常规的 Box,而标签则是将常规的 target_bboxes 转化成了距离对应 Anchor 坐标的距离,与 pred_dist 的形式相匹配。

l n = − w y n log ⁡ exp ⁡ ( x n , y n ) ∑ c = 1 C exp ⁡ ( x n , c ) ⋅ 1 { y n ≠ i g n o r e _ i n d e x } l_{n}=-w_{y_{n}} \log \frac{\exp \left(x_{n, y_{n}}\right)}{\sum_{c=1}^{C} \exp \left(x_{n, c}\right)} \cdot 1\left\{y_{n} \neq \mathrm { ignore\_index }\right\} ln=wynlogc=1Cexp(xn,c)exp(xn,yn)1{yn=ignore_index}

上式为 F.cross_entropy 计算公式,等价于用 Softmax 计算分类概率 p p p l n = − log ⁡ p n l_n = -\log p_n ln=logpn 作为损失,其中 p n p_n pn 为标签类别对应的概率。
l d f l = ( y i + 1 − y ) l i + ( y − y i ) l i + 1 l_{dfl} = (y_{i+1} - y)l_i + (y-y_i)l_{i+1} ldfl=(yi+1y)li+(yyi)li+1
其中 y y y 为真实的标签,即某个坐标到 Anchor 坐标的距离, y i + 1 y_{i+1} yi+1 代表对 y y y 向上取整, y i y_i yi 代表对 y y y 向下取整。

  举例进行说明,假设 y = 3.7 y = 3.7 y=3.7 l d f l = ( 4 − 3.7 ) l 3 + ( 3.7 − 3 ) l 4 l_{dfl} = (4-3.7)l_3 + (3.7-3)l_4 ldfl=(43.7)l3+(3.73)l4,由于实际的 y y y 通常不会是个整数,单纯的将输出往单一类别收敛是不行的,理想情况就是让 3 和 4 两个类别的概率都比较高,那么通过 DFL 层的计算坐标就会落在 3~4 之间,同时以 y y y 到两边的距离作为权重来平衡两个类别的概率,往理想的 0.3 × 3 + 0.7 × 4 = 3.7 0.3 \times 3 + 0.7 \times 4=3.7 0.3×3+0.7×4=3.7 收敛。

注:每个正样本的损失为 4 个坐标的 DFL 损失的均值,最终当前 Batch 的 DFL 损失为所有正样本损失的和除以 target_scores_sum

模型优化

1. Lightweight Classification Head

  在 YOLOv8 的检测头中,分类头比回归头的参数量和计算量都要大。论文中表示在分析了分类误差和回归误差的影响后,发现回归头意义更大,因此减少分类头的开销不会对性能有较大损害。
  论文的分析实验为分别用真实的回归值和分类值代替输出,使回归或分类的损失置零,最终在验证集上的精度无回归的要比无分类的高很多。个人理解就是类似单独训练两个头,已知检测框位置训练分类,和已知分类训练检测框位置,得出的结论就是针对检测框的回归任务更难,分类任务简单很多,那么分类头就可以简化一些,至少参数量没必要比回归头还多。

  分类头从两个 3 × 3 3\times 3 3×3 卷积轻量化为两个的深度可分离卷积,深度可分离卷积由一个 3 × 3 3\times 3 3×3 的深度卷积和一个 1 × 1 1\times 1 1×1 的逐点卷积构成。

  在结构图中,卷积上方参数对应 [size, stride, padding, groups]。下面结合代码,感受 groups 对于卷积计算的影响(在深度可分离卷积中通常输入通道数和输出通道数相同,为了更直观,示例中使用不同的通道数)

import torch.nn as nn
from ptflops import get_model_complexity_info

conv1 = nn.Conv2d(64, 128, 3, 1, 1, bias=False)
conv2 = nn.Conv2d(64, 128, 3, 1, 1, groups=64, bias=False)
conv3 = nn.Conv2d(64, 128, 3, 1, 1, groups=2, bias=False)

flops, params = get_model_complexity_info(conv1, (64, 320, 320), as_strings=False, print_per_layer_stat=True)
print(f"FLOPs: {flops}, {64*3*3*128*320*320}")
print(f"Parameters: {params}, {128*64*3*3}")

flops, params = get_model_complexity_info(conv2, (64, 320, 320), as_strings=False, print_per_layer_stat=True)
print(f"FLOPs: {flops}, {1*3*3*128*320*320}")
print(f"Parameters: {params}, {128*1*3*3}")

flops, params = get_model_complexity_info(conv3, (64, 320, 320), as_strings=False, print_per_layer_stat=True)
print(f"FLOPs: {flops}, {32*3*3*128*320*320}")
print(f"Parameters: {params}, {128*32*3*3}")

  in_channelsout_channels 都需要可被 groups 整除,标准卷积 groups=1。可以看作把输入按通道维度划分为 groups 组,并将卷积核按输出通道即卷积核个数也划分为 groups 组,然后进行 groups 个并行的标准卷积。

2. SCDown

在这里插入图片描述
  YOLOv8 使用一个标准卷积同时实现空间下采样 h , w → h / 2 , w / 2 h,w\to h/2,w/2 h,wh/2,w/2 和通道变化 c → 2 c c \to 2c c2c,计算成本高。
  SCDown(Spatial-channel decoupled downsampling)将上面两个操作——空间和通道解耦。先通过 1 × 1 1\times 1 1×1 的逐点卷积调节通道数,再通过 3 × 3 3\times 3 3×3 的深度卷积做空间下采样,在降低计算成本的同时最大限度保留信息。

3. CIB

在这里插入图片描述
  在结构上 C2fCIB 就是用 CIB (compact inverted block)替换了原本 C2f 中的 BottleneckCIB 则是将 Bottleneck 中的标准卷积用深度卷积加逐点卷积进行替换。

  论文中,计算每个 stage 最后一个 basic block 中最后一个卷积的数值秩(numerical rank),数值秩越低表示冗余越多,即可进行简化。数值秩是先计算矩阵的奇异值,奇异值大于阈值的数量为数值秩,奇异值大代表重要的信息,奇异值小代表冗余或噪声。
  最终得到的结论如下图,stage 越大,模型越大,冗余越多。这里的 stage 看起来是对应了网络中的 8 个 C2f
在这里插入图片描述
  在模块替换时,大致方法为对数值秩进行排序,逐步替换,直到出现性能下降。因此不同 scale 的模型在模块选择上也有所不同。(具体原理和细节暂不深究)

4. Large-kernel Conv

  用 size 较大的深度卷积可以扩大感受野,增强模型能力,但简单使用会影响针对小目标的浅层特征,也会在高分辨率阶段引入 I/O 开销和延迟。建议在小模型的深层阶段(例如 YOLOv10n 的 22 层),将 CIB 中的第二个深层卷积替换为 7 × 7 7\times 7 7×7 3 × 3 3\times3 3×3 的深度卷积,具体还是看代码比较直观。

class CIB(nn.Module):
    def __init__(self, c1, c2, shortcut=True, e=0.5, lk=False):
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = nn.Sequential(
            Conv(c1, c1, 3, g=c1),
            Conv(c1, 2 * c_, 1),
            Conv(2 * c_, 2 * c_, 3, g=2 * c_) if not lk else RepVGGDW(2 * c_),
            Conv(2 * c_, c2, 1),
            Conv(c2, c2, 3, g=c2),
        )
        
class RepVGGDW(torch.nn.Module):
    def __init__(self, ed) -> None:
        super().__init__()
        self.conv = Conv(ed, ed, 7, 1, 3, g=ed, act=False)
        self.conv1 = Conv(ed, ed, 3, 1, 1, g=ed, act=False)
        self.dim = ed
        self.act = nn.SiLU()
    
    def forward(self, x):
        return self.act(self.conv(x) + self.conv1(x))

5. PSA

在这里插入图片描述
  论文中对这个模块并没有原理上的说明,主要是自注意力的开销比较大,所以设计了 PSA(Partial self-attention)对分辨率最低的特征的一半进行计算,将对于全局的学习能力以较小的计算成本融入 YOLO 中。

  Attention 部分的具体计算方式结合下图和代码查看:
在这里插入图片描述

class Attention(nn.Module):
    def __init__(self, dim, num_heads=8, attn_ratio=0.5):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        self.key_dim = int(self.head_dim * attn_ratio)
        self.scale = self.key_dim ** -0.5
        nh_kd = self.key_dim * num_heads
        h = dim + nh_kd * 2
        self.qkv = Conv(dim, h, 1, act=False)
        self.proj = Conv(dim, dim, 1, act=False)
        self.pe = Conv(dim, dim, 3, 1, g=dim, act=False)

    def forward(self, x):
        B, C, H, W = x.shape
        N = H * W
        qkv = self.qkv(x)
        q, k, v = qkv.view(B, self.num_heads, self.key_dim*2 + self.head_dim, N).split([self.key_dim, self.key_dim, self.head_dim], dim=2)

        attn = ((q.transpose(-2, -1) @ k) * self.scale)
        attn = attn.softmax(dim=-1)
        x = (v @ attn.transpose(-2, -1)).view(B, C, H, W) + self.pe(v.reshape(B, C, H, W))
        x = self.proj(x)
        return x

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1806719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (277)-- 算法导论20.3 4题

四、如果调用 vEB-TREE-INSERT 来插入一个已包含在 vEB 树中的元素,会出现什么情况?如果调用 vEB-TREE-DELETE 来删除一个不包含在 vEB 树中的元素,会出现什么情况?解释这些函数为什么有相应的运行状况?怎样修改 vEB 树…

【Git】详解本地仓库的创建、配置以及工作区、暂存区、版本库的认识

一、创建本地仓库 需要将本地仓库放在一个目录下,所以在创建本地仓库之前,应该先创建一个目录,再进入这个目录: 在这个目录中创建一个本地仓库: git init 创建完成后,我们就会发现当前目录下多了一个.git…

ApsaraMQ Copilot for RocketMQ:消息数据集成链路的健康管家

作者:文婷 引言 如何正确使用消息队列保证业务集成链路的稳定性,是消息队列用户首要关心的问题。ApsaraMQ Copilot for RocketMQ 从集成业务稳定性、成本、性能等方面帮助用户更高效地使用产品。 背景 消息队列产品通过异步消息的传递,来…

用Kimi开发部署上线一个完整的Web网页应用

首先问Kimi:我想写一个网页版的计算器应用,如何做项目规划? 根据kimi的回答,选择前端开发技术HTML、CSS、JavaScript,使用HTML和CSS构建基础结构和样式,使用JavaScript添加交互性,实现计算器的核…

Pytorch 实现目标检测二(Pytorch 24)

一 实例操作目标检测 下面通过一个具体的例子来说明锚框标签。我们已经为加载图像中的狗和猫定义了真实边界框,其中第一个 元素是类别(0代表狗,1代表猫),其余四个元素是左上角和右下角的(x, y)轴坐标(范围…

嵌入式仪器模块:音频综测仪和自动化测试软件

• 24 位分辨率 • 192 KHz 采样率 • 支持多种模拟/数字音频信号的输入/输出 应用场景 • 音频信号分析:幅值、频率、占空比、THD、THDN 等指标 • 模拟音频测试:耳机、麦克风、扬声器测试,串扰测试 • 数字音频测试:平板电…

C++笔试强训day42

目录 1.最大差值 2.兑换零钱 3.小红的子串 1.最大差值 链接https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204?tpId182&tqId34396&rp1&ru/exam/company&qru/exam/company&sourceUrl%2Fexam%2Fcompany&difficulty2&judgeSta…

非线性模型预测控制NMPC例子

NMPC概述 非线性模型预测控制(Nonlinear Model Predictive Control, NMPC)是一种用于控制非线性系统的高级控制策略。与线性MPC不同,NMPC需要处理系统的非线性特性,这使得优化问题更加复杂。NMPC通常使用迭代优化算法来求解非线性优化问题 NMPC基本原理 NMPC的目标是最小…

Diffusers代码学习: T2I Adapter

T2I Adapter是一款轻量级适配器,用于控制文本到图像模型并为其提供更准确的结构指导。它通过学习文本到图像模型的内部知识与外部控制信号(如边缘检测或深度估计)之间的对齐来工作。 T2I Adapter的设计很简单,条件被传递到四个特征…

[Cloud Networking] Layer 2 Protocol

文章目录 1. STP / RSTP / MSTP Protocol1.1 STP的作用1.2 STP 生成树算法的三个步骤1.3 STP缺点 2. ARP Protocol3. DHCP Protocol3.1 DHCP 三种分配方式3.2 DHCP Relay (中继) 4. MACSEC 1. STP / RSTP / MSTP Protocol 1.1 STP的作用 消除二层环路:通过阻断冗余…

牛客 NC129 阶乘末尾0的数量【简单 基础数学 Java/Go/PHP/C++】

题目 题目链接: https://www.nowcoder.com/practice/aa03dff18376454c9d2e359163bf44b8 https://www.lintcode.com/problem/2 思路 Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改&#xff…

SpringBoot之Mybatis-plus实战

文章目录 MybatisPlus 介绍一、MyBatisPlus 集成步骤第一步、引入依赖第二步、定义mapper 二、注解TableNameTableldTableField 加解密实现步骤 在SpringBoot项目中使用Mybatis-plus,记录下来,方便备查。 MybatisPlus 介绍 为简化开发而生,官…

【源码】Spring Data JPA原理解析之事务注册原理

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 5、Spring Data JPA自定…

git一次提交多个项目之windows

方案1:【快速】单个/多个项目提交到一个已有地址 步骤: 1,在git仓库,创建新的地址 2,在代码所在文件夹,编辑脚本 2.1,获得所有文件名:编写bat脚本,获得所有文件名称【非必须】; dir *.* /b/s>test.txt 获取所有文件之后,复制对应的文件名; 2.2,编写bat脚…

软件游戏提示msvcp120.dll丢失的解决方法,总结多种靠谱的解决方法

在电脑使用过程中,我们可能会遇到一些错误提示,其中之一就是“找不到msvcp120.dll”。那么,msvcp120.dll是什么?它对电脑有什么影响?有哪些解决方法?本文将从以下几个方面进行探讨。 一,了解msv…

微服务之基本介绍

一、微服务架构介绍 1、微服务架构演变过程 单体应用架构->垂直应用架构一>分布式架构一>SOA架构-->微服务架构 ① 单体应用架构 互联网早期, 一般的网站应用流量较小,只需一个应用,将所有功能代码都部署在一起就可以&#x…

Java面向对象-[封装、继承、多态、权限修饰符]

Java面向对象-封装、继承、权限修饰符 一、封装1、案例12、案例2 二、继承1、案例12、总结 三、多态1、案例 四、权限修饰符1、private2、default3、protected4、public 一、封装 1、案例1 package com.msp_oop;public class Girl {private int age;public int getAge() {ret…

ReactRouter——路由配置、路由跳转、带参跳转、新route配置项

目录 写在前面 (一)初步使用router 1.安装react-router-dom 2.创建router结构 3.嵌套路由 4.配置not found页面 (1)确切路由报错页面 (2)未配置路由报错页面 5.重定向 (二)路由跳转 1.组件跳转 2.NavLink 3.js跳转 (三)传递参数 1.searchParams(query)参数 2…

OS复习笔记ch8-3

驻留集 驻留集:指请求分页存储管理中给进程分配的内存块的集合。 在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。 驻留集,从某种角度可以看成是进程可以常驻内存的内存块的集合。 若驻留集太小,会导致缺页频繁…