#
Training loop Lengkap

Sekarang kita akan melihat bagaimana mencapai hasil yang sama seperti di bagian sebelumnya tanpa menggunakan kelas `Trainer`, dengan mengimplementasikan training loop dari nol menggunakan praktik terbaik PyTorch modern. Sekali lagi, kita asumsikan Anda telah menyelesaikan pemrosesan data di bagian 2. Berikut ringkasan singkat yang mencakup semua yang akan Anda butuhkan:

🏗️ Pelatihan dari Nol: Bagian ini membangun dari konten sebelumnya. Untuk panduan menyeluruh mengenai training loop PyTorch dan praktik terbaik, lihat dokumentasi pelatihan 🤗 Transformers dan custom training cookbook.

    from datasets import load_dataset
    from transformers import AutoTokenizer, DataCollatorWithPadding

    raw_datasets = load_dataset("glue", "mrpc")
    checkpoint = "bert-base-uncased"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
    data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

###
Persiapan untuk Pelatihan

Sebelum menulis training loop, kita perlu mendefinisikan beberapa objek. Yang pertama adalah dataloader yang akan kita gunakan untuk melakukan iterasi per batch. Tapi sebelum itu, kita harus melakukan sedikit pasca-pemrosesan pada `tokenized_datasets` untuk menangani beberapa hal yang sebelumnya dilakukan otomatis oleh `Trainer`. Secara spesifik, kita perlu:

- Menghapus kolom yang tidak dibutuhkan model (seperti `sentence1` dan `sentence2`).
- Mengganti nama kolom `label` menjadi `labels` (karena model mengharapkan argumen bernama `labels`).
- Mengatur format dataset agar mengembalikan tensor PyTorch, bukan list biasa.

Langkah-langkah ini dapat dilakukan seperti berikut:

    tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
    tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
    tokenized_datasets.set_format("torch")
    tokenized_datasets["train"].column_names

Kita bisa periksa bahwa hasil akhirnya hanya memiliki kolom yang dapat diterima oleh model:

    ["attention_mask", "input_ids", "labels", "token_type_ids"]

Sekarang kita bisa mendefinisikan dataloader:

    from torch.utils.data import DataLoader

    train_dataloader = DataLoader(
        tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
    )
    eval_dataloader = DataLoader(
        tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
    )

Untuk memastikan tidak ada kesalahan, kita bisa lihat isi satu batch:

    for batch in train_dataloader:
        break
    {k: v.shape for k, v in batch.items()}

    {
        "attention_mask": torch.Size([8, 65]),
        "input_ids": torch.Size([8, 65]),
        "labels": torch.Size([8]),
        "token_type_ids": torch.Size([8, 65]),
    }

Catatan: bentuk tensor (shape) mungkin akan sedikit berbeda karena `shuffle=True` dan padding dilakukan hingga panjang maksimum dalam setiap batch.

Sekarang setelah kita benar-benar selesai dengan praproses data (sebuah tujuan yang memuaskan namun sering kali sulit dicapai bagi praktisi ML), mari kita beralih ke model. Kita menginisialisasinya persis seperti yang kita lakukan di bagian sebelumnya:

    from transformers import AutoModelForSequenceClassification

    model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

Untuk memastikan bahwa semuanya berjalan lancar selama pelatihan, kita memasukkan batch kita ke dalam model ini:

    outputs = model(**batch)
    print(outputs.loss, outputs.logits.shape)

    tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])

Semua model 🤗 Transformers akan mengembalikan nilai loss ketika `labels` diberikan, dan kita juga akan mendapatkan logits (dua untuk setiap input dalam batch kita, sehingga menghasilkan tensor berukuran 8 x 2).

Sekarang kita hampir siap menulis training loop! Yang tersisa hanyalah optimizer dan scheduler. Kita akan meniru pengaturan default dari `Trainer`. Optimizer yang digunakan adalah `AdamW`:

    from torch.optim import AdamW

    optimizer = AdamW(model.parameters(), lr=5e-5)

💡 Tips Optimasi Modern: Untuk performa lebih baik, Anda bisa coba:

- AdamW dengan weight decay: `AdamW(model.parameters(), lr=5e-5, weight_decay=0.01)`
- Adam 8-bit: Gunakan `bitsandbytes` untuk optimasi yang hemat memori
- Learning rate berbeda: Biasanya 1e-5 hingga 3e-5 lebih baik untuk model besar

🚀 Sumber Optimasi: Pelajari lebih lanjut di panduan optimasi 🤗 Transformers

Akhirnya, scheduler laju pembelajaran (learning rate scheduler) yang digunakan secara default adalah penurunan linier dari nilai maksimum (5e-5) ke 0. Untuk mendefinisikannya dengan benar, kita perlu mengetahui jumlah langkah pelatihan (training steps) yang akan dilakukan, yaitu jumlah epoch yang ingin dijalankan dikalikan dengan jumlah batch pelatihan (yang merupakan panjang dari dataloader pelatihan kita). `Trainer` menggunakan tiga epoch secara default, jadi kita akan mengikuti pengaturan tersebut:

    from transformers import get_scheduler

    num_epochs = 3
    num_training_steps = num_epochs * len(train_dataloader)
    lr_scheduler = get_scheduler(
        "linear",
        optimizer=optimizer,
        num_warmup_steps=0,
        num_training_steps=num_training_steps,
    )
    print(num_training_steps)

    1377

###
Training Loop

Satu hal terakhir: kita akan ingin menggunakan GPU jika tersedia (karena pada CPU, pelatihan bisa memakan waktu beberapa jam dibandingkan hanya beberapa menit di GPU). Untuk melakukan ini, kita mendefinisikan sebuah `device` tempat kita akan menempatkan model dan batch kita:

    import torch

    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    model.to(device)
    device

    device(type="cuda")

Sekarang kita siap untuk melakukan pelatihan! Untuk mengetahui sejauh mana proses pelatihan telah berlangsung, kita menambahkan progress bar berdasarkan jumlah langkah pelatihan kita, menggunakan pustaka `tqdm`:

    from tqdm.auto import tqdm

    progress_bar = tqdm(range(num_training_steps))

    model.train()
    for epoch in range(num_epochs):
        for batch in train_dataloader:
            batch = {k: v.to(device) for k, v in batch.items()}
            outputs = model(**batch)
            loss = outputs.loss
            loss.backward()

            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            progress_bar.update(1)

💡 Optimasi Pelatihan Modern: Untuk efisiensi pelatihan yang lebih baik, pertimbangkan:

- Gradient Clipping: Tambahkan `torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)` sebelum `optimizer.step()`
- Presisi Campuran (Mixed Precision): Gunakan `torch.cuda.amp.autocast()` dan `GradScaler`
- Gradient Accumulation: Kumpulkan gradien dari beberapa batch sebelum melakukan update
- Checkpointing: Simpan model secara berkala untuk mencegah kehilangan data jika pelatihan terputus

🔧 Panduan Implementasi: Lihat contoh di efisiensi pelatihan 🤗 Transformers dan daftar optimizer

Anda dapat melihat bahwa inti dari training loop ini mirip seperti yang diperkenalkan di awal. Karena kita belum menambahkan evaluasi atau logging, loop ini tidak akan memberi tahu performa model. Untuk itu, kita perlu menambahkan evaluation loop.

###
Loop Evaluasi

Seperti yang telah kita lakukan sebelumnya, kita akan menggunakan metrik dari pustaka 🤗 Evaluate. Kita sudah melihat metode `metric.compute()`, tetapi metrik sebenarnya bisa mengakumulasi batch secara bertahap menggunakan metode `add_batch()`. Setelah semua batch dikumpulkan, kita bisa menghitung hasil akhirnya dengan `metric.compute()`. Berikut implementasi lengkap dalam loop evaluasi:

📊 Praktik Terbaik Evaluasi: Untuk strategi evaluasi dan metrik yang lebih canggih, jelajahi dokumentasi 🤗 Evaluate dan evaluation cookbook komprehensif.

    import evaluate

    metric = evaluate.load("glue", "mrpc")
    model.eval()
    for batch in eval_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        with torch.no_grad():
            outputs = model(**batch)

        logits = outputs.logits
        predictions = torch.argmax(logits, dim=-1)
        metric.add_batch(predictions=predictions, references=batch["labels"])

    metric.compute()

    {'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}

Hasil Anda mungkin sedikit berbeda karena inisialisasi acak pada kepala model dan shuffling data, tetapi seharusnya berada pada kisaran yang sama.

✏️ Coba Sendiri! Modifikasi training loop sebelumnya untuk melakukan fine-tuning pada dataset SST-2.

###
Percepat Training Loop Anda dengan 🤗 Accelerate

Loop pelatihan yang kita definisikan sebelumnya bekerja dengan baik pada satu CPU atau GPU. Namun, dengan menggunakan pustaka 🤗 Accelerate, hanya dengan beberapa penyesuaian kita dapat mengaktifkan pelatihan terdistribusi pada beberapa GPU atau TPU. 🤗 Accelerate secara otomatis menangani kompleksitas pelatihan terdistribusi, mixed precision, dan penempatan perangkat (device placement). Dimulai dari pembuatan dataloader untuk pelatihan dan validasi, berikut adalah tampilan loop pelatihan manual kita:

⚡ Pendalaman Accelerate: Pelajari semua tentang pelatihan terdistribusi, presisi campuran, dan optimasi perangkat keras di dokumentasi 🤗 Accelerate serta contoh praktis di dokumentasi Transformers.

    from accelerate import Accelerator
    from torch.optim import AdamW
    from transformers import AutoModelForSequenceClassification, get_scheduler

    accelerator = Accelerator()

    model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
    optimizer = AdamW(model.parameters(), lr=3e-5)

    train_dl, eval_dl, model, optimizer = accelerator.prepare(
        train_dataloader, eval_dataloader, model, optimizer
    )

    num_epochs = 3
    num_training_steps = num_epochs * len(train_dl)
    lr_scheduler = get_scheduler(
        "linear",
        optimizer=optimizer,
        num_warmup_steps=0,
        num_training_steps=num_training_steps,
    )

    progress_bar = tqdm(range(num_training_steps))

    model.train()
    for epoch in range(num_epochs):
        for batch in train_dl:
            outputs = model(**batch)
            loss = outputs.loss
            accelerator.backward(loss)

            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            progress_bar.update(1)

Langkah pertama adalah mengimpor dan membuat objek `Accelerator`, yang akan secara otomatis mengatur konfigurasi pelatihan terdistribusi sesuai lingkungan Anda. 🤗 Accelerate akan menangani semua penempatan perangkat, jadi Anda tidak perlu lagi menggunakan `model.to(device)` — meskipun jika ingin, Anda bisa ganti dengan `accelerator.device`.

Kemudian, sebagian besar pekerjaan dilakukan pada baris yang mengirimkan dataloader, model, dan optimizer ke `accelerator.prepare()`. Baris ini akan membungkus objek-objek tersebut dalam kontainer yang sesuai untuk memastikan bahwa pelatihan terdistribusi Anda berjalan sebagaimana mestinya. Perubahan lain yang perlu dilakukan adalah menghapus baris yang memindahkan batch ke `device` (jika Anda ingin tetap menggunakannya, Anda bisa menggantinya dengan `accelerator.device`) dan mengganti `loss.backward()` dengan `accelerator.backward(loss)`.

⚠️ Untuk mendapatkan kecepatan maksimum pada Cloud TPU, sebaiknya Anda melakukan padding ke panjang tetap dengan `padding="max_length"` dan `max_length` saat menggunakan tokenizer.

Jika ingin mencoba langsung, berikut versi lengkap training loop menggunakan 🤗 Accelerate:

    from accelerate import Accelerator
    from torch.optim import AdamW
    from transformers import AutoModelForSequenceClassification, get_scheduler

    accelerator = Accelerator()

    model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
    optimizer = AdamW(model.parameters(), lr=3e-5)

    train_dl, eval_dl, model, optimizer = accelerator.prepare(
        train_dataloader, eval_dataloader, model, optimizer
    )

    num_epochs = 3
    num_training_steps = num_epochs * len(train_dl)
    lr_scheduler = get_scheduler(
        "linear",
        optimizer=optimizer,
        num_warmup_steps=0,
        num_training_steps=num_training_steps,
    )

    progress_bar = tqdm(range(num_training_steps))

    model.train()
    for epoch in range(num_epochs):
        for batch in train_dl:
            outputs = model(**batch)
            loss = outputs.loss
            accelerator.backward(loss)

            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            progress_bar.update(1)

Meletakkan ini dalam sebuah skrip `train.py` akan membuat skrip tersebut dapat dijalankan pada jenis setup terdistribusi apa pun. Untuk mencobanya di setup terdistribusi Anda, jalankan perintah berikut:

    accelerate config

yang akan meminta Anda menjawab beberapa pertanyaan dan menyimpan jawaban Anda dalam sebuah file konfigurasi yang digunakan oleh perintah berikut:

    accelerate launch train.py

yang akan menjalankan pelatihan terdistribusi.

Jika Anda ingin mencoba ini di sebuah Notebook (misalnya, untuk menguji dengan TPU di Colab), cukup tempelkan kodenya ke dalam sebuah `training_function()` dan jalankan sel terakhir dengan:

    from accelerate import notebook_launcher

    notebook_launcher(training_function)

Contoh lainnya tersedia di repo 🤗 Accelerate.

🌐 Pelatihan Terdistribusi: Untuk panduan menyeluruh tentang pelatihan multi-GPU dan multi-node, lihat panduan pelatihan terdistribusi 🤗 Transformers dan scaling training cookbook.

###
Langkah Selanjutnya dan Praktik Terbaik

Setelah Anda mempelajari cara melakukan pelatihan dari awal, berikut beberapa pertimbangan tambahan untuk digunakan dalam produksi:

Evaluasi Model: Jangan hanya bergantung pada akurasi. Gunakan pustaka 🤗 Evaluate untuk evaluasi yang komprehensif.

Tuning Hyperparameter: Gunakan pustaka seperti Optuna atau Ray Tune untuk optimasi sistematis.

Pemantauan Model: Pantau metrik pelatihan, kurva pembelajaran, dan performa validasi.

Berbagi Model: Setelah pelatihan selesai, unggah ke Hugging Face Hub agar bisa digunakan komunitas.

Efisiensi: Untuk model besar, gunakan teknik seperti gradient checkpointing, fine-tuning parameter-efisien (LoRA, AdaLoRA), atau metode kuantisasi.

Ini mengakhiri pembahasan mendalam kita tentang fine-tuning menggunakan training loop kustom. Keterampilan yang Anda pelajari di sini akan sangat berguna ketika Anda butuh kontrol penuh atas proses pelatihan atau ingin menerapkan logika pelatihan khusus yang tidak didukung oleh API `Trainer`.

##
Kuis Bagian

Uji pemahaman Anda tentang custom training loop dan teknik pelatihan lanjutan:

###
1. Apa perbedaan utama antara optimizer Adam dan AdamW?

AdamW menggunakan jadwal laju pembelajaran yang berbeda. AdamW menyertakan regularisasi weight decay yang dipisahkan. AdamW hanya bekerja dengan model transformer. AdamW membutuhkan lebih sedikit memori dibanding Adam.

###
2. Apa urutan operasi yang benar dalam training loop?

Forward pass → Backward pass → Optimizer step → Zero gradients Forward pass → Backward pass → Optimizer step → Scheduler step → Zero gradients Zero gradients → Forward pass → Optimizer step → Backward pass Forward pass → Zero gradients → Backward pass → Optimizer step

###
3. Apa fungsi utama dari pustaka 🤗 Accelerate?

Membuat model Anda lebih cepat dengan mengoptimalkan forward pass. Secara otomatis memilih hyperparameter terbaik. Memungkinkan pelatihan terdistribusi di banyak GPU/TPU dengan perubahan kode minimal. Mengkonversi model ke framework lain seperti TensorFlow.

###
4. Mengapa kita memindahkan batch ke device dalam training loop?

Untuk mempercepat pelatihan. Karena model dan data harus berada di perangkat (CPU/GPU) yang sama untuk komputasi. Untuk menghemat memori. Karena itu diwajibkan oleh DataLoader.

###
5. Apa yang dilakukan model.eval() sebelum evaluasi?

Membekukan parameter model agar tidak bisa diperbarui. Mengubah perilaku layer seperti dropout dan batch normalization agar sesuai untuk inferensi. Mengaktifkan perhitungan gradien untuk metrik evaluasi. Secara otomatis menghitung metrik evaluasi.

###
6. Apa tujuan penggunaan torch.no_grad() saat evaluasi?

Untuk mencegah model membuat prediksi. Untuk menghemat memori dan mempercepat komputasi dengan menonaktifkan pelacakan gradien. Untuk mengaktifkan mode evaluasi pada model. Untuk memastikan hasil yang konsisten.

###
7. Apa yang berubah ketika Anda menggunakan 🤗 Accelerate dalam training loop?

Anda harus menulis ulang seluruh training loop dari awal. Anda membungkus objek penting dengan `accelerator.prepare()` dan menggunakan `accelerator.backward()`. Anda harus menentukan jumlah GPU secara eksplisit dalam kode. Anda harus menggunakan optimizer dan scheduler yang berbeda.

💡 Poin Penting:

- Training loop manual memberi Anda kontrol penuh, tapi perlu memahami urutan operasi yang tepat: forward → backward → optimizer step → scheduler step → zero gradients
- Gunakan AdamW dengan regularisasi weight decay sebagai optimizer utama untuk model transformer
- Selalu gunakan `model.eval()` dan `torch.no_grad()` saat evaluasi untuk hasil yang benar dan efisien
- 🤗 Accelerate memungkinkan pelatihan terdistribusi dengan perubahan kode minimal
- Manajemen device sangat penting untuk operasi PyTorch yang sukses
- Teknik modern seperti mixed precision, akumulasi gradien, dan gradient clipping dapat meningkatkan efisiensi pelatihan secara signifikan

< > Update on GitHub