news 2026/9/8 9:06:46

从ViT到Swin Transformer:视觉注意力机制的核心演进与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从ViT到Swin Transformer:视觉注意力机制的核心演进与工程实践

先问一个问题:你已经把 Transformer 的公式背下来了,也知道 Q、K、V 分别是什么,但合上电脑之后,是不是仍然解释不清“为什么图像可以像句子一样被 Transformer 处理”,也说不出 ViT 和 Swin Transformer 到底差在哪里?

如果是,这篇文章就是为你写的。

Transformer 从 NLP 杀入计算机视觉,并不是简单地把文本 token 换成图像 patch。它真正改变的,是视觉模型理解图像的方式:从“局部卷积堆叠”变成了“全局关系建模”。而 ViT 和 Swin Transformer,分别是这条技术路线上的两个关键里程碑。只学其中任意一个,都会留下认知断层。

本文会沿着“注意力机制 → ViT → Swin Transformer”这一条主线,把两者串起来讲清楚。不需要你提前掌握多少数学,但希望你能带上一张纸一支笔,因为有些关键点需要亲手推一遍才记得住。

1. 这篇文章要讲通什么:为什么视觉 Transformer 值得成套学

很多人的学习路径是这样的:先看 Attention is All You Need,勉强理解了 Self-Attention 的点乘公式;然后打开 ViT 论文,发现图像被切成 16×16 的 Patch 当 Token;再切换到 Swin Transformer,发现它又在窗口内做注意力。每一步都像是“看懂了”,但知识是散的,串不起来。

问题出在哪?在于大多数人学的是“单个模型怎么跑”,而不是“注意力机制为什么能在视觉上行得通”。如果只记公式,你会发现 ViT 和 Swin 的注意力公式几乎一样,根本说不清 Swin 为什么要设计移动窗口;如果只背结论,你又很难在真实项目里判断该用 ViT 还是 Swin。

这篇文章要做的,是把这条线理顺:

  • 注意力机制解决的是“长距离依赖”问题,这是 CNN 的短板;
  • ViT 验证了“图像可以直接当序列建模”,但代价是需要大量数据;
  • Swin Transformer 通过窗口注意力重新引入视觉先验,让 Transformer 在中小数据集上也变得可用。

学完这篇文章,你应该能回答三个问题:图像是如何变成 Token 的?位置编码到底加在哪里?Swin 的移动窗口为什么比 ViT 的全局注意力更适合做视觉任务?

2. 注意力机制:Transformer 改变视觉的第一块基石

2.1 从“全局依赖”说起:为什么 RNN 和 CNN 都不够

在 Transformer 出现之前,序列建模主要靠 RNN/LSTM,图像建模主要靠 CNN。两者有个共同点:处理信息的路径是局部的。

RNN 处理长句子时,信息要一步一步往后传。遇到“我昨天在公园遇到的那只狗,今天又跑来我家门口叫了很久”这种句子,RNN 往往记住前半句就忘了后半句。CNN 处理图像时,卷积核只能在局部窗口内滑动。想要让两个相隔很远的像素产生关联,必须通过很多层卷积一步步扩大感受野,路径太长,信息容易丢失。

Transformer 的思路完全不同:它让序列中任意两个位置直接建立关联。只要网络输入了“狗”这个位置的向量和“门口”这个位置的向量,两者就能在一步之内完成信息交互。这种“全局任意位置直达”的能力,就是自注意力机制的核心价值。

2.2 Self-Attention 到底在算什么:用“问、查、取”理解 QKV

Self-Attention 里有三个核心向量:Query、Key、Value。初学者很容易被这三个名字吓到,其实可以换一种说法:

  • Query 是“我要找什么”;
  • Key 是“我有什么标签”;
  • Value 是“我实际携带的内容”。

整个注意力的计算过程,可以类比成在图书馆查资料:你带着一个问题(Query),去和每本书的索引标签(Key)比对,算出哪些书最相关;然后把最相关的那几本书里的内容(Value)抽取出来,加权合并成最终答案。

写成公式就是:

Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V

这里的 sqrt(d_k) 是缩放因子,作用是防止 Q 和 K 的点积结果过大,把 softmax 推向梯度饱和区。

在视觉场景里,这个过程同样直观。一张图片被切分成若干个 Patch 后,每个 Patch 既提供自己的内容描述,也充当其他 Patch 的检索对象。某个 Patch 想知道自己周围有什么,就会拿自己的 Query 去查其他 Patch 的 Key,再把相关性高的 Value 加权回来。经过这一层,模型就知道“这个 Patch 应该关注哪些区域”。

2.3 多头注意力:不是一种注意力,而是多组视角并行

单个注意力头只能表达一种“关联方式”,但图像中的关系是多样的:可能有颜色关系、有形状关系、有空间位置关系。为了捕捉多种关系,Transformer 引入了多头注意力(Multi-Head Attention)。

多头注意力做的事很简单:把 Q、K、V 分别切成多份,每份独立做一次注意力计算,最后把结果拼起来再过一层线性映射。

import torch import torch.nn as nn class MultiHeadSelfAttention(nn.Module): def __init__(self, embed_dim=768, num_heads=8): super().__init__() self.num_heads = num_heads self.head_dim = embed_dim // num_heads self.qkv = nn.Linear(embed_dim, embed_dim * 3) self.proj = nn.Linear(embed_dim, embed_dim) def forward(self, x): # x: (B, N, D),N 是序列长度,D 是特征维度 B, N, D = x.shape qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim) qkv = qkv.permute(2, 0, 3, 1, 4) q, k, v = qkv[0], qkv[1], qkv[2] attn = (q @ k.transpose(-2, -1)) * (self.head_dim ** -0.5) attn = attn.softmax(dim=-1) x = attn @ v x = x.transpose(1, 2).reshape(B, N, D) return self.proj(x)

这里要注意,embed_dim 必须能被 num_heads 整除,否则最后一个 reshape 会报维度错误。这在写代码时也是很容易踩的第一个坑。

多头注意力的意义在于:每个头可以关注不同的关系模式,比如一个头关注前景物体,另一个头关注背景纹理。多个头组合起来,模型就有了同时建模多种关系的能力。

3. ViT:把图像当成“16×16 个词的句子”

3.1 图像怎么变成序列:Patch Embedding

ViT(Vision Transformer)的核心思想非常直接:图像不是序列,但可以把它切成小块,再把每个小块当成一个 Token,这不就变成序列了吗?

假设输入图像是 224×224×3,patch_size 设为 16,那么图像会被切成 (224/16) × (224/16) = 14×14 = 196 个 Patch。每个 Patch 经过线性投影后变成一个向量,比如 768 维。这样,原始图像就变成了一个长度为 196 的 Token 序列。

在代码实现中,Patch Embedding 通常直接用卷积完成:

import torch import torch.nn as nn class PatchEmbed(nn.Module): def __init__(self, in_channels=3, embed_dim=768, patch_size=16): super().__init__() self.patch_size = patch_size self.proj = nn.Conv2d( in_channels, embed_dim, kernel_size=patch_size, stride=patch_size ) def forward(self, x): # x: (B, C, H, W) B, C, H, W = x.shape x = self.proj(x) # (B, embed_dim, H/patch, W/patch) B, D, Hp, Wp = x.shape x = x.flatten(2).transpose(1, 2) # (B, num_patches, embed_dim) return x

卷积核大小和步长都等于 patch_size,这样卷积输出特征图的每个位置就对应原图的一个 Patch。之后把空间维度展平,就得到了 Transformer 需要的序列格式。

3.2 位置编码:视觉 Transformer 里最容易忽略的细节

图像被切成 Patch 后,还面临一个问题:序列本身是无序的。把第 1 个 Patch 和第 20 个 Patch 的位置互换,Transformer 根本不知道这一点。但图像的空间位置信息极其重要,所以必须把位置信息编码进去。

ViT 采用的方式是“可学习的位置编码(Learnable Position Embedding)”。具体做法是初始化一个与 Token 序列等长的位置向量表,把它加到 Patch Embedding 的输出上。

在 ViT 中还有一个特殊设计:在 Patch 序列的最前面额外加一个可学习的 [class] token。这个 Token 不携带具体 Patch 的信息,但会在每一层 Transformer 中通过注意力机制与所有 Patch 交互。最终分类时,直接取这个 [class] token 对应的输出向量做分类,而不是把所有 Patch 的输出做平均。

关于位置编码,这里涉及一个常被误解的点:ViT 使用的位置编码是“绝对位置”的。它告诉模型“第 i 个 Patch 在位置 i”,但并没有显式建模 Patch 之间的相对距离。Swin Transformer 后续使用相对位置编码,正是为了弥补这个不足。

3.3 ViT 的局限:归纳偏置缺失、数据要求高、分辨率敏感

ViT 在视觉任务上能成立,靠的是 Transformer 强大的全局建模能力。但它的局限也非常明显:

第一,缺少卷积的归纳偏置。CNN 天然假设“相邻像素更相关”,也天然具备平移等变性。ViT 没有这些先验,它必须从数据中学出来。数据量不够时,ViT 很难自己悟出“局部性”这个视觉常识。

第二,训练成本高。原论文中,ViT 在 ImageNet-1K 上从头训练时,效果不如同量级的 ResNet / EfficientNet,只有在大规模数据集(如 JFT-300M)上预训练后,ViT 才能明显超过 CNN。这个结论直接影响工程选型:如果你只有几万张私有图片,直接用 ViT 往往不是好选择。

第三,分辨率迁移困难。ViT 在低分辨率上训练后,如果直接换到高分辨率推理,Patch 数量会翻倍,位置编码的长度就对不上了。虽然可以通过插值处理,但效果会打折扣。

ViT 的意义在于证明了“视觉任务不需要卷积也能做”,但它还不是一个适合直接落地到中小规模任务的通用架构。

4. Swin Transformer:让 Transformer 重新拥有“层级感”

4.1 ViT 留下的两个大问题

ViT 把整张图切成 196 个 Patch,每一层都在全局范围内做注意力计算。这在 196 个 Token 时还能接受,但如果输入是高清大图,Patch 数量会急剧增长。假设输入变成 1024×1024,patch_size 仍是 16,Token 数量就变成 64×64 = 4096 个。全局注意力的计算量会随 Token 数量的平方增长,显存直接拉满。

除了计算量大,ViT 还缺少图像金字塔结构。CNN 通过逐层下采样得到不同尺度的特征图,底层负责细节,高层负责语义。ViT 只有一个尺度,所有层都在同一分辨率上做全局建模,对检测和分割这类需要多尺度特征的任务来说很不利。

Swin Transformer 正是针对这两个问题设计的。

4.2 窗口注意力(W-MSA)如何降低复杂度

Swin Transformer 的关键设计之一,是在每个 Stage 内把特征图划分成多个互不重叠的窗口,只在每个窗口内部做自注意力。以 8×8 的特征图、4×4 的窗口为例,一共被分成 4 个窗口,每个窗口内有 16 个 Token。原来的全局注意力需要计算 8×8 = 64 个 Token 两两之间的关联,窗口注意力只需要计算 4 个窗口各自内部的 16×16 关联。

复杂度从全局的 O(N²) 降到了 O(N_window² × 窗口数),而由于 N_window 远小于 N,整体计算量大幅下降。更重要的是,窗口数量变多不会让显存连续暴涨,这让 Swin 能够处理更高分辨率的输入。

4.3 移动窗口(SW-MSA):窗口边界的信息怎么流动

如果只做窗口注意力,信息只能在一个窗口内部流动,窗口之间完全隔离,这又会退化成“局部操作”,丢失全局信息。

Swin 的解法是“移动窗口”:在下一层 Transformer Block 中,把窗口的划分方式整体向右下角偏移。这样,上一层位于窗口边界的 Token,在下一层就会和相邻窗口的 Token 处在同一个新窗口中。通过两层交替,信息就能跨越窗口边界流动。

理解移动窗口时,最容易困惑的是偏移后的窗口数量不均匀。原论文使用 cyclic shift 技巧:把特征图上移、左移,使窗口数量恢复均匀,并在计算注意力时用 mask 屏蔽掉不该关联的区域。实现层面比较复杂,但设计思路可以这样理解:移动窗口不是让窗口变大了,而是让窗口之间的“邻接关系”每隔一层就发生变化,从而在控制计算量的同时保留跨窗口信息交换。

4.4 相对位置编码:用偏置表描述“相对距离”

Swin 另一个重要改进是使用相对位置编码。它不直接给每个位置一个绝对向量,而是维护一张相对位置偏置表(relative position bias table),根据 Query 和 Key 的相对偏移量取一个可学习偏置,加到注意力分数上。

这样设计的优点很明显:模型学习的是“两者相隔多远”的关系,而不是“两者各自在哪”的关系。平移不变性在窗口内部被重新引入。即使输入分辨率变化,相对位置关系的基本模式仍然可以迁移,这比 ViT 的绝对位置编码更灵活。

4.5 Swin Transformer 的整体结构:从序列回到特征图

Swin 的整体结构也值得梳理一下。它包含四个 Stage,每个 Stage 内部先做 Patch Embedding / Patch Merging 降采样,再堆叠若干基于窗口注意力的 Transformer Block。这样输出的特征图具有和 CNN 骨干网络一样的层级结构:浅层分辨率高,通道数少;深层分辨率低,通道数多。

这意味着 Swin Transformer 可以直接接入已有的检测、分割框架,比如 Mask R-CNN、FCN 等。它不是一个“经验上的新模型”,而是真正填补了 Transformer 在视觉骨干网络上的工程空白。

5. 最小代码实现路线:从公式到可运行示例

这一节我们动手写代码。目标不是完整复现 ViT 或 Swin,而是把两个最核心的机制跑通:Patch Embedding 和窗口注意力。这会让你从“看公式”切换到“看代码”,对原理的理解会上一个台阶。

5.1 环境依赖

建议使用 Python 3.8 以上环境,PyTorch 至少 1.10 版本。版本不需要刻意追求最新,以下代码在 2.x 版本上也能运行。代码中不会引入 extra 依赖,只用 torch 和 torch.nn。

pip install torch

如果显卡支持 CUDA,建议安装对应的 CUDA 版本 PyTorch;没有显卡也可以先用 CPU 跑小尺寸示例。

5.2 示例 1:Patch Embedding 的完整实现

Patch Embedding 是 ViT 的入口。我们复用前面贴过的代码,并补充一个便于打印形状的测试逻辑:

import torch import torch.nn as nn class PatchEmbed(nn.Module): def __init__(self, in_channels=3, embed_dim=768, patch_size=16): super().__init__() self.patch_size = patch_size self.proj = nn.Conv2d( in_channels, embed_dim, kernel_size=patch_size, stride=patch_size ) def forward(self, x): B, C, H, W = x.shape x = self.proj(x) # (B, embed_dim, H/p, W/p) B, D, Hp, Wp = x.shape x = x.flatten(2).transpose(1, 2) # (B, N, D) return x if __name__ == "__main__": x = torch.randn(2, 3, 224, 224) model = PatchEmbed(in_channels=3, embed_dim=768, patch_size=16) out = model(x) print("输入形状:", x.shape) print("输出形状:", out.shape) # (2, 196, 768)

输出中的 196 就是 14×14 个 Patch。到这里,图像已经变成了一个长度为 196 的 Token 序列,可以直接输入 Transformer Encoder。

5.3 示例 2:加入 [class] token 和位置编码的 ViT 前向过程

了解了 Patch Embedding 后,把 [class] token 和位置编码接上,就是一个可运行的 ViT 前向主干:

import torch import torch.nn as nn class ViTForward(nn.Module): def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768, num_heads=8, depth=12, num_classes=1000): super().__init__() self.patch_embed = PatchEmbed(in_channels, embed_dim, patch_size) num_patches = (img_size // patch_size) ** 2 self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) self.encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer( d_model=embed_dim, nhead=num_heads, batch_first=True ), num_layers=depth ) self.norm = nn.LayerNorm(embed_dim) self.head = nn.Linear(embed_dim, num_classes) def forward(self, x): B = x.shape[0] x = self.patch_embed(x) # (B, N, D) cls_tokens = self.cls_token.expand(B, -1, -1) x = torch.cat([cls_tokens, x], dim=1) # (B, N+1, D) x = x + self.pos_embed x = self.encoder(x) x = self.norm(x[:, 0]) # 取 [class] token return self.head(x)

这段代码用nn.TransformerEncoder封装了多层 Transformer Block,对理解结构足够了。需要提醒的是,真实 ViT 使用的是 Pre-LN 和 GELU 等细节,建议复现时以原论文为准。

5.4 示例 3:Swin 窗口切分与还原

Swin 的窗口注意力版图比较长,这里只展示最关键的窗口切分和还原函数。这两个函数是理解移动窗口的基础:

import torch def window_partition(x, window_size): # x: (B, H, W, C) B, H, W, C = x.shape x = x.view(B, H // window_size, window_size, W // window_size, window_size, C) windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view( -1, window_size, window_size, C ) return windows def window_reverse(windows, window_size, H, W): # windows: (num_windows, window_size, window_size, C) B = int(windows.shape[0] / (H * W / window_size / window_size)) x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1) x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) return x if __name__ == "__main__": x = torch.randn(1, 8, 8, 96) w = window_partition(x, 4) print("切分后形状:", w.shape) # (4, 4, 4, 96) y = window_reverse(w, 4, 8, 8) print("还原后形状:", y.shape) # (1, 8, 8, 96) print("还原是否一致:", torch.allclose(x, y))

切分后的每个窗口是 4×4×96,也就是 16 个 Token。窗口数量是 (8/4)×(8/4)=4。还原函数可以验证切分和还原是否互逆,这是调试窗口注意力时常做的一件事。

5.5 如何运行和验证

把上面三个脚本分别保存成patch_embed.pyvit_forward.pyswin_window.py,在命令行执行:

python patch_embed.py python vit_forward.py python swin_window.py

如果一切正常,你会看到:

  • Patch Embedding 输出形状是(2, 196, 768)
  • ViT 前向输出形状是(2, 1000)
  • 窗口切分还原后,torch.allclose返回True

如果某个脚本报错,优先检查 PyTorch 版本和输入张量形状。后续排查思路可以看第 7 节。

6. 视觉 Transformer 怎么选:ViT、Swin 还是改良版

理解了基本原理之后,工程选型还需要回到实际任务。

维度ViTSwin Transformer
核心操作全局注意力窗口注意力 + 移动窗口
Token 数量敏感度高,全局注意力随序列二次增长低,窗口内计算量可控
特征金字塔无,单一分辨率特征有,四个 Stage 层级特征
位置编码可学习绝对位置编码相对位置偏置
数据需求高,依赖大规模预训练相对友好,更适合中小数据集
检测/分割迁移需要额外设计可直接接入主流框架
典型场景大规模预训练、长序列全局建模通用视觉骨干、检测分割

从上表可以得出一个比较实用的判断:

如果做分类,且你有大规模预训练条件,可以考虑 ViT 路线;如果做检测、分割,或者数据规模在万级到百万级之间,Swin 风格的分层 Transformer 更稳妥。

另外,不必把视野局限于 ViT 和 Swin 两个名字。DeiT 用蒸馏方法让 ViT 在 ImageNet-1K 上从头训练也能收敛;PVT 重新引入金字塔结构;Swin 后续还有 Swin V2 和改进版本;Restormer 则把 Transformer 引入低层视觉任务。这些工作都可以看成是“在全局注意力和局部先验之间做平衡”的不同尝试。

7. 常见理解误区与排查思路

视觉 Transformer 的代码不算难,但踩坑点很集中。我整理了几个高频问题:

问题现象可能原因排查方式解决方案
维度不匹配,报错embed_dim 无法被 num_heads 整除模型配置里 embed_dim 和 num_heads 不兼容打印各模块输入输出形状调整 embed_dim 或 num_heads,确保整除
训练时 loss 不下降,或效果明显不如 CNN小数据集直接训练 ViT,没有预训练检查训练集规模和是否加载预训练权重改用 Swin 风格模型,或使用 DeiT 蒸馏预训练
换输入分辨率后推理报错位置编码长度对不上打印 pos_embed 和 Patch 序列长度对位置编码做双线性插值,并重新初始化
Swin 窗口切分后无法还原特征图尺寸不能整除窗口大小检查模板尺寸和 window_size 的整除关系使用 padding,或在 Patch Merging 时保持尺寸对齐
显存不够Token 数量过多,全局注意力二次复杂度查看层数和 Patch 数量减小 patch_size 并不是办法;换窗口注意力或降低 batch size
注意力权重全是均匀分布缩放因子没加,或训练初始化异常检查 QK 点积尺度确认是否除以 sqrt(d_k),并检查初始化方式

这些排查思路同样适用于以后阅读其他 Transformer 变体。遇到报错先看形状,再看超参数,最后看数据量,基本可以覆盖 80% 的问题。

8. 工程实践与学习建议

8.1 训练稳定比刷高精度更重要

如果你是在新数据集上训练视觉 Transformer,建议关注几个通用技巧:

  • 优化器优先选择 AdamW,学习率使用 warmup + cosine decay 策略;
  • 数据增强要跟上,MixUp、CutMix、Random Augment 都能有效提升 Transformer 的泛化能力;
  • 使用混合精度训练,Transformer 显存占用普遍比同参数量的 CNN 高;
  • 小 batch 训练时,额外注意 LayerNorm 的稳定性,必要时调整学习率。

8.2 不要一上来就复现完整论文

很多人学到这里会直接打开 ViT 原版代码仓库,结果被几百行配置和分布式训练逻辑劝退。更推荐的做法是:先在自己的小数据集上,把本文中的最小示例跑通;然后修改 Patch 大小、注意力头数、层数,观察特征图变化;最后再对照原论文,逐行阅读官方实现。

8.3 从“选模型”到“改模块”

工程中不必把 ViT 和 Swin 当成两个孤立的模型。你完全可以在 Swin 的主干上,把某个 Stage 的窗口注意力替换成全局注意力,或者引入 CBAM 这类通道注意力模块做补充。理解它们的设计动机之后,模型就是可以自由调整的积木。

8.4 如果你的任务是检测或分割

直接使用带 FPN 的检测框架,把 Backbone 从 ResNet 换成 Swin Transformer,通常是最平滑的迁移路径。把预训练权重加载好,其他模块不需要大改。需要重点检查的是输入尺寸和窗口划分的整除关系,这是唯一容易踩坑的地方。


如果只看结论,可以把整条主线压缩成一句话:ViT 让图像变成了序列,Swin 通过窗口让序列重新拥有了空间结构。下一步不需要背诵更多公式,挑一个最小实现改几行代码,观察一下特征图的变化,比看十篇论文更有效。建议先收藏这篇文章,动手跑通第 5 节的三个脚本,再回头重读原论文,你会发现以前看不懂的细节突然通顺了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 9:06:33

从需求拆解到运维迭代:构建可落地的AIAS人工智能应用系统

简介:在人工智能渗透到语音助手、自动驾驶等领域的背景下,AIAS 人工智能加速套件是一款面向智能应用开发的 SDK 工具包,定位为缩短 AI 项目从原型到落地的时间,适合算法工程师、Java 开发者和技术学习者使用,覆盖图像处…

作者头像 李华
网站建设 2026/9/8 9:05:32

AES50 光纤传输器:突破100米限制,实现500米舞台音频双备份部署

做演出扩声的人,基本都遇过同一个尴尬:舞台接口箱在台口,调音台在观众区后方的音控室,中间隔着看台、过道和机房,距离轻松超过 100 米。模拟蛇太重、太贵、抗干扰差;想把百灵达 X32/M32 和 S16/S32、MIDAS …

作者头像 李华
网站建设 2026/9/8 9:05:09

STM32智能家居语音控制系统开源实战:离线语音+Proteus仿真全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 9:04:52

Jumpserver堡垒机部署实操:Docker Compose安装与审计配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 9:04:50

从费马大定理看Lean 4与AI文风:机器校验的可信度

如果你平时关注形式化验证,最近应该被一条消息刷屏了:Anthropic 放出了一个基于 Lean 4 的费马大定理机器校验证明,Ethan Mollick 很快指出,整份文档明显带着 Claude 文风。这条消息有意思的地方,不在于“AI 又证明了某…

作者头像 李华
网站建设 2026/9/8 9:04:11

开放权重模型强化学习微调实战:从GRPO原理到工程落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华