Transformers documentation
Sabit uzunluklu modellerin perplexity değeri
Sabit uzunluklu modellerin perplexity değeri
Perplexity (PPL), dil modellerini değerlendirmek için kullanılan en yaygın metriklerden biridir. Konuya derinlemesine girmeden önce, bu metriğin özellikle klasik dil modelleri (bazen otoregresif veya nedensel dil modelleri olarak adlandırılır) için geçerli olduğunu ve BERT gibi maskeli dil modelleri için iyi tanımlanmadığını belirtmek gerekir.
Perplexity, bir dizinin üstel ortalama negatif log-olasılıklığı olarak tanımlanır. $X = (x_0, x_1, \dots, x_t)$ şeklinde tokenize edilmiş bir diziye sahipsek, perplexity değeri
burada $\log p\theta (x_i|x{<i})$, modelimize göre önceki $x_{<i}$ tokenlarına koşullu olarak i. token'ın log-olabilirlik değeridir. Sezgisel olarak, bu, modelin bir korpus içinde belirtilen token kümesi arasında eşit dağılımla tahmin yapma yeteneğinin bir değerlendirmesi olarak düşünülebilir. Önemli olan, tokenizasyon sürecinin bir modelin perplexity değerini doğrudan etkilediğidir ve bu durum, farklı modeller karşılaştırılırken her zaman göz önünde bulundurulmalıdır.
Bu, veriler ile model tahminleri arasındaki çapraz entropinin üslenmesine de eşdeğerdir. Perplexity (şaşkınlık) hakkında daha fazla sezgi ve bunun Karakter Başına Bit (BPC) ile veri sıkıştırması arasındaki ilişki için, The Gradient'teki bu mükemmel blog yazısına göz atın.
Sabit uzunluklu modellerle PPL hesaplama
Bir modelin bağlam boyutuyla sınırlı olmasaydık, aşağıda gösterildiği gibi bir diziyi otoregresif olarak faktörize ederek ve her adımda tüm önceki alt diziye koşullayarak modelin perplexity değerini değerlendirirdik.
Ancak yaklaşık modellerle çalışırken, modelin işleyebileceği token sayısı üzerinde genellikle bir kısıtlama bulunur. Örneğin, GPT-2, nun en büyük sürümü 1024 tokenlik sabit bir uzunluğa sahiptir, bu nedenle\theta(x_t|x{<t})$ değerini doğrudan hesaplayamayız, çünkü 1024'ten büyükse.
Bunun yerine dizi, modelin maksimum giriş boyutuna eşit alt dizilere bölünür. Bir modelin maksimum giriş boyutu $k$ ise, bir token'ın olasılığı sadece kendisinden önce gelen token'a koşullandırılarak yaklaşık olarak hesaplanır, tüm bağlam yerine. Bir dizinin modelin perplexity değerini değerlendirirken, kışkırtıcı ama optimal olmayan bir yaklaşım, diziyi ayrık parçalara bölmek ve her segmentin ayrıştırılmış log-olasılıklarını bağımsız olarak toplamaktır.
Her segmentin sürpriz değerinin (perplexity) tek bir ileri geçişte (forward pass) hesaplanabilmesi nedeniyle bu işlem hızlıdır; ancak tam faktörize edilmiş sürpriz değerinin (perplexity) zayıf bir yaklaşımıdır ve modelin tahmin adımlarının çoğunda daha az bağlama sahip olacağı için genellikle daha yüksek (daha kötü) bir PPL değeri verir.
Bunun yerine, sabit uzunluklu modellerin PPL değeri, kayan pencere stratejisiyle değerlendirilmelidir. Bu, modelin her tahmini yaparken daha fazla bağlama sahip olması için bağlam penceresinin tekrar tekrar kaydırılmasını içerir.
Bu, dizgi olasılığının gerçek ayrışımına daha yakın bir yaklaşımdır ve genellikle daha elverişli bir skor verir. Dezavantajı, korpusdaki her token için ayrı bir ileri geçiş (forward pass) gerektirmesidir. İyi bir pratik uzlaşma, bağlamı her seferinde 1 token kaydırmak yerine daha büyük adımlarla kaydıran, aralıklı bir kayan pencere (strided sliding window) kullanmaktır. Bu, modelin her adımda tahmin yapması için büyük bir bağlam sunarken, hesaplamaların çok daha hızlı ilerlemesine olanak tanır.
Örnek: 🤗 Transformers ile GPT-2 kullanılarak perplexity hesaplama
Bu süreci GPT-2 ile gösterelim.
from transformers import GPT2LMHeadModel, GPT2TokenizerFast
from accelerate import Accelerator
device = Accelerator().device
model_id = "openai-community/gpt2-large"
model = GPT2LMHeadModel.from_pretrained(model_id).to(device)
tokenizer = GPT2TokenizerFast.from_pretrained(model_id)WikiText-2 veri setini yükleyip karmaşıklığı (perplexity) birkaç farklı kayan pencere (sliding-window) stratejisiyle değerlendireceğiz. Bu veri seti küçük olduğu ve veri seti üzerinde yalnızca bir ileri geçiş (forward pass) yapacağımız için, tüm veri setini belleğe yükleyip kodlayabiliriz.
from datasets import load_dataset
test = load_dataset("Salesforce/wikitext", "wikitext-2-raw-v1", split="test")
encodings = tokenizer("\n\n".join(test["text"]), return_tensors="pt")🤗 Transformers ile input_ids değerlerini modelimize doğrudan labels olarak iletebiliriz; bu durumda her token için ortalama negatif log-olasılık, kayıp (loss) olarak döndürülür. Ancak kayan pencere yaklaşımımızda, her yinelemede modele ilettiğimiz tokenler arasında bir örtüşme vardır. Sadece bağlam olarak ele aldığımız tokenlerin log-olasılıklarının kaybımıza dahil edilmesini istemediğimiz için, bu hedefleri -100 olarak ayarlayarak yok sayılmasını sağlayabiliriz. Aşağıda, 512 adım (stride) ile bunu nasıl yapabileceğimize dair bir örnek verilmiştir. Bu, herhangi bir tokenin koşullu olasılığını hesaplarken modelin bağlam olarak en az 512 tokene sahip olacağı anlamına gelir (koşullanmak için 512 önceki tokenin mevcut olması koşuluyla).
import torch
from tqdm import tqdm
max_length = model.config.n_positions
stride = 512
seq_len = encodings.input_ids.size(1)
nll_sum = 0.0
n_tokens = 0
prev_end_loc = 0
for begin_loc in tqdm(range(0, seq_len, stride)):
end_loc = min(begin_loc + max_length, seq_len)
trg_len = end_loc - prev_end_loc # 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)
# loss is calculated using CrossEntropyLoss which averages over valid labels
# N.B. the model only calculates loss over trg_len - 1 labels, because it internally shifts the labels
# to the left by 1.
neg_log_likelihood = outputs.loss
# Accumulate the total negative log-likelihood and the total number of tokens
num_valid_tokens = (target_ids != -100).sum().item() # number of valid tokens in target_ids
batch_size = target_ids.size(0)
num_loss_tokens = num_valid_tokens - batch_size # subtract batch_size due to internal label shift
nll_sum += neg_log_likelihood * num_loss_tokens
n_tokens += num_loss_tokens
prev_end_loc = end_loc
if end_loc == seq_len:
break
avg_nll = nll_sum / n_tokens # average negative log-likelihood per token
ppl = torch.exp(avg_nll)Bu işlemi, stride uzunluğu maksimum giriş uzunluğuna eşit olacak şekilde çalıştırmak, yukarıda tartıştığımız alt optimum, kayan pencere kullanmayan stratejiye eşdeğerdir. Stride ne kadar küçükse, modelin her tahmini yaparken sahip olacağı bağlam o kadar fazla olur ve genellikle raporlanan perplexity değeri o kadar iyi olur.
Yukarıdaki kodu stride = 1024 ile, yani üst üste binme olmadan çalıştırdığımızda elde edilen PPL değeri 19.44 olur; bu değer, GPT-2 makalesinde raporlanan 19.93 değerine yakındır. stride = 512 kullanarak ve böylece kayan pencere stratejimizi devreye sokarak bu değer 16.44’e düşer. Bu, yalnızca daha elverişli bir skor olmakla kalmaz, aynı zamanda bir dizinin olasılığının gerçek otoregresif ayrışmasına daha yakın bir şekilde hesaplanır.