1. 为什么我们需要嵌入层?
在自然语言处理任务中,我们经常会遇到这样的场景:模型需要处理"我爱吃苹果"这样的文本序列。但计算机无法直接理解文字,我们需要先将词语转换为数字表示。最直观的做法就是给每个词分配一个整数索引,比如:
code复制"我" → 4
"爱" → 5
"吃" → 6
"苹果" → 7
这样,句子"我爱吃苹果"就可以表示为[4,5,6,7]的整数序列。看起来简单直接,但这种表示方式存在两个致命缺陷:
1.1 整数索引的本质缺陷
语义缺失问题:整数索引只是简单的编号,完全无法体现词语之间的语义关系。比如:
- 索引7比4大,但这并不表示"苹果"比"我"更重要
- "苹果"和"香蕉"都是水果,但它们的索引数字之间没有任何关联
维度不匹配问题:RNN等神经网络的核心运算是矩阵乘法,需要输入的是连续的低维向量。而整数索引是离散的标量值,无法直接参与这些运算。
举个生活化的例子:整数索引就像给每个学生分配学号,学号大小与学生能力无关;而我们需要的是能反映学生各科成绩的特征向量。
1.2 嵌入层的解决方案
嵌入层(Embedding)的核心作用就是解决上述两个问题:
- 将离散的整数索引转换为连续的稠密向量
- 在转换过程中保留并学习语义关系
用专业术语来说,嵌入层实现了:
- 分布式表示:每个词被表示为整个向量空间中的一个点
- 语义编码:语义相似的词在向量空间中距离相近
这种转换可以用一个形象的比喻理解:
- 整数索引是词语的"身份证号" - 唯一但无意义
- 嵌入向量是词语的"特征档案" - 全面描述其特性
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 嵌入层的实现机制
2.1 嵌入矩阵:可学习的查找表
嵌入层的核心是一个可学习的参数矩阵,称为嵌入矩阵(Embedding Matrix)。其维度为:
code复制[vocab_size, embedding_dim]
其中:
vocab_size:词汇表大小,即所有唯一token的数量embedding_dim:每个token对应的向量维度
2.1.1 矩阵维度选择原则
| 维度参数 | 典型取值 | 选择原则 |
|---|---|---|
| vocab_size | 与词汇表一致 | 必须覆盖所有token+特殊token |
| embedding_dim | 50/128/256/512 | 数据量越大,维度可以越高 |
例如,对于一个包含8个token的词汇表,设置embedding_dim=4,嵌入矩阵可能长这样:
python复制[
[0.0, 0.0, 0.0, 0.0], # <PAD>(0)
[0.1, 0.2, 0.3, 0.4], # <UNK>(1)
[0.5, 0.6, 0.7, 0.8], # <SOS>(2)
[0.9, 1.0, 1.1, 1.2], # <EOS>(3)
[1.3, 1.4, 1.5, 1.6], # "我"(4)
[1.7, 1.8, 1.9, 2.0], # "爱"(5)
[2.1, 2.2, 2.3, 2.4], # "吃"(6)
[2.5, 2.6, 2.7, 2.8] # "苹果"(7)
]
2.2 索引到向量的映射过程
嵌入层的运作机制本质上是一个查表过程:
- 输入:整数索引(如4)
- 查找:取嵌入矩阵的第4行
- 输出:对应的向量
[1.3,1.4,1.5,1.6]
对于序列输入,如[4,5,6,7],嵌入层会并行查表,输出:
python复制[
[1.3,1.4,1.5,1.6], # 我
[1.7,1.8,1.9,2.0], # 爱
[2.1,2
