Keep and carry on.
post @ 2025-05-28

PFNet

伪装目标分割

定位与聚焦网络,由两个关键模块组成,定位模块PM和聚焦模块FM

image-20250320165149152

  • 给定一张 RGB 图像,我们首先将其输入到 ResNet - 50 [18] 骨干网络中,以提取多级特征

  • 这些特征会进一步输入到四个卷积层进行通道数缩减。

  • 将定位模块(PM)应用于最高层级的特征,以定位潜在的目标物体。

  • 利用多个聚焦模块(FM)逐步发现并去除误报和漏报的干扰因素,从而准确识别伪装物体。

PM:通道注意力模块和空间注意力模块

通道注意力计算

输入特征image-20250320171350675

首先根据chw的矩阵计算QKV,维度为CxN,N=HxW

接下来计算通道注意力图X,对Q和K的转置进行矩阵乘法,然后通过softmax层得到通道注意力图X

image-20250320170105947

Qi :表示矩阵Q的第i行 ,xij衡量的是第j个通道对第i个通道的影响程度。通过这个公式,可以得到一个C×C的通道注意力图X,其中每个元素x*ij表示不同通道之间的关联程度

在得到通道注意力图X后,会让X和V进行矩阵乘法,根据计算出的通道注意力,对V进行加权求和

最后,为了增强容错能力,我们将结果乘以一个可学习的尺度参数 γ,并进行恒等映射操作,以获得最终输出image-20250320171308082

image-20250320171256239

其中,γ 从初始值 1 开始逐渐学习权重。最终的特征F′对特征图通道之间的长距离语义依赖进行了建模,因此比输入特征F更具判别力。

空间注意力计算

对输入F`使用1x1的卷积降低通道,使用三个全新的特征图Q,K,Vimage-20250321102001587

继续进行矩阵乘法运算,并使用softmax归一化来生成空间注意力图

image-20250321102209400

与通道注意力模块类似,将结果乘以一个可学习的尺度参数γ,添加一个跳跃链接,获得最终的输出

image-20250321103114428

FM:聚焦模块设计

​ 由于伪装物体通常与背景外观相似,在初始分割中自然会出现误报和漏报的预测结果。聚焦模块(FM)旨在先发现然后消除这些错误预测。它将骨干网络提取的当前层级特征以及更高层级的预测和特征作为输入,输出优化后的特征和更准确的预测结果

比较模糊区域与确定区域

​ 对更高级的模块预测进行上采样,通过sigmoid进行归一化处理,使用该映射及其反向版本分别对当前层级的特征相乘,生成前景注意力特征和背景注意力特征,将这两种类型的特征分别输入到两个并行的上下文探索模块中进行上下文推理

ce模块设计如下:

image-20250321132323266

​ 有四个上下文探索分支组成,每个分支都包含一个用于通道缩减的3x3卷积层,一个用于局部特征提取的kxk卷积层,分别设置为1,3,5,7,以及一个扩张率为3x3的扩张卷积层(空洞卷积)用于感知上下文信息。

在发现干扰后,通过以下方式进行消除:

image-20250321133500891

其中Fh是来自输入的高级特征,Fr’是来自输出的优化特征,CBR是卷积,归一化,和ReLu的组合,U是双线性上采样,α和β是可学习的缩放参数,通过逐元素减法操作来抑制模糊的背景,并使用逐元素的加法操作来增强缺失的前景

Loss函数设计

PFNet有四个输出预测结果 ,一个来自定位模块(PM),三个来自聚焦模块(FM),对于定位模块,在其输出上施加二元交叉熵损失函数bce和交并比损失Liou,得到公式image-20250321134529089对于聚焦模块,结合了加权二元交叉熵函数和加权交并比损失函数image-20250321134631425促使聚焦模块更加关注可能存在的干扰区域

最后总体的Loss函数为:

image-20250321134714834

阅读此文

摘要

​ Compared to the great progress of large-scale vision transformers (ViTs) in recent years, large-scale models based on convolutional neural networks (CNNs) are still in an early state. This work presents a new large-scale CNN-based foundation model, termed InternImage, which can obtain the gain from increasing parameters and training data like ViTs. Different from the recent CNNs that focus on large dense kernels, InternImage takes deformable convolution as the core operator, so that our model not only has the large effective receptive field required for downstream tasks such as detection and segmentation, but also has the adaptive spatial aggregation conditioned by input and task information. As a result, the proposed InternImage reduces the strict inductive bias of traditional CNNs and makes it possible to learn stronger and more robust patterns with large-scale parameters from massive data like ViTs. The effectiveness of our model is proven on challenging benchmarks including ImageNet, COCO, and ADE20K. It is worth mentioning that InternImage-H achieved a new record 65.4 mAP on COCO test-dev and 62.9 mIoU on ADE20K, outperforming current leading CNNs and ViTs.

作者认为,现如今CNN网络与主流的Vit框架的差距主要在于两个方面:

  1. 多头自注意力机制具有长程依赖性和自适应空间聚集性,vit会比cnn在大量数据上学习到更加强大且鲁棒性高的数据表示。
  2. 从架构角度来说,Vit含有许多CNN不具备的高级组件,如LayerNormalization,FFN ,GELU等。

Method

为了设计一个大规模的CNN网络模型,作者使用一个灵活的卷积变体,命名为DCNv2,并且为其进行了调整以更好适应大规模模型。接着,作者将基本模块与在现如今架构中先进的模块设计结合,最后作者探索了基于DCN的块的堆叠和缩放原理,以构建能够从海量数据中学习强表示的大规模卷积模型。

Deformable Convolution v3

之前的工作[ 21、22、50]已经广泛讨论了CNNs和ViTs的区别。在确定Intern Image的核心算子之前,作者首先总结了正则卷积和MHSA的主要区别。

  1. 长程依赖:尽管人们早已认识到具有较大有效感受野(长程依赖)的模型通常在下游视觉任务上表现更好,但是33的规则卷积核的实际感受野较小,即使是非常深的模型基于CNN的模型也无法获得vit的长程依赖,这限制了他的性能。
  2. 自适应空间聚集:自适应空间聚合是指模型在处理视觉信息时,能够根据输入数据的特征和任务需求,动态调整对不同空间位置信息的聚合方式,而非采用固定的规则。常规卷积是一种具有静态权重和强归纳偏置的算子,例如二维局部性、邻域结构、平移不变性等。这高度归纳的特性使得常规卷积组成的模型可能比vit收敛的更快,所需要的训练数据更少,但也限制了卷积神经网络从大规模数据中学习到更加通用,具有鲁棒性的能力。

弥合卷积和MHSA之间鸿沟的一个简单方法是在正则卷积中引入长程依赖和自适应空间聚合。

可变形卷积 v2(DCNv2)正是这样一种改进的卷积算子,它通过灵活调整采样位置和权重,成为连接卷积与 MHSA 的关键一步。

image-20250523143139013

image-20250523143159510

  • 输入特征图:x 是维度为 (\mathbb{R}^{C \times H \times W}) 的特征图,其中 C 为通道数,(H \times W) 为空间尺寸。

  • 当前像素位置:(p_0 = (i, j)) 表示输出特征图中当前像素的坐标(如二维图像中的行和列)。

  • 采样位置计算:(p_0 + p_k + Delta p_k)

    表示输入特征图中实际采样点的坐标,其中:

    • pk是预定义的固定网格偏移(如 3×3 卷积的 9 个基础位置,如 (p_1 = (-1, -1)) 对应左上角);
    • 是可学习的动态偏移量(如通过训练得到的额外偏移,允许采样点 “变形”)。

在传统应用中,DCNv2 通常作为常规卷积的 “插件” 使用,依赖预训练权重(如 ImageNet 预训练的 ResNet 权重)进行微调:

  1. 参数与内存复杂度高:原始 DCNv2 中每个卷积神经元(如 3×3 卷积的 9 个采样点)拥有独立的投影权重 (w_k \in \mathbb{R}^{C \times C}),参数总量与采样点数量呈线性关系(如 C=256 时,9 个采样点的权重参数达 (9 \times 256 \times 256 = 589,824)),在大规模模型中(如 10 亿参数)会导致内存爆炸。
  2. 训练策略依赖预训练:常规 DCNv2 通过加载预训练权重初始化,再微调优化,这种方式适用于中小规模模型,但大规模基础模型(如需要从 4 亿图像中从头训练)难以依赖预训练,需独立学习参数。
  3. 稳定性问题:原始 DCNv2 中调制标量 (m_k) 使用 Sigmoid 归一化,其和不固定(范围 0 到 K),在大规模数据训练时易导致梯度不稳定,尤其当模型深度和宽度增加时,优化难度显著上升。

作者对DCNv2进行改进以使其适用于大规模训练,主要优化如下:

Sharing weights among convolutional neurons.将wk分解为深度-wise部分和点-wise部分,其中深度部分由mk替代,点部分由所有采样点共享一组投影权重w

image-20250523144849748

Introducing multi-group mechanism.作者受到多头注意力机制的启发,将空间聚合过程拆分为G个组,每个组具有单独的采样偏移量∆pgk和调制尺度mgk,因此单个卷积层上的不同组可以具有不同的空间聚合模式,从而为下游任务提供更强的特征。得到DCN v3

v3的三个优点:

  1. 弥补了卷积网络对长程依赖和适应性空间聚集的不足
  2. 与多头自注意力相比更加节省内存和训练时间
  3. 该算子基于稀疏采样,比之前的方法如MHSA和重新参数化大核等具有更高的计算和内存效率。

InternImage Model

基础模块搭建:与传统的CNN网络不同,internimage的模块设计更加接近于Vit模型,装配了更加先进的组件,包括LN,FFN前馈网络,GELU等

采样偏置和调制尺度通过可分离卷积输入特征x来决定

image-20250523230117215

Stem & downsampling layers:为了获得层次化的特征图,使用卷积主干和下采样层将特征图调整到不同的尺度。stem层将输入分辨率缩小四倍,stem设计如下:image-20250523232840170

下采样模块设计:image-20250523232946614

堆叠方式:采用四阶段堆叠,堆叠规则可概括为以下四点:

  1. 通道数堆叠规则:第i阶段的通道数(C_i)由第 1 阶段通道数(C_1)决定,遵循指数递增,通过指数级通道数增长,匹配视觉任务中特征图分辨率递减的需求
    • stage1:C1
    • stage2:2*C1
    • stage3:4*C1
    • stage4:8*C1
  2. 分组数与通道数关联:

image-20250525155452892

  1. 模块数遵循AABA分布:

image-20250525155538292

  1. 超参数降维

image-20250525155620131

阅读此文
post @ 2025-05-22

MoCo

前置知识:什么是对比学习

将三个图输入到一个神经网络中得到对应的特征向量f1、f2、f3。 在特征空间中f1,f2接近,f1,f3疏远(相似类别接近,不同类别疏远),如果不明白可以先看一下Embedding。为什么要说这个呢?以最左边的猫的图片为例,我们对其进行简单变换(平移、旋转等)得到两个新图(当然这两张图片还是猫!!没有变成狗!!对吧?),这两张新图对应的特征向量,在特征空间中接近,或者说相似度很高。生成新图,通过编码器将它们转化成Embedding向量,训练使,接近,, 疏远这个过程就是对比学习是无监督,可以看出这个编码器是很关键的,它没有因为图片的变动,而把它编码成狗。这可以作为一个pretext,当我们拥有的数据中不带标签的数据要远远大于带标签的数据的时候,我们可以用不带标签的数据进行对比学习的一个初始编码器,此编码器已经掌握了数据中的一部分特性,可以实现聚类功能,然后在用带标签数据进行微调。

  • 数据增强
  • 特征编码器
  • MLP层
  • 目标函数作用阶段,正样本是一个样本和增强样本,负样本是除此之外的所有样本

摘要

我们提出了用于无监督视觉表示学习的动量对比 (MoCo)。从对比学习[29]作为字典查找的角度来看,我们构建了一个带有队列和移动平均编码器的动态字典。这使我们能够动态构建一个庞大且一致的字典,以促进对比无监督学习。MoCo在ImageNet分类的通用线性协议下提供了具有竞争力的结果。更重要的是,MoCo学习的表示可以很好地转移到下游任务。在PASCAL VOC、COCO和其他数据集上的7个检测/分割任务中,MoCo可以优于其有监督的预训练对应物,有时大大超过了它。这表明无监督表示学习和监督表示学习之间的差距在许多视觉任务中很大程度上是接近的。

Introduce

在视觉领域,对比学习需要为样本生成字典,而encoder编码器的作用就是施行字典查找的角色,一个编码的序列应该和它对应的关键字相同而和其他的不同,这样的学习需要被表述为最小化对比损失,从这个角度来看我们应该建立符合以下条件的字典:

  • 庞大一致并且在训练中会持续更新

把字典维护成一个数据样本队列:将当前小批量数据的编码表示入队,最旧的数据表示则出队。

该队列将字典大小与小批量数据的大小解耦,从而使字典能够很大。、

由于字典中的键来自前面的几个小批量数据,提出使用一个缓慢更新的键编码器(它是通过查询编码器基于动量的滑动平均来实现的),以保持一致性。

自监督学习方法通常涉及两个方面:前置任务和损失函数

损失函数

Contrastive losses对比损失:

在表征空间中测量两个样本之间的相似性,不同于固定的类别标签,它的接近目标是不固定的,在训练期间目标会根据网络对数据的表征适时调整,例如在无监督学习的对比学习场景下,会从数据中选取不同样本,这些样本经过网络编码后形成的表征就用于动态确定对比损失中的目标。通过这种方式,对比损失能够更灵活地引导模型学习数据间的相似性和差异性,挖掘数据内在的特征表示。

Adversarial losses对抗损失:

衡量概率分布之间的差异

Pretext Tasks 前置任务

用于辅助模型学习数据特征的任务,虽然这些任务本身并非最终目的,但通过完成他们模型可以学到更有效的数据表示

  • 恢复受损输入的任务:在受损的输入中恢复原始数据,比如去噪自动编码器,它以带有噪声的图像作为输入,目标是去除噪声,还原出原始清晰的图像,在这个过程中,模型学习到图像的特征和结构信息;上下文自动编码器则是通过利用图像的上下文信息来恢复输入,在恢复过程中让模型理解图像不同部分之间的关系;跨通道自动编码器(如彩色化任务),以灰度图像为输入,生成对应的彩色图像,帮助模型学习颜色信息与图像内容之间的关联。
  • 生成伪标签的任务:通过对数据进行特定操作来生成伪标签,以此引导模型学习。对单个(“示例”)图像进行变换,根据变换前后的关系生成伪标签,让模型学习到图像在不同变换下的不变性特征;

Method

把对比学习当做字典查找方法

在对比学习的场景下,我们考虑这样一种情况:有一个经过编码的查询向量q,以及一组同样经过编码的样本k0,k1,k2,…,这些样本充当字典中的键。假设在这组键中,只有一个键(记为k+ )与查询向量q匹配。

在衡量查询向量q和其他的键的相似度时使用对比损失函数InfoNCE

image-20250411153259061

在无监督学习场景下,没有像监督学习那样预先标注好的类别标签,对比损失发挥着关键作用。它通过鼓励编码器网络将来自同一实例的查询(query)和键(key)映射到相似的特征空间,同时将来自不同实例的查询和键映射到不同的特征空间,以此来训练编码器网络。这样,编码器网络就能学习到数据的有效特征表示,为后续任务(如分类、检测等)奠定基础。

动量对比

从上述角度来看,对比学习是一种在图像等高维连续输入上构建离散字典的方法。这里所说的字典具有动态性,体现在两个方面:其一,字典中的键是随机采样得到的;其二,在训练过程中,用于编码这些键的编码器也在不断变化。我们提出这样一个假设:一个能够涵盖丰富负样本的大字典有助于学习到良好的特征,并且在训练过程中,尽管字典键的编码器会不断变化,但应尽可能保持其一致性。基于这一动机,将介绍动量对比(Momentum Contrast,MoCo)方法。

将字典维护成一个数据样本队列。在之前的方法中对比学习常常被视为在高维连续输入上构建离散字典的过程,以往的方法在构建字典时字典大小往往受限于小批量数据的大小,而当把字典当成一个队列时,当前小批量数据的编码表示会被添加到队列中,同时最旧的小批量数据的编码表示会从队列中移除,这种方法会使得字典可以积累多个小批量数据的信息。

动量更新

使用队列可以使字典变大,但同时也会导致反向传播更新key编码器变得棘手

最简单的解决方法是将从查询编码器复制键编码器,忽略梯度问题,但效果不佳

image-20250411172946171

在更新中,只有查询编码器的参数根据反向传播来更新,而键编码器的参数根据动量更新公式来进行调整

其中m接近1,则证明键编码器的参数很大程度上会保留原有的信息,只有少部分被查询编码器更新

实验发现,相对较大的动量值(如默认的(m = 0.999) )比小动量值(如(m = 0.9) )效果好得多。这表明在利用队列进行字典构建和对比学习时,让键编码器缓慢演变是核心要点。缓慢演变的键编码器能够更好地维护队列中键表示的一致性,从而提升模型在无监督视觉表征学习任务中的性能。如果动量值较小,键编码器变化相对较快,就难以保证不同小批量数据编码得到的键之间的一致性,导致模型性能下降。

image-20250411191442286

三种不同的更新策略:

  • 端到端:字典大小与mini-batch size严格相关,受限于GPU显存
  • 记忆库:库中信息的更新不符合一致性
  • MoCo

前置任务

正负样本:如果来自同一张图片的增强样本,则就是正样本对,否则是负样本对

分别用编码器(f_{q})和(f_{k})对同一图像的不同增强 “视图” 进行编码,并通过对比损失优化编码器参数。如果 q 和 k 来自不同图像,对比损失的优化方向可能会偏离学习同一图像不同特征表示相似性的目标,导致模型难以准确学习到图像的有效特征表示。在文中所采用的方法里,会对同一幅图像进行随机数据增强操作,产生两个不同的随机 “视图”,一个 “视图” 用于生成 q,另一个 “视图” 用于生成 k

image-20250411194009287

技术细节

采用resnet作为编码器,在全局平均池化后其最后一个全连接层会输出固定维度128的向量,在输出向量后会根据L2范数进行归一化处理,处理后的向量就作为查询或者键的表征。loss函数中的温度超参数 τ 设置为 0.07(参照文献 [61]) 。数据增强的设置:从随机调整大小后的图像中裁剪出 224×224 像素的区域,接着对其进行随机颜色抖动、随机水平翻转以及随机灰度转换操作,这些操作都可通过 PyTorch 的 torchvision 工具包来实现。

单纯的使用BN会让学习表征的效果变差,所以突出新的BN策略

shuffling BN:

在训练时,使用对个GPU,对每个GPU上的样本独立进行BN操作,对于键编码器,在将当前小批量样本分配到各个GPU之前,打乱样本顺序,而查询编码器的小批量样本顺序则保持不变,这样做可以确保用于计算查询和其正样本键的批量统计信息来自两个不同的子集。

阅读此文

SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation

摘要

We present SegNeXt, a simple convolutional network architecture for semantic segmentation. Recent transformer-based models have dominated the field of semantic segmentation due to the efficiency of self-attention in encoding spatial information. In this paper, we show that convolutional attention is a more efficient and effective way to encode contextual information than the self-attention mechanism in transformers. By re-examining the characteristics owned by successful segmentation models, we discover several key components leading to the performance improvement of segmentation models. This motivates us to design a novel convolutional attention network that uses cheap convolutional operations. Without bells and whistles, our SegNeXt significantly improves the performance of previous state-of-the-art methods on popular benchmarks, including ADE20K, Cityscapes, COCO-Stuff, Pascal VOC, Pascal Context, and iSAID. Notably, SegNeXt outperforms EfficientNet-L2 w/ NAS-FPN and achieves 90.6% mIoU on the Pascal VOC 2012 test leaderboard using only 1/10 parameters of it. On average, SegNeXt achieves about 2.0% mIoU improvements compared to the state-of-the-art methods on the ADE20K datasets with the same or fewer computations. Code is available.

Method

卷积encoder

使用MSCA来替代原本的注意力机制encoder,主要分为三个部分:

  • 深度卷积来提取局部信息
  • 多分支深度卷积来捕获多尺度上下文
  • 1X1卷积来建模不同通道的关系

1x1卷积的输出被当作注意力权重来直接和输入相乘
image-20250409164933938

Att和模型的输入进行元素级别的相乘

image-20250409170139279

将一系列的块进行堆叠就形成了论文提出的卷积编码器MSCAN

采用等级制度结构,包含了四个递减的空间分辨率,H/4 × W/4 , H/8 × W/8 , H/16 × W/16 和 H/32 × W/32

每个阶段都包含了一个下采样模块

image-20250409171938063

Decoder

三种解码器可以选择:

  • segformer 解码器,纯MLP
  • 直接输入到解码头
  • 从最后三个阶段聚合特征,并使用轻量级Hamburger[21]进一步建模全局上下文。结合我们强大的卷积编码器,我们发现使用轻量级解码器可以提高性能计算效率。这也是论文所采用的解码器模式
阅读此文
post @ 2025-01-22

操作系统概述

什么是操作系统

计算机硬件:极简的公理系统(导线,逻辑门,时钟,触发器)就能够支持非常复杂的数字系统设计

硬件和软件的中间层,需要了解全部但不要细究全部

  • 对单机作出抽象
  • 支撑多个程序执行

从应用角度看操作系统

image-20250117155511892

image-20250117164939996

可以对如图所示的文件进行gcc的编译得到想要的exe可执行文件,也可以使用gcc -c来先进行编译得到.o文件在进行链接

image-20250117165106573

在计算机中,我们所有的指令都是计算和move指令,并没有关闭程序和关闭计算机的指令,那么这个过程是谁在发挥作用?

答案是操作系统。

借助操作系统来实现

1
2
3
movq $SYS_exit , %rax #exit(
movq $1, %rdi # status=1
syscall #);
  • 将系统调用的参数放到寄存器中
  • 执行sys call,操作系统接管程序,操作系统可以任意改变程序状态(甚至终止程序)

应用程序=计算+操作系统API

举例说明:

  • 窗口管理器:能直接管理屏幕设备

    ​ 能够和其他进程通信

  • 任务管理器:能访问操作系统提供的进程对象

  • 杀毒软件:文件静态扫描(read),主动防御(ptrace)

操作系统的职责:提供令应用程序舒适的抽象(对象+API)

理解高级语言程序

  • C语言代码经过编译之后得到二进制文件,执行二进制文件就可以依次执行指令,每次执行一条“语句”

  • 在使用gdb调试代码的时候可以发现,c语言文件的执行也是一种状态机,那我们可以试着使用c语言的源代码来模拟状态机的运行,也就是将代码写成“simpleC”

状态机是拥有严格数学定义的对象,这意味着可以用定义的方式写出来

  • 状态就是各种栈帧和全局变量的组合。
  • 初始状态下仅有一个栈帧(main函数栈帧),且全局变量均为初始值(PC=0)
  • 状态迁移:执行栈帧PC

试图把c代码改写成simpleC:

  • 每一条语句至多一次运算
  • 条件语句中不包含运算

编译器与编译优化

  • 编译器的输入:高级c语言代码
  • 编译器的输出:汇编代码(指令序列)
  • 编译器就是状态机之间的翻译器

simpleC翻译

  • 运算:把操作数load到寄存器,执行运算,store写回结果
  • 分支/循环:使用条件跳转分别执行代码
  • 函数调用:专门留一个寄存器给栈(SP,stackPointer),将stackframe的信息保存到内存中

编译优化三板斧

  • 函数内联: 将函数调用替换为函数体本身的内容
  • 常量传播:在编译时计算常量表达式的值并替换
  • 死代码消除:删除永远不会执行到的代码

那么给定两个程序A和B,编译器到底允不允许把A编译成B?

  • 首先要看在syscall调用后两个程序的输出序列是否一致,如果一致则可以编译。
  • 系统调用是使程序计算结果可见的唯一方法,不改变语义=不改变可见结果,即如果两个状态机生成的所有syscall序列是完全一致的,则该优化就是允许的。

C语言代码中不可优化的部分

  • External function calls外部函数调用(链接时才能确定到底是什么代码)

    • 未知的代码可能包含系统调用
    • 因此不可删除,移除循环等,且要保证参数传递完全一致
  • 编译器提供的不可优化标注

从硬件视角理解操作系统

计算机系统的状态机模型

  • 状态:内存,寄存器的数值

​ 寄存器,内存

1
2
3
4
5
struct CPUstate{
uint32_t regs[32],csrs[CSR_COUNT];
uint8_t *mem;
uint32_t mem_offset,mem_size;
};

​ 还有外部世界的态:

  1. ​ 设备上的寄存器
  2. ​ 中断指令Interrput
  3. ​ 客观存在但计算机系统不能直接访问,进程只能通过syscall访问外部输入
  • 初始状态:由系统设计者规定(CPU Reset)
  • 状态迁移:从PC取指令执行

计算机系统中的固件

reset是计算机硬件和系统程序员的第一个接口,如果想要在裸机上面进行编程,只需要做一个电路并在适当的内存放上适当的代码,只要cpu在reset状态,就执行相应的某些代码,此时就可以对cpu进行控制。

什么是固件?

  • 固件就是厂商在出厂时放置到计算机系统里的代码

    • 之所以称之为固件是因为早期时rom,如果想要更新升级则需要换芯片
  • 固件的功能:

    • 运行程序前的计算机系统配置
    • CPU电压,内存时序,接口开关等
  • 不严格的说,加载操作系统

    • 加载存储设备上的引导程序
  • 可以成为一个小的操作系统,在CPU reset后初始化硬件,对接操作系统Boot Loader

  • 又称为BIOS(basic io system)

  • 现在有了UEFI(undefined extensible firmware interface)固件提供更丰富的支持如指纹锁蓝牙键盘等

阅读此文
post @ 2024-11-04

Tarjan算法(寻找强连通分量)

铺垫:连通图的两种DFS遍历方式, 在tarjan算法中采用方式2来遍历顶点image-20240911155905523

顶点X(i,j)其中

i:DFS中x点被访问的时间点

j: x通过可以回溯到的最早时间点

image-20240911162218892

1129 颜色交替的最短路径

给定一个整数 n,即有向图中的节点数,其中节点标记为 0n - 1。图中的每条边为红色或者蓝色,并且可能存在自环或平行边。

给定两个数组 redEdgesblueEdges,其中:

  • redEdges[i] = [ai, bi] 表示图中存在一条从节点 ai 到节点 bi 的红色有向边,
  • blueEdges[j] = [uj, vj] 表示图中存在一条从节点 uj 到节点 vj 的蓝色有向边。

返回长度为 n 的数组 answer,其中 answer[X] 是从节点 0 到节点 X 的红色边和蓝色边交替出现的最短路径的长度。如果不存在这样的路径,那么 answer[x] = -1

示例 1:

1
2
输入:n = 3, red_edges = [[0,1],[1,2]], blue_edges = []
输出:[0,1,-1]

示例 2:

1
2
输入:n = 3, red_edges = [[0,1]], blue_edges = [[2,1]]
输出:[0,1,-1]

提示:

  • 1 <= n <= 100
  • 0 <= redEdges.length, blueEdges.length <= 400
  • redEdges[i].length == blueEdges[j].length == 2
  • 0 <= ai, bi, uj, vj < n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def shortestAlternatingPaths(self, n, redEdges, blueEdges):
"""
:type n: int
:type redEdges: List[List[int]]
:type blueEdges: List[List[int]]
:rtype: List[int]
"""
e=[[] for i in range(n)]
for x,y in redEdges:
e[x].append((y,0))
for x,y in blueEdges:
e[x].append((y,1))

ans=[-1]*n
vis={(0,0),(0,1)}
q=[(0,0),(0,1)]
distance=0
while q:
tmp=q
q=[]
for x,y in tmp:
if ans[x]==-1:
ans[x]=distance
for p in e[x]:
if p[1]!=y and p not in vis:
vis.add(p)
q.append(p)
distance+=1
return ans

解题思路:在路径长度都为一情况下,可以用广度优先遍历一遍便是到各个点的最短路径

前缀和

560.和为k的数组个数

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数

子数组是数组中元素的连续非空序列。

示例 1:

1
2
输入:nums = [1,1,1], k = 2
输出:2

示例 2:

1
2
输入:nums = [1,2,3], k = 3
输出:2

提示:

  • 1 <= nums.length <= 2 * 104

  • -1000 <= nums[i] <= 1000

  • -107 <= k <= 107

    解题思路:

    用map映射前缀和值和出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int count = 0, pre = 0;
for (auto& x:nums) {
pre += x;
if (mp.find(pre - k) != mp.end()) {
count += mp[pre - k];
}
mp[pre]++;
}
return count;
}
};

238.除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

不要使用除法,且在 O(n) 时间复杂度内完成此题。

示例 1:

1
2
输入: nums = [1,2,3,4]
输出: [24,12,8,6]

示例 2:

1
2
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]

提示:

  • 2 <= nums.length <= 105
  • -30 <= nums[i] <= 30
  • 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内

进阶:你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int l=nums.size();
vector<int> L(l,0),R(l,0);
L[0]=1;
for(int i=1;i<l;i++){
L[i]=L[i-1]*nums[i-1];
}
R[l-1]=1;
for(int i=l-2;i>=0;i--){
R[i]=R[i+1]*nums[i+1];
}
vector<int>ans(l,0);
for (int i=0;i<l;i++){
ans[i]=L[i]*R[i];
}
return ans;
}
};

优先队列

在C++中,priority_queue是一个容器适配器,它提供了常数时间的最大元素查找。它通常实现为堆。堆是一种数据结构,其中最大(或最小)元素始终位于顶部。priority_queue是一个模板类,定义在头文件中。它有三个模板参数:元素类型、容器类型和比较函数类型(可选)。默认情况下,它使用std::vector作为其底层容器 。

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

1
2
3
4
5
6
7
8
9
10
11
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

示例 2:

1
2
输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<pair<int, int>> q;
for (int i = 0; i < k; ++i) {
q.emplace(nums[i], i);
}
vector<int> ans = {q.top().first};
for (int i = k; i < n; ++i) {
q.emplace(nums[i], i);
while (q.top().second <= i - k) {
q.pop();
}
ans.push_back(q.top().first);
}
return ans;
}
};

动态规划

53.最大子数列和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组

是数组中的一个连续部分。

示例 1:

1
2
3
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

1
2
输入:nums = [1]
输出:1

示例 3:

1
2
输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto &x: nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
};

代码

测试用例

测试结果

测试结果

56.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

示例 1:

1
2
3
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

1
2
3
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

思路:先对vector进行排序,再根据题意插入

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.size() == 0) {
return {};
}
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (int i = 0; i < intervals.size(); ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (!merged.size() || merged.back()[1] < L) {
merged.push_back({L, R});
}
else {
merged.back()[1] = max(merged.back()[1], R);
}
}
return merged;
}
};

快慢指针

234.回文列表

给你一个单链表的头节点 head ,请你判断该链表是否为

回文链表

。如果是,返回 true ;否则,返回 false

示例 1:

img

1
2
输入:head = [1,2,2,1]
输出:true

示例 2:

img

1
2
输入:head = [1,2]
输出:false

提示:

  • 链表中节点数目在范围[1, 105]
  • 0 <= Node.val <= 9

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

尝试用O(1)的空间复杂度进行解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr) {
return true;
}

// 找到前半部分链表的尾节点并反转后半部分链表
ListNode* firstHalfEnd = endOfFirstHalf(head);
ListNode* secondHalfStart = reverseList(firstHalfEnd->next);

// 判断是否回文
ListNode* p1 = head;
ListNode* p2 = secondHalfStart;
bool result = true;
while (result && p2 != nullptr) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}

// 还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}

ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}

ListNode* endOfFirstHalf(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next; //快慢指针,慢指针一次移动一步,快指针一次移动两步
slow = slow->next;
}
return slow;
}
};

阅读此文

前置知识

海森矩阵

是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率。在工程实际问题的优化设计中,所列的目标函数往往很复杂,为了使问题简化,常常将目标函数在某点邻域展开成泰勒多项式来逼近原函数,此时函数在某点泰勒展开式的矩阵形式中会涉及到黑塞矩阵。

对于一个函数

image-20241024095230966

如果f的所有二阶导数都存在,那么f的海森矩阵为:

image-20241024095330522

将二元函数的泰勒展开式推广到多元函数,则在一点处的泰勒展开的矩阵形式为:

image-20241024100815506

其中image-20241024101048882是函数在X0点的梯度,G(X0)就是海森矩阵。

海森矩阵是由目标函数在x点处的二阶偏导数组成的n*n阶对称矩阵

  • 如果海森矩阵是一个正定矩阵,则临界点是一个局部极小值
  • 如果海森矩阵是一个负定矩阵,则临界点是一个局部极大值
  • 如果是不定矩阵,则临界点处不是极值

牛顿法优化问题

牛顿法的基本思想是利用迭代点Xk处的一阶导数(梯度)和二阶导数(Hessen矩阵)对目标函数进行二次函数近似,然后把二次模型的极小点作为新的迭代点,并不断重复这一过程,直至求得满足精度的近似极小值。牛顿法的速度相当快,而且能高度逼近最优值。

将一个需要求解的损失函数函数f(x)进行泰勒展开到二阶,得到

image-20241024104750653

对上式求关于x的导数并令其等于0

image-20241024104853712

那么求得极值点的x坐标为image-20241024104946040

这就是牛顿法的更新公式

牛顿法的精髓就是二阶收敛,不仅利用了损失函数的一阶偏导数,也用到了损失函数的二阶偏导数,即梯度变化的趋势,因此比梯度下降法更快的确定合适的搜索方向,具有二阶收敛速度。

牛顿法的缺点:

  • 计算复杂
  • 对函数有较为严格的要求,必须具有连续的一阶和二阶偏导数,海森矩阵必须是正定的

LBFGS优化算法

在一些经典的优化算法中,比如梯度下降,SGD,Adam等都是在一阶法的基础上进行改进,加快收敛的速率。而牛顿法则是利用二阶法,在收敛速度上远快于一阶,但如果目标函数非凸时,二阶法可能收敛到鞍点。针对二阶法存在的问题提出了BFGS和低存储的LBFGS方法

阅读此文
post @ 2024-10-23

斯坦福吴恩达2022机器学习

简单的决策树模型

image-20241023140523587

类似于完全二叉树,有根节点和叶节点

模型建立的两个关键点:

  • 如何决定分割的特征?
  • 如何判断叶节点的分类结果好坏?

纯度测量

用熵来作为纯度的度量

image-20241023144121398 image-20241023144347508

当结点中的样本全为猫时,p1=1,此时熵的值为0,当样本中全为狗时同理

当结点中的样本为一半猫一半狗时,p1=0.5,此时熵最高为1

image-20241023144944385

在p=0的情形中,log(0)是无法计算的,那么我们需要提前约定0log(0)=0,才能正确计算熵

image-20241023145116195

选择分离的特征

image-20241023145853878

在上图中,每个特征筛选出来的分支都有各自的纯度熵,那么如何根据这些熵来判断哪个特征分类情况最好呢?我们使用每个特征的熵的加权平均来作为评估方式。

image-20241023151127603

在耳朵形状这一特征中,五个样本被分到了立耳,五个样本被分到了塌耳,那么则可通过上图公式1来进行加权平均计算出式子2

根节点的熵为H(5/10)=1,因为根节点在用特征分类前是十个样本,其中五个样本是猫。

image-20241023151728440

最后的评估指标等于根节点的熵减去式子2得到熵的减少,即拆分信息增益,分类特征2和3同理,选择熵的减少量最大的特征。

image-20241023190739340

随机森林算法

有随机抽样的样本来构建不同的树构成森林

image-20241102153124412

For b=1 to B:

​ Use sampling with replacement to create a new training set of size m Train a decision tree on the new dataset

信息增熵

image-20241104143614684信息熵的计算公式

pk为当前样本集合D中第k类样本所占的比例

信息熵的值越小则D的纯度越高

image-20241104144112122 image-20241104144006739

信息增益越大,则样本在该属性下的纯度提升越大,属性选择更好

信息增益准则对取值较多的属性有所偏好,仅仅采用增益来选取合适的属性会导致决策树的泛化能力很差

增益率

image-20241104145551003

image-20241104145716126为属性a的固有值

属性a的取值数目越多则固有值越大

增益率对取值可能较少的属性有所偏好,在C4.5算法中并不是直接取增益率最大的候选划分属性,而是使用了启发式:先从候选属性中选择信息增益高于平均水平的属性集合,再从该集合中选择增益率最高的属性

CART决策树

classification and regression tree 分类和回归都可用

使用基尼指数来选择划分属性

数据集D的纯度可用基尼值来度量image-20241104151327634

Gini反应了从数据集D中随机抽取两个样本其类别标记不一样的概率

属性a的基尼指数定义为image-20241104152734663

选择基尼指数最小的属性

XGBoost

阅读此文

课程是斯坦福吴恩达2022机器学习

交叉验证集的预测误差指标

image-20241022162017194
  • TP:True Positive预测的分类和实际分类相符且均为正例
  • TN:True Negative预测的分类和实际分类相符且均为负例
  • FP:False Negative预测的分类和实际分类不符,且预测的是正例
  • FN:False Negative预测的分类和实际分类不符,且预测的是负例

预测精确度的计算:

image-20241022162508083

对于精确度我们可以理解为,假设在病人预测中,我们用模型预测的有病的个体中,实际真的患病的人占预测有病的人数的多少,也就是我们模型的预测正例正确的样本数占预测为正例的样本数的多少。

Recall指标:

image-20241022162717945

recall指标可以理解为在所有确实患病的样本中,我们的模型正确预测出了多少样本,即预测正例且正确的样本数占实际为正例的样本数的多少。

精确度和recall之间的权衡

image-20241022164144857

假设在病人患病预测中,我们使用罗杰斯蒂回归来预测病人患病的概率,函数返回值大于0.5则预测为正例,若函数返回值小于0.5则预测为负例。但如果患病的话会接受非常痛苦且昂贵的治疗,且患病不治疗的后果并没有那么严重,那么我们可以想要让模型在预测为正例时更加谨慎些,即提高阈值到0.7。

在提高阈值之后,我们在模型预测为正例的样本中,实际患病的样本数占正例样本更多了,因为我们预测的非常谨慎,那么预测中真的患病的人数也会占更多数,此时我们的精确率会提高。但是相反的,在所有真正患病的样本中,我们预测的患病人数占的比例也少了,recall指标会下降。

同样,如果假设患病治疗费用和代价很低,但不治疗后果严重,那么我们希望模型在预测患病时没那么严谨,来保证更多的病人被正确预测出疾病,那么就可以降低阈值,此时精确度降低但recall升高。

F1 score

如何让模型自动选择合适的权衡?

image-20241022170135926

在这三个算法中,各有优点很难根据精确度和recall评判哪个是最优的算法,这时就需要F1Score帮助

image-20241022170421858

F1Score更高的算法会被选择

阅读此文
post @ 2024-10-18

课程是b站刘二大人的课,算是速成吧,基础知识不是很多,专注于应用,中间不懂得可以再补

GoogleNet

image-20241018145146213

Inception Module

image-20241018144916751

上图的Inception模型是GoogleNet模型中的小块,红色圈中即为Inception

Concatenate将每一路径得到的张量拼接成一个张量

在进行Average Pooling池化时,可以进行相应的padding和stride来保证张量的w和h不会改变

1*1卷积核的作用是什么?

在1*1卷积核中,我们可以对不同通道的相同位置的像素值进行计算并加和,有此将CxWxH的张量转化为1xWxH的张量,实现降通道操作,具体图示如下:

image-20241018150809527

为什么需要1*1的卷积核?

可以对图像数据进行降通道的操作,在卷积运算中显著的减少运算量

image-20241019132722039

再上图的卷积运算中,我们将192x28x28的数据卷积成32x28x28的数据,需要进行的运算操作数为
$$
5^2\times28^2\times192\times32=120422400
$$
image-20241019133516579

如果先对原数据进行1*1的卷积降低通道数,再进行5x5卷积,所需的运算操作数为
$$
1^2\times28^2\times192\times16+5^2\times28^2\times16\times32=12433648
$$
由此可见运算操作数明显减少

代码实现及注释

image-20241019150142932

池化层对应代码如下:

1
2
3
4
self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)

branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)

image-20241019150259362

1x1卷积层对应代码如下:

1
2
3
self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)

branch1x1 = self.branch1x1(x)

image-20241019150648224

5x5卷积层对应代码如下:

1
2
3
4
5
self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)

branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)

image-20241019151101886

3x3卷积层对应代码如下:

1
2
3
4
5
6
7
self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

最后对每一块得到的卷积张量在通道维度进行合并形成新的张量

1
2
outputs = [branch1x1, branch3x3, branch5x5, branch_pool]
return torch.cat(outputs, dim=1) # 将卷积结果按照通道维度进行拼接

dim指定在某一维度进行合并张量,包含(batch,通道,宽,高)四个维度

总体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class InceptionA(torch.nn.Module):
def __init__(self, in_channels):
super(InceptionA, self).__init__()
# 1x1块
self.branch1x1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5块
self.branch5x5_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch5x5_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 3x3块
self.branch3x3_1 = torch.nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 池化块
self.branch_pool = torch.nn.Conv2d(in_channels, 24, kernel_size=1)

def forward(self, x):
branch1x1 = self.branch1x1(x)

branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)

outputs = [branch1x1, branch3x3, branch5x5, branch_pool]
return torch.cat(outputs, dim=1) # 将卷积结果按照通道维度进行拼接

image-20241020154033087

残差网络Resnet

随着神经网络层数的不断增加,在反向传播的过程中可能出现梯度爆炸或者梯度消失的情况

为什么会出现梯度消失或者梯度爆炸?

现在假设一个模型,其中连接层数为4层,反向传播(Backpropagation)用于计算每一层的权重 WWW 的梯度。这是通过链式法则计算的。假设神经网络的层数从1到4,每一层的输入、权重和激活函数可以定义如下

image-20241020163910140

在计算离输入层较近的层的梯度时,会发现梯度的计算来源于上层的计算总和

image-20241020184804511

链式法则是一个连乘的操作,在多次乘积操作后,最后一层的梯度可能趋近于零,也就是梯度消失,但若偏导数值大则会出现梯度爆炸的情况。

梯度消失/梯度爆炸的解决方法

  1. 梯度剪切:针对于梯度爆炸的情况,将梯度限制在一个限定的阈值内,如果更新梯度时梯度超过了阈值则将梯度改为阈值的边界,防止梯度过大的情况出现。

  2. 权重正则化:通过对权重做正则化来避免过拟合的情况出现,在梯度爆炸是权重的值可能会非常高,用正则化项来限制权重的大小,防止梯度爆炸的情况发生。

    image-20241021141852661

    其中α为正则化项的系数

  3. 用Relu代替sigmoid作为激活函数:使用sigmoid函数作为激活函数时梯度值小于等于0.25,在连乘操作后显然会出现梯度消失或梯度爆炸的问题,但如果我们使用Relu函数作为激活函数,在对激活函数求导时,大于0的部分导数是恒等于1的,连乘不会出现梯度消失问题。

  4. Batchnorm:通过规范化操作将输出信号x规范化到均值为0,方差为1保证网络的稳定性。

  5. 残差网络

残差网络如何解决梯度消失问题?

image-20241021145340356

在原始的堆叠模型中,我们将需要的底层映射结果设为H(x),则H(x)=F(x)

但是在残差学习块中,我们假设需要的底层映射结果仍为H(x),在堆叠的非线性层我们让其训练另一个映射:F(x)=H(x)-x,则原始的底层映射结果改变为H(x)=F(x)+x,那么在上文的反向传播梯度计算中,公式可变为:

image-20241021151441848

由此可见,连乘的因式有大于一的项,能够有效避免梯度消失问题。

如果还没有理解可以参考链接:http://www.atait.se.ritsumei.ac.jp/AIArc/WangZC/ResNet.pdf

模型实现及代码复现

image-20241021160358150

与原始的堆叠模型不同的是,在堆叠块中添加跳连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ResidualBlock(torch.nn.Module):
def __init__(self, channels):
super(ResidualBlock, self).__init__()
self.channels = channels
self.conv1 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1) # padding来保证维度不会改变
self.conv2 = torch.nn.Conv2d(channels, channels, kernel_size=3, padding=1)

def forward(self, x):
y = F.relu(self.conv1(x))
y = self.conv2(y)
return F.relu(x + y)


class ResNet(torch.nn.Module):
def __init__(self):
super(ResNet, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 16, kernel_size=5)
self.conv2 = torch.nn.Conv2d(16, 32, kernel_size=5)
self.mp = torch.nn.MaxPool2d(2)
self.rblock1 = ResidualBlock(16)
self.rblock2 = ResidualBlock(32)
self.fc = torch.nn.Linear(512, 10)

def forward(self, x):
in_size = x.size(0)
x = self.mp(F.relu(self.conv1))
x = self.rblock1(x)
x = self.mp(F.relu((self.conv2)))
x = self.rblock2(x)
x = x.view(in_size, -1)
return self.fc(x)
阅读此文
⬆︎TOP