BERT用于语言模型的方案

背景

BERT取得了较大成功,但是原始的模型并不能直接用于对句子打分 #35,也就是常规的语言模型任务。
如何利用bert的预训练模型对句子打分?

序列掩码的方式应该比较合理。

并非autoregressive结构,速度较快

方案

  1. 双向
    • 采用sequential mask. 计算量较大,精度也最高
  2. 单向,类似GPT。计算量稍微小点,但是作为12层的transformer,计算量仍然不小
  3. 只用embedding(768维),自己构造小结构的语言模型
    • 动态embedding: 用bert作为embedding layer
    • 静态embedding

1比较靠谱,因此后面重点介绍方案一。

简介

对于一个句子 $S = w_1, w_2,…, w_k$,通常可以这样来表示整个句子的概率

传统的语言模型,比如RNN中, $context = w1, …, w{i-1}$,

如何利用bert对句子打分?一个简单的思路就是每次mask掉一个词 $w i$ ,然后利用bert得到该词的概率 $p(w i)$。即作为双向语言模型,BERT具有更大的上下文信息,$context = w1, …, w{i-1}, w_{i+1}, …, w_k$,

进一步可以转化为句子的ppl。

总体评价

char-level的语言模型,由于词组内的高概率,会使整个句子ppl普遍偏高。句子间的相对ppl还靠谱。

单个word/char的概率预测效果较好。

建议:
用分词后的中文重新pretrain,然后进行word-level language model predict。

中文测试

1
2
3
4
5
6
7
8
9
export BERT_BASE_DIR=model/chinese_L-12_H-768_A-12
export INPUT_FILE=data/lm/test.zh.tsv
python run_lm_predict.py \
--input_file=$INPUT_FILE \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
--max_seq_length=128 \
--output_dir=/tmp/lm_output/

以下是部分结果,更多见result.zh.json

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
58
59
60
61
[
{
"tokens": [
{
"token": "2016",
"prob": 0.06563900411128998
},
{
"token": "全",
"prob": 0.4981258511543274
},
{
"token": "国",
"prob": 0.9088247418403625
},
{
"token": "低",
"prob": 1.6259804397122934e-05 # 低概率
},
{
"token": "考",
"prob": 0.4023572504520416
},
...
],
"ppl": 13.400421357093588
},
{
"tokens": [
{
"token": "落",
"prob": 0.1483132392168045
},
{
"token": "霞",
"prob": 0.42232587933540344
},
{
"token": "与",
"prob": 0.8615185022354126
},
{
"token": "孤",
"prob": 0.9975666999816895
},
{
"token": "鹜",
"prob": 0.5613960027694702
},
{
"token": "齐",
"prob": 0.18012434244155884
},
{
"token": "跑",
"prob": 1.3388593288254924e-05 # 低概率
},
...
],
"ppl": 11.983086642867598
},

中文测试样例来源于百度云dnnlm

更多测试

中文model给英文句子打分

1
2
3
4
hello world
gone with bad
gone with the wind
slavery african

采用的chinese_L-12_H-768_A-12。即中文词典跑英文,会出现很多OOV,造成较多subtoken。
ppl较大。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
[
{
"tokens": [
{
"token": "hello",
"prob": 0.01559396181255579
},
{
"token": "world",
"prob": 7.31540481524462e-08
}
],
"ppl": 29607.558139141183 # 为什么ppl这么高?
},
{
"tokens": [
{
"token": "go",
"prob": 0.16923364996910095
},
{
"token": "##ne",
"prob": 0.0627128928899765
},
{
"token": "with",
"prob": 0.019953709095716476
},
{
"token": "bad",
"prob": 9.239820428774692e-07 # 这里低概率,比较合理
}
],
"ppl": 267.37285553187235 # ppl也算合理
},
{
"tokens": [
{
"token": "go",
"prob": 0.026223953813314438
},
{
"token": "##ne",
"prob": 0.06661253422498703
},
{
"token": "with",
"prob": 0.04489848017692566
},
{
"token": "the",
"prob": 0.7770023941993713 # 局部高概率,ngram能都有多大的概率?
},
{
"token": "wind",
"prob": 7.36875972506823e-06
}
],
"ppl": 74.05246212422989
},
{
"tokens": [
{
"token": "s",
"prob": 0.15798506140708923
},
{
"token": "##la",
"prob": 0.06484799832105637
},
{
"token": "##ver",
"prob": 0.04713333398103714
},
{
"token": "##y",
"prob": 0.022265272215008736
},
{
"token": "af",
"prob": 0.9910877346992493
},
{
"token": "##ric",
"prob": 0.9998739957809448 # african是一个词汇,african局部高概率
},
{
"token": "##an",
"prob": 0.17350609600543976
}
],
"ppl": 6.592083876588999
}
]

我觉得,不能按照token来mask

char-level lm的一大缺陷,就是会造成局部概率过高,使得整体ppl失真。
ppl的本意是要考虑word间的

参考