Transformers documentation

Perplexity model berpanjang tetap

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Perplexity model berpanjang tetap

Perplexity (PPL) adalah salah satu metrik yang paling umum digunakan untuk mengevaluasi model bahasa. Sebelum masuk ke pembahasan lebih lanjut, perlu dicatat bahwa metrik ini secara spesifik berlaku untuk model bahasa klasik (kadang disebut model bahasa autoregresif atau kausal) dan tidak terdefinisi dengan baik untuk model bahasa ber-mask seperti BERT.

Perplexity didefinisikan sebagai rata-rata negatif log-likelihood dari sebuah urutan yang dieksponensiasi. Jika kita memiliki urutan yang ditokenisasi $X = (x_0, x_1, \dots, x_t)$, maka perplexity dari XX adalah, PPL(X)=exp⁡{−1t∑itlog⁡pθ(xi∣x<i)} \text{PPL}(X) = \exp\left\{ -\frac{1}{t}\sum_i^t \log p_\theta (x_i|x_{<i}) \right\}

di mana $\log p\theta (x_i|x{<i})$ adalah log-likelihood dari token ke-i yang dikondisikan pada token-token sebelumnya $x_{<i}$ menurut model kita. Secara intuitif, hal ini dapat dipikirkan sebagai evaluasi kemampuan model untuk memprediksi secara seragam di antara himpunan token yang ditentukan dalam korpus. Penting untuk dicatat bahwa ini berarti prosedur tokenisasi memiliki dampak langsung pada perplexity model, yang harus selalu dipertimbangkan saat membandingkan model yang berbeda.

Hal ini juga ekuivalen dengan eksponensial dari cross-entropy antara data dan prediksi model. Untuk intuisi lebih lanjut tentang perplexity dan hubungannya dengan Bits Per Character (BPC) serta kompresi data, silakan lihat artikel blog yang luar biasa di The Gradient.

Menghitung PPL dengan model berpanjang tetap

Jika kami tidak dibatasi oleh ukuran konteks model, kami akan mengevaluasi perplexity model dengan memfaktorkan sebuah sekuens secara autoregresif dan mengondisikan pada seluruh subsekuens pendahulu di setiap langkah, seperti yang ditunjukkan di bawah ini.

Full decomposition of a sequence with unlimited context length

Namun, saat bekerja dengan model aproksimasi, biasanya terdapat batasan pada jumlah token yang dapat diproses oleh model. Versi terbesar dari GPT-2, misalnya, memiliki panjang tetap 1024 token, sehingga kita tidak dapat menghitung $p\theta(x_t|x{<t})$ secara langsung ketika tt lebih besar dari 1024.

Sebagai gantinya, urutan biasanya dipecah menjadi sub-urutan yang sama dengan ukuran input maksimum model. Jika ukuran input maksimum model adalah $k$, kita kemudian mengaproksimasi kemungkinan token xtx_t dengan hanya mengondisikan pada k−1k-1 token yang mendahuluinya, bukan seluruh konteks. Saat mengevaluasi perplexity model untuk suatu urutan, pendekatan yang menggoda tetapi tidak optimal adalah memecah urutan menjadi potongan-potongan yang tidak tumpang tindih dan menjumlahkan log-kemungkinan terurai dari setiap segmen secara independen.

Suboptimal PPL not taking advantage of full available context

Perhitungan ini cepat karena perplexity setiap segmen dapat dihitung dalam satu forward pass, namun menjadi aproksimasi yang buruk untuk perplexity yang terfaktor penuh dan biasanya menghasilkan PPL yang lebih tinggi (lebih buruk) karena model akan memiliki konteks yang lebih sedikit pada sebagian besar langkah prediksi.

Sebagai gantinya, PPL dari model berpanjang tetap harus dievaluasi dengan strategi sliding-window. Hal ini melibatkan pergeseran berulang pada jendela konteks sehingga model memiliki lebih banyak konteks saat membuat setiap prediksi.

Sliding window PPL taking advantage of all available context

Ini adalah pendekatan yang lebih mendekati dekomposisi sebenarnya dari probabilitas sekuens dan biasanya menghasilkan skor yang lebih menguntungkan. Kekurangannya adalah bahwa hal ini memerlukan satu kali *forward pass* terpisah untuk setiap token dalam korpus. Kompromi praktis yang baik adalah dengan menggunakan *sliding window* berjarak (*strided*), di mana konteks dipindahkan dengan langkah yang lebih besar daripada geser 1 token pada satu waktu. Hal ini memungkinkan komputasi berjalan jauh lebih cepat sambil tetap memberikan model konteks yang besar untuk membuat prediksi pada setiap langkah.

Contoh: Menghitung perplexity dengan GPT-2 di 🤗 Transformers

Mari kita demonstrasikan proses ini dengan GPT-2.

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)

Kita akan memuat dataset WikiText-2 dan mengevaluasi perplexity menggunakan beberapa strategi sliding-window yang berbeda. Karena dataset ini kecil dan kita hanya melakukan satu forward pass pada set tersebut, kita dapat memuat dan meng-encode seluruh dataset di memori.

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")

Dengan 🤗 Transformers, kita cukup meneruskan input_ids sebagai labels ke model kita, dan rata-rata negative log-likelihood untuk setiap token dikembalikan sebagai loss. Namun, dengan pendekatan sliding window kita, terdapat tumpang tindih pada token yang diteruskan ke model pada setiap iterasi. Kita tidak ingin log-likelihood untuk token yang hanya kita perlakukan sebagai konteks ikut dihitung dalam loss kita, sehingga kita dapat mengatur target-token tersebut menjadi -100 agar diabaikan. Berikut adalah contoh cara melakukan ini dengan stride 512. Artinya, model akan memiliki setidaknya 512 token untuk konteks saat menghitung likelihood kondisional dari satu token (asalkan tersedia 512 token pendahulu yang dapat dijadikan dasar kondisional).

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)

Menjalankan ini dengan panjang stride yang sama dengan panjang input maksimum setara dengan strategi non-sliding-window yang tidak optimal yang telah kita bahas di atas. Semakin kecil stride, semakin banyak konteks yang dimiliki model dalam membuat setiap prediksi, dan biasanya perplexity yang dilaporkan akan semakin baik.

Ketika kami menjalankan kode di atas dengan stride = 1024, yaitu tanpa tumpang tindih, PPL yang dihasilkan adalah 19.44, yang kurang lebih sama dengan 19.93 yang dilaporkan dalam makalah GPT-2. Dengan menggunakan stride = 512 dan dengan demikian menerapkan strategi jendela striding kami, nilai ini turun menjadi 16.44. Ini bukan hanya skor yang lebih menguntungkan, tetapi juga dihitung dengan cara yang lebih mendekati dekomposisi autoregresif yang sebenarnya dari kemungkinan sekuens.

Update on GitHub