论文笔记-预训练语言模型2-ULMFiT

Motivation

对比之前的几种模型

concatenate embeddings: ELMo

Recent approaches that concatenate embeddings derived from other tasks with the input at different layers (Peters et al., 2017; McCann et al., 2017; Peters et al., 2018) still train the main task model from scratch and treat pretrained embeddings as fixed parameters, limiting their usefulness.

这篇 paper 是在 elmo 之后,而 elmo 虽然相对出名,影响力更大,但是 elmo 仍旧只是一种 word embedding 的预训练,在下游任务中还是需要从头训练模型。

ELMo有以下几个步骤:

  • 利用LM任务进行预训练

  • 再利用目标领域的语料对LM模型做微调

  • 最后针对目标任务进行 concatenate embedding,然后训练模型

pretraining LM:

In light of the benefits of pretraining (Erhan et al., 2010), we should be able to do better than randomly initializing the remaining parameters of our models. However, inductive transfer via finetuning has been unsuccessful for NLP (Mou et al., 2016). Dai and Le (2015) first proposed finetuning a language model (LM) but require millions of in-domain documents to achieve good performance, which severely limits its applicability.

直接使用在 general-domain 上预训练好的语言模型,然后通过 fine-tune 进行迁移学习, 仍旧需要大量的 in-domain 的文档才能获得比较好的 performance.

ULMFiT

We show that not the idea of LM fine-tuning but our lack of knowledge of how to train them effectively has been hindering wider adoption. LMs overfit to small datasets and suffered catastrophic forgetting when fine-tuned with a classifier. Compared to CV, NLP models are typically more shallow and thus require different fine-tuning methods.

作者认为,预训练语言模型的方式并不是不好,只是训练方法的问题导致了他们表现局限性。想对于 CV, NLP 中的很多任务所需要的语义更浅层。而将 LMs 在小数据集上 fine-tune 时会导致严重的遗忘。

于是,作者提出了 Universal Language Model Fine-tuning(ULMFiT)

  • 通用的语言模型微调

  • discriminative fine-tuning, slanted triangular learning rates

  • gradual unfreezing

Universal Language Model Fine-tuning

主要分为 3 部分:

  • General-domain LM pretraining

  • Target task LM fine-tuning

  • Target task classifier fine-tuning

General-domain LM pretraining

Wikitext-103 (Merity et al., 2017b) consisting of 28,595 preprocessed Wikipedia articles and 103 million words.

在足够大的 general-domain 语料库上进行预训练。

Target task LM fine-tuning

discriminative fine-tunin

在目标语料库 in-domain 上进行 fine-tune. 这部分会收敛的很快,并且在小数据集上依旧会有很好的泛化性。

As different layers capture different types of information (Yosinski et al., 2014), they should be fine-tuned to different extents.

不同的 layer 能捕捉不同程度的信息,于是,作者提出了 discriminative fine-tuning. 不同的 layer 具有不同的 learning rate. L 表示总的 layer 数目。

$${\theta^1,\theta^2, …, \theta^L}$$

$${\eta^1,\eta^2, …, \eta^L}$$

Instead of using the same learning rate for all layers of the model, discriminative fine-tuning allows us to tune each layer with different learning rates.

原本的 SGD 是这样的:

$$\theta_t = \theta_{t-1}-\eta\cdot\nabla_{\theta}J(\theta)$$

改进之后:

$$\theta_t^l = \theta_{t-1}^l-\eta^l\cdot\nabla_{\theta^l}J(\theta)$$

作者通过经验发现:先选择最后一层的学习率 $\eta^L$,然后计算每一层的学习率 $\eta^{l-1}=\eta^l/2.6$

Slanted triangular learning rates

  • T 是迭代次数,这里实际上是 $epochs \times \text{number of per epoch}$

  • cut_frac 是增加学习率的迭代步数比例

  • cut 是学习率增加和减少的临界迭代步数

  • p 是一个分段函数,分别递增和递减

  • ratio 表示学习率最小时,与最大学习率的比例。比如 t=0时,p=0, 那么 $\eta_0=\dfrac{\eta_{max}}{ratio}$

作者通过实验发现,cut_frac=0.1, ratio=32, $\eta_max=0.01$

Target task classifier fine-tuning

针对分类任务,加上 two additional linear blocks.

concat pooling

gradul unfreezing

逐渐 unfreeze layers:

We first unfreeze the last layer and fine-tune all unfrozen layers for one epoch. We then unfreeze the next lower frozen layer and repeat, until we finetune all layers until convergence at the last iteration.

BPTT for Text Classification

backpropagation through time(BPTT)

We divide the document into fixed length batches of size b. At the beginning of each batch, the model is initialized with the final state of the previous batch; we keep track of the hidden states for mean and max-pooling; gradients are back-propagated to the batches whose hidden states contributed to the final prediction. In practice, we use variable length backpropagation sequences (Merity et al., 2017a).

什么意思?并不是一个 batch 更新一次梯度,而是累加一定的 batch 之后在更新梯度?

能增加泛化性?

Bidirectional language model

独立的对 forward-LM, backward-LM 进行 fine-tune, 然后平均。

experiment

与其他模型对比

ablations

“from scratch”: 没有 fine-tune

“supervised”: 表示仅仅在 label examples 进行 fine-tune

“semi-supervised”: 表示在 unable examples 上也进行了 fine-tune

对 tricks 进行分析

“full” :fine-tuning the full model

“discr”: discriminative fine-tuning

“stlr”: slanted triangular learning rates

论文笔记-BERT

BERT(Bidirectional Encoder Representations from Transformers.)

对于 BERT 重点在于理解 Bidirectional 和 masked language model.

Why Bidirectional?

对于预训练的表示,单向语言模型因为无法融合下文的信息,其能力是非常有限的,尤其是对类似于 SQuAD 这样需要结合上下文信息的任务。

对比 OpenAI GPT 和 BERT. 为什么 OpenAI GPT 不能采用双向 self-attention 呢?

传统的语言模型的定义,计算句子的概率:

$$P(S)=p(w_1,w_2, …, w_n)=p(w1)p(w_2|w_1)…p(w_n|w_1…w_{n-1})=\prod_{i=1}^m p(w_i|w_1…w_{i-1})$$

前向 RNN 语言模型:

$$P(S)=\prod_{i=1}^m p(w_i|w_1…w_{i-1})$$

也就是当前词的概率只依赖前面出现词的概率。

后向 RNN 语言模型

$$P(S)=\prod_{i=1}^m p(w_i|w_{i+1}…w_{m})$$

也就是当前词的概率只依赖后面出现的词的概率。

ELMo 就是这样的双向语言模型(BiLM)

但是 RNN 相比 self-attention 对上下文信息 (contextual information)的利用相对有限,而且 ELMo 只能是一层双向,并不能使用多层。其原因和 GPT 无法使用 双向 编码的原因一样。

对于 GPT 如果它使用双向,那么模型就能准确的学到到句子中的下一个词是什么,并能 100% 的预测出下一个词。比如 “I love to work on NLP.” 在预测 love 的下一个词时,模型能看到 to,所以能很快的通过迭代学习到 “to” 100% 就是 love 的下一个词。所以,这导致模型并不能学到想要的东西(句法、语义信息)。

那么 BERT 是怎么处理双向这个问题的呢? 它改变了训练语言模型的任务形式。提出了两种方式 “masked language model” and “next sentence generation”. 再介绍这两种训练方式之前,先说明下输入形式。

Input representation

  • position embedding: 跟 Transformer 类似

  • sentence embedding, 同一个句子的词的表示一样,都是 $E_A$ 或 $E_B$. 用来表示不同的句子具有不同的含义

  • 对于 [Question, Answer] 这样的 sentence-pairs 的任务,在句子末尾加上 [SEP].

  • 对于文本分类这样的 single-sentence 的任务,只需要加上 [CLS], 并且 sentence embedding 只有 $E_A$.

masked language model

何为 “masked LM”? idea 来源于 closed tasked. 原本的语言模型是预测所有语料中的下一个词,而 MLM 是在所有的 tokens 中随机选取 15% 的进行 mask,然后只需要预测被 mask 的词。这样以来,就能训练双向语言模型了。

但是存在一个问题,这样 pre-training 训练出来的语言模型并不能拿去做 fine-tune. 原因是在 fine-token 中从来没有见过 <MASK> 这个词。作者采用这样的策略:

具体的操作,以 “My dog is hairy” 为例,mask “hairy” 这个词:

  • “My dog is <MASK>“. 80% 被 代替

  • “My dog is apple”. 10% 被一个随机的 token 代替

  • “My dog is hairy”. 10% 保持原来的样子

为什么不用 <MASK> 代替所有的 token?

If the model had been trained on only predicting ‘<MASK>’ tokens and then never saw this token during fine-tuning, it would have thought that there was no need to predict anything and this would have hampered performance. Furthermore, the model would have only learned a contextual representation of the ‘<MASK>’ token and this would have made it learn slowly (since only 15% of the input tokens are masked). By sometimes asking it to predict a word in a position that did not have a ‘<MASK>’ token, the model needed to learn a contextual representation of all the words in the input sentence, just in case it was asked to predict them afterwards.

如果模型在预训练的时候仅仅只预测 <MASK>, 然后在 fine-tune 的时候从未见过 <MASK> 这个词,那么模型就不需要预测任何词,在 fine-tune 时会影响性能。

更严重的是,如果仅仅预测 <MASK>, 那么模型只需要学习 <MASK> 的上下文表示,这会导致它学习的很慢。

如果让模型在某个位置去预测一个不是 <MASK> 的词,那么模型就需要学习所有 tokens 的上下文表示,因为万一需要预测这个词呢。

只需要 random tokens 足够吗?为什么还需要 10% 的完整的 sentence?

Well, ideally we want the model’s representation of the masked token to be better than random. By sometimes keeping the sentence intact (while still asking the model to predict the chosen token) the authors biased the model to learn a meaningful representation of the masked tokens.

使得模型具有偏置,更倾向于获得有意义的 masked token.

在知乎上问了这个问题,大佬的回复跟这篇 blog 有点差异,但实际上意思是一样的:

总结下:

为什么不能完全只有 <MASK> ? 如果只有 <MASK>, 那么这个预训练模型是有偏置的,也就是学到一种方式,用上下文去预测一个词。这导致在 fine-tune 时,会丢一部分信息,也就是知乎大佬第一部分所说的。

所以加上 random 和 ture token 是让模型知道,每个词都是有意义的,除了上下文信息,还要用到它本身的信息,即使是 <MASK>. 也就是知乎上说的,提取这两方面的信息。

再回过头,从语言模型的角度来看,依然是需要预测每一个词,但是绝大多数词它的 cross entropy loss 会很小,而主要去优化得到 <MASK> 对应的词。而 random/true token 告诉模型,你需要提防每一个词,他们也需要好好预测,因为他们不一定就是对的。

感谢知乎大佬!

random tokens 会 confuse 模型吗?

不会, random tokens 只占 15% * 10% = 1.5%. 这不会影响模型的性能。

还有一个问题, <MASK> 所占的比例很小,主要优化对象迭代一次对整个模型影响会很小,因而需要更多次迭代.

next sentence generation

对于下游是 Question Answering(QA), Natural Language Inference(NLI) 这样需要理解句子之间的相关性的任务,仅仅通过语言模型并不能获得这方面的信息。为了让模型能够理解句子之间的关系,作者提出了一个 binarized next sentence prediction.

具体方式是:

50% 是正确的相邻的句子。 50% 是随机选取的一个句子。这个任务在预训练中能达到 97%-98% 的准确率,并且能很显著的提高 QA NLI 的任务。

pre-training procudure

作者预训练使用的语料:BooksCorpus (800M words),English Wikipedia (2,500M words)。 使用文档级别的语料很关键,而不是 shffule 的句子级别的语料,这样可以获得更长的 sentence.

获得训练样本:从预料库中抽取句子对,其中 50% 的两个句子之间是确实相邻的,50% 的第二个句子是随机抽取的。具体操作看代码吧

  • batch_size 256.

  • 每一个 sentences 对: 512 tokens

  • 40 epochs

  • Adam lr=1e-4, $\beta_1=0.9$, $\beta_2=0.999$, L2 weight decay 0.01

  • learning rate warmup 10000 steps

  • 0.1 dropout

  • gelu instead of relu

Fine-tune procedure

sequence-level tasks

  • 比如 sentences pairs 的 Quora Question Pairs(QQP) 预测两个句子之间语义是否相同。如下图中(a).

  • 如果是 single sentence classification 比如 Stanford Sentiment Treebank(SST-2)和 Corpus of Linguistic Acceptability(CoLA)这种分类问题。如下图(b)

只需要输出 Transformer 最后一层的隐藏状态中的第一个 token,也就是 [CLS]. 然后接上一个全链接映射到相应的 label 空间即可。

fine-tune 时的超参数跟 pre-training 时的参数大致相同。但是训练速度会很快

  • Batch size: 16, 32

  • Learning rate (Adam): 5e-5, 3e-5, 2e-5

  • Number of epochs: 3, 4

语料库越大,对参数的敏感度越小。

token-level tasks.

对于token-level classification(例如NER),取所有token的最后层transformer输出,喂给softmax层做分类。

如何使用 BERT

文本分类

https://github.com/huggingface/pytorch-pretrained-BERT/blob/master/examples/run_classifier.py

主要涉及到两个 类:

  • 数据预处理

  • 预训练模型加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

from pytorch_pretrained_bert import BertTokenizer, BertForSequenceClassification, BertConfig, BertAdam, PYTORCH_PRETRAINED_BERT_CACHE



tokenizer = BertTokenizer.from_pretrained(args.bert_model, do_lower_case=args.do_lower_case)

tokenizer = BertTokenizer.from_pretrained("./pre_trained_models/bert-base-uncased-vocab.txt")



model = BertForSequenceClassification.from_pretrained('bert-base-uncased',

cache_dir=PYTORCH_PRETRAINED_BERT_CACHE / 'distributed_{}'.format(args.local_rank),

num_labels = num_labels)

model = BertForSequenceClassification.from_pretrained("pre_trained_models/bert-base-uncased.tar.gz", num_labels=2)

其中 bert-base-uncased 可以分别用具体的 词表文件 和 模型文件 代替。从源代码中提供的链接下载即可。

数据处理

1
2
3
4
5
6
7
8
9

from pytorch_pretrained_bert import BertTokenizer, BertForSequenceClassification, BertConfig, BertAdam, PYTORCH_PRETRAINED_BERT_CACHE



tokenizer = BertTokenizer.from_pretrained(args.bert_model, do_lower_case=args.do_lower_case)

tokenizer = BertTokenizer.from_pretrained("./pre_trained_models/bert-base-uncased-vocab.txt")

前一种方式是根据代码中提供的 url 去下载词表文件,然后缓存在默认文件夹下 /home/panxie/.pytorch_pretrained_bert 。后者是直接下载词表文件后,放在本地。相对来说,后者更方便。

这部分代码相对比较简单,根据自己的任务,继承 DataProcessor 这个类即可。

作为模型的输入,features 主要包括三个部分:

  • input_ids 是通过词典映射来的

  • input_mask 在 fine-tune 阶段,所有的词都是 1, padding 的是 0

  • segment_ids 在 text_a 中是 0, 在 text_b 中是 1, padding 的是 0

这里对应了前面所说的,input_idx 就是 token embedding, segment_ids 就是 Sentence Embedding. 而 input_mask 则表示哪些位置被 mask 了,在 fine-tune 阶段都是 1.

加载预训练模型

1
2
3
4
5
6
7
8
9

!tar -tf pre_trained_models/bert-base-uncased.tar.gz



./pytorch_model.bin

./bert_config.json

下载好的文件包中含有两个文件,分别是 config 信息,以及模型参数。

如果不用具体的文件,则需要从代码中提供的 url 下载,并缓存在默认文件夹 PYTORCH_PRETRAINED_BERT_CACHE = /home/panxie/.pytorch_pretrained_bert

作为分类任务, num_labels 参数默认为 2.

运行时会发现提取预训练模型会输出如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

12/26/2018 17:00:41 - INFO - pytorch_pretrained_bert.modeling -

loading archive file pre_trained_models/bert-base-uncased.tar.gz

12/26/2018 17:00:41 - INFO - pytorch_pretrained_bert.modeling -

extracting archive file pre_trained_models/bert-base-uncased.tar.gz to temp dir /tmp/tmpgm506dcx

12/26/2018 17:00:44 - INFO - pytorch_pretrained_bert.modeling -

Model config {

"attention_probs_dropout_prob": 0.1,

"hidden_act": "gelu",

"hidden_dropout_prob": 0.1,

"hidden_size": 768,

"initializer_range": 0.02,

"intermediate_size": 3072,

"max_position_embeddings": 512,

"num_attention_heads": 12,

"num_hidden_layers": 12,

"type_vocab_size": 2,

"vocab_size": 30522

}



12/26/2018 17:00:45 - INFO - pytorch_pretrained_bert.modeling -

Weights of BertForSequenceClassification not initialized from pretrained model:

['classifier.weight', 'classifier.bias']

12/26/2018 17:00:45 - INFO - pytorch_pretrained_bert.modeling -

Weights from pretrained model not used in BertForSequenceClassification:

['cls.predictions.bias', 'cls.predictions.transform.dense.weight',

'cls.predictions.transform.dense.bias', 'cls.predictions.decoder.weight',

'cls.seq_relationship.weight', 'cls.seq_relationship.bias',

'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias']

不得不去观察 from_pretrained 的源码:https://github.com/huggingface/pytorch-pretrained-BERT/blob/8da280ebbeca5ebd7561fd05af78c65df9161f92/pytorch_pretrained_bert/modeling.py#L448

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

missing_keys = []

unexpected_keys = []

error_msgs = []

# copy state_dict so _load_from_state_dict can modify it

metadata = getattr(state_dict, '_metadata', None)

state_dict = state_dict.copy()

if metadata is not None:

state_dict._metadata = metadata



def load(module, prefix=''):

local_metadata = {} if metadata is None else metadata.get(prefix[:-1], {})

module._load_from_state_dict(

state_dict, prefix, local_metadata, True, missing_keys, unexpected_keys, error_msgs)

for name, child in module._modules.items():

if child is not None:

load(child, prefix + name + '.')

load(model, prefix='' if hasattr(model, 'bert') else 'bert.')

if len(missing_keys) > 0:

logger.info("Weights of {} not initialized from pretrained model: {}".format(

model.__class__.__name__, missing_keys))

if len(unexpected_keys) > 0:

logger.info("Weights from pretrained model not used in {}: {}".format(

model.__class__.__name__, unexpected_keys))

if tempdir:

# Clean up temp dir

shutil.rmtree(tempdir)

return model

这部分内容解释了如何提取模型的部分参数.

missing_keys 这里是没有从预训练模型提取参数的部分,也就是 classifier ['classifier.weight', 'classifier.bias']层,因为这一层是分类任务独有的。

unexpected_keys 则是对于分类任务不需要的,但是在预训练的语言模型中是存在的。查看 BertForMaskedLM 的模型就能看到,cls 层,是专属于语言模型的,在下游任务中都需要去掉。

所以这部分代码实际上学到了如何选择预训练模型的部分参数~~棒啊!

论文笔记-character embedding and ELMO

character embedding

Motivation

A language model is formalized as a probability distribution over a sequence of strings (words), and traditional methods usually involve making an n-th order Markov assumption and estimating n-gram probabilities via counting and subsequent smoothing (Chen and Goodman 1998). The count-based models are simple to train, but probabilities of rare n-grams can be poorly estimated due to data sparsity (despite smoothing techniques).

对语言模型的描述:语言模型是 一个单词序列的概率分布 的形式化描述(什么意思?就是比如这个句子长度为 10, 那么每个位置可能是词表中的任意一个词,而出现当前词是有一个概率的, 这个概率是依赖于之前的词的)。

在传统的方法主要是运用 n阶马尔可夫假设来估计 n-gram 的概率,通过统计计数,以及子序列平滑的方式。这种基于计数的模型虽然简单,但是在数据稀疏的情况下,对不常见的 n-gram 的概率估计会很差。

While NLMs have been shown to outperform count-based n-gram language models (Mikolov et al. 2011), they are blind to subword information (e.g. morphemes). For example, they do not know, a priori, that eventful, eventfully, uneventful, and uneventfully should have structurally related embeddings in the vector space. Embeddings of rare words can thus be poorly estimated, leading to high perplexities for rare words (and words surrounding them). This is especially problematic in morphologically rich languages with long-tailed frequency distributions or domains with dynamic vocabularies (e.g. social media).

neural language models 将词嵌入到低维的向量中,使得语义相似的词在向量空间的位置也是相近的。然后 Mikolov word2vec 这种方式不能有效的解决子单词的信息问题,比如一个单词的各种形态,也不能认识前缀。这种情况下,不可避免的会造成不常见词的向量表示估计很差,对于不常见词会有较高的困惑度。这对于词语形态很丰富的语言是一个难题,同样这种问题也是动态词表的问题所在(比如社交媒体)。

Recurrent Neural Network Language Model

给定词表为 V,之前的序列是 $w_{1:t}=[w_1,..,w_t]$,在 RNN-LM 中通过全链接 affine transformation 计算 $w_{t+1}$ 个词的概率分布:

$$Pr(w_{t+1}=j|w_{1:t})=\dfrac{exp(h_t\cdot p^j+q^j)}{\sum_{j’\in V}exp(h_t\cdot p^{j’}+q^{j’})}$$

其中 $h_t$ 是当前 t 时刻的隐藏状态。也就是先通过全链接映射到词表的 V 的维度,然后通过 softmax 计算其是词表中第 j 个词的概率。

然后假设训练预料库的 sentence 是 $w_{1:T}=[w_1,…,w_T]$,那么训练也就是最小化这个序列的 似然概率的负对数:

$$NLL=-\sum_{T}^{t=1}logPr(w_t|w_{1:t-1})$$

Chracter-level Convolution Neural Network

以单词 absurdity 为例,有 l 个字符(通常会 padded 到一个固定size),通过 character embedding 映射成矩阵 $C\in R^{d\times l}$. d 是 embedding size. 图中 embedding size 为 4.

然后使用卷积核 kernel H 做卷积运算, $H\in R^{d\times w}$,所以得到的 feature map $f^k\in R^{l-w+1}$. 跟之前 CNN 做文本分类其实挺像的, kernel 的长是 embedding size d, 宽度 w 分别是 2,3,4. 上图中蓝色区域为例,filter 宽度为 2 的个数是3, 那么卷积得到的 featur map 是 $3 \times (9-2+1) = 3\times 8$.

$$f^k[i]=tanh(<C^k[* ,i:i-w+1], H> +b)$$

<>表示做卷积运算(Frobenius inner product). 然后加上 bias 和 非线性激活函数 tanh.

接着基于 times 维度做 max pooling. 上图中 filter 宽度为 3,2,4 的个数分别为 4,3,5.所以得到长度为 4+3+5=12 的向量。

这里每一个 filter matrix 得到一个相应的特征 feature. 在通常的 NLP 任务中这些 filter 的总数 $h\in[100, 1000]$

Highway Network

通过卷积层得到单词 k 的向量表示为 $y^k$.

Highway Network 分为两层 layer.

  • one layer of an MLP applies an affine transformation:

$$z=g(W_y+b)$$

  • one layer 有点类似 LSTM 中的 gate 机制:

$$z=t\circ g(W_Hy+b_H)+(1-t)\circ y$$

其中 g 是非线性函数。$t=\sigma(W_Ty+b_T)$. t 成为 transform gate, (1-t) 是 carry gate. 同 LSTM 类似, highway network 允许输出能自适应的从 $y^k$ 中直接获取信息。

ELMo

传统的提取 word embedding 的方法,比如 word2vec 和 language model, 前者是通过词与词之间的共现,后者是 contextual,但他们都是获得固定的 embedding,也就是每一个词对应一个单一的 embedding. 而对于多义词显然这种做法不符合直觉, 而单词的意思又和上下文相关, ELMo的做法是我们只预训练 language model, 而 word embedding 是通过输入的句子实时输出的, 这样单词的意思就是上下文相关的了, 这样就很大程度上缓解了歧义的发生. 且 ELMo 输出多个层的 embedding 表示, 试验中已经发现每层 LM 输出的信息对于不同的任务效果不同, 因此对每个 token 用不同层 embedding 表示会提升效果.

个人觉得,可以从这个角度去理解。RNN 可以看做一个高阶马尔可夫链,而不同于 马尔可夫模型,RNN 中的状态转移矩阵是用神经网络来模拟的,也就是我们计算隐藏层所用的 $h_t=tanh(w_{hh}h_{t-1}+w_{hx}x_t)$. 这个状态转移是动态的,也是不断更新的。而使用 语言模型 来训练 RNN/LSTM 目的就是得到这样的一套参数,使得它能学习到任何 合理的,自然的 sentence. 所以,这个语料库越大越好。事实上,有监督的训练也可以达到这个目的,但是有监督的数据有限,并且整个模型是有偏置的,比如文本分类的任务去训练,那么它更倾向于 局部信息。相比之下,机器翻译作为有监督的效果会更好,最好的还是语言模型呢,不仅可用的数据量很大,而且因为要预测每一个词的信息,它会努力结合每一个词的上下文去学习这个词的表示。这也正是我们需要的。ELMo 和 BERT 都是这样的道理,而 BERT 的优势前一篇 blog 说过了。

Bidirectional language models

给定 sentence $t_1, t_2,…,t_N$, 通过前面的词 $t_1,..,t_{k-1}$ 计算 token $t_k$ 的概率分布:

反向:

语言模型的训练就是采用极大似然估计,最大化这个概率:

传统的方法就是 提取出对应位置的向量表示作为对应位置的词向量 context-independent token representation $x_k^{LM}$.

ELMo

ELMo is a task specific combination of the intermediate layer representations in the biLM.

ELMo 实际上只是下游任务的中间层,跟 BERT 一样。但也有不同的是, ELMo 每一层的向量表示会获得不同的 信息。底层更能捕捉 syntax and semantics 信息,更适用于 part-of-speech tagging 任务,高层更能获得 contextual 信息,更适用于 word sense disambiguation 任务。所以对不同的任务,会对不同层的向量表示的利用不同。

在使用 ELMo 进行下游有监督训练时,通常是这样 $[x_k; ELMo_k^{task}]$. 对于 SQuAD 这样的任务,$[h_k, ELMo_k^{task}]$.

Model architecture

The final model uses L = 2 biLSTM layers with 4096 units and 512 dimension projections and a residual connection from the first to second

layer. The context insensitive type representation uses 2048 character n-gram convolutional filters followed by two highway layers and a linear projection down to a 512 representation.

具体模型还是得看代码。