Embedding 学习笔记
通过 8 道由浅入深的问题,系统理解深度学习中的 Embedding 概念。 本笔记包含:原题、个人作答、详细解析。
学习背景
Embedding(嵌入)是深度学习中将"离散符号"转换为"稠密向量"的核心技术。它解决了神经网络"只能输入张量"这一限制下,如何处理单词、用户 ID、商品 ID 等非数值型数据的问题。
第 1 题:动机题 —— One-hot 的问题
题目
假设要做电影评论情感分类,词表 1 万个词。最朴素的做法是把每个词变成 one-hot 向量:
- "好" →
[0, 0, ..., 1, 0, ..., 0](1 万维,第 372 位是 1) - "棒" →
[0, 1, 0, ..., 0](1 万维,第 2 位是 1)
问:用 one-hot 喂神经网络有什么问题?至少说出 2 个。
我的回答
工程量大,只能分析严格的好坏词。
解析
方向对,但只说到一半。完整的 one-hot 4 个核心问题:
1. 维度灾难("工程量大"对应这点 ✅)
- 1 万个词 → 每个词都是 1 万维向量,99.99% 都是 0
- 现实词表常达 5 万~10 万,向量太稀疏,存储和计算都浪费
2. 没有语义信息(这点最关键,初答没抓到)
- "猫" =
[0,0,1,0,...],"狗" =[0,1,0,0,...],"桌子" =[1,0,0,0,...] - 任意两个 one-hot 向量的距离完全相等(都是 √2)
- 在 one-hot 空间里,"猫"和"狗"的距离 = "猫"和"桌子"的距离,模型看不出"猫狗都是动物"
3. 不可学习
- One-hot 是死的,训练再久它也不会变
4. 泛化差
- 训练时见过"好看",没见过"漂亮",模型完全不知道这俩相关
核心结论
Embedding 的核心动机:把高维稀疏的 one-hot 压缩成低维稠密、且语义相近的词向量也相近的表示。
第 2 题:本质题 —— Embedding 向量从哪来?
题目
Embedding 通常被描述成"把一个词映射成一个稠密向量",比如:
- "猫" →
[0.2, -0.5, 0.8, 0.1, ...](64 维)
问:这个 64 维向量是怎么"产生"的?是人类设计的、查字典查到的、还是其他?
我的回答
是机器自己生成的。
解析
✅ 完全正确!更精确地说:
- Embedding 矩阵的初始值是随机的(一开始毫无意义)
- 训练过程中,它和模型的其他参数一起被梯度下降优化
- 最终学出来的向量,是模型为了完成任务"自己摸索出来的最佳数字表示"
核心结论
Embedding 是可学习参数,不是预先定义的字典。它和"权重矩阵 W"是同等地位的可训练对象。
第 3 题:代码题 —— Embedding 内部结构
题目
import torch.nn as nn embedding = nn.Embedding(num_embeddings=10000, embedding_dim=64) word_id = torch.tensor([42]) vector = embedding(word_id) print(vector.shape)问:
embedding这个对象的内部"长什么样"?embedding(word_id)这一步到底在做什么运算?
我的回答
10000×64 的矩阵?转成数字矩阵?
解析
第 1 问:"10000×64 的矩阵" ✅ 完全正确!
nn.Embedding(10000, 64)内部就是一个形状为(10000, 64)的矩阵,每一行对应一个词的 64 维向量:
维度0 维度1 维度2 ... 维度63 词0: [ 0.21, -0.5, 0.8, ..., 0.1] 词1: [-0.3, 0.4, 0.2, ..., -0.7] 词2: [ 0.5, 0.1, -0.9, ..., 0.3] ... 词9999:[ 0.0, -0.2, 0.6, ..., 0.4]第 2 问:关键认知需要纠正!
embedding(word_id)做的不是矩阵乘法,而是查表(取行):
embedding = nn.Embedding(10000, 64) embedding(torch.tensor([42])) # 等价于: embedding.weight[42] # 直接取第 42 行它就是个查字典操作:给我编号 42,我就把矩阵的第 42 行返回给你。
为什么这点重要?
理论上,one-hot 乘以 embedding 矩阵也能得到同样结果:
[0,0,...,1,...,0] @ W(10000×64) = W 的第 42 行 ↑ 第 42 位是 1但这种乘法 99.99% 在乘 0,完全是浪费。所以工程上直接用查表实现,速度快几百倍。
核心结论
Embedding 是"索引"不是"乘法"。理解这点能帮你看懂工程实现。
第 4 题:语义题 —— 神经网络怎么学到语义?
题目
人们常说:"训练好的 embedding 中,语义相近的词,向量也相近。" 比如:
vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")问:神经网络从来没有人告诉它"国王和女王是相关的",它是怎么学到这种语义关系的?关键的"训练信号"来自哪里?
我的回答
通过 embedding 的相似数字。
解析
⚠️ 这个回答描述的是结果,但没回答原因。
答案的关键是:上下文(context)
经典的 Word2Vec 训练任务:
给定句子: "国王 在 城堡 里 统治" "女王 在 城堡 里 统治" "国王 戴着 王冠" "女王 戴着 王冠"训练目标:根据上下文预测中心词(或反过来)。
模型注意到:
- "国王"和"女王"总是出现在相似的上下文里(城堡、统治、王冠...)
- 为了让模型在这些相似上下文中都能预测对,它必须给"国王"和"女王"分配相似的向量
- 训练完成后,这两个词的向量自然就靠在一起了
核心思想(语言学上叫"分布假说"):
"You shall know a word by the company it keeps." —— J.R. Firth, 1957 (一个词的意义由它周围的词决定)
核心结论
训练信号不是"有人告诉模型它们相关",而是**"它们出现在相似的语境里"** 这个统计规律,被神经网络通过反向传播自动发现。
你不需要标注任何"语义相似"的数据,只要让模型读海量文本做"完形填空",它自动就学出了语义。
第 5 题:推广题 —— 哪些数据适合 Embedding?
题目
Embedding 不仅能用在词上,还能用在用户 ID、商品 ID、棋盘位置、类别变量等。
问:什么样的数据适合用 Embedding?
(提示:对比"图像像素"和"用户 ID"——为什么图像不需要 embedding,但用户 ID 需要?)
我的回答
图像本身有红绿蓝的数据,但是用户 ID 没有数据信息。
解析
✅ 直觉非常正确!抓到了本质。提炼成更通用的判断标准:
适合 Embedding 的数据:离散符号(Discrete Symbols)
特征:
- ✅ 取值是离散的、有限的(用户 ID、商品 ID、单词、类别)
- ✅ 编号本身没有数值意义(用户 #42 和用户 #43 不是"相邻"的)
- ✅ 编号之间没有大小关系("星期一"和"星期二"在数值上差 1,但语义上没那种线性关系)
不适合(不需要)Embedding 的数据:连续数值、有真实意义的数字
特征:
- ❌ 取值连续(像素值 0~255、温度、价格)
- ❌ 数值本身有意义(像素 200 比像素 100 更亮,这是真实的物理意义)
- ❌ 可以直接做数学运算
对比清晰版:
| 数据 | 编号意义 | 处理方式 |
|---|---|---|
| 像素值 200 | "亮度是 200",本身有意义 | 直接用 / 归一化 |
| 用户 ID 42 | "随便编的号",没含义 | 查 Embedding |
| 单词编号 42 | "词表里的位置",没含义 | 查 Embedding |
| 温度 25°C | "温度是 25 度",有物理含义 | 直接用 |
| 类别 "星期三" → 3 | 编号无意义 | 查 Embedding |
核心结论
Embedding 是"离散符号 → 稠密向量"的可学习翻译器。 判断要不要用 embedding,看输入是不是编号无意义的离散符号。
第 6 题:陷阱题 —— Embedding 工作流程
题目
哪段代码对、哪段错?为什么?
# 代码 A embedding = nn.Embedding(10000, 64) text = "我爱深度学习" vector = embedding(text) # 代码 B embedding = nn.Embedding(10000, 64) token_ids = torch.tensor([101, 234, 567, 890]) vector = embedding(token_ids) print(vector.shape)我的回答
B 报错,A 输出形式 10000×64,不知道什么环节。
解析
❌ 答反了!正确答案:A 报错,B 正确
代码 A 错在哪?
embedding(text) # text = "我爱深度学习"nn.Embedding的输入必须是整数张量(索引),不能直接喂字符串!
正确流程是 3 步:
原始文本 "我爱深度学习" ↓ 第 1 步:分词(Tokenization) ['我', '爱', '深度', '学习'] ↓ 第 2 步:映射成整数 ID(查词表) [101, 234, 567, 890] ← 这一步把字符串变成数字 ↓ 第 3 步:Embedding 查表 tensor of shape (4, 64) ← 每个词变成 64 维向量关键认知:Embedding 不负责"文字到数字"的转换,它只负责"整数 ID → 向量"的转换。前面的分词和 ID 映射要靠Tokenizer(分词器)完成。
代码 B 输出的形状:
token_ids = torch.tensor([101, 234, 567, 890]) # shape: (4,) vector = embedding(token_ids) # shape: (4, 64)输入 4 个 ID → 输出 4 个 64 维向量,所以是(4, 64)。
注意这里4就是行,是那10000行的子集
# ========== 第一步:准备工作(训练前一次性完成)==========
# Tokenizer 训练(基于大语料统计)
# 生成词表 vocab.txt:(生成就固定不动了)
# "我" → 101
# "爱" → 234
# "深度" → 567
# "学习" → 890
# ... 共 10000 个词# ========== 第二步:模型定义(搭建模型结构时)==========
import torch
import torch.nn as nn# 创建一个 10000×64 的 embedding 矩阵
# 初始值是随机的!还没经过训练,向量没有意义
embedding = nn.Embedding(10000, 64)# ========== 第三步:使用流程(每次前向传播)==========
# 1) 用 Tokenizer 把文本转成 ID(查固定的对照表)
text = "我爱深度学习"
token_ids = torch.tensor([101, 234, 567, 890]) # 来自固定词表
print(token_ids.shape) # torch.Size([4])# 2) 用 Embedding 把 ID 转成向量(查可学习的矩阵)
vectors = embedding(token_ids)
print(vectors.shape) # torch.Size([4, 64])# ========== 第四步:训练(这里 Embedding 矩阵会被更新)==========
# 训练循环中,loss.backward() + optimizer.step() 会更新
# embedding.weight 这个 (10000, 64) 矩阵的数值
# Tokenizer 词表始终不动
通用规则:
输入 shape: (...) 输出 shape: (..., embedding_dim) 例: (4,) → (4, 64) 单个句子 4 个 token (32, 100) → (32, 100, 64) 32 个句子,每句 100 个 token,一共32*100行embedding 10000行子集核心结论
Embedding 在完整 NLP 流程中的位置:
原始文本 → [Tokenizer] → 整数 ID → [Embedding] → 稠密向量 → [模型主体] → 输出 ↑ ↑ 字符串变数字 数字变向量整个流程像装配线,分工明确。
第 7 题:对比题 —— PCA vs Embedding
题目
考虑两种方案,把 1 万个词的词表变成 64 维向量:
- 方案 A:用 PCA 对 one-hot 矩阵降维到 64 维
- 方案 B:用
nn.Embedding(10000, 64)在下游任务上训练
问:这两种方案得到的 64 维向量有什么本质区别?哪种通常效果更好?
我的回答
PCA 其实不太了解。
解析
先补 PCA 知识:
PCA(主成分分析):一种线性降维方法。把高维数据投影到低维空间,保留方差最大的方向。它是无监督的,不需要任务标签,只看数据本身的统计结构。
两种方案的本质区别:
| PCA 降维 | nn.Embedding 训练 | |
|---|---|---|
| 学习目标 | 保留数据方差 | 服务下游任务 |
| 是否监督 | 无监督 | 有监督(跟着任务一起学) |
| 数学本质 | 线性变换(一次到位) | 梯度下降迭代优化 |
| 是否任务相关 | 不相关 | 强相关 |
关键洞察:
- PCA 给所有词分配的向量,对所有任务都一样——情感分析、机器翻译、命名实体识别都用同一套向量。
- nn.Embedding 学到的向量,是为了让你的特定任务表现最好而调整的——做情感分析任务时,"好"和"棒"会被拉到一起;做语法分析任务时,可能"动词"会被拉到一起。
通常哪个效果好?:Embedding 训练几乎总是赢,因为它是任务驱动的。但如果数据极少,PCA 这类简单方法有时反而更稳。
核心结论
Embedding 不是"通用的词向量",而是"为任务而生的词向量"。 这也是为什么大模型的 embedding 那么强——它们见过的数据和任务足够多,学出的向量足够通用。
第 8 题:综合题 —— 维度的权衡
题目
现代大模型(如 GPT、BERT)的 embedding 维度往往是 768、1024、4096 甚至更高。
问:
- 维度选大还是小,各有什么权衡?
- 1 万个词、每个 64 维 vs 1 万个词、每个 4096 维,参数量差多少?
- 如果只有 1000 条训练数据,你会选 64 维还是 4096 维?
我的回答
不会。embedding 的大小和 batch 的大小的区别是什么?
解析
先回答反问:embedding_dim vs batch_size 有什么区别?
batch_size = 32 # 每次处理 32 个句子 seq_length = 100 # 每个句子 100 个 token embedding_dim = 64 # 每个 token 用 64 维向量表示 vocab_size = 10000 # 词表大小 embedding = nn.Embedding(vocab_size, embedding_dim) input_ids = torch.randint(0, 10000, (batch_size, seq_length)) # (32, 100) output = embedding(input_ids) # (32, 100, 64)形状演化:
input: (32, 100, ) ↑ ↑ batch 序列长度 output: (32, 100, 64) ↑ ↑ ↑ batch 序列长度 embedding_dim| 概念 | 含义 | 维度作用 |
|---|---|---|
| batch_size | 一次处理多少个样本 | 训练时的并行度,不影响模型结构 |
| embedding_dim | 每个符号用多少维向量表示 | 模型的"表达容量",模型结构的一部分 |
类比:
- batch_size = 一次同时教多少个学生
- embedding_dim = 每个词用多丰富的语义来描述
batch_size 改了,模型本身不变(只是训练效率不同);embedding_dim 改了,模型结构变了(参数量不一样了)。
回到原题:
1. 维度大小的权衡:
| 维度 | 优点 | 缺点 |
|---|---|---|
| 维度大(4096) | 表达能力强,能捕捉细微语义差别 | 参数多、容易过拟合、训练慢 |
| 维度小(64) | 参数少、训练快、不易过拟合 | 表达能力有限,可能欠拟合 |
2. 参数量对比:
10000 词 × 64 维 = 64 万参数 (~2.5 MB) 10000 词 × 4096 维 = 4096 万参数 (~160 MB)差64 倍!这意味着:
- 显存占用差 64 倍
- 需要的训练数据差很多倍(参数越多越容易过拟合)
3. 1000 条数据,选哪个?
毫无疑问选 64 维!
原因:参数量必须和数据量匹配。4096 维有 4 千万参数,1000 条数据连"塞牙缝"都不够,模型会严重过拟合——它会"背"下训练集而不是"学"规律。
经验法则(粗略版):
| 数据量 | 推荐 embedding_dim |
|---|---|
| < 1 万 | 32~128 |
| 1 万~100 万 | 128~512 |
| 100 万+ | 512~4096 |
| 上亿(GPT 级别) | 4096~12288 |
核心结论
embedding_dim 是模型容量参数,必须和数据规模匹配。盲目加大维度只会让模型过拟合,而不是变强。
总结:3 个最重要的核心认知
复习时重点掌握这 3 点:
1. Embedding 是"查表"不是"乘法"
embedding(torch.tensor([42])) # 等价于 embedding.weight[42]理论上是"one-hot @ 矩阵",工程上直接索引取行,速度快几百倍。
2. Embedding 学到语义靠的是"上下文统计"
"You shall know a word by the company it keeps."
模型从来没被告知"国王和女王相关",它只是发现这两个词出现在相似上下文里,于是给了它们相似向量。核心训练信号 = 上下文共现统计。
3. Embedding 在完整流程中的位置
原始文本 → [Tokenizer] → 整数 ID → [Embedding] → 稠密向量 → [模型主体] → 输出 ↑ ↑ 字符串变数字 数字变向量Embedding不负责字符串处理,它只接受整数索引输出向量。前面的分词和编号要靠 Tokenizer 完成。
关键概念速查表
| 概念 | 定义 | 关键点 |
|---|---|---|
| One-hot | 用一个长向量、只有一位是 1 表示符号 | 维度高、稀疏、无语义 |
| Embedding | 把离散符号映射为稠密向量的查找表 | 可学习、稠密、有语义 |
| embedding_dim | 每个符号对应的向量维度 | 模型容量参数 |
| vocab_size | 词表大小(能处理的不同符号数) | 决定 embedding 矩阵的行数 |
| Tokenizer | 把原始文本切分并编号的工具 | Embedding 的"前置处理器" |
| batch_size | 一次训练处理的样本数 | 训练超参,与模型结构无关 |
| 分布假说 | 词的意义由其上下文决定 | Embedding 学到语义的理论基础 |
延伸学习方向
掌握这些基础后,可以进一步学习:
- Word2Vec / GloVe:经典的预训练词向量算法
- Position Embedding:Transformer 中的位置编码
- Tokenizer 的演化:从字粒度 → BPE → WordPiece → SentencePiece
- 预训练 Embedding 的迁移使用:直接用 BERT/GPT 的 embedding 做特征
- Embedding 可视化:用 t-SNE / UMAP 把高维 embedding 投到 2D 看效果
