Attention-Based Seq2Seq:为什么会自然过渡到 Transformer

副标题 / 摘要 这篇文章专门解释一个关键过渡:为什么 fixed-length 的 seq2seq 很快会不够用,attention-based seq2seq 是怎么补上“按需读取源序列”这个能力的,以及这个思路为什么几乎会自然长成 Transformer。最后会收束到一份最小可运行的 PyTorch GRU + additive attention 实现。 从“苹果”为什么老翻不准开始 还是用这个最小翻译任务: src: <bos> I really love green apples <eos> tgt: <bos> 我 真的 喜欢 青 苹果 <eos> 当 decoder 走到要生成“苹果”这一步时,最理想的行为其实很明确: 它应该重点回头看源序列里的 apples 它可能顺手也看一眼 green 它不能只依赖一个已经被反复压缩过很多轮的最终隐藏状态 如果你用上一篇里那个最小 seq2seq: encoder 把整句读完 只把最后一个 hidden_enc 交给 decoder decoder 后面每一步都只靠这个固定长度状态和自己的历史 那么句子一长,这里就会出现一个很具体的问题: decoder 明明需要“现在按需去看源序列的某几个位置”,但 fixed-length seq2seq 只给了它“一次性打包好的整句摘要”。 这就是 attention-based seq2seq 出现的真实压力。 它不是为了“概念更高级”,而是因为 decoder 在每个时间步都需要重新决定自己该看源序列的哪里。 快速掌握地图 fixed-length seq2seq:encoder outputs -> 丢弃大部分,只保留 final hidden attention-based seq2seq:decoder step t -> 对所有 encoder outputs 打分 -> 加权求和得到 context_t 核心收益:不同目标位置可以读取不同源位置 仍然存在的限制:encoder 和 decoder 还是循环结构,时间上依然串行 通向 Transformer 的关键桥:decoder state 作为 query,encoder outputs 作为 memory 这篇文章重点深挖的两个概念 对齐分数与上下文向量:decoder 怎样在每一步决定“该看源序列哪里” 从 attention-based seq2seq 到 Transformer 的结构映射:哪些东西被保留了,哪些东西被替换了 大师级心智模型 fixed-length seq2seq 的核心假设是: ...

2026年4月23日 · 9 分钟 · map[name:Jeanphilo]

Seq2Seq 与 Encoder-Decoder:从翻译任务到最小可运行 PyTorch 实现

副标题 / 摘要 这篇文章不把 seq2seq 和 encoder-decoder 当成术语表来讲,而是从一个最小翻译任务出发,解释为什么“输入一段序列、输出另一段序列”会自然逼出编码器和解码器的分工,最后收束成一份最小可运行的 PyTorch GRU 实现。 从一个最小翻译任务开始 假设源序列是: <bos> I love apples <eos> 目标序列是: <bos> 我 喜欢 苹果 <eos> 当模型要生成“苹果”时,它至少要解决三件事: 它必须知道整句英文大意,而不只是当前一个词 它必须记住自己前面已经生成了“我 喜欢” 它必须按顺序一个词一个词地产生输出,而不是一次吐出整个目标序列 如果你只用一个普通分类器把源句子映射成一个类别,这个任务做不成。 因为这里的输出不是一个固定标签,而是长度可变的目标序列。 所以这里天然会逼出一个更具体的数据流: 先把源序列读完,压成可传递的状态 再从 <bos> 开始,逐步生成目标序列 每生成一步,都要同时依赖“源侧信息”和“目标侧历史” 这就是 sequence-to-sequence 的最小问题形态。 seq2seq 说的是任务:输入一段序列,输出另一段序列。 encoder-decoder 说的是实现:先编码输入,再逐步解码输出。 下面不先堆名词,直接按这个压力把代码一步一步长出来。 快速掌握地图 问题形态:src -> encoder -> hidden/context -> decoder -> logits 核心目标:学习条件概率 p(y_t | y_{<t}, x) 最小实现:Embedding + GRU Encoder + GRU Decoder + output projection 何时适用:翻译、摘要、改写、问答这类“输入序列 -> 输出序列”任务 明显局限:如果所有源信息都被压进一个固定长度向量,长句子会吃力 这篇文章重点深挖的两个概念 隐藏状态交接:encoder 到底把什么交给 decoder 右移目标序列与 teacher forcing:训练时 decoder 为什么不能直接喂完整真实答案 大师级心智模型 这类模型的核心抽象不是“两个 RNN 拼起来”,而是: ...

2026年4月23日 · 7 分钟 · map[name:Jeanphilo]

Transformer 结构推导:一步一步搭出最小可运行 PyTorch 实现

副标题 / 摘要 这篇文章不把 Transformer 当成一个现成黑盒来介绍,而是直接从一个最小翻译任务开始,让需要的结构一层一层长出来,最后收束成一份最小可运行的 PyTorch encoder-decoder Transformer。 从一个最小翻译任务开始 假设源序列是: <bos> I love apples <eos> 目标序列是: <bos> 我 喜欢 苹果 当模型要生成“苹果”时: 它不能看目标序列里未来还没生成的位置 它需要重点读取源序列中的 apples 它可能还需要参考前面的“我 喜欢”来决定当前词 所以这里天然会逼出三件事: 目标侧必须有因果约束 源侧和目标侧都需要全局读取 解码器不仅要读自己,还要读编码器输出 RNN 和 CNN 也能处理序列,但它们在长距离依赖和全并行训练上都有明显限制。 所以这里真正要解决的,不只是“做一个更深的网络”,而是让任意位置能直接交互,并且显式控制信息流方向。 下面开始按这个压力一步一步长代码。 Step 1:先有输入表示,但先不谈注意力 先看一个已经分词并编号后的最小输入: <bos> I love apples <eos> -> [1, 15, 982, 204, 2] 这里的 1, 15, 982, 204, 2 还不是模型已经理解后的表示。 它们只是词表里的编号,作用更接近“标签”或“学号”: 15 比 204 小,不代表它们语义上更接近 直接拿这种离散编号去做线性变换或点积,含义也不对 所以第一步只解决一个更具体的问题: 怎样把“词表编号”变成“模型后面可以继续计算的一组连续数值”? 这里再引入两个词: token id:分词后查词表得到的离散编号 embedding:把每个离散编号映射成长度为 d_model 的可学习向量 先写最小版,不额外引入别的机制: ...

2026年4月23日 · 10 分钟 · map[name:Jeanphilo]

Self-Attention 计算公式与 Softmax 数值稳定:从推导到工程实现

副标题 / 摘要 Self-Attention 的公式很短,但工程细节很长:从 Q/K/V 计算到 softmax 数值稳定、mask 与缩放,每一步都影响效果与性能。本文用 ACERS 结构给出推导、实践步骤与可运行示例。 预计阅读时长:12~16 分钟 标签:attention、transformer、softmax SEO 关键词:Self-Attention, Softmax, Scaled Dot-Product, 数值稳定 元描述:Self-Attention 的计算公式与 softmax 稳定实现方法,含工程实践与示例代码。 目标读者 想真正理解 Self-Attention 公式含义的学习者 需要处理训练不稳定/溢出的工程实践者 关注注意力数值稳定与实现细节的开发者 背景 / 动机 在 Transformer 中,Self-Attention 是计算量最大、数值最敏感的模块之一。 很多训练不稳定、输出 NaN 的问题,都来自 softmax 的溢出/下溢或 mask 的错误处理。 理解公式与稳定实现,可以显著减少工程“踩坑”。 核心概念 Q/K/V:查询、键和值,来自输入线性投影 缩放点积注意力:$\text{softmax}(QK^\top/\sqrt{d_k})V$ 数值稳定:通过减去行最大值避免 softmax 溢出 思路推导(从朴素到稳定实现) 朴素做法 先算所有相似度 $S = QK^\top$,再做 softmax 得到权重 $P$,最后 $O = PV$。 这个实现最直观,但当 $S$ 很大时会出现 exp 溢出。 关键观察 softmax 对每行同时加上或减去一个常数不改变输出: $\text{softmax}(x) = \text{softmax}(x - \max(x))$。 ...

2026年1月25日 · 3 分钟 · map[name:Jeanphilo]

空洞卷积(Dilated Convolution):扩大感受野的工程利器

副标题 / 摘要 空洞卷积通过插入“空洞”扩大感受野,在不显著增加参数的情况下捕获长距离上下文。本文按 ACERS 结构解析原理、复杂度与工程场景,并提供最小可运行示例。 预计阅读时长:14~18 分钟 标签:dilated-convolution、segmentation、vision SEO 关键词:空洞卷积, Dilated Convolution, Atrous 元描述:解释空洞卷积的原理、复杂度与工程应用,含最小示例。 目标读者 想理解感受野扩大策略的入门读者 从事语义分割、时序建模的工程实践者 需要在算力与效果间权衡的开发者 背景 / 动机 传统卷积增大感受野通常靠加深网络或增大核尺寸,但这会带来更多参数与计算。 空洞卷积用“稀疏采样”的方式扩大感受野,是更高效的替代方案。 核心概念 空洞率(dilation):卷积核元素之间的间隔。 感受野:输出特征与输入区域的覆盖范围。 稀疏采样:在输入上跳步取样。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 空洞卷积就是“把卷积核撑开”,让核的元素之间有空洞,从而覆盖更大的输入范围。 基础示例(1) 3x3 卷积,dilation=2 → 覆盖 5x5 的感受野。 基础示例(2) 不增加参数数量,但能捕捉更大上下文。 实践指南 / 步骤 选择基础卷积核(如 3x3)。 设置 dilation(常用 2、4、8)。 观察感受野与特征分辨率变化。 避免过大 dilation 导致“栅格效应”。 可运行示例(最小 PyTorch 空洞卷积) import torch import torch.nn as nn x = torch.randn(1, 3, 32, 32) conv = nn.Conv2d(3, 8, kernel_size=3, dilation=2, padding=2) out = conv(x) print(out.shape) 解释与原理 有效感受野:k_eff = k + (k-1) * (d-1)。 参数量与标准卷积相同,计算量近似不变。 C — Concepts(核心思想) 方法类型 空洞卷积属于扩大感受野的卷积变体,常用于分割与时序模型。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

NMS 描述:非极大值抑制的原理与工程实践

副标题 / 摘要 NMS(Non-Maximum Suppression)是目标检测后处理的核心步骤。本文用 ACERS 框架拆解 NMS 的原理、流程与工程实践,并提供可运行的 PyTorch 示例。 预计阅读时长:14~18 分钟 标签:nms、object-detection、iou SEO 关键词:NMS, 非极大值抑制, IoU, 目标检测 元描述:讲清 NMS 的核心算法、复杂度与工程取舍。 目标读者 想理解目标检测后处理的初学者 需要调参 IoU 阈值的工程实践者 关注推理速度与精度平衡的开发者 背景 / 动机 检测模型通常会输出多个重叠框。 如果不做抑制,会出现“同一目标被重复检测”。 NMS 用最简单的规则实现去重,是工业界的标准方案。 核心概念 IoU(Intersection over Union):衡量两个框重叠程度。 score:置信度分数,决定优先保留的框。 阈值:IoU 超过阈值则抑制。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 NMS 的逻辑很直观: 选出最高分的框。 删除与它重叠度过高的框。 重复直到没有框。 基础示例(1) 两个高度重叠的人脸框,只保留分数更高的一个。 基础示例(2) 多个类别的检测结果,先按类别分开再做 NMS(class-wise)。 实践指南 / 步骤 对检测框按 score 排序。 取最高分框作为保留结果。 计算 IoU,过滤高重叠框。 重复直到框集合为空。 可运行示例(最小 PyTorch NMS) import torch def iou(box, boxes): x1 = torch.maximum(box[0], boxes[:, 0]) y1 = torch.maximum(box[1], boxes[:, 1]) x2 = torch.minimum(box[2], boxes[:, 2]) y2 = torch.minimum(box[3], boxes[:, 3]) inter = torch.clamp(x2 - x1, min=0) * torch.clamp(y2 - y1, min=0) area1 = (box[2] - box[0]) * (box[3] - box[1]) area2 = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) union = area1 + area2 - inter return inter / (union + 1e-6) def nms(boxes, scores, thresh=0.5): idx = scores.argsort(descending=True) keep = [] while idx.numel() > 0: i = idx[0] keep.append(i.item()) if idx.numel() == 1: break rest = idx[1:] ious = iou(boxes[i], boxes[rest]) idx = rest[ious <= thresh] return keep boxes = torch.tensor([ [0.0, 0.0, 1.0, 1.0], [0.1, 0.1, 1.1, 1.1], [2.0, 2.0, 3.0, 3.0], ]) scores = torch.tensor([0.9, 0.8, 0.7]) print(nms(boxes, scores, thresh=0.5)) 解释与原理 NMS 的核心是“先保留最可信框”。 IoU 阈值越大,保留框越多;越小,抑制越强。 C — Concepts(核心思想) 方法类型 NMS 属于后处理过滤算法,用局部贪心策略去重。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

CNN 参数量计算:从卷积核到整网规模

副标题 / 摘要 CNN 的参数量取决于卷积核大小、通道数与偏置项。本文用 ACERS 框架给出计算公式、示例与工程实践,帮助你快速评估模型规模。 预计阅读时长:12~16 分钟 标签:cnn、parameter-count、convolution SEO 关键词:CNN, 参数量, 卷积, 模型大小 元描述:讲清 CNN 参数量的计算公式与工程取舍。 目标读者 想快速估算模型规模的初学者 关注部署成本与显存预算的工程实践者 需要做模型压缩与设计取舍的开发者 背景 / 动机 模型参数量直接影响训练速度、推理成本与部署体积。 对于 CNN,参数量可精确计算,但容易被忽略或算错。 掌握计算方法是做结构设计与成本评估的基础。 核心概念 卷积核参数量:核高 * 核宽 * 输入通道 * 输出通道。 偏置项:每个输出通道一个偏置。 组卷积:参数量随 groups 减少。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 CNN 参数量的核心是: “每个输出通道有一组卷积核,核大小覆盖所有输入通道”。 基础示例(1) 卷积:3x3, in=3, out=64 参数量:333*64 + 64 = 1,792 基础示例(2) 1x1 卷积:in=256, out=128 参数量:11256*128 + 128 = 32,896 实践指南 / 步骤 明确卷积核大小 (KxK)。 确认输入通道数 C_in 与输出通道数 C_out。 计算参数量:K*K*C_in*C_out + C_out。 若是组卷积,再除以 groups。 可运行示例(最小 PyTorch 计算) import torch import torch.nn as nn conv = nn.Conv2d(3, 64, kernel_size=3, bias=True) params = sum(p.numel() for p in conv.parameters()) print(params) # 1792 解释与原理 卷积层参数量与输入图像大小无关,只与核与通道有关。 1x1 卷积参数量依然可能很大,因为通道数通常很高。 C — Concepts(核心思想) 方法类型 CNN 参数量计算属于模型规模评估方法,用于衡量存储与计算成本。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

动量(Momentum)优化的过程:从直觉到公式

副标题 / 摘要 动量通过累积历史梯度“惯性”来加速收敛、减少震荡。本文用 ACERS 框架拆解动量更新过程、公式与工程场景,并提供最小 PyTorch 示例。 预计阅读时长:12~16 分钟 标签:momentum、sgd、optimizer SEO 关键词:动量, Momentum, SGD, 优化器 元描述:系统讲清动量优化的更新过程与工程实践。 目标读者 想理解动量优化机制的入门读者 需要解决训练震荡与收敛慢问题的工程实践者 关注优化器调参的开发者 背景 / 动机 纯 SGD 在陡峭方向上容易震荡、在平缓方向上推进缓慢。 动量引入“速度”概念,让更新方向更稳定、收敛更快。 它是许多优化器(如 Adam)的核心组件之一。 核心概念 速度(Velocity):累计梯度形成的方向与幅度。 动量系数:控制历史梯度影响程度。 平滑更新:减少梯度噪声带来的震荡。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 动量可以理解为: 每一步不仅看当前梯度,还看过去的梯度方向。 像滚小球一样,惯性会让它更容易越过浅坑。 基础示例(1) 在狭长“谷地”里,纯 SGD 左右摆动,而动量能沿谷底快速前进。 基础示例(2) 在噪声梯度场景,动量能平均掉噪声,方向更稳定。 实践指南 / 步骤 选择 momentum(常见 0.9)。 如果震荡明显,适当提高动量或降低学习率。 观察训练/验证曲线,确认收敛速度。 可运行示例(最小 PyTorch 动量更新) import torch torch.manual_seed(42) w = torch.tensor([5.0], requires_grad=True) velocity = torch.zeros_like(w) lr = 0.1 mu = 0.9 for _ in range(5): loss = (w - 1.0).pow(2) loss.backward() with torch.no_grad(): velocity = mu * velocity + w.grad w -= lr * velocity w.grad.zero_() print(w.item()) 解释与原理 速度累积让更新方向“更平滑”。 在弯曲损失面上,动量减少横向摆动。 学习率与动量需要联合调参。 C — Concepts(核心思想) 方法类型 动量属于一阶优化增强策略,通过历史梯度平滑更新。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

图像自编码是怎么做的:原理、流程与最小实现

副标题 / 摘要 图像自编码通过“编码-解码-重构”学习紧凑表征。本文用 ACERS 框架讲清原理、训练流程与工程应用,并给出最小可运行的 PyTorch 示例。 预计阅读时长:14~18 分钟 标签:autoencoder、image、pytorch SEO 关键词:图像自编码, Autoencoder, 重构 元描述:讲解图像自编码的核心机制与工程场景,含最小示例。 目标读者 想理解自编码器原理的入门读者 需要构建图像表示学习的工程实践者 关注异常检测与去噪应用的开发者 背景 / 动机 标注数据昂贵,但图像数据充足。 自编码器通过“重构输入”学习特征表示,适合无监督或弱监督场景。 在去噪、压缩、异常检测等任务中,自编码器是一条高性价比路径。 核心概念 编码器(Encoder):把图像压缩成低维特征。 解码器(Decoder):从特征重建图像。 重构损失:衡量输入与输出差异(MSE/MAE)。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 图像自编码器的流程很直观: 把图像压缩为低维向量。 用低维向量重建图像。 用重构误差训练模型。 基础示例(1) 去噪自编码:输入带噪图像,输出干净图像。 基础示例(2) 异常检测:正常样本重构误差小,异常样本误差大。 实践指南 / 步骤 选择编码器/解码器结构(CNN 或 MLP)。 设定瓶颈维度(压缩比)。 选择重构损失(MSE/MAE)。 训练后用重构误差评估应用效果。 可运行示例(最小 PyTorch 自编码器) import torch import torch.nn as nn torch.manual_seed(42) class AE(nn.Module): def __init__(self): super().__init__() self.encoder = nn.Sequential( nn.Conv2d(1, 8, 3, stride=2, padding=1), nn.ReLU(), nn.Conv2d(8, 16, 3, stride=2, padding=1), nn.ReLU(), ) self.decoder = nn.Sequential( nn.ConvTranspose2d(16, 8, 4, stride=2, padding=1), nn.ReLU(), nn.ConvTranspose2d(8, 1, 4, stride=2, padding=1), nn.Sigmoid(), ) def forward(self, x): z = self.encoder(x) return self.decoder(z) x = torch.randn(4, 1, 28, 28) model = AE() out = model(x) print(out.shape) 解释与原理 编码器学习“压缩表示”,解码器学习“重构映射”。 重构损失逼近输入分布,从而学习数据结构。 去噪版本在输入端加噪,输出仍对齐原图。 C — Concepts(核心思想) 方法类型 自编码器属于无监督表示学习与生成式重构模型范式。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

ViT 结构描述:从 Patch Embedding 到 Transformer 编码器

副标题 / 摘要 ViT 把图像切成 patch 序列,再交给 Transformer 编码器处理。本文用 ACERS 框架拆解 ViT 的核心结构与工程选择,并提供最小可运行的 PyTorch 示例。 预计阅读时长:16~20 分钟 标签:vit、transformer、vision SEO 关键词:ViT, Vision Transformer, Patch Embedding, 图像分类 元描述:系统描述 ViT 架构与工程实践,含最小 PyTorch 示例。 目标读者 想理解 ViT 架构的入门读者 需要做视觉模型选型的工程实践者 想从 CNN 迁移到 Transformer 的开发者 背景 / 动机 CNN 通过局部卷积捕获特征,但长程依赖与全局建模能力有限。 ViT 把图像当成序列,直接用自注意力做全局建模, 在大规模数据预训练下表现非常强。 核心概念 Patch Embedding:将图像切成 patch 并线性投影。 Position Embedding:补充位置信息。 [CLS] Token:聚合全局特征用于分类。 Transformer Encoder:多头自注意力 + FFN 堆叠。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 ViT 的核心流程: 把图像切成固定大小 patch。 每个 patch 拉平成向量并投影成 token。 加上位置编码和 [CLS] token。 送入 Transformer Encoder 得到全局表征。 用 [CLS] token 输出做分类。 基础示例(1) 图像 224x224,patch 16x16 → 196 个 patch + 1 个 [CLS]。 基础示例(2) 只保留编码器,就能做图像分类与检索。 实践指南 / 步骤 选择 patch 大小(8/16/32)。 设置隐藏维度与层数(如 12 层,768 维)。 添加位置编码与 [CLS] token。 训练:优先用预训练权重再微调。 可运行示例(最小 ViT 前向) import torch import torch.nn as nn torch.manual_seed(42) class MiniViT(nn.Module): def __init__(self, img_size=32, patch=8, dim=64, depth=2, heads=4): super().__init__() self.patch = patch self.unfold = nn.Unfold(kernel_size=patch, stride=patch) num_patches = (img_size // patch) ** 2 self.proj = nn.Linear(3 * patch * patch, dim) self.cls = nn.Parameter(torch.zeros(1, 1, dim)) self.pos = nn.Parameter(torch.zeros(1, num_patches + 1, dim)) enc_layer = nn.TransformerEncoderLayer(d_model=dim, nhead=heads, batch_first=True) self.encoder = nn.TransformerEncoder(enc_layer, num_layers=depth) self.head = nn.Linear(dim, 10) def forward(self, x): patches = self.unfold(x).transpose(1, 2) # B, N, patch_dim tokens = self.proj(patches) cls = self.cls.expand(x.size(0), -1, -1) tokens = torch.cat([cls, tokens], dim=1) + self.pos z = self.encoder(tokens) return self.head(z[:, 0]) x = torch.randn(2, 3, 32, 32) model = MiniViT() print(model(x).shape) 解释与原理 patch embedding 把图像变成序列。 self-attention 能在全局范围建模依赖。 [CLS] token 作为全局聚合向量用于分类。 C — Concepts(核心思想) 方法类型 ViT 属于基于注意力的视觉表征模型,用 Transformer Encoder 替代卷积堆叠。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]