一、为什么要提出ELMo
ELMo(Embeddings from Language Models)是由华盛顿大学于2018年3月提出的一种词嵌入模型。ELMo源于论文《Deep contextualized word representations》(Matthew E. Peters等),主要是为了解决“一词多义”的问题。传统的词向量模型,如Word2Vec和GloVe,虽然在许多任务中表现良好,但存在明显的不足。这些模型生成的词向量是静态的,即同一个词在不同的上下文中总是具有相同的向量表示,无法适应不同的语境。例如,“apple”这个词既可以指水果,又可以指苹果公司,二者具有完全不同的含义,但传统模型无法体现这种差异。而且传统模型没有捕捉到词性等语法信息,也没有解决一词多义问题。而ELMo通过引入上下文信息,为每个词语生成动态的词向量,能更准确地捕捉词语的语义信息。
二、ELMo模型结构详解

ELMo模型由三部分组成:字符集嵌入模块、双层双向LSTM模块和动态词向量表征模块。网络结构如上图所示。
- 字符级嵌入模块:该模块输入为字符级别的,而非单词级别。(1)先将输入词拆分为字符,如"apple"被拆分为"a"、"p"、"p"、"l"、"e",每个字符采用one-hot编码表示。(2)通过嵌入层将one-hot向量变为低维的稠密向量。(3)将每个字符的稠密向量组成单词的稠密矩阵(4)采用CNN卷积对单词的稠密矩阵进行局部采样,捕捉字符间的形态学特征(如词根、前后缀等)。(5)对卷积后的特征图进行最大池化操作,提取每个通道的最显著特征。(6)池化后的向量输入Highway层中,通过门控机制控制信息流动,增强非线性表达能力。
- 双层双向LSTM模块:(1)将Highway层输出作为输入,输入第一层双向LSTM中,正向和反向LSTM的输入均为Highway层输出。正向和反向LSTM的输出进行拼接(维度拼接,维度变为2倍)后,输入至第二层双向LSTM中。(2)将第二层中的正向和反向LSTM的输出进行拼接后进行输出。
- 动态词向量表征模块:每个词的表征由三部分加权求得:(1)字符集嵌入模块的输出(底层特征,捕获词形信息);(2)第一层LSTM的输出(中层特征,侧重句法);(3)第二层LSTM的输出(高层特征,侧重语义)。
下面对每个模块进行详细讲解。
2.1 字符级嵌入模块
2.1.1 输入层与嵌入层
首先,每个字符通过字符嵌入(character embeddings)被转换为固定维度的向量。假设字符总数为,字符嵌入的维度为
,先将每个字符用one-hot的方式进行表示,每个字符就是
的向量。通过线性变换,将每个字符的维度从
压缩为
:
其中,为对应的权重矩阵,维度为
,
为对应的偏置项,维度为
。
为字符的one-hot向量,
为
从维度
映射为维度
后的字符的向量,通常
。
这种字符嵌入的方式与Word2Vec中的两种词嵌入方式不同,Word2Vec中的两种方法都是把 单词 转换为one-hot向量,而在ELMo的输入中,将 字符 转换为one-hot向量,使用矩阵表示单词,这样可以从字符层面挖掘单词的特征。
通过上面的字符嵌入,我们就可以用字符的向量来构建单词的向量。在ELMo中,规定一个单词的最大字符个数不超过,超过
时会进行截断,不足
时会使用特殊字符的字符编码进行填充。这样,我们可以用
表示一个单词,即:
其中,是单词中第i个字符的嵌入向量。这样,我们就完成了character-embedding的过程。
下面介绍AllenNLP中实现的ELMo,character-embedding处理的方式。
在AllenNLP的ELMo的character-embedding中,利用Unicode 字符级对单词的每个字符进行编码,其中n=262(n=262也是一个经验值,它可以覆盖大部分情况)。在AllenNLP的ELMo的character-embedding实现中,这 262 个字符向量会被固定为维度为(字符嵌入维度为16,即d=16)的向量,采用的方法是将262个字符以one-hot向量的形式进行表示,随后通过线性变换的形式转为固定维度为
的向量。
在AllenNLP的 EMLo实现中,,我们以单词opened为例子,首先将其分解成‘o’,‘p’,‘e’,‘n’,‘e’,‘d’,共计6个字符,分别使用相应的字符向量表示。由于单词向量使用50个字符向量福鼎,后面44个字符均使用填充,这样我们就将其固定成字符数量大小为50.于是我们就可以完成opened这个单词的编码。此时单词的维度
。这里由于AllenNLP中嵌入维度较大不方便展示。
2.1.2 卷积层与池化层
对于一个单词经过character-embedding之后,就会进行卷积层。卷积层的核心是使用卷积神经网络CNN。我们考虑一个单词,其字符表示为,
是一个
的矩阵,d是每个字符的嵌入维度,L是单词的最大长度,则我们可以进行卷积操作:
对于给定卷积核K:
,
卷积核尺寸为,其中:
表示卷积核矩阵集合(张量),
表示卷积核矩阵,
表示卷积核中各个位置的元素值
是卷积核覆盖的字符嵌入维度(AllenNLP中,卷积核覆盖的字符维度与字符嵌入维度相同)
是卷积核的宽度(覆盖的字符数)
是该卷积核输出的特征数量(或称为过滤器数量)
卷积操作的输出对于每个特征图
的每个位置
在单词长度方向上可以表示为:
输出的维度为
,假设没有填充,并且步长为1。
在卷积后进行最大池化操作,最大池化在每个特征图上独立进行,选取每个特征图中的最大值。对于每个过滤器,池化操作可表示为:
最终,池化后的所有特征被拼接起来形成一个特征向量
,用作单词的嵌入表示:
以上得到了一个单词的向量。由于后续会输入至LSTM中,所以会对每个单词进行上述操作,形成向量
。随后,我们将单词向量
传入highway层中。
在AllenNLP的ELMo的character-CNN中,包含了7种过滤器,分别为、
、
、
、
、
、
。过滤器尺寸依次为32、32、64、128、256、512和1024。
通过卷积层,得到的输出维度为,然后顺着第一个维度取最大值(最大池化),得到的向量分别为:
、
、
、
、
、
、
,然后进行拼接,得到
维的向量。
2.1.3 highway层
在ELMo中使用highway层还是借鉴LSTM的做法,引入门控机制,从而解决深层神经网络中的梯度消失问题,并提升模型对长距离依赖关系的建模能力。
- 保留与变换:Highway层通过两个主要的门控机制实现信息的保留与变换——一个是变换门(Transform Gate),另一个是携带门(Carry Gate)。变换门控制有多少当前层的原始信息需要被变换,而携带门控制有多少原始信息需要不加修改地传递到下一层。
- 门控特征表示:因此,Highway层的输出是原始输入特征的一个门控版本,其中一部分特征被保留并直接传递,而另一部分特征经过变换以提供额外的信息或调整。
变换门:
其中,是sigmoid激活函数,确保输出在0和1之间,表示每个特征的转换程度。
是输入特征,
和
是变换门的权重和偏置。
携带门:
携带门的计算很简单,就是1减去变换门的输出,确保变换和携带的总和为1,这样可以保持信息的完整性,
输出:
其中,是输入特征,
是变换操作的权重,
为对应的偏置值,
是对输入
的非线性变换(通常是ReLU激活函数),
是变换门的激活,
是携带门的激活,而
是该highway层的输入,
是点乘。
在AllenNLP的ELMo中,进行运算完highway后,我们得到的结果还是的向量。最后的MLP层就是一层线性映射,将维度为2048的向量压缩成512维的向量。
在MLP层,就是对highway得到的特征进行压缩,然后输入给双向LSTM,具体来说就是一个简单的线性映射,即:
2.2 双层双向LSTM
为了更具一般性,我们先将双层双向LSTM推广至多层双向LSTM,结构如下图所示:

2.2.1 符号定义
- 第一层LSTM的输入序列:
,其中
,
为经过MLP层后的输出
- 层数:共计
层,第
层的参数用上标(
)标记
- 隐藏层状态维度:每层前向/后向LSTM的隐藏状态维度为
- 合并操作:双向LSTM的输出通常将前向和后向隐藏状态拼接(记作
),维度为
2.2.2 计算过程
单层单向LSTM的计算过程本文不再赘述,详细可参考 我之前的文章《LSTM思想解析—论文精读(Long Short-Term Memery)》和《LSTM思想解析—论文精读(Learning to Forget: Continual Prediction with LSTM)》
为了更具一般性,我们讲解多层双向LSTM:
对于第层的第
个时刻,前向传播输出的隐藏状态为
,反向传播输出的隐藏状态为
,两个隐藏状态进行拼接,得到
作为第
层的第
时刻的数据输入(相当于第1层的数据输入
),分别输入到第
层的第
时刻的前向传播和反向传播LSTM中。第
层的第
时刻的前向传播除了接收
作为数据输入外,还接收第
层的第
时刻输出的隐藏状态
,最终计算出
;同理,第
层的第
时刻的反向传播除了接收
作为数据输入外,还接收第
层的第
时刻输出的隐藏状态
,最终计算出
。然后再进行拼接操作,形成
,如此反复。
2.3 动态词向量表征模块
在这一层,需要将字符级嵌入模块的输出、第一层双向LSTM的输出、第二层双向LSTM的输出进行加权,得到最终的词向量表示。动态加权公式为:,其中:
:表示总层数(字符级嵌入层+2层LSTM,共计3层)
:第k个词在第j层的输出向量
:可训练的层权重(会用softmax进行归一化)
:可训练的缩放因子(标量)
三层获取的词向量特征对比:
| 特征维度 | 字符级层 | LSTM1层 | LSTM2层 |
|---|---|---|---|
| 抽象级别 | 词素级 | 语法级 | 语义级 |
| 上下文范围 | 单词内部 | 局部(3-5词) | 全局(整句) |
| 主要功能 | 形态分析 | 语法结构 | 语义理解 |
| 敏感度 | 拼写变化 | 词序变化 | 语境变化 |
| 典型任务 | OOV处理 | 词性标注 | 情感分析 |
三、ELMo模型前向传播与反向传播过程
3.1 前向传播过程
前向传播的整体流程如下:

以句子“I like apples”为例,详细讲解前向传播过程。
3.1.1 字符级嵌入层
(1)将输入单词“apples”进行字符分解:['a','p','p','l','e','s'],然后填充至50字符。
(2)字符嵌入:50x16矩阵
(3)多尺度卷积:
宽度为3的卷积—>得到128维的特征图
宽度为4的卷积—>得到128维的特征图
宽度为5的卷积—>得到128维的特征图
(4)进行最大池化:得到384维的向量
(5)Highway网络:门控特征融合
(6)全链接层:变为512维的向量输出—>h_char
3.1.2 第一层双向LSTM
# 输入序列: [h_I, h_like, h_apples] h_I, h_like, h_apples为字符级嵌入层输出的向量
# 前向LSTM (→)
→h1_I = LSTM(h_I, →h0) # 初始状态h0为0向量
→h1_like = LSTM(h_like, →h1_I)
→h1_apples = LSTM(h_apples, →h1_like)
# 后向LSTM (←)
←h1_apples = LSTM(h_apples, ←h0) # 从右向左
←h1_like = LSTM(h_like, ←h1_apples)
←h1_I = LSTM(h_I, ←h1_like)
# 拼接输出
h1_I = [→h1_I; ←h1_I] # 2048维 t1时刻输出
h1_like = [→h1_like; ←h1_like] #t2时刻输出
h1_apples = [→h1_apples; ←h1_apples] #t3时刻输出
3.1.3 第二层双向LSTM
# 输入序列: [h1_I, h1_like, h1_apples]
# 前向LSTM (→)
→h2_I = LSTM(h1_I, →h0) #初始状态h0为0向量
→h2_like = LSTM(h1_like, →h2_I)
→h2_apples = LSTM(h1_apples, →h2_like)
# 后向LSTM (←)
←h2_apples = LSTM(h1_apples, ←h0) # 从右向左
←h2_like = LSTM(h1_like, ←h2_apples)
←h2_I = LSTM(h1_I, ←h2_like)
# 拼接输出
h2_I = [→h2_I; ←h2_I] # 2048维 t1时刻输出
h2_like = [→h2_like; ←h2_like] #t2时刻输出
h2_apples = [→h2_apples; ←h2_apples] #t3时刻输出
3.1.4 语言模型预测
# 前向语言模型 (预测下一个词)
P(like|I) = softmax(W_fw * →h2_I)
P(apples|like) = softmax(W_fw * →h2_like)
P(<EOS>|apples) = softmax(W_fw * →h2_apples)
# 后向语言模型 (预测前一个词)
P(<BOS>|I) = softmax(W_bw * ←h2_I) # 无前词
P(I|like) = softmax(W_bw * ←h2_like)
P(like|apples) = softmax(W_bw * ←h2_apples)
3.1.5 前向传播示例
假设单词"like"的处理:
字符级输出: [0.21, -0.15, 0.32, ...] (512维)
第一层LSTM:
前向: [0.82, 0.05, -0.15, ...] (1024维)
后向: [0.78, -0.12, 0.21, ...] (1024维)
拼接: [0.82, 0.05, -0.15, ..., 0.78, -0.12, 0.21] (2048维)
第二层LSTM:
前向: [0.92, -0.15, 0.87, ...] (1024维)
后向: [0.88, 0.12, 0.91, ...] (1024维)
拼接: [0.92, -0.15, 0.87, ..., 0.88, 0.12, 0.91] (2048维)
前向预测:
输入: →h2_like = [0.92, -0.15, 0.87, ...]
输出: logits = [1.2, 3.5, -0.8, ...] (vocab_size维)
softmax: P("apples") = 0.85
3.2 反向传播过程
3.2.1 损失函数计算
# 前向损失 连乘通过log函数变为了连加
L_fw = -[log P(like|I) + log P(apples|like) + log P(<EOS>|apples)]
# 后向损失 连乘通过log函数变为了连加
L_bw = -[log P(<BOS>|I) + log P(I|like) + log P(like|apples)]
# 总损失
L_total = L_fw + L_bw
3.2.2 反向传播路径

3.2.3 关键梯度计算(以位置t="like"为例)
(1) 输出层梯度
# 前向输出层
δ_fw_out = (y_pred - y_true) # y_true = "apples"的one-hot
# 后向输出层
δ_bw_out = (y_pred - y_true) # y_true = "I"的one-hot
(2) 第二层LSTM梯度
# 前向LSTM梯度
δ_fw_lstm2 = W_fw.T @ δ_fw_out + δ_next_fw # 来自时间流
# 后向LSTM梯度
δ_bw_lstm2 = W_bw.T @ δ_bw_out + δ_next_bw
(3) LSTM单元内部梯度
# 以第二层前向LSTM为例
# 输入: [h1_like; h1_like] (2048维)
# 遗忘门梯度
df = δ_fw_lstm2 * ot * (1 - tanh(ct)^2 * c_prev * f*(1-f)
# 输入门梯度
di = δ_fw_lstm2 * ot * (1 - tanh(ct)^2 * gt * i*(1-i)
# 输出门梯度
do = δ_fw_lstm2 * tanh(ct) * o*(1-o)
# 候选状态梯度
dg = δ_fw_lstm2 * ot * (1 - tanh(ct)^2 * it * (1 - gt^2)
(4) 字符级嵌入层梯度
# 反向传播至字符嵌入层
δ_char_emb = W_lstm1.T @ δ_lstm1
# 继续反向至Highway网络
δ_highway = (g * δ_char_emb) + ((1-g) * δ_char_emb) # 简化表示
# 卷积层梯度
δ_conv = unpool(δ_highway) # 最大池化位置梯度
3.2.4 参数更新
# 通用更新规则
for param in [char_embed, conv_weights, lstm_params, output_weights]:
param -= learning_rate * ∇L/∇param
3.3 前向传播与反向传播示例
前向传播片段(位置t="like"):
输入: h_char = [0.21, -0.15] (简化2维)
LSTM1前向计算:
→h1 = [0.82, 0.05] (隐藏状态)
LSTM1后向计算:
←h1 = [0.78, -0.12]
拼接: h1 = [0.82, 0.05, 0.78, -0.12]
LSTM2前向计算:
→h2 = [0.92, -0.15]
前向预测:
logits = W_fw @ →h2 = [0.5*0.92 + (-0.2)*(-0.15), ...] = [0.49, ...]
softmax = [0.3, 0.6, 0.1] # 假设词汇表3词
真实标签: "apples" = [0,1,0]
损失: -log(0.6) = 0.51
反向传播片段
# 输出层梯度
δ_fw_out = [0.3-0, 0.6-1, 0.1-0] = [0.3, -0.4, 0.1]
# LSTM2梯度 (假设W_fw=[[0.5, -0.2],[0.3,0.1],[ -0.4,0.6]])
δ_fw_lstm2 = W_fw.T @ δ_fw_out = [0.5*0.3 + 0.3*(-0.4) + (-0.4)*0.1,
-0.2*0.3 + 0.1*(-0.4) + 0.6*0.1]
= [0.15-0.12-0.04, -0.06-0.04+0.06] = [-0.01, -0.04]
# LSTM门梯度计算 (简化)
假设当前LSTM状态:
f=0.6, i=0.7, o=0.65, c_prev=0.4, c_t=0.5, g=0.3
df = -0.04 * 0.65 * (1-tanh(0.5)^2 * 0.4 * 0.6*(1-0.6) ≈ -0.0002
di = -0.04 * 0.65 * (1-tanh(0.5)^2 * 0.3 * 0.7*(1-0.7) ≈ -0.0001
# 参数更新示例
W_fw += 0.001 * (-δ_fw_out @ →h2.T) = 0.001 * -([0.3,-0.4,0.1] @ [0.92,-0.15])
= 0.001 * -[0.3*0.92, -0.4*0.92, 0.1*0.92;
0.3*(-0.15), -0.4*(-0.15), 0.1*(-0.15)]
≈ 0.001 * -[[0.276, -0.368, 0.092],
[-0.045, 0.06, -0.015]]
参考文章:ELMo模型计算详解-优快云博客
ELMo词嵌入模型:解决一词多义问题
详解&spm=1001.2101.3001.5002&articleId=148683603&d=1&t=3&u=6e208140f088465a89e9a67495db37db)
878

被折叠的 条评论
为什么被折叠?



