언어모델을 평가하기 위한 하나의 척도인 Perplexity에 대해 정리하고자 한다. 원문 내용을 많이 참고하였다.
1. 언어모델이란?
언어모델(Language Model)은 가능한 단어 시퀀스에 대한 확률을 계산하는 모델이다. 어떤 문장이 주어질 때, 언어모델이 보유한 모든 토큰들에 대해 그 다음에 올 확률을 계산하면 엔지니어가 태스크에 맞게 최대 확률값을 가진 토큰을 1개 이상 사용하여 문장을 완성할 수 있다. 좋은 언어모델은 real 또는 syntactically correct한 문장에 더 높은 화률을 부여하는 모델이다.
$n$개의 단어 $(w_{1}, w_{2}, \cdots, w_{n})$로 이루어진 문장 $W$에 대한 확률은 다음과 같이 표현할 수 있다(문장에 대한 확률이라는 말이 조금 어색할 수 있는데, 해당 문장이 발생 가능한 정도 또는 인간에게 자연스럽게 느껴지는 정도로 이해하면 될 것 같다).
$$ P(W) = P(w_{1}, w_{2}, \cdots, w_{n}) $$
unigram은 이전 각각의 단어 확률을 사용하고 bi-gram은 직전 1개에 대한 조건부 확률을 사용한다.
- unigram: $P(W) = P(w_{1})P(w_{2})\cdots P(w_{n})$
- bi-gram: $P(W) = P(w_{1})P(w_{2}|w_{1})P(w_{3}|w_{2})\cdots P(w_{N}|w_{N-1})$
2. 언어모델 평가하기
Perplexity는 언어모델의 평가 메트릭인데 왜 사용하는가? 언어모델을 평가하는 접근 방식에는 두 가지가 있다.
- 외부적 평가(Extrinsic): 언어모델을 특정 태스크에 적용해서 loss/accuracy를 사용하여 확인하는 방법이다.
- 내부적 평가(Intrinsic): 태스크에 적용하지 않고 언어모델의 자체적인 역량을 평가하는 방법이다. 외부적 평가방법에 비해 정확하지는 않겠지만 모델별 비교에 적합하다. Perplexity는 내부적 평가에 해당한다.
3. Perplexity는 normalised inverse probability of the test set이다.
$$PP(W) = \sqrt[N]{\frac{1}{P(w_{1},w_{2},\cdots, w_{N})}}$$
1) Probabilty of the test set
먼저 좋은 모델이란 무엇일까? 좋은 언어모델은 적합한 문장에게 높은 확률을 부여하고 부정확하거나 오류가 있는 문장에 낮은 확률을 부여하는 모델이다. Test set을 올바른 문장들로 구성했을 때 언어모델이 해당 문장들에 높은 확률을 부여한다면 언어모델이 언어에 대해 잘 이해하고 있다고 할 수 있다.
2) Normalising
문장에 대한 확률 계산할 때 한 가지 문제가 있다. 문장의 길이는 매우 짧거나 매우 길 수도 있다. 확률값은 $[0,1]$ 사이의 값이므로 문장 길이에 많은 영향을 받기 때문에 문장 길이로 정규화를 해야하고, 이를 통해 단어별 측정(per-word measure)를 할 수 있다.
확률은 곱셈이므로 log probability로 바꿔서 계산해야 한다. unigram모델을 예시로 들어보면 문장 확률은 다음과 같이 계산된다.
$$P(W) = P(w_{1})P(w_{2})\cdots P(w_{n}) = \prod_{i=1}^{N}P(w_{i})$$
Log를 사용해 곱셈을 덧셈으로 바꿔준다.
$$ln(P(W)) = ln(\prod_{i=1}^{N}P(w_{i})) = \sum_{i=1}^{N}lnP(w_{i})$$
그리고 $N$으로 나눠서 normalising을 한다.
$$\frac{ln(P(W))}{N} = \frac{\sum_{i=1}^{N}lnP(w_{i})}{N}$$
$$e^{\frac{ln(P(W))}{N}} = e^{\frac{\sum_{i=1}^{N}lnP(w_{i})}{N}}$$
$$(e^{ln(P(W)})^{\frac{1}{N}} = (e^{\sum_{i=1}^{N}lnP(w_{i})})^{\frac{1}{N}}$$
$$P(w)^{\frac{1}{N}} = (\prod_{i=1}^{N}P(w_{i}))^{\frac{1}{N}}=\sqrt[N]{\frac{1}{P(w_{1},w_{2},\cdots, w_{N})}}$$
3) 참고사항
- Perplexity가 낮을 수록 좋은 모델이다.
- Test set을 구성할 때 <SOS>, <EOS> 토큰도 사용하게 된다. 때문에 문장이 두개라면 W= (<SOS>, 첫번째, 문장, <EOS>, <SOS>, 두번째, 문장, <EOS>)으로 구성되며 N=8이다.
4. Perplexity as the exponential of the cross-entropy
Perplexity는 cross-entropy를 사용해서 계산 가능하며 그 해석은 원글의 4.2 섹션을 참고하면 된다.
Entropy와 cross-entopy는 각각 $E(p) = -\sum_{i}p(x_{i})log_{2}p(x_{i})$, $H(p,q) = -\sum_{i}p(x_{i})log_{2}q(x_{i})$로 정의된다. 언어모델에서 $p$는 언어의 real distribution이나 이는 알 수 없는 값이다. 하지만 $W$의 크기가 크다면(문장의 길이가 길며 $N$이 클 때) Shannon-McMilan-Breiman theorem에 따라 cross-entropy를 다음과 같이 근사시킬 수 있다.
$$H(p,q) \approx -\frac{1}{N}log_{2}q(W)$$
언어모델에 적용해보면 길이 $N$의 단어 배열 $W$과 학습된 언어모델 $P$가 있을 때, 언어의 real distribution과 언어모델의 distribution 간의 cross-entropy를 다음과 같이 정의할 수 있다.
$$H(W) \approx -\frac{1}{N}log_{2}P(W) = -\frac{1}{N}log_{2}P(w_{1}, w_{2}, \cdots, w_{N})$$
이를 활용하여 Perplexity는 cross-entropy의 exponential로 재정의할 수 있다.
$$PP(W) = 2^{H(W)} = 2^{-\frac{1}{N}log_{2}P(w_{1}, w_{2}, \cdots, w_{N})}$$
$$\begin{array}{rcl}
PP(W) & = & 2^{-\frac{1}{N}log_{2}P(w_{1}, w_{2}, \cdots, w_{N})} \\
& = & (2^{log_{2}P(w_{1}, w_{2}, \cdots, w_{N})})^{-\frac{1}{N}} \\
& = & P(w_{1}, w_{2}, \cdots, w_{N})^{-\frac{1}{N}} \\
& = & \sqrt[N]{\frac{1}{P(w_{1},w_{2},\cdots, w_{N})}}
\end{array}$$
즉, perplexity는 test set에 대한 loss로 계산할 수 있다!
1) Masked Language Model에서의 perplexity 계산
BERT 스타일의 encoder 모델의 경우 masked token에 대한 nll loss의 exponential로 계산할 수 있다.(관련 논문, stackoverflow글)
from transformers import AutoModelForMaskedLM, AutoTokenizer
import torch
import numpy as np
model_name = 'cointegrated/rubert-tiny'
model = AutoModelForMaskedLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
def score(model, tokenizer, sentence):
tensor_input = tokenizer.encode(sentence, return_tensors='pt')
repeat_input = tensor_input.repeat(tensor_input.size(-1)-2, 1)
mask = torch.ones(tensor_input.size(-1) - 1).diag(1)[:-2]
masked_input = repeat_input.masked_fill(mask == 1, tokenizer.mask_token_id)
labels = repeat_input.masked_fill( masked_input != tokenizer.mask_token_id, -100)
with torch.inference_mode():
loss = model(masked_input, labels=labels).loss
return np.exp(loss.item())
print(score(sentence='London is the capital of Great Britain.', model=model, tokenizer=tokenizer))
# 4.541251105675365
print(score(sentence='London is the capital of South America.', model=model, tokenizer=tokenizer))
# 6.162017238332462
2) Autoregressive Language Modeld에서의 perplexity 계산
GPT 스타일의 decoder 모델에서는 다음 token에 대한 nll loss의 exponential로 계산할 수 있다(huggingface 문서). 근사적으로 계산하기 위한 몇 가지 방법이 있는데 huggingface에서는 sliding window를 적용하였으며(마지막 token에 대한 loss만 계산하기 위해 tar_len까지는 모두 -100으로 두어 ignore함), 이렇게 계산한 nll loss의 평균을 사용하였다.
from transformers import GPT2LMHeadModel, GPT2TokenizerFast
from datasets import load_dataset
import torch
from tqdm import tqdm
device = "cuda"
model_id = "gpt2-large"
model = GPT2LMHeadModel.from_pretrained(model_id).to(device)
tokenizer = GPT2TokenizerFast.from_pretrained(model_id)
test = load_dataset("wikitext", "wikitext-2-raw-v1", split="test")
encodings = tokenizer("\n\n".join(test["text"]), return_tensors="pt")
max_length = model.config.n_positions
stride = 512
nlls = []
for i in tqdm(range(0, encodings.input_ids.size(1), stride)):
begin_loc = max(i + stride - max_length, 0)
end_loc = min(i + stride, encodings.input_ids.size(1))
trg_len = end_loc - i # may be different from stride on last loop
input_ids = encodings.input_ids[:, begin_loc:end_loc].to(device)
target_ids = input_ids.clone()
target_ids[:, :-trg_len] = -100
with torch.no_grad():
outputs = model(input_ids, labels=target_ids)
neg_log_likelihood = outputs[0] * trg_len
nlls.append(neg_log_likelihood)
ppl = torch.exp(torch.stack(nlls).sum() / end_loc)
'논문 및 개념 정리' 카테고리의 다른 글
[2021] GPT Understands, Too (P-tuning) (0) | 2022.04.29 |
---|---|
[2020] Poly-encoders: architectures and pre-training strategies for fast and accurate multi-sentence scoring (0) | 2022.04.04 |
[GPT3] 주요 내용 정리 (0) | 2022.02.16 |
[2020] Spot The Bot: A Robust and Efficient Framework for the Evaluation of Conversational Dialogue Systems (0) | 2021.08.13 |
[2017] On Calibration of Modern Neural Networks (0) | 2021.05.26 |