Thursday

19-06-2025 Vol 19

πŸš€ Training a GPT Model from Scratch with PyTorch (Tokenizer + Transformer + Inference)

πŸš€ Melatih Model GPT dari Awal dengan PyTorch: Tokenizer + Transformer + Inferensi

Apakah Anda ingin membangun model GPT Anda sendiri dari awal? Dalam postingan blog ini, kita akan mempelajari langkah demi langkah cara melatih model GPT (Generative Pre-trained Transformer) menggunakan PyTorch. Kami akan membahas tokenisasi, arsitektur Transformer, dan inferensi. Panduan komprehensif ini ditujukan bagi para pemula dan praktisi berpengalaman yang ingin mendapatkan pemahaman mendalam tentang cara kerja model GPT dan menerapkannya dalam PyTorch.

Mengapa Melatih Model GPT dari Awal?

Meskipun ada banyak model GPT yang sudah dilatih sebelumnya yang tersedia, melatih model dari awal menawarkan beberapa keuntungan:

  1. Kustomisasi: Anda dapat menyesuaikan model dengan kumpulan data dan tugas khusus Anda.
  2. Pemahaman: Anda mendapatkan pemahaman mendalam tentang arsitektur dan pelatihan Transformer.
  3. Kontrol: Anda memiliki kontrol penuh atas arsitektur model, proses pelatihan, dan implementasi.
  4. Optimasi: Anda dapat mengoptimalkan model untuk sumber daya dan kebutuhan perangkat keras tertentu.
  5. Pembelajaran: Proses ini adalah pengalaman belajar yang luar biasa yang akan meningkatkan pemahaman Anda tentang pembelajaran mendalam.

Prasyarat

Sebelum kita mulai, pastikan Anda memiliki prasyarat berikut:

  1. Python: Pengetahuan dasar tentang Python diperlukan.
  2. PyTorch: Anda harus menginstal PyTorch. Anda dapat menginstalnya dari situs web PyTorch.
  3. Pemahaman Pembelajaran Mendalam: Familiaritas dengan konsep pembelajaran mendalam seperti backpropagation, penurunan gradien, dan jaringan saraf dianjurkan.
  4. Akses GPU (Opsional): GPU akan mempercepat proses pelatihan secara signifikan.

Kerangka Kerja Posting Blog

  1. Pengantar
    • Mengapa melatih model GPT dari awal?
    • Prasyarat
  2. Langkah 1: Persiapan Data
    • Mengumpulkan dan membersihkan data
    • Membuat vocabulary (kosakata)
  3. Langkah 2: Implementasi Tokenizer
    • Subword Tokenization (Byte-Pair Encoding – BPE)
    • Implementasi Tokenizer dengan Python
  4. Langkah 3: Membangun Arsitektur Transformer
    • Encoder dan Decoder (hanya Decoder untuk GPT)
    • Multi-Head Attention
    • Feed Forward Network
    • Layer Normalization dan Residual Connections
    • Implementasi dengan PyTorch
  5. Langkah 4: Pelatihan Model
    • Mempersiapkan Data untuk Pelatihan (Batching)
    • Loss Function (Cross-Entropy)
    • Optimizer (AdamW)
    • Training Loop
  6. Langkah 5: Inferensi
    • Generasi Teks
    • Sampling Strategies (Greedy, Temperature Sampling)
    • Implementasi Inferensi
  7. Langkah 6: Evaluasi dan Tuning
    • Perplexity
    • Evaluasi Kualitatif
    • Hyperparameter Tuning
  8. Kesimpulan
    • Ringkasan
    • Langkah Selanjutnya

Langkah 1: Persiapan Data

Persiapan data adalah langkah penting dalam melatih model GPT. Kualitas data Anda akan secara langsung memengaruhi kinerja model Anda. Proses ini melibatkan pengumpulan, pembersihan, dan persiapan data agar sesuai untuk pelatihan.

Mengumpulkan dan Membersihkan Data

Sumber data dapat sangat bervariasi tergantung pada tujuan Anda. Beberapa sumber umum meliputi:

  • Teks berbasis web: Wikipedia, Common Crawl, artikel berita.
  • Buku: Project Gutenberg, perpustakaan digital.
  • Kumpulan data khusus: Data dari aplikasi tertentu, misalnya, transkrip obrolan pelanggan, kode, atau teks ilmiah.

Setelah Anda mengumpulkan data Anda, langkah selanjutnya adalah membersihkannya. Pembersihan mungkin melibatkan:

  • Menghapus HTML atau markup lain: Hilangkan semua tag HTML dan format yang tidak perlu.
  • Menangani karakter khusus: Konversi atau hapus karakter khusus yang mungkin menyebabkan masalah.
  • Koreksi kesalahan ejaan: Perbaiki kesalahan ejaan untuk meningkatkan kualitas data.
  • Menghapus teks yang tidak relevan: Hilangkan teks yang tidak sesuai dengan tujuan Anda.

Contoh membersihkan data dengan Python:


    import re

    def clean_text(text):
        # Remove HTML tags
        text = re.sub(r'<[^>]+>', '', text)
        # Remove special characters
        text = re.sub(r'[^a-zA-Z0-9\s]', '', text)
        # Convert to lowercase
        text = text.lower()
        return text

    # Example usage
    text = "

This is a Sample Text!

With some HTML tags.

" cleaned_text = clean_text(text) print(cleaned_text) # Output: this is a sample text with some html tags

Membuat Vocabulary (Kosakata)

Vocabulary adalah daftar semua token unik (kata-kata atau subkata) dalam kumpulan data Anda. Ini adalah komponen penting dari model GPT, karena ia memetakan token ke indeks numerik, yang digunakan model untuk memproses teks.

  1. Menghitung Frekuensi Token: Hitung frekuensi setiap token dalam kumpulan data Anda.
  2. Membuat Vocabulary: Pilih sejumlah token yang paling sering muncul untuk disertakan dalam vocabulary Anda.
  3. Menambahkan Token Khusus: Tambahkan token khusus seperti:
    • <PAD>: Untuk padding urutan agar memiliki panjang yang sama.
    • <UNK>: Untuk token yang tidak ada dalam vocabulary.
    • <BOS>: Awal dari urutan.
    • <EOS>: Akhir dari urutan.
  4. Membuat Pemetaan: Buat pemetaan antara token dan indeks mereka (token2id) dan indeks ke token (id2token).

Contoh membuat vocabulary dengan Python:


    from collections import Counter

    def build_vocabulary(text, vocab_size=10000):
        # Split text into tokens
        tokens = text.split()
        # Count token frequencies
        token_counts = Counter(tokens)
        # Get the most common tokens
        most_common_tokens = [token for token, count in token_counts.most_common(vocab_size - 4)]
        # Add special tokens
        vocabulary = ['<PAD>', '<UNK>', '<BOS>', '<EOS>'] + most_common_tokens
        # Create token to index mapping
        token2id = {token: idx for idx, token in enumerate(vocabulary)}
        # Create index to token mapping
        id2token = {idx: token for idx, token in enumerate(vocabulary)}
        return vocabulary, token2id, id2token

    # Example usage
    text = "this is a sample text this is a sample text this is a sample"
    vocabulary, token2id, id2token = build_vocabulary(text)
    print("Vocabulary:", vocabulary)
    print("Token to ID:", token2id)
    print("ID to Token:", id2token)
  

Langkah 2: Implementasi Tokenizer

Tokenizer bertugas untuk memecah teks mentah menjadi token yang dapat diproses oleh model. Teknik tokenisasi umum untuk model GPT adalah Subword Tokenization, terutama Byte-Pair Encoding (BPE).

Subword Tokenization (Byte-Pair Encoding – BPE)

BPE adalah algoritma tokenisasi data yang mempelajari cara menggabungkan karakter menjadi token. Ia memulai dengan vocabulary dasar karakter dan secara iteratif menggabungkan pasangan karakter yang paling sering terjadi hingga vocabulary mencapai ukuran yang diinginkan.

  1. Inisialisasi: Mulai dengan vocabulary dasar karakter.
  2. Iterasi:
    • Hitung frekuensi pasangan token yang berdekatan.
    • Gabungkan pasangan yang paling sering terjadi ke dalam token baru.
    • Tambahkan token baru ke vocabulary.
  3. Ulangi: Ulangi langkah iterasi hingga vocabulary mencapai ukuran yang diinginkan.

Contoh BPE:

Misalkan kita memiliki teks “low lower newest widest” dan kita ingin membuat vocabulary dengan ukuran 10.

  1. Inisialisasi: Vocabulary awal adalah {'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd'}
  2. Iterasi 1: Pasangan yang paling sering terjadi adalah ‘e’ ‘s’ (muncul dua kali). Gabungkan menjadi ‘es’. Vocabulary menjadi {'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd', 'es'}
  3. Iterasi 2: Pasangan yang paling sering terjadi adalah ‘es’ ‘t’ (muncul dua kali). Gabungkan menjadi ‘est’. Vocabulary menjadi {'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd', 'es', 'est'}
  4. Lanjutkan: Ulangi proses hingga ukuran vocabulary mencapai 10 (atau ukuran yang diinginkan).

Implementasi Tokenizer dengan Python

Implementasi BPE dari awal bisa jadi kompleks. Untungnya, ada pustaka yang tersedia yang menyederhanakan prosesnya, seperti tokenizers dari Hugging Face.


    from tokenizers import ByteLevelBPETokenizer

    # Initialize a tokenizer
    tokenizer = ByteLevelBPETokenizer()

    # Customize training
    tokenizer.train(
        files=["path/to/your/data.txt"],
        vocab_size=10000,
        min_frequency=2,
        special_tokens=["<PAD>", "<UNK>", "<BOS>", "<EOS>"]
    )

    # Save the tokenizer
    tokenizer.save_model("path/to/save/tokenizer")

    # Load the tokenizer
    from tokenizers.implementations import ByteLevelBPETokenizer
    tokenizer = ByteLevelBPETokenizer(
        "path/to/save/tokenizer/vocab.json",
        "path/to/save/tokenizer/merges.txt"
    )

    # Example usage
    encoded = tokenizer.encode("This is a sample text.")
    print("Encoded:", encoded.ids)
    decoded = tokenizer.decode(encoded.ids)
    print("Decoded:", decoded)
  

Langkah 3: Membangun Arsitektur Transformer

Arsitektur Transformer adalah inti dari model GPT. Arsitektur ini didasarkan pada mekanisme self-attention, yang memungkinkannya untuk mempertimbangkan hubungan antara semua kata dalam urutan.

Encoder dan Decoder (hanya Decoder untuk GPT)

Arsitektur Transformer asli memiliki komponen encoder dan decoder. Namun, model GPT hanya menggunakan tumpukan decoder Transformer. Decoder bertugas untuk menghasilkan teks, dengan mempertimbangkan input dan konteks yang sebelumnya dihasilkan.

Multi-Head Attention

Multi-Head Attention memungkinkan model untuk menghadiri bagian yang berbeda dari input secara bersamaan. Ini melibatkan proyeksi input ke dalam beberapa ruang representasi (head) dan menghitung perhatian secara paralel di setiap ruang.

Rumus untuk Attention:

Attention(Q, K, V) = softmax((Q * K^T) / sqrt(d_k)) * V

  • Q: Queries
  • K: Keys
  • V: Values
  • d_k: Dimensi dari keys

Multi-Head Attention melakukan proses ini beberapa kali (dengan “heads” yang berbeda) dan menggabungkan hasilnya.

Feed Forward Network

Feed Forward Network adalah jaringan saraf feed-forward yang diterapkan secara independen ke setiap posisi. Ini biasanya terdiri dari dua lapisan linier dengan fungsi aktivasi ReLU di antaranya.

Layer Normalization dan Residual Connections

Layer Normalization menstabilkan proses pelatihan dan mempercepat konvergensi. Residual connections (skip connections) membantu mengatasi masalah gradien menghilang dengan memungkinkan gradien untuk mengalir langsung melalui jaringan.

Implementasi dengan PyTorch

Berikut adalah implementasi PyTorch dari blok Decoder Transformer:


    import torch
    import torch.nn as nn
    import torch.nn.functional as F

    class MultiHeadAttention(nn.Module):
        def __init__(self, d_model, num_heads):
            super(MultiHeadAttention, self).__init__()
            self.d_model = d_model
            self.num_heads = num_heads
            self.d_k = d_model // num_heads
            self.W_q = nn.Linear(d_model, d_model)
            self.W_k = nn.Linear(d_model, d_model)
            self.W_v = nn.Linear(d_model, d_model)
            self.W_o = nn.Linear(d_model, d_model)

        def scaled_dot_product_attention(self, Q, K, V, mask=None):
            attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))
            if mask is not None:
                attn_scores = attn_scores.masked_fill(mask == 0, float('-inf'))
            attn_probs = F.softmax(attn_scores, dim=-1)
            output = torch.matmul(attn_probs, V)
            return output

        def split_heads(self, x):
            batch_size, seq_length, d_model = x.size()
            return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)

        def combine_heads(self, x):
            batch_size, _, seq_length, d_k = x.size()
            return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)

        def forward(self, Q, K, V, mask=None):
            Q = self.W_q(Q)
            K = self.W_k(K)
            V = self.W_v(V)

            Q = self.split_heads(Q)
            K = self.split_heads(K)
            V = self.split_heads(V)

            attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
            output = self.combine_heads(attn_output)
            output = self.W_o(output)
            return output

    class FeedForwardNetwork(nn.Module):
        def __init__(self, d_model, d_ff):
            super(FeedForwardNetwork, self).__init__()
            self.linear1 = nn.Linear(d_model, d_ff)
            self.linear2 = nn.Linear(d_ff, d_model)

        def forward(self, x):
            x = F.relu(self.linear1(x))
            x = self.linear2(x)
            return x

    class TransformerBlock(nn.Module):
        def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
            super(TransformerBlock, self).__init__()
            self.attn = MultiHeadAttention(d_model, num_heads)
            self.feed_forward = FeedForwardNetwork(d_model, d_ff)
            self.layer_norm1 = nn.LayerNorm(d_model)
            self.layer_norm2 = nn.LayerNorm(d_model)
            self.dropout = nn.Dropout(dropout)

        def forward(self, x, mask=None):
            attn_output = self.attn(x, x, x, mask)
            x = self.layer_norm1(x + self.dropout(attn_output))
            ff_output = self.feed_forward(x)
            x = self.layer_norm2(x + self.dropout(ff_output))
            return x

    class GPT(nn.Module):
        def __init__(self, vocab_size, d_model, num_layers, num_heads, d_ff, max_seq_length, dropout=0.1):
            super(GPT, self).__init__()
            self.embedding = nn.Embedding(vocab_size, d_model)
            self.positional_encoding = torch.zeros(max_seq_length, d_model)
            pos = torch.arange(0, max_seq_length).unsqueeze(1)
            div_term = torch.exp(torch.arange(0, d_model, 2) * (-torch.log(torch.tensor(10000.0)) / d_model))
            self.positional_encoding[:, 0::2] = torch.sin(pos * div_term)
            self.positional_encoding[:, 1::2] = torch.cos(pos * div_term)
            self.positional_encoding = self.positional_encoding.unsqueeze(0)
            self.transformer_blocks = nn.ModuleList([
                TransformerBlock(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)
            ])
            self.layer_norm = nn.LayerNorm(d_model)
            self.linear = nn.Linear(d_model, vocab_size)

        def forward(self, x, mask=None):
            batch_size, seq_length = x.size()
            x = self.embedding(x)
            x = x + self.positional_encoding[:, :seq_length, :]
            for block in self.transformer_blocks:
                x = block(x, mask)
            x = self.layer_norm(x)
            x = self.linear(x)
            return x
  

Langkah 4: Pelatihan Model

Pelatihan model GPT melibatkan pemberian makan data ke model, menghitung loss, dan memperbarui bobot model menggunakan backpropagation.

Mempersiapkan Data untuk Pelatihan (Batching)

Data harus di batch agar dapat dilatih secara efisien. Batching melibatkan pengelompokan beberapa urutan menjadi satu tensor. Karena urutan mungkin memiliki panjang yang berbeda, padding digunakan untuk memastikan bahwa semua urutan dalam batch memiliki panjang yang sama.

Loss Function (Cross-Entropy)

Loss function mengukur perbedaan antara keluaran model dan target yang benar. Untuk model GPT, loss function yang umum digunakan adalah Cross-Entropy Loss.

Optimizer (AdamW)

Optimizer memperbarui bobot model berdasarkan gradien loss function. AdamW adalah optimizer yang umum digunakan yang sering memberikan hasil yang baik.

Training Loop

Training loop melibatkan iterasi atas data pelatihan, menghitung loss, dan memperbarui bobot model.


    import torch.optim as optim
    from torch.utils.data import DataLoader, Dataset

    # Assume you have a list of tokenized sequences called 'tokenized_data'
    # and token2id, id2token from previous steps.

    class TextDataset(Dataset):
        def __init__(self, tokenized_data, max_seq_length):
            self.tokenized_data = tokenized_data
            self.max_seq_length = max_seq_length

        def __len__(self):
            return len(self.tokenized_data)

        def __getitem__(self, idx):
            sequence = self.tokenized_data[idx]
            # Pad or truncate sequence
            if len(sequence) < self.max_seq_length:
                sequence = sequence + [token2id['<PAD>']] * (self.max_seq_length - len(sequence))
            else:
                sequence = sequence[:self.max_seq_length]

            sequence = torch.tensor(sequence)
            return sequence

    # Hyperparameters
    vocab_size = len(vocabulary)
    d_model = 256
    num_layers = 4
    num_heads = 8
    d_ff = 1024
    max_seq_length = 128
    dropout = 0.1
    batch_size = 32
    learning_rate = 0.0001
    num_epochs = 10

    # Create the model
    model = GPT(vocab_size, d_model, num_layers, num_heads, d_ff, max_seq_length, dropout)

    # Create the dataset and dataloader
    dataset = TextDataset(tokenized_data, max_seq_length)
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

    # Define the loss function and optimizer
    criterion = nn.CrossEntropyLoss(ignore_index=token2id['<PAD>'])
    optimizer = optim.AdamW(model.parameters(), lr=learning_rate)

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

    for epoch in range(num_epochs):
        for batch in dataloader:
            batch = batch.to(device)

            # Prepare inputs and targets
            inputs = batch[:, :-1]
            targets = batch[:, 1:]

            # Forward pass
            outputs = model(inputs)

            # Compute loss
            loss = criterion(outputs.reshape(-1, vocab_size), targets.reshape(-1))

            # Backward pass and optimization
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

    print("Training finished!")
  

Langkah 5: Inferensi

Inferensi melibatkan penggunaan model terlatih untuk menghasilkan teks. Proses ini melibatkan pemberian makan input ke model dan menghasilkan token secara iteratif hingga kondisi penghentian terpenuhi.

Generasi Teks

Generasi teks dilakukan secara iteratif. Dimulai dengan token awal (misalnya, <BOS>), model memprediksi token berikutnya, yang ditambahkan ke urutan yang dihasilkan. Proses ini diulangi hingga token <EOS> dihasilkan atau panjang maksimum tercapai.

Sampling Strategies (Greedy, Temperature Sampling)

Sampling strategies memengaruhi keberagaman dan kualitas teks yang dihasilkan.

  • Greedy Sampling: Pilih token dengan probabilitas tertinggi di setiap langkah. Ini menghasilkan teks yang deterministik dan sering kali berulang.
  • Temperature Sampling: Bagi logit dari prediksi model dengan suhu (temperature) dan kemudian menerapkan softmax. Temperatur yang lebih tinggi meningkatkan keacakan, sedangkan temperatur yang lebih rendah membuat prediksi lebih deterministik.

Implementasi Inferensi


    def generate_text(model, tokenizer, start_sequence, max_length=50, temperature=1.0):
        model.eval()
        device = next(model.parameters()).device

        input_ids = tokenizer.encode(start_sequence).ids
        input_ids = torch.tensor(input_ids).unsqueeze(0).to(device)

        generated_sequence = input_ids.tolist()[0]

        with torch.no_grad():
            for _ in range(max_length):
                outputs = model(input_ids)
                # Apply temperature
                logits = outputs[:, -1, :] / temperature
                probs = F.softmax(logits, dim=-1)

                # Sample from the distribution
                next_token = torch.multinomial(probs, num_samples=1).item()

                generated_sequence.append(next_token)
                input_ids = torch.tensor([generated_sequence]).to(device)

                if next_token == tokenizer.token_to_id('<EOS>'):
                    break

        # Decode the generated sequence
        decoded_sequence = tokenizer.decode(generated_sequence)
        return decoded_sequence

    # Example Usage:
    start_sequence = "<BOS> This is"
    generated_text = generate_text(model, tokenizer, start_sequence)
    print(f"Generated text: {generated_text}")
  

Langkah 6: Evaluasi dan Tuning

Evaluasi dan tuning sangat penting untuk meningkatkan kinerja model GPT.

Perplexity

Perplexity mengukur seberapa baik model memprediksi urutan. Semakin rendah perplexity, semakin baik modelnya.

Rumus Perplexity: Perplexity = exp(CrossEntropyLoss)

Evaluasi Kualitatif

Evaluasi kualitatif melibatkan pemeriksaan teks yang dihasilkan oleh model dan mengevaluasi koherensi, relevansi, dan kualitas keseluruhannya.

Hyperparameter Tuning

Hyperparameter tuning melibatkan eksperimen dengan nilai yang berbeda dari hyperparameter seperti learning rate, ukuran batch, jumlah lapisan, dan ukuran dimensi embedding untuk menemukan konfigurasi optimal untuk kumpulan data dan tugas Anda.

Kesimpulan

Dalam postingan blog ini, kita telah membahas langkah-langkah yang terlibat dalam melatih model GPT dari awal menggunakan PyTorch. Kami telah membahas persiapan data, tokenisasi, arsitektur Transformer, pelatihan model, inferensi, dan evaluasi. Dengan mengikuti langkah-langkah ini, Anda dapat membangun model GPT Anda sendiri dan menyesuaikannya dengan kebutuhan khusus Anda.

Langkah Selanjutnya

  • Eksperimen dengan arsitektur yang berbeda: Cobalah arsitektur Transformer yang berbeda seperti Transformer-XL atau Reformer.
  • Gunakan teknik regulasi yang berbeda: Eksperimen dengan teknik regulasi seperti dropout, weight decay, dan label smoothing.
  • Fine-tune pada tugas khusus: Fine-tune model GPT terlatih Anda pada tugas NLP tertentu seperti klasifikasi teks atau terjemahan bahasa.
  • Perluas vocabulary: Kembangkan vocabulary Anda untuk mencakup token tambahan yang relevan dengan kumpulan data Anda.
  • Eksplorasi teknik optimalisasi: Terapkan teknik optimalisasi seperti gradient accumulation dan mixed-precision training.

Semoga beruntung dengan petualangan pelatihan model GPT Anda!

“`

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *