目录
引言
语言模型(Language Model,LM),即给出一句话的前k个token,希望它可以预测第k+1个token是什么,即给出一个第k+1个token可能出现的概率的分布 p ( x k + 1 ∣ x 1 , x 2 , . . . , x k ) p(x_{k+1}|x_1,x_2,...,x_k) p(xk+1∣x1,x2,...,xk)。
本文试图理清Language Modeling的评估指标基本计算过程和指标含义,目的有二:
(1)本文介绍的评估指标对于机器学习或者NLP从业人员来说是比较常用,但未必系统学习过、未必能说清楚这些指标之间的关系和指标本身的含义,掌握这些知识不管是对工作、求职面试都是有帮助的。
(2)企图设计新的指标为本人进一步的AGI模型的建模研究做铺垫,这是主要的。
交叉熵(Cross-Entropy)
定义
在language modeling中,要评价训练模型的好坏,可以通过度量模型预测序列Q和真实序列P的分布之间的差异来评价。相对熵的定义恰好就是度量两个分布差异的,相对熵又称Kullback-Leibler散度(简称KL散度)。那实际language modeling及其他机器学习问题中为什么很少用相对熵而用交叉熵呢?用关于信息熵、相对熵、交叉熵的定义、关系及推导可以参考本人另一篇文章《度量方法总结》。此处不再详细介绍,下面直接给出几个主要计算公式。
信息熵H(P(X)):
H
(
P
(
X
)
)
=
E
[
f
(
x
i
)
]
=
−
∑
i
n
P
(
x
i
)
l
o
g
P
(
x
i
)
\begin {alignedat}{2} H(P(X))&=E[f(x_i)] \\ &=-\sum_i^n P(x_i) log\ P(x_i) \end {alignedat}
H(P(X))=E[f(xi)]=−i∑nP(xi)log P(xi)
相对熵
D
K
L
(
P
(
X
)
∣
∣
Q
(
X
)
)
D_{KL}(P(X)||Q(X))
DKL(P(X)∣∣Q(X)):
D
K
L
(
P
(
X
)
∣
∣
Q
(
X
)
)
=
∑
i
=
1
n
P
(
x
i
)
l
o
g
(
P
(
x
i
)
Q
(
x
i
)
)
\begin {alignedat}{2} D_{KL}(P(X)||Q(X)) = \sum_{i=1}^nP(x_i)log(\frac{P(x_i)}{Q(x_i)}) \end {alignedat}
DKL(P(X)∣∣Q(X))=i=1∑nP(xi)log(Q(xi)P(xi))
交叉熵
H
(
P
(
X
)
,
Q
(
X
)
)
H(P(X),Q(X))
H(P(X),Q(X)):
H
(
P
(
X
)
,
Q
(
X
)
)
=
−
∑
i
=
1
n
P
(
x
i
)
l
o
g
(
Q
(
x
i
)
)
\begin {alignedat}{2} H(P(X),Q(X)) = −∑_{i=1}^{n}P(x_i)log(Q(x_i)) \end {alignedat}
H(P(X),Q(X))=−i=1∑nP(xi)log(Q(xi))
三者之间的关系:
D
K
L
(
P
(
X
)
∣
∣
Q
(
X
)
)
=
−
H
(
P
(
X
)
)
+
H
(
P
(
X
)
,
Q
(
X
)
)
\begin {alignedat}{2} D_{KL}(P(X)||Q(X)) =-H(P(X))+H(P(X),Q(X)) \end {alignedat}
DKL(P(X)∣∣Q(X))=−H(P(X))+H(P(X),Q(X))
深入理解
引用香农在信息论中的一句话:“Entropy can be interpreted as the average number of bits required to store the information in a variable.”这句话意思就是说当我们的熵,也就是Entropy越小时,存储信息需要的bits数越少,因为信息的结构更有序了。
从bits数视角看:
信息熵H(P(X))是存储真实序列P所需的平均bit数;
相对熵 D K L ( P ( X ) ∣ ∣ Q ( X ) ) D_{KL}(P(X)||Q(X)) DKL(P(X)∣∣Q(X))是基于预测的Q序列来编码真实序列P所需要的额外bit数;
交叉熵 H ( P ( X ) , Q ( X ) ) H(P(X),Q(X)) H(P(X),Q(X))是存储真实序列P所需的平均bit数与基于预测的Q序列来编码真实序列P所需要的额外bit数的总和。
从评价语言/模型的角度说,如果基于预测的Q序列来编码真实序列P所需要的额外bit数越少,即相对熵越小,那么模型从从真实序列P中学习到的知识也越多。
从优化的角度说,相对熵和交叉熵都能达到相同的优化效果,但交叉熵计算更简单一些。
BPC(Bits-Per-Character)
在language modeling中,BPC也是衡量预测序列Q和真实序列P之间差异的指标,只不过做了一下平均。BPC和Cross-Entropy之间有如下计算关系:
BPC
=
1
T
∑
i
=
1
T
H
(
P
(
X
)
,
Q
(
X
)
)
\text{BPC} = \frac {1} {T} \sum^T_{i=1} H(P(X),Q(X))
BPC=T1i=1∑TH(P(X),Q(X))
也就是说BPC/BPW是cross-entropy对字符/单词长度T的平均,可以很容易地得出它的信息论含义:
基于预测的Q序列来编码P序列所需要的额外bit数在句子长度上的平均,也就是平均每一个字母/单词需要额外的bit数。这也是它名字的由来,也是我们为什么要从存储bit数量的角度来解释Cross-Entropy。
注意:T这里是字符/单词的个数,不是token的数量,因为不同编码方式下token可能是不一样的。
PPL(Perplexity)
定义
PPL也是用来衡量语言模型好坏的指标。它主要是根据每个词来估计一句话出现的概率,并用句子长度n作normalize,PPL的计算公式推导:
PPL
=
2
H
(
P
(
X
)
,
Q
(
X
)
)
=
2
1
n
∑
i
=
1
n
H
i
(
P
(
X
)
,
Q
(
X
)
)
=
2
1
n
∑
i
=
1
n
∑
j
=
1
n
−
P
(
x
j
)
log
2
Q
(
x
j
)
=
2
−
1
n
∑
i
=
1
n
log
2
Q
(
x
i
)
=
(
∏
i
=
1
n
Q
(
x
i
)
)
−
1
n
=
p
(
x
1
,
x
2
,
.
.
x
n
)
−
1
n
\begin {align} \text{PPL} &=2^{H(P(X),Q(X))} \\ &= 2^{\frac{1}{n} \sum_{i=1}^n {H_i(P(X),Q(X))}}\\ &= 2^{\frac{1}{n} \sum_{i=1}^n \sum_{j=1}^n -P(x_j) \log_2^{Q(x_j)}}\\ &= 2^{-\frac{1}{n} \sum_{i=1}^n \log_2^{Q(x_i)}}\\ &= (\prod_{i=1}^n{Q(x_i)})^{-\frac{1}{n}}\\ &= p(x_1,x_2,.. x_n) ^{-\frac{1}{n}}\\ \end {align}
PPL=2H(P(X),Q(X))=2n1∑i=1nHi(P(X),Q(X))=2n1∑i=1n∑j=1n−P(xj)log2Q(xj)=2−n1∑i=1nlog2Q(xi)=(i=1∏nQ(xi))−n1=p(x1,x2,..xn)−n1
这里有几点细节需要注意理解:
- 底数取2并不是必须的,取自然对数底 e e e或者其他值对计算结果并没有影响,取2更容易从bit数角度理解。
- 式(2)对句子的n个token都计算了交叉熵并求和取平均值,取平均的目的是为消除句子长度不一对困惑值的影响。
- 式(3)中真实值P(x)采用one-hot编码时会得到式(4)。
- 式(5)采用uni-gram语言模型(一元语言模型)作为假设条件时会得到式(6),其采用了单个token概率独立的简化假设,即token序列的概率等于每个token概率的乘积。
- 式(6)中 p ( w 1 , w 2 , . . w n ) − 1 n p(w_1,w_2,.. w_n) ^{-\frac{1}{n}} p(w1,w2,..wn)−n1取的是概率倒数的几何平均,概率越大,倒数越小,几何平均越小;取几何平均的目的是为消除句子长度的影响,否则当句子的长度越长时,概率倒数反而会越大这。
Perplexity的影响因素
- 训练数据集越大,PPL会下降得更低,1billion dataset和10万dataset训练效果是很不一样的;
- 数据中的标点会对模型的PPL产生很大影响,一个句号能让PPL波动几十,标点的预测总是不稳定;
- 预测语句中的“的,了”等词也对PPL有很大影响,可能“我借你的书”比“我借你书”的指标值小几十,但从语义上分析有没有这些停用词并不能完全代表句子生成的好坏。
所以,语言模型评估时我们可以用perplexity大致估计训练效果,作出判断和分析,但它不是完全意义上的标准,具体问题还是要具体分析。
pytorch实现:
def evaluate(model, val_dataloader, config):
model.eval()
total_val_loss = 0
with torch.no_grad():
for step, batch in enumerate(val_dataloader):
batch[0].clone().detach().to(config.device)
batch[1].clone().detach().to(config.device)
loss, logits = model(batch[0], token_type_ids=None, attention_mask=(batch[0] > 0), labels=batch[1])
if isinstance(model, torch.nn.DataParallel):
loss = loss.mean()
total_val_loss += loss.mean().item()
average_val_loss = total_val_loss / len(val_dataloader)
perplexity = math.exp(average_val_loss)
return loss, perplexity
总结
- 困惑度的计算和比较中有诸多陷阱参见文献 [1],一些细节上的差错会导致不同方法或者不同工作中的PPL不可比较。如果哪天你惊喜地发现自己的模型PPL超越了SOTA,很可能是你用错误的方法计算了PPL,需要仔细检查,下面是一些可以关注的点:
- 当使用PPL来评估语言模型时,需要固定数据集,并确保数据集的预处理等细节与比较对象一致。
- 当使用PPL来评估生成质量时,确保使用同一个打分的语言模型PPL定义中使用了 ,但是一般使用pytorch, tensorflow中实现的交叉熵 ,所以直接使用交叉熵计算的PPL与定义会有一些差距,最好确定比较对象的具体计算方式。
- 对于同样的数据集,采用不同的分词方式可能会有很大的PPL差别,不可直接用于比较。比如使用字符级别(character), 子词级别(subword)或者词语(word)级别来分词。特别是像上述的采用了不同粒度分词的模型问题会更大。
- 使用BPC指标可以解决PPL指标某些条件下存在的不可比较问题。
参考资料
[1].Evaluation Metrics for Language Modeling,2019.10.18
[2].Perplexity of fixed-length models.
[3].一文搞懂Language Modeling三大评估标准, 硅谷谷主
[4].https://zhuanlan.zhihu.com/p/114432097
[5].https://blog.csdn.net/qq_41775769/article/details/121796159