想象一位经验丰富的老演员,抽屉里却只有一张定妆照——那是刚入行时拍的。无论是演《哈姆雷特》里的忧郁王子,还是《华尔街之狼》里的金融精英,导演翻遍他的简历,只能看到一模一样的那张脸。这就是 Word2Vec 时代词向量的尴尬:每个词只有一张“定妆照”,无论出现在什么语境里,向量都完全相同。ELMo 的出现改变了这一切。它不再给演员发定妆照,而是让演员拿到完整剧本,通读前后剧情后再开始表演。“bank“出现在”河边“就是河岸,出现在”银行”就是金融机构——同一张脸,完全不同的演绎。这篇文章将带你看懂 ELMo 如何用双向 LSTM 动态生成上下文相关词向量,它凭什么成为预训练语言模型的里程碑,又为 BERT 铺下了怎样的道路。
掌握 ELMo 是理解 BERT 时代的钥匙:它让你看清词嵌入从“查表”到“推理”的范式跃迁,也是面试中高频考察的 NLP 演进脉络关键节点。
可视化演示
展示ELMo双向LSTM架构、字符级CNN与层级加权机制,以及静态嵌入vs上下文嵌入对比。切换模式探索ELMo如何解决一词多义问题。
本节配有交互动效(进入视口后自动加载,约 3KB)。
ELMo双向LSTM架构 字符级CNN提取子词特征,双向LSTM捕获上下文信息 前向LSTM从左到右编码,后向LSTM从右到左编码,拼接得到上下文嵌入 ELMo = γ·(s₀·e + s₁·h₁_fwd + s₁·bwd + s₂·h₂_fwd + s₂·bwd) 双向LSTM是ELMo的核心架构
一词多义困境
想象一位演员的简历上只有一张定妆照。他演大侠是这张照片,演医生是这张照片,演反派还是这张照片。导演想看的“角色感”完全看不出来。这个画面就是 2013 年 Word2Vec 诞生以来词向量的真实写照——每个词只有一张“定妆照”,是一段写死的固定向量。
Word2Vec 和 GloVe 属于静态词嵌入:训练完成后,一个词对应一个唯一的向量,直接查表得到。“bank“无论出现在”河边 bank 钓鱼“还是”银行 bank 存钱“,查出来的向量一模一样。这种”一词一向量“机制,本质上是在给一个词的所有含义取”平均”。
语言是一词多义的重灾区。“苹果”可以是水果也可以是公司,“Java“是岛屿也是编程语言。当同一个向量要同时表示多种含义时,它只能落在所有含义的”几何中心”——对任何一个具体语境都不精确。对下游任务来说,这种模糊表示就像一张五官模糊的照片,导演怎么选角都别扭。
要让“词”真正会演戏,就得丢掉那张定妆照,让词根据“剧本”(上下文)现场发挥。这正是 ELMo 登场的理由。
ELMo核心思想
ELMo 选演员的方式完全不同。它不再翻照片,而是递给演员一本完整剧本说:“你把整个故事读一遍,再告诉我这个角色该怎么演。”同一个演员,拿到《百万英镑》里的 bank 和《河岸疑云》里的 bank,表演一定不一样。
ELMo 的全称是 Embeddings from Language Models——“来自语言模型的嵌入”。它用大规模语料训好一个语言模型,当我们要得到某个词 w 的向量时,不是去查表,而是把 w 所在的完整句子输入语言模型,让模型“读完”之后,从隐藏状态中抽出 w 对应的表示。这个向量不再属于“词典”,而属于“这句话里的这个词”。
这里的语言模型怎么“读句子”很关键。如果它只从左往右读,那就只能看到 w 左边的词;如果只从右往左读,只能看到右边的词。ELMo 选择两个方向都读——前向 LSTM 和后向 LSTM 各读一遍,把两个方向的隐藏状态拼在一起,这样 w 的向量同时携带了“左邻右舍”的全部信息。
所以 ELMo 的词向量是“演出来”的,不是“查出来”的。同一个 bank,剧本不一样,表演就不一样。这就是上下文相关词嵌入的灵魂。
双向LSTM架构
一名真正的好演员,拿到剧本后不会只看自己出场的前一页。他会先翻到最后一页,搞清楚整个故事的走向,再回头审视自己的每一句台词。ELMo 的双向 LSTM 就是这种“既顾前又顾后”的读法。
LSTM(长短期记忆网络)是一种按顺序处理序列的神经网络,它有一个“记忆单元”,能一边读新词一边选择性遗忘和更新旧信息。单向 LSTM 从左往右读,每个位置的状态只能依赖左侧输入。双向 LSTM 则同时准备两套:前向 LSTM 从左读,后向 LSTM 从右读。
设输入句子为 $x_1$ 到 $x_N$,前向 LSTM 在位置 $t$ 输出隐藏状态 $\vec{h}_t$,后向 LSTM 输出 $\overleftarrow{h}_t$。两者按维度拼接就得 $h_t = [\vec{h}_t;\, \overleftarrow{h}_t]$。这个拼接向量既编码了 $x_t$ 左边的内容,又编码了右边的内容,是 ELMo 理解的“当前语境”。
就像演员既看前情也看后续,才敢对当前这句台词定调——$h_t$ 就是这个“定调”。
- 前向编码LSTM 从左到右依次读取词序列,每读到一个位置 $t$,输出该位置的隐藏状态,它浓缩了从开头到 $t$ 的全部信息。
- 后向编码另一个 LSTM 从右到左读取,输出依赖右侧上下文的隐藏状态,它浓缩了从结尾到 $t$ 的全部信息。
- 拼接合成两个方向的隐藏状态按向量维度拼接,得到既含前文又含后文信息的表示。
预训练任务
演员“会看剧本”这项能力,是几十年舞台经验沉淀出来的。ELMo 的双向 LSTM 也不是天生就会理解语言,它需要在大规模语料上“演戏”——这就是预训练。
ELMo 的预训练任务是双向语言模型。语言模型的经典玩法是:给你前面几个词,让你猜下一个词。前向 LSTM 干的就是这个;后向 LSTM 反着来:给你后面几个词,猜前面那个。ELMo 把两个方向的猜测损失加起来,让两个 LSTM 同时学会“预测”。
给定一个句子 $x_1, \ldots, x_N$,训练目标是在每个位置 $t$ 同时最大化前向预测概率和后向预测概率,再对整个序列求和。
在海量剧本中摸爬滚打之后,两个方向的 LSTM 都成了“老戏骨”,对语境的理解炉火纯青。预训练完毕,它们的隐藏状态就是高质量的上下文嵌入。
- 前向语言模型项已知 $x_t$ 之前的词,模型给出当前词 $x_t$ 的预测概率,取对数后作为该项得分。
- 后向语言模型项已知 $x_t$ 之后的词,模型反向预测 $x_t$ 的概率,取对数后作为该项得分。
- 求和对句子中每个位置的词都做同样的计算并相加,让模型在整句话上都能做出好预测。
嵌入合成策略
导演剪辑一部电影时,不会只用演员某一个镜头的表现。眼神特写、肢体动作、语气起伏,不同层次的表演各有价值,关键是怎么按剧情需要组合它们。ELMo 的“导演”也面临同样的取舍。
双向 LSTM 一叠就是好多层,每一层的隐藏状态编码了不同粒度的信息:底层更接近词语本身,捕捉词法和句法;高层经过更多抽象,更偏向语义和语境。ELMo 不武断地只取某一层,而是对各层来一个“加权和”。
设第 $j$ 层在位置 $t$ 的输出为 $h_t^j$(其中第 0 层是词嵌入本身),ELMo 用可学习的 softmax 权重 $s_j$ 对各层加权求和,再乘一个任务相关缩放系数 $\gamma$,得到最终的 ELMo 向量。这个设计让下游任务可以自己决定:我主要靠底层句法还是高层语义。
导演按剧情需要把不同层次的“表演”混在一起,这才有了 ELMo 既能当“手感派”又能当“学术派”的适应能力。
- 各层表示第 $j$ 层 LSTM 在位置 $t$ 的隐藏状态为 $h_t^j$,第 0 层是输入的词嵌入本身。
- 加权求和用 softmax 归一化后的权重 $s_j$ 乘上各层表示再相加,得到融合了多层信息的向量。
- 尺度缩放$\gamma$ 是任务相关的标量系数,控制整个向量的模长,避免拼接后数值过大或过小。
与静态嵌入对比
定妆照和实景表演的区别,就是静态嵌入和 ELMo 的区别。Word2Vec 是“摄影师拍完就收工”,ELMo 是“摄影机一直开着,演员每时每刻都在状态里”。
Word2Vec 用神经网络预测周边词来学静态向量;GloVe 用全局词共现统计分解矩阵得到静态向量。两者训完之后都是一张“查表”:输入词 ID,输出固定向量。ELMo 则完全不同:输入词 ID 加完整句子,输出一个“当场计算”的向量。
静态嵌入无法区分“河岸”和“银行”;ELMo 的 bank 向量在两个语境中落在向量空间的不同区域。另一方面,静态嵌入查表只需一步,而 ELMo 需要对整句做一次前向传播,计算成本高得多。这是“动态表示”的代价,也是它的价值。
导演想拍的是一场“当下”的戏,当然需要一个“现在进行时”的演员,而不是一张“过去完成时”的照片。
下游任务应用
大腕演员进组之后,不会抢主角的戏,而是把自己的表演经验“递给”对手戏演员,让整场戏更丰满。ELMo 进入下游任务也是这个姿态——不替代任务模型,而是把自己的上下文向量当作一份“表演心得”拼进任务框架里。
典型的用法是:对句子里每个词,把它的 ELMo 向量和任务自己的词嵌入、字符嵌入拼在一起,组成一个更丰富的输入特征,再喂给任务模型(比如做命名实体识别的 BiLSTM-CRF,或做问答的 BiDAF)。
训练时有两种选择:冻结 ELMo 参数只训练任务部分(把 ELMo 当特征提取器),或者让 ELMo 跟着任务一起微调。论文在 NER、SQuAD 问答、SNLI 推理等多个任务上验证:加上 ELMo 特征,效果普遍大幅提升——因为模型同时获得了“字典知识”(任务自己的嵌入)和“语境知识”(ELMo)。
有了这位“老戏骨”在边上搭把手,任务的准确率自然水涨船高。这也是预训练模型“借力”下游任务的标准姿势。
历史意义影响
一位方法派大师不仅自己表演精湛,还开创了一套新的“演员训练法”,后来的年轻演员全都按这套方法成长,但用上了更先进的技术装备。ELMo 就是这位大师,BERT 就是其中最有天赋的继任者。
ELMo 是第一个把“预训练语言模型 + 下游任务适配”这张王牌打出来的深度上下文嵌入模型。它证明了:用一个任务无关的大模型在海量文本上预训练,再插到具体任务里,能大幅提升效果——这在当时是革命性的。
这个范式直接启发了 BERT。BERT 做的两件事:把 LSTM 换成 Transformer(自注意力,可并行、能建模长距离依赖),把“特征拼接”升级为“全参数微调”。但 ELMo 开创的“用语言模型做上下文表征”的思路,是它最重要的遗产。当然 ELMo 也有局限:双向只是“拼接”而非“深度融合”,LSTM 串行计算慢,无法像 Transformer 那样并行。
没有 ELMo 在 2018 年点亮的那盏灯,就不会有后来 BERT 的一路狂奔。理解了 ELMo,你就抓住了整个预训练语言模型时代的第一块多米诺骨牌。
AI 应用场景
命名实体识别中的多义词消歧
阶段:文本特征表示 / 上下文编码
解决的问题:传统 NER 用 Word2Vec 查表,“苹果”在“苹果很好吃”和“苹果发布新品”中向量相同,无法区分水果和公司。ELMo 的上下文向量让实体类型判断有了语境依据,银行、苹果这类词不再“一个标签走天下”。
具体案例:在金融新闻 NER 系统中,“银行”在“河边银行”与“商业银行”中分别被识别为地理名词和机构名词。接入 ELMo 特征后,多义词实体的 F1 值提升了约 3-5 个百分点,误报率明显下降。
抽取式问答中的语境推理
阶段:文本语义匹配 / 阅读理解
解决的问题:阅读理解中,问“bank 在哪里”——是河边还是银行?单向模型无法利用后文线索,只靠词形匹配容易答错。ELMo 的双向表示能同时利用前后文,找出真正被指代的对象。
具体案例:在 SQuAD 抽取式问答中,把 ELMo 向量拼接到 BiDAF 模型的嵌入层,对包含多义词的样本回答准确率显著提高。ELMo 论文报告显示,SQuAD 上的提升幅度在多个任务中名列前茅。
情感分析中的语境感知
阶段:文本分类特征层
解决的问题:“这部电影很无聊”和“这个人一点也不无聊”中“无聊”的情感极性完全不同,静态嵌入无法区分。ELMo 能根据修饰词和句式结构动态调整表示,让分类器捕捉到“反讽”的线索。
具体案例:在电商评论情感二分类任务里,接入预训练 ELMo 特征后,包含反讽和语境反转的评论被更准确地分类,准确率提升 2-4%,尤其对“差评话中带夸”这类难例改善明显。
Python 演示
"""Mini ELMo: 同一个词在不同语境下得到不同向量使用 PyTorch 构建一个微型双向 LSTM 编码器"""import torchimport torch.nn as nnimport torch.nn.functional as F # 小语料: 两个包含 bank 的句子sent1 = ["我", "在", "bank", "边", "钓鱼"]sent2 = ["我", "去", "bank", "存", "钱"]vocab = sorted(set(sent1 + sent2))word2idx = {w: i for i, w in enumerate(vocab)} def to_tensor(sent): return torch.tensor([word2idx[w] for w in sent]).unsqueeze(0) class MiniBiLSTM(nn.Module): def __init__(self, vocab_size, embed_dim=16, hidden_dim=16): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True) def forward(self, sent): emb = self.embedding(sent) out, _ = self.lstm(emb) # (1, seq_len, 2*hidden_dim) return out model = MiniBiLSTM(len(vocab))model.eval() with torch.no_grad(): rep1 = model(to_tensor(sent1))[0, sent1.index("bank")] rep2 = model(to_tensor(sent2))[0, sent2.index("bank")] cos = F.cosine_similarity(rep1, rep2, dim=0) print("句子1:", sent1) print("句子2:", sent2) print("bank向量1:", rep1.numpy().round(2)) print("bank向量2:", rep2.numpy().round(2)) print("余弦相似度:", cos.item()) print("两个向量不同 -> 同一个词在不同语境中有不同表示")
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True)定义双向 LSTM,前向和后向各输出一份隐藏状态out, _ = self.lstm(emb)一次性对整句做双向编码,输出每个位置的拼接表示rep1 = model(to_tensor(sent1))[0, sent1.index("bank")]取出句子 1 中 bank 位置的上下文向量rep2 = model(to_tensor(sent2))[0, sent2.index("bank")]取出句子 2 中 bank 位置的上下文向量cos = F.cosine_similarity(rep1, rep2, dim=0)计算两个向量的余弦相似度,数值越小说明区分度越大
常见误区
误区我以为 ELMo 就是一个升级版的 Word2Vec,训练完之后每个词有一个固定向量,用的时候查表就行。
正解ELMo 没有固定的词向量表。它的向量是“语言模型读完整句话后即时计算出来的”,同一个词在不同句子里向量完全不同。就像同一个演员在不同剧本里表演不一样,不存在一张“万能定妆照”。
为何常见:因为 Word2Vec 先入为主,让人误以为所有词嵌入都是“训练一次、查表永久”的结构。
误区BERT 用 Transformer,那 ELMo 应该也是某种 Transformer 结构吧?毕竟都是预训练语言模型。
正解ELMo 用的是双向 LSTM,不是 Transformer。LSTM 按顺序处理序列,善于建模短到中距离依赖;BERT 的 Transformer 用自注意力,并行度高且能建模长距离依赖。从 LSTM 到 Transformer 正是 BERT 比 ELMo 强大的关键之一。
为何常见:因为 ELMo 和 BERT 都属预训练语言模型,大家习惯把“新一代”的技术套在“上一代”上。
误区深层神经网络最后一层最“高级”,所以 ELMo 应该只用最后一层 LSTM 的输出作为词向量。
正解ELMo 论文明确发现不同层编码不同信息——底层更偏词法/句法,顶层更偏语义。所以 ELMo 把各层输出做了加权求和,让下游任务自己选“信息配方”。这也是名字里 Embeddings(复数)的含义。
为何常见:直觉上“最后一层最好”在分类任务里常见,但表示学习里多粒度信息往往都有价值。
误区用了 ELMo 是不是要在自己的任务数据上重新训练一遍语言模型?感觉又贵又麻烦。
正解通常不需要。ELMo 像 Word2Vec 一样有公开预训练权重,下游任务直接加载冻结的 ELMo 提取上下文特征即可。只有当任务语料与预训练语料领域差异极大时,才考虑在领域语料上继续预训练。
为何常见:因为“预训练”这个概念让初学者误以为每次都要从零训练,忽略了“迁移”正是预训练模型的初衷。
面试题
Q1. ELMo 的全称是?
- A.Embedding Language Model
- B.Embeddings from Language Models← 正确答案
- C.Enhanced Language Modeling
- D.Efficient Language Model
答案:B。Embeddings from Language Models 直译就是“来自语言模型的嵌入”,强调 ELMo 用语言模型的隐藏状态生成词向量,而不是直接查表。A 和 C、D 都故意混淆了词序或替换了关键词。
Q2. Word2Vec 生成词向量的核心特点是?
- A.一个词只对应一个固定向量← 正确答案
- B.一个词在不同语境中向量不同
- C.向量由双向 LSTM 实时生成
- D.向量由 Transformer 编码器输出
答案:A。Word2Vec 是静态嵌入的典型代表,训练后一个词对应一个固定向量,查表即得。它无法根据句子变化,这正是 ELMo 要解决的问题。B、C、D 描述的都是动态上下文嵌入的特征。
Q3. ELMo 提取上下文特征的网络结构是?
- A.双向长短期记忆网络← 正确答案
- B.Transformer 编码器
- C.卷积神经网络 CNN
- D.多层感知机 MLP
答案:A。ELMo 的骨干是双向 LSTM,前向和后向各读一遍句子,再把隐藏状态拼接起来。Transformer 是 BERT 的选择,CNN 和 MLP 都不是 ELMo 用来建模序列的语言模型结构。
Q4. 关于 ELMo 的上下文向量,下列说法正确的有?
- A.同一个词在不同句子中向量不同← 正确答案
- B.融合左右两个方向的上下文← 正确答案
- C.每个词只有一种固定表示
- D.需要输入完整句子才能计算← 正确答案
答案:ABD。C 错在“只有一种固定表示”,这正是静态嵌入的特征。ELMo 的向量依赖完整句子,双向 LSTM 让同一个 bank 在不同语境中产生不同向量,这正是上下文相关词嵌入的核心。
Q5. ELMo 预训练任务的核心目标是?
- A.利用上下文预测当前词← 正确答案
- B.预测两个句子是否相邻
- C.还原被掩码的词
- D.生成某个句子的摘要
答案:A。ELMo 的预训练是双向语言模型:前向根据前文预测当前词,后向根据后文预测当前词,两个方向的损失相加。“还原被掩码的词”是 BERT 的 Masked LM,“预测句子是否相邻”是 BERT 的下一句预测,都不是 ELMo 的做法。
Q6. ELMo 的最终嵌入是如何合成的?
- A.只取最后一层 LSTM 输出
- B.各层表示加权求和← 正确答案
- C.所有层输出取平均
- D.随机选取其中一层输出
答案:B。ELMo 不只用最后一层,而是把所有层的隐藏表示按 softmax 权重加权求和,再乘上任务相关缩放系数 γ。这样下游任务可以自由选择偏重词法层还是语义层的信息,“平均”和“随机”都不具备这种灵活性。
Q7. ELMo 中的“双向”具体指?
- A.两个相同 LSTM 层堆叠
- B.前向与后向两个 LSTM← 正确答案
- C.输入与输出双向变换
- D.编码与解码两阶段流程
答案:B。“双向”指同时用一个从左往右读的 LSTM 和一个从右往左读的 LSTM,两个方向的隐藏状态拼接后融合左右上下文。它既不是两层堆叠,也不是编码-解码结构,更不是输入输出变换。
Q8. ELMo 在下游任务中的典型使用方式是?
- A.将上下文向量拼入任务模型← 正确答案
- B.用 ELMo 替换全部任务参数
- C.直接用 ELMo 输出做预测
- D.把 ELMo 当损失函数
答案:A。ELMo 的典型用法是把它的输出向量作为额外特征拼接到下游任务模型的输入层或更高层,模型再接自己的分类层做预测。它不替代整个任务模型,也不直接输出最终标签,更不是损失函数。
Q9. ELMo 相比 Word2Vec 的改进体现在?
- A.能够区分一词多义← 正确答案
- B.不同语境生成不同向量← 正确答案
- C.每个词只有一个向量
- D.利用全句上下文建模← 正确答案
答案:ABD。C 恰恰是 Word2Vec 的特征而非改进。ELMo 用双向 LSTM 建模全句上下文,对不同语境动态生成不同向量,从而解决了静态嵌入无法区分多义词的问题。这正是它被称为上下文相关嵌入的原因。
Q10. ELMo 对后续 NLP 模型发展的影响包括?
- A.为 BERT 的诞生铺路← 正确答案
- B.确立预训练加微调范式← 正确答案
- C.首次提出 CBOW 词向量方法
- D.验证双向上下文建模的重要性← 正确答案
答案:ABD。“首次提出 CBOW”是 Word2Vec 论文的贡献,与 ELMo 无关。ELMo 确立了“预训练 + 下游适配”的范式,证明了双向上下文建模能大幅提升下游任务表现,直接启发了 BERT 把这一范式推向新高度。