0%

Transformer 架构三剑客:Encoder、Decoder 与 Encoder-Decoder 的区别与应用

在深度学习自然语言处理(NLP)领域,Transformer 无疑是皇冠上的明珠。但Transformer 并不是只有一种形态

根据任务需求的不同,它演化出了三个各具特色的分支。今天我们来拆解 Transformer 家族的三大主力:仅编码器(Encoder-only)仅解码器(Decoder-only)编解码器(Encoder-Decoder)

一句话总结

  • Encoder-only 是“阅读理解大师”;
  • Decoder-only 是“即兴演讲天才”;
  • Encoder-Decoder 则是“同声传译专家”。

一、架构总览:一张图看懂三剑客

为了直观展示它们的区别,我们先看一张架构图:

graph TB
    subgraph "仅编码器 (Encoder-only)"
        E_IN[输入序列] --> E_ENC["编码器堆叠
(双向关注)"] E_ENC --> E_OUT[特征向量/分类结果] end subgraph "仅解码器 (Decoder-only)" D_IN[输入序列] --> D_DEC["解码器堆叠
(因果掩码/单向)"] D_DEC --> D_OUT[输出序列] end subgraph "编解码器 (Encoder-Decoder)" ED_IN[源序列] --> ED_ENC[编码器] ED_ENC --> ED_CROSS[交叉注意力] ED_DEC_IN[目标序列] --> ED_DEC[解码器] ED_CROSS --> ED_DEC ED_DEC --> ED_OUT[目标序列] end
架构 核心公式 训练方式
Encoder-only H=Encoder(X)H=Encoder(X) 掩码语言模型(MLM)
Decoder-only P(yt∥y<t,X)P(y**ty<t,X) 自回归语言模型(CLM)
Encoder-Decoder H=Encoder(X);Y=Decoder(H,Y<t)H=Encoder(X);Y=Decoder(H,Y<t) 去噪自编码器 / Seq2Seq

核心特点

  • 双向注意力机制(Bidirectional Attention):这是它的杀手锏。在处理句子中的某个词时,它能同时看到左边和右边的所有词。就像你阅读时,会结合上下文来理解一个多义词的含义。
  • 擅长“读”而非“写”:它的强项在于把一段文字压缩成高质量的向量表示(Embedding),从中提取特征。它是天生的“分析型”选手。

典型应用

阅读全文 »

大模型第一课:手写推导线性回归的“灵魂”

在深度学习和人工智能的宏大叙事中,Transformer、大语言模型动辄拥有千亿参数,令人望而生畏。但如果我们剥去这些复杂的外衣,会发现一切神经网络的基石,其实都源于一个最简单的模型——线性回归

今天这篇博客,我们不谈复杂的框架代码,而是回归数学本质。我们将通过一份手写的学习笔记,彻底搞懂机器是如何通过“试错”来学会预测的。

核心观点:理解线性回归,就是掌握了深度学习的“第一性原理”。


一、模型的诞生:给数据画一条直线

想象一下,你是一名房产中介,手头有一堆历史成交数据(房子的面积和对应的价格)。你的目标是找到一条规律,能最好地“穿过”这些数据点,从而根据新房子的面积来预测它的价格。

这就是线性回归要解决的问题:拟合

为了实现这个目标,我们需要定义两个核心要素:

  • 输入值 (x):喂给模型的特征数据(例如:房子面积)。
  • 输出值 (z):模型给出的预测结果(例如:预测房价)。

为了让 x 变成 z,我们需要两个可调节的参数——这也是模型真正要去“学习”的东西:

  1. 权重 ($w$, Weight):决定输入 x 对结果的影响程度。在几何上,它是直线的斜率
  2. 偏置 ($b$, Bias):决定当 x=0 时的基础值。在几何上,它是直线的截距

于是,我们得到了机器学习界最经典的公式:

💡 笔记划重点:这个简单的线性公式,就是所有神经网络神经元计算的雏形。


二、怎么才算“学得好”?引入 Loss 函数

模型给出了预测值 z,但怎么知道它准不准呢?我们需要一个“裁判”来打分。这个裁判就是损失函数(Loss Function)

同时,我们需要一个参照标准——目标值 (Y, Ground Truth),也就是真实答案(例如:房子的实际成交价)。

笔记中使用了最直观的均方误差(MSE)思想。为了简化推导,我们只看单个样本的误差平方:

阅读全文 »

从静态词向量到动态理解:NLP 预训练模型的完整进化图谱

在自然语言处理(NLP)的学习过程中,理清模型的演进路线至关重要。这不仅仅是算法的更迭,更是我们对“如何让机器理解语言”这一认知不断深化的过程。回顾过去几年,我们经历了一场从简单的词嵌入到大规模预训练模型的技术革命。

今天,我们就来梳理一下这条从 Word2Vec 到 ELMo,再到 BERT 的进阶之路,看看它们是如何一步步改变我们对“语言表示”的理解的。


一、起点:Word2Vec 与静态词向量

一切的起点是从 Word2Vec 开始的。

维度 说明
核心思想 将离散的词语映射为连续的向量空间(Embedding)
核心贡献 解决了计算机无法直接处理文本的问题,并且捕捉到了词语之间的语义关系。比如:King - Man + Woman ≈ Queen
致命局限 它是静态的。无论上下文如何变化,“苹果”这个词的向量永远是一样的,无法区分是水果还是科技公司。

这种“一词多义”的困境,严重限制了模型对上下文的理解能力。


二、序列建模:RNN 及其变体

为了让机器理解句子的顺序和语境,我们引入了循环神经网络(RNN)。

原理:RNN 具有“记忆”功能,当前的输出不仅取决于当前的输入,还取决于上一时刻的状态。这使得它能够处理变长的序列数据。

痛点:标准的 RNN 存在严重的梯度消失/梯度爆炸问题,导致它很难记住长句子开头的信息(即长距离依赖问题)。

改进:为了解决这个问题,LSTM(长短期记忆网络)和 GRU(门控循环单元)应运而生。它们通过引入“门控机制”,有效地控制了信息的遗忘和保留,成为了当时处理序列任务的主流选择。

模型 核心创新 解决的问题
RNN 循环结构,具有“记忆” 处理变长序列
LSTM 遗忘门、输入门、输出门 长距离依赖(梯度消失)
GRU 更新门、重置门(LSTM 的简化版) 长距离依赖,参数更少

三、架构升级:Seq2Seq 模型

有了强大的 RNN/LSTM 作为基础组件,Seq2Seq(Sequence-to-Sequence)架构诞生了。

结构:它由两个 RNN 组成——编码器(Encoder)解码器(Decoder)

工作流

阅读全文 »

Python 多进程:8 核 CPU 只有 1 个在跑?那是 GIL 在”捣乱”

写了一个计算密集型的程序,跑起来 CPU 占用 100%,但仔细一看——8 个核心只有 1 个在满负荷运转,剩下 7 个几乎闲置。

这不是程序写得不对,是 Python 的 GIL(全局解释器锁) 在”从中作梗”。GIL 规定:同一时刻,只有一个线程能执行 Python 代码。所以多线程在计算任务上,形同虚设。

多进程是绕过 GIL 的方案——每个进程有自己独立的 Python 解释器,各自跑各自的,操作系统把它们调度到不同的 CPU 核心上,真正实现了并行。

什么场景用多进程? CPU 密集型——大量计算、加密解密、图像处理、数据压缩。这种任务”算”的时间多,”等”的时间少,多线程没用,多进程才是答案。

先看证据:多线程 vs 多进程,差距一目了然

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
import multiprocessing
import threading
import time
from concurrent.futures import ThreadPoolExecutor

def cpu_work(n):
"""纯计算,不干别的"""
count = 0
for i in range(2, n):
is_prime = all(i % j != 0 for j in range(2, int(i**0.5) + 1))
if is_prime:
count += 1
return count

# 单线程
start = time.time()
cpu_work(100000)
print(f"单线程: {time.time() - start:.2f}s")

# 多线程(4个)
start = time.time()
with ThreadPoolExecutor(4) as ex:
[cpu_work(100000) for _ in range(4)]
print(f"多线程: {time.time() - start:.2f}s") # 比单线程还慢!

# 多进程(4个)
start = time.time()
with multiprocessing.Pool(4) as pool:
pool.map(cpu_work, [100000] * 4)
print(f"多进程: {time.time() - start:.2f}s") # 约单线程的 1/4

典型结果:

1
2
3
单线程: 2.35s
多线程: 2.78s ← 不仅没快,还慢了
多进程: 0.68s ← 接近 4 倍提速

多线程在计算任务上不仅无效,线程切换还带来额外开销。多进程才是真正的并行。

多进程池:别反复创建进程,用池复用

每次创建进程都要分配内存、启动解释器,开销不小。实际开发用进程池——预先创建一批进程,任务来了分配,干完了回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import multiprocessing

def worker(n):
return n ** 2

if __name__ == "__main__":
# 默认进程数 = CPU 核心数
with multiprocessing.Pool() as pool:
# map:批量提交,保持顺序
results = pool.map(worker, range(100))

# apply_async:异步提交单个任务
async_result = pool.apply_async(worker, (10,))
result = async_result.get() # 获取结果(会阻塞)

进程数设多少?

任务类型 建议进程数
CPU 密集型 CPU 核心数
I/O 密集型(但不想用异步) CPU 核心数 × 2
内存密集型 CPU 核心数 ÷ 2

设多了没用,操作系统频繁切换进程反而拖慢速度。

阅读全文 »

深度学习三大支柱:CNN、RNN 与预训练语言模型

核心认知:深度学习并非单一技术,而是由不同架构组成的“武器库”——CNN 擅长捕捉空间特征(图像),RNN 擅长处理时序依赖(文本、语音),而预训练语言模型(PLM)则通过“预训练+微调”范式,彻底改变了自然语言处理的方式。

本文将从原理到应用,系统讲解三种核心深度学习架构,帮助你建立完整的知识体系。


目录

  1. 卷积神经网络(CNN)
  2. 循环神经网络(RNN)与双向RNN
  3. 预训练语言模型与微调
  4. 三者对比与选型

卷积神经网络(CNN)

为什么需要 CNN?

传统的全连接神经网络在处理图像时会遇到两个致命问题:

问题 说明 后果
参数爆炸 一张 224×224 的彩色图片有 150,528 个像素,全连接层的参数数量 = 输入维度 × 输出维度 需要海量数据和计算资源
丢失空间结构 全连接层将像素展平为一维向量,忽略像素间的空间关系 无法识别边缘、形状等局部模式

CNN 的解决方案:通过局部连接权重共享,大幅减少参数数量,同时保留空间结构信息。

CNN 的核心组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────────────┐
│ CNN 整体架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 输入图像 卷积层 池化层 卷积层 池化层 全连接层 输出 │
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─┐ │
│ │ │ ──→ │ │ ──→ │ │ ──→ │ │ ──→ │ │ ──→ │ │ ──→ │ │ │
│ │ 224 │ │ 222 │ │ 111 │ │ 110 │ │ 55 │ │ 4096│ │10│ │
│ │ ×224│ │ ×222│ │ ×111│ │ ×110│ │ ×55 │ │ │ │ │ │
│ │ ×3 │ │ ×32 │ │ ×32 │ │ ×64 │ │ ×64 │ │ │ └─┘ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
3 32 32 64 64 4096 10
│ (RGB通道) (特征图) (降维后) (特征图) (降维后) (特征向量) (类别) │
│ │
└─────────────────────────────────────────────────────────────────────┘

1. 卷积层(Convolutional Layer)

卷积核(Filter/Kernel):一个小的权重矩阵(如 3×3 或 5×5),在输入图像上滑动,计算局部区域的点积,生成特征图。

1
2
3
4
5
6
7
8
9
10
11
12
卷积操作可视化(步长=1,无填充)

输入图像 (5×5) 卷积核 (3×3) 输出特征图 (3×3)
┌─────────────────┐ ┌─────────┐ ┌─────────────────┐
1 1 1 0 0 │ │ 1 0 1 │ │ 4 3 4
0 1 1 1 0 │ ⊙ │ 0 1 0 │ = │ 2 4 3
0 0 1 1 1 │ │ 1 0 1 │ │ 2 3 4
0 0 1 1 0 │ └─────────┘ └─────────────────┘
0 1 1 0 0
└─────────────────┘

每个输出位置 = 卷积核与输入对应区域的逐元素乘积之和

关键参数

阅读全文 »