ELMo动态词向量模型

ELMo(Embeddings from Language Models)是一种生成动态词向量的预训练语言模型。它由阿里·米勒(Alec Radford)、杰克·克莱曼(Jack Clements)、克里斯·多明戈(Chris Dyer)、卡梅伦·哈里森(Cameron Neylon)、凯尔·麦克唐纳(Kelvin Xu)和理查德·索罗门诺夫(Richard Socher)等人在2018年提出,发表于《Transactions of the Association for Computational Linguistics》。

ELMo模型的核心特点是:

  1. 动态词向量表示:ELMo不同于传统的静态词向量方法(如Word2Vec、GloVe等),它为每个单词在不同的上下文中提供不同的向量表示。这意味着同一个单词在不同的句子或语境中会得到不同的词向量,解决了传统方法无法有效处理词汇多义性的问题。

  2. 双向LSTM:ELMo利用了双向长短期记忆网络(Bi-LSTM)来构建语言模型,分别从前向和后向两个方向遍历整个句子,捕获词语的左、右上下文信息。

  3. 层次化表示:ELMo不仅输出一层的词向量,而是输出整个深度神经网络的所有隐藏层的词向量,这些向量可以线性组合并用于下游NLP任务,允许模型考虑不同抽象层级的信息。

  4. 预训练与微调:ELMo首先在一个大规模无标签文本语料库上进行预训练,然后在特定任务上进行微调。在实际应用中,针对不同的NLP任务(例如情感分析、命名实体识别、问答系统等),可以根据具体任务的需求综合不同层的词向量表示来优化模型性能。

总结来说,ELMo通过结合大量文本数据训练的语言模型,实现了词义的动态性和上下文敏感性,并且能够有效地提升下游NLP任务的表现。虽然BERT后来在许多方面超越了ELMo,但ELMo仍然是推动NLP领域动态词向量发展的重要里程碑

1、动态词向量表示

ELMo(Embeddings from Language Models)与Word2Vec、GloVe这类静态词向量方法之间的一个显著区别在于其生成的词向量具有动态性,即所谓的上下文敏感性

在Word2Vec和GloVe中,每个单词被赋予一个固定的、全局唯一的向量表示,这个向量试图捕捉单词在整个语料库中的共现模式以及潜在语义信息。然而,这样的静态词向量不能充分反映单词在不同上下文中的多义性,比如“bank”这个词,在“沿着河岸走”和“银行存款”的句子中含义完全不同。

而ELMo则采用了基于深度学习的语言模型框架,通过训练一个双向的长短时记忆网络(Bi-LSTM)模型,使得每个单词的词向量不再是固定的,而是根据它在具体句子中的上下文位置和环境动态生成。换句话说,对于同一个单词在不同的句子或语境中,ELMo会产生不同的词向量表示,从而能够更好地适应各种自然语言处理任务中的多义词问题和复杂的上下文依赖关系。在实际应用中,ELMo通常会将不同层次的LSTM单元输出按一定权重组合起来,形成最终的上下文相关的词嵌入向量。

2、对比

ELMo模型的核心创新之处就在于引入了上下文敏感的词嵌入机制。在传统静态词向量方法中,例如Word2Vec通过CBOW或者Skip-gram模型,以及GloVe通过全局矩阵因子分解的方法,为每个词生成一个固定不变的向量来表征其语义特征,但这些方法忽视了词义随上下文变化这一重要特性

相比之下,ELMo利用深度神经网络(通常是双向LSTM)对大量文本数据进行训练,并从模型的内部层中抽取特征向量作为词向量。这样,对于同一个单词出现在不同的上下文中时,ELMo可以依据该单词周围的上下文信息动态地生成不同的词向量,这不仅能够较好地捕捉到词汇的多义性,还能够反映出更丰富和细致的语义结构信息,从而极大地提升了下游自然语言处理任务的性能。

这种动态词向量表示方法赋予了模型理解和适应词汇在不同语境中含义变化的能力。比如,在“苹果”这个词的例子中:

  • 在句子“我正在吃苹果”中,“苹果”指的是水果;
  • 而在句子“苹果公司发布了新款手机”中,“苹果”则代表一家科技公司。

使用ELMo这样的动态词向量模型时,系统会基于上下文计算出针对这两个不同情境下“苹果”的特定向量表示,分别反映了它们在各自语境下的含义。这就克服了静态词向量模型无法区分同一词汇在不同语境中可能携带不同含义的局限性,使得模型在理解复杂语言现象时更加准确和灵活。

3、思想与实现 

ELMo利用深度双向长短期记忆网络(Bi-LSTM)来捕获词汇的上下文依赖性。在具体的实现过程中,对于给定的输入序列,ELMo模型首先对每个单词进行字符级的嵌入编码,接着将这些字符级的嵌入拼接起来形成单词级别的初始嵌入。接下来,这些单词嵌入被输入到一个多层双向LSTM中。

在双向LSTM中,每个单词都会得到两个方向上的隐藏状态:一个是向前传播的状态,它考虑了单词左侧的上下文信息;另一个是向后传播的状态,它包含了单词右侧的上下文信息。最终,ELMo将这两个方向上的隐藏状态连结或合并起来,得到一个单词在特定上下文中的综合表示。

在代码层面,这通常表现为获取多层双向LSTM每一层的前向和后向隐藏状态,并将这些隐藏状态进行线性变换和加权求和,生成最终的上下文相关词嵌入。例如,在PyTorch或其他深度学习框架中,这部分可能体现为:

  1. 定义并初始化双向LSTM模型;
  2. 将输入序列传递给LSTM;
  3. 获取每一层LSTM的隐藏状态(forward_hidden_states和backward_hidden_states);
  4. 对每层的前向和后向隐藏状态做某种形式的融合,如连结(concatenation)或者求和(sum);
  5. 应用权重矩阵对融合后的隐藏状态进行变换,这些权重可以根据下游任务进行训练微调;
  6. 最终得到的每个单词的向量就是其对应的上下文相关的ELMo词嵌入。

简化的伪代码片段可能如下所示:

Python

1# 假设 `char_embeddings` 是经过字符级嵌入层后的单词表示
2# `bidirectional_lstm` 是一个预训练好的多层双向LSTM模型
3
4# 通过双向LSTM得到各层的隐藏状态
5hidden_states = bidirectional_lstm(char_embeddings)
6
7# 对于每一层的前向和后向隐藏状态进行融合
8contextualized_word_representations = []
9
10for layer_output in hidden_states:
11    # 假设 forward_hidden 和 backward_hidden 分别是前向和后向隐藏状态
12    concatenated_states = torch.cat((forward_hidden, backward_hidden), dim=-1)
13    
14    # 可能进一步应用权重层和非线性激活函数
15    weighted_summed_state = some_weight_matrix * concatenated_states + bias
16    
17    contextualized_word_representations.append(weighted_summed_state)
18
19# 对所有层的表示进行求和或加权求和,生成最终的上下文相关的词嵌入
20final_elmo_word_embedding = sum(contextualized_word_representations) / len(hidden_states)

请注意,上述代码仅为示意性的伪代码,并未涉及ELMo模型的具体细节和参数设置。在实际实现中,还需要考虑如何结合不同层级的上下文信息以及是否需要使用预训练时学到的权重对不同层级赋予不同的贡献等细节。

3.1 模型结构和细节

  1. 字符级CNN(Character-Level CNN)

    ELMo首先使用一个字符级的卷积神经网络(CNN)对每个单词进行编码,生成单词级别的嵌入。这些嵌入能够捕获词汇内部的子结构信息。
  2. 双向LSTM

    这些单词级别的嵌入随后被输入到一个多层双向长短期记忆网络(BiLSTM)。双向LSTM在两个方向(向前和向后)遍历输入序列,从而捕捉每个单词在句子上下文中的前向和后向依赖关系。
  3. 层次组合

    ELMo模型通常包含多层LSTM,每层的输出都会被保留下来,并且它们会被赋予不同的权重,这些权重在下游任务中是可学习的。这意味着ELMo为每个单词提供了不同层级的上下文信息综合表示。
  4. 最终嵌入

    对于每一个单词,ELMo不是简单地选择某个特定层的输出作为最终嵌入,而是对所有层的输出进行线性组合,组合的权重是针对特定下游任务训练出来的。这样生成的嵌入包含了不同抽象层级的信息。

参数设置

  • 字符级CNN的具体参数包括卷积核的数量、大小、步长以及最大池化层的大小等。
  • LSTM层的参数包括隐藏单元数、层数(通常是2或3层),以及是否使用门控机制。
  • 权重组合时的参数是指在每个单词的嵌入向量中,各层LSTM输出所对应的权重系数,这些系数通过学习任务的训练过程自动调整。

在实际实施中,ELMo使用的LSTM层的隐藏单元数量可以很大(比如1024),并且通常会在大规模语料库上预先训练语言模型。

在实际应用中,ELMo模型的一个关键特性就是它如何整合不同层级的上下文信息。具体来说:

  1. 层级融合

    • 在ELMo模型中,对于每个单词位置,双向LSTM每一层都会生成一个上下文相关的向量表示。
    • 这些来自不同层级的向量会被线性加权组合成单一的嵌入向量。这个线性组合的过程允许模型动态地调整不同层级对于最终嵌入的重要性。
    Code
    1ELMo embedding for word i = γ_1 * h^1_i + γ_2 * h^2_i + ... + γ_L * h^L_i

    其中 h^l_i 表示第i个单词在LSTM第l层的输出向量,而 γ_l 是在特定任务训练阶段学习到的对应层的权重。

  2. 任务适应性权重

    • 对于不同的NLP任务,例如命名实体识别、情感分析等,模型会学习不同的权重 γ_l,这表明不同任务可能需要利用不同层级的上下文信息。
    • 在微调过程中,这些权重与下游任务的其他参数一起更新,使得ELMo嵌入更适应特定任务的需求。

因此,在实际使用ELMo时,不仅需要预先训练好一个强大的语言模型来获取多层次的上下文信息,还要设计并训练一个机制来动态调整不同层级嵌入在最终嵌入中的贡献度。这样的设计极大地增强了模型的泛化能力和对复杂语言现象的理解能力。

 3.2 实现:基于TensorFlow Keras库

在实际实现中,ELMo模型确实会为不同层次的LSTM输出分配不同的权重,并且这些权重是通过对下游任务进行微调而学习到的。下面是一个基于TensorFlow Keras库的更为接近真实实现的示例代码片段,展示了如何构建ELMo模型并提取上下文相关的词嵌入:

Python

1import tensorflow as tf
2from tensorflow.keras.layers import Input, LSTM, Dense, concatenate, LayerNormalization
3from tensorflow.keras.models import Model
4
5# 假设已有一个预处理过的字符级嵌入层,用于生成单词级别的初始嵌入
6def get_char_cnn_embeddings(input_sequence):
7    # 这部分应包含字符级CNN操作,返回单词级别的嵌入
8    pass
9
10# 定义多层双向LSTM模型
11def build_elmo_model(num_layers, embedding_size, units):
12    inputs = Input(shape=(None,), dtype='int32')  # 输入序列
13    char_embeddings = Lambda(get_char_cnn_embeddings)(inputs)  # 转换为单词嵌入
14
15    lstm_outputs = []
16    for _ in range(num_layers):
17        lstm_fw = LSTM(units=units, return_sequences=True, return_state=True, go_backwards=False)
18        lstm_bw = LSTM(units=units, return_sequences=True, return_state=True, go_backwards=True)
19
20        output_fw, _, _ = lstm_fw(char_embeddings)
21        output_bw, _, _ = lstm_bw(char_embeddings)
22
23        # 合并前后向的隐藏状态
24        concat_outputs = concatenate([output_fw, output_bw])
25
26        # 可选地,可以添加LayerNormalization等层
27        normed_outputs = LayerNormalization()(concat_outputs)
28
29        lstm_outputs.append(normed_outputs)
30
31    # 对不同层次的LSTM输出应用可训练的权重
32    elmo_layers = [Dense(embedding_size, activation='linear')(output) for output in lstm_outputs]
33    
34    # 使用softmax函数为各个层分配权重
35    layer_weights = Dense(len(elmo_layers), activation='softmax')(lstm_outputs[-1])
36
37    # 加权求和生成最终的上下文嵌入
38    weighted_sum = tf.reduce_sum(tf.stack(elmo_layers) * layer_weights, axis=0)
39
40    elmo_model = Model(inputs=inputs, outputs=weighted_sum)
41
42    return elmo_model
43
44# 初始化模型
45num_layers = 2  # 假设使用两层LSTM
46embedding_size = 512  # ELMo嵌入维度
47units = 1024  # LSTM单元数
48elmo = build_elmo_model(num_layers, embedding_size, units)
49
50# 预训练或微调模型
51# ...
52
53# 使用模型获取上下文相关的词嵌入
54sentences = ...  # 输入句子数据
55sentence_embeddings = elmo(sentences)

请注意,上述代码依然简化了很多细节,例如字符级嵌入的生成和模型的训练过程。在实践中,为了获得与原始论文一致的结果,你需要参考官方实现或开源版本(如AllenNLP库),并遵循相应的配置和训练步骤。

  3.3 实现:PyTorch

在PyTorch中,构建ELMo模型可以采用类似的方法。以下是基于PyTorch的一个简化版实现示例,该示例展示了如何创建一个多层双向LSTM网络,并为不同层级的输出分配可训练的权重:

Python

1import torch
2import torch.nn as nn
3from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
4
5class CharCNNEmbeddings(nn.Module):  # 假设已经实现了字符级CNN嵌入模块
6    def __init__(self, **kwargs):
7        super(CharCNNEmbeddings, self).__init__()
8        # 在此处定义字符级CNN嵌入的相关层...
9
10    def forward(self, input_sequence):
11        # 返回单词级别的嵌入向量
12        pass
13
14class ELMo(nn.Module):
15    def __init__(self, num_layers, embedding_size, hidden_size):
16        super(ELMo, self).__init__()
17
18        self.char_cnn = CharCNNEmbeddings()  # 实例化字符级CNN嵌入模块
19        self.lstm_layers = nn.ModuleList([
20            nn.LSTM(input_size=char_embedding_dim, 
21                    hidden_size=hidden_size, 
22                    num_layers=1, 
23                    bidirectional=True,
24                    batch_first=True) for _ in range(num_layers)
25        ])
26        
27        # 可训练的权重矩阵,用于不同层级的加权求和
28        self.layer_weights = nn.Parameter(torch.Tensor(num_layers, 1))
29
30    def forward(self, inputs, input_lengths):
31        word_embeddings = self.char_cnn(inputs)  # 获取单词级别的嵌入
32        
33        packed_embeddings = pack_padded_sequence(word_embeddings, input_lengths, batch_first=True)
34
35        lstm_outputs = []
36        for lstm_layer in self.lstm_layers:
37            packed_output, _ = lstm_layer(packed_embeddings)
38            output, _ = pad_packed_sequence(packed_output, batch_first=True)
39
40            lstm_outputs.append(output)
41
42        # 对每一层的输出应用softmax权重
43        weighted_outputs = [layer_weight * lstm_output for layer_weight, lstm_output in zip(
44            torch.softmax(self.layer_weights, dim=0),
45            lstm_outputs)]
46
47        # 加权求和得到最终的上下文嵌入
48        context_embeddings = sum(weighted_outputs)
49
50        return context_embeddings
51
52# 初始化模型
53num_layers = 2
54embedding_size = 512
55hidden_size = 1024
56elmo_model = ELMo(num_layers, embedding_size, hidden_size)
57
58# 定义优化器和其他训练组件
59optimizer = torch.optim.Adam(elmo_model.parameters())
60
61# 假设我们已经有了预处理好的数据
62input_sequences, input_lengths = ..., ...
63
64# 获取上下文相关的词嵌入
65with torch.no_grad():
66    embeddings = elmo_model(input_sequences, input_lengths)
67
68# 训练模型(这里省略了训练循环)

在这个示例中,请注意CharCNNEmbeddings类是用来模拟从字符级别生成单词嵌入的模块,实际项目中你可能需要一个完整的字符级CNN模型来实现这一功能。此外,在训练过程中,您需要正确处理变长序列,这通常通过pack_padded_sequencepad_packed_sequence函数来完成。同时,这里的权重分配是直接在每个层级上应用Softmax后的权重,然后进行加权求和。在实际使用中,对于权重的学习策略可能会有更复杂的实现,具体取决于所参考的原始ELMo论文或其他开源实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值