NLP Fundamentals

4 jam11 min baca
Tujuan

Paham bagaimana komputer "memproses bahasa" — tokenization, embedding, dan basic NLP tasks. Fondasi sebelum Transformer.

04 — NLP Fundamentals

Estimasi: 4 jam Tujuan: Paham bagaimana komputer "memproses bahasa" — tokenization, embedding, dan basic NLP tasks. Fondasi sebelum Transformer.


Kenapa Materi Ini Penting?

Komputer tidak mengerti bahasa — dia cuma mengerti angka. Pertanyaan inti NLP: bagaimana mengubah teks (yang abstrak, ambigu, tergantung konteks) jadi angka (yang bisa diproses neural network) tanpa kehilangan makna? Materi ini membahas perjalanan teks dari raw string sampai jadi vektor yang bisa dimasukkan ke model — pipeline yang sama persis dipakai di GPT, Claude, dan semua LLM modern, hanya skalanya jauh lebih besar.

Bayangkan kamu jadi penerjemah untuk alien yang cuma bisa berkomunikasi dalam angka. Kamu harus: (1) memecah kalimat jadi unit kecil yang manageable (tokenization), (2) memetakan tiap unit ke kode angka unik (vocabulary), (3) mengubah kode itu jadi vektor yang menangkap makna (embedding) — di mana kata mirip punya vektor mirip. Begitu kamu paham 3 langkah ini, kamu paham fondasi semua NLP modern.

Tiga insight yang akan kamu kuasai: (1) Tokenization — bagaimana subword (BPE) mengalahkan word-level, (2) Embedding — kenapa "raja - pria + wanita ≈ ratu" itu mungkin secara matematis, dan (3) Pipeline klasik vs modern (Transformer) — kamu akan paham kenapa BERT/GPT melompati semua ini.

Peta Mental: Pipeline NLP Modern

Cara Membaca Diagram:

  • Kiri = kata raw, kanan = strategi tokenization yang dipilih
  • Tiap baris = trade-off berbeda (jumlah token vs OOV vs vocab size)
  • BPE (subword) = standar modern, kompromi terbaik
  • Word-level mati di kata baru, char-level boros panjang sequence

Walkthrough Step-by-Step:

  1. Input kata 'unbelievable'
  2. Word-level: jadi 1 token tapi out-of-vocab kalau kata baru tidak ada di vocab
  3. Char-level: jadi 12 token (semua huruf), no OOV tapi sequence super panjang
  4. BPE: jadi ['un', 'believ', 'able'] — 3 token, vocabulary kecil tapi cover semua
  5. Tokenizer modern (BERT, GPT) semua pakai variant subword (BPE / WordPiece / SentencePiece)

Analogi Sehari-hari: Bayangkan menyusun puzzle. Word-level = piece super besar (gampang tapi koleksi terbatas). Char-level = piece super kecil (fleksibel tapi banyak). BPE = piece sedang yang adaptif: kata umum jadi 1, kata jarang dipecah. Optimal.

Diagram statis Mermaid sebagai fallback:

flowchart LR
    Raw["📝 Raw Text<br/>'Saya suka AI'"] --> Tok["✂️ Tokenizer<br/>(BPE/WordPiece)"]
    Tok --> IDs["🔢 Token IDs<br/>[101, 4521, 8923, 102]"]
    IDs --> Emb["🧠 Embedding<br/>(N, D) vectors"]
    Emb --> Model["🤖 Model<br/>(Transformer)"]
    Model --> Out["🎯 Output<br/>(class/text)"]
    style Raw fill:#dbeafe
    style Tok fill:#fef3c7
    style IDs fill:#fed7aa
    style Emb fill:#fce7f3
    style Out fill:#d1fae5

Bagian 1 — Pipeline Klasik NLP

Raw text
   ↓
Cleaning (lowercase, remove punct, etc.)
   ↓
Tokenization (split into tokens)
   ↓
Vocabulary building
   ↓
Vectorization (token → numbers)
   ↓
Model (ML/DL)

Bagian 2 — Text Cleaning

import re

def clean_text(text: str) -> str:
    text = text.lower()
    text = re.sub(r"http\S+", "", text)        # URL
    text = re.sub(r"@\w+", "", text)            # mention
    text = re.sub(r"#\w+", "", text)            # hashtag
    text = re.sub(r"[^\w\s]", "", text)         # punctuation
    text = re.sub(r"\d+", "", text)             # numbers
    text = re.sub(r"\s+", " ", text).strip()
    return text

Stopwords

import nltk
nltk.download("stopwords")
from nltk.corpus import stopwords

stop_id = stopwords.words("indonesian")    # atau "english"

text = "saya mau pergi ke pasar"
words = [w for w in text.split() if w not in stop_id]
print(words)    # ['mau', 'pergi', 'pasar']

Bagian 3 — Tokenization

Analogi Tokenization: Bayangkan kamu menyusun puzzle. Word-level = puzzle dengan piece besar (1 kata = 1 piece) — gampang disusun tapi koleksi piece terbatas, kalau ada kata baru (typo, slang) tidak ada piece-nya. Character-level = puzzle dengan piece super kecil (1 huruf = 1 piece) — fleksibel tapi butuh banyak piece untuk membentuk kata. Subword (BPE) = piece ukuran sedang yang adaptif: kata umum jadi 1 piece ("the"), kata jarang dipecah ("transformasi" → "trans" + "formasi"). Kompromi terbaik: kosakata kecil tapi cover semua kata.

Perbandingan Strategi Tokenization

flowchart TD
    T["📝 'unbelievable'"] --> W["Word-level"]
    T --> C["Char-level"]
    T --> B["BPE (subword)"]
    W --> WO["['unbelievable']<br/>1 token, OOV risk"]
    C --> CO["['u','n','b','e',...]<br/>12 token, no OOV"]
    B --> BO["['un','believ','able']<br/>3 token, sweet spot ✅"]
    style W fill:#fee2e2
    style C fill:#fef3c7
    style B fill:#d1fae5

Word-level

text = "Halo dunia! Apa kabar?"
tokens = text.split()
# ['Halo', 'dunia!', 'Apa', 'kabar?']

# Lebih baik (regex)
tokens = re.findall(r"\b\w+\b", text)
# ['Halo', 'dunia', 'Apa', 'kabar']

Subword (BPE) — Modern

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
tokens = tokenizer.tokenize("Halo dunia")
# ['Hal', '##o', 'duni', '##a']

ids = tokenizer.encode("Halo dunia")
# [101, 11974, 4900, 102]   ← input untuk model

Tokenizer modern (BPE, WordPiece, SentencePiece) split kata jadi subword. Tangani out-of-vocab dengan baik.


Bagian 4 — Vectorization

Bag of Words

from sklearn.feature_extraction.text import CountVectorizer

texts = ["saya suka kucing", "saya suka anjing", "kucing manis"]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)

print(vectorizer.get_feature_names_out())
# ['anjing', 'kucing', 'manis', 'saya', 'suka']

print(X.toarray())
# [[0 1 0 1 1]
#  [1 0 0 1 1]
#  [0 1 1 0 0]]

❌ Tidak capture order, makna.

TF-IDF

from sklearn.feature_extraction.text import TfidfVectorizer

vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(texts)

Kata yang sering muncul di banyak doc → less weight. Kata khas → more weight.

N-grams

vectorizer = TfidfVectorizer(ngram_range=(1, 2))    # unigram + bigram

Capture pasangan kata: "tidak suka" beda dari "suka".


Bagian 5 — Word Embeddings (Word2Vec, GloVe)

Konsep

Setiap kata → vektor di ruang dim 100-300.

Analogi Embedding: Bayangkan peta makna raksasa berdimensi 300. Tiap kata ditempatkan di lokasi tertentu di peta. Kata-kata yang mirip makna (kucing, anjing, kelinci) tinggal bertetangga. Kata-kata yang berlawanan (panas vs dingin) berseberangan. Yang menakjubkan: arah antar lokasi punya makna konsisten — arah dari "raja" ke "ratu" sama dengan arah dari "pria" ke "wanita" (vektor "gender"). Itu sebabnya vec(raja) - vec(pria) + vec(wanita) ≈ vec(ratu) — kamu jalan menempuh "anti-pria + wanita" dari titik raja, tiba di sekitar ratu.

Property magic:

vec("raja") - vec("pria") + vec("wanita") ≈ vec("ratu")

Skip-gram vs CBOW

Cara Membaca Diagram:

  • Kiri = kata-kata di "ruang embedding" 300-D (visualisasi disederhanakan)
  • Tengah = operasi vektor: raja - pria + wanita
  • Kanan = hasil yang dekat dengan ratu
  • Edge putus-putus = arah konsisten antar pasangan kata (gender, jamak, dll)

Walkthrough Step-by-Step:

  1. Tiap kata punya vektor 300-D yang dipelajari Word2Vec/GloVe
  2. Kata mirip punya vektor mirip (bertetangga di ruang)
  3. Operasi vektor raja - pria = mengeluarkan komponen "pria" dari "raja"
  4. Tambah wanita = injek komponen "wanita"
  5. Hasilnya secara matematis ≈ ratu (gender swap dengan royalti utuh)

Analogi Sehari-hari: Embedding = peta GPS makna. Tiap kata adalah lokasi di peta 300 dimensi. Arah dari satu lokasi ke lokasi lain punya makna konsisten — arah "gender" sama untuk semua pasangan (raja-ratu, pria-wanita, ayah-ibu).

Diagram statis Mermaid sebagai fallback:

flowchart LR
    subgraph SG["📤 Skip-gram"]
        SC["📍 Center word<br/>'kucing'"] --> SP["Predict context"]
        SP --> SO1["'saya'"]
        SP --> SO2["'suka'"]
        SP --> SO3["'lucu'"]
    end
    subgraph CB["📥 CBOW"]
        CC1["'saya'"] --> CO["Combine"]
        CC2["'suka'"] --> CO
        CC3["'lucu'"] --> CO
        CO --> CP["Predict center<br/>'kucing'"]
    end
    style SG fill:#dbeafe
    style CB fill:#fef3c7
Aspek Skip-gram CBOW
Input 1 kata center Banyak kata context
Output Banyak kata context 1 kata center
Best for Rare words, dataset kecil Frequent words, dataset besar
Speed Lebih lambat Lebih cepat

Pakai Pretrained

# Gensim
from gensim.models import KeyedVectors

# Download dari https://nlp.stanford.edu/projects/glove/
model = KeyedVectors.load_word2vec_format("glove.6B.100d.txt", binary=False)

# Vector
vec = model["king"]    # numpy array (100,)

# Similar words
print(model.most_similar("king"))
# [('queen', 0.78), ('prince', 0.74), ...]

# Analogy
print(model.most_similar(positive=["king", "woman"], negative=["man"]))
# [('queen', 0.71), ...]

Train Sendiri (untuk Bahasa Indonesia)

from gensim.models import Word2Vec

sentences = [["saya", "suka", "kucing"], ["saya", "suka", "anjing"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)

print(model.wv["saya"])
print(model.wv.most_similar("saya"))

Bagian 6 — Embedding Layer di PyTorch

import torch.nn as nn

# Bikin embedding layer
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=128)

# Input: token IDs
token_ids = torch.tensor([1, 5, 3, 7])
vectors = embedding(token_ids)
# Shape: (4, 128)

Embedding dilatih bersama model (end-to-end), atau di-init dengan pretrained.


Bagian 7 — Common NLP Tasks

Text Classification

Spam, sentiment, topic detection.

  • Input: text
  • Output: class label

NER (Named Entity Recognition)

Identify person/place/organization in text.

  • Input: text
  • Output: tag per token

Question Answering

  • Input: pertanyaan + konteks
  • Output: jawaban (substring dari konteks)

Translation

  • Input: source language
  • Output: target language

Summarization

  • Input: long text
  • Output: summary

Text Generation

  • Input: prompt
  • Output: continuation

Bagian 8 — Sentiment Analysis Project (Klasik)

Dataset

IMDB review (50k reviews, label positive/negative).

Pipeline

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

# Embedding-based LSTM model
class SentimentLSTM(nn.Module):
    def __init__(self, vocab_size, embed_dim=100, hidden_dim=128, num_classes=2):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=2, 
                             batch_first=True, bidirectional=True, dropout=0.3)
        self.fc = nn.Linear(hidden_dim * 2, num_classes)
    
    def forward(self, x):
        x = self.embedding(x)            # (B, T, E)
        _, (h, _) = self.lstm(x)
        h = torch.cat([h[-2], h[-1]], dim=1)   # bidirectional
        return self.fc(h)

Detailed implementation di Karpathy "makemore" series.


Bagian 9 — Common Mistakes & FAQ

1. Stopwords Removal Sebelum Tokenizer Modern

# ❌ Buang stopwords sebelum BERT/GPT — model butuh kata fungsional
text = "saya tidak suka kucing"
words = [w for w in text.split() if w not in stop_id]    # buang 'saya', 'tidak'
# 'tidak' itu PENTING untuk sentiment! Hilang = sentiment flip.

# ✅ Untuk Transformer, JANGAN remove stopwords. Tokenizer modern handle semua.

Aturan main: Cleaning agresif (lowercase, remove stopword/punctuation) hanya untuk klasik (BoW, TF-IDF). Untuk Transformer, biarkan teks utuh.

2. Vocabulary Mismatch Saat Inference

# ❌ Train pakai vocab A, inference pakai vocab B → semua jadi <UNK>
train_vocab = build_vocab(train_texts)
test_ids = [test_vocab[w] for w in test_text.split()]    # vocab beda!

# ✅ Save vocab dari training, reuse di inference
import pickle
pickle.dump(train_vocab, open("vocab.pkl", "wb"))

3. Padding di Posisi Salah

# ❌ Untuk RNN, pad di kiri = info penting di akhir = oke
# Tapi untuk LSTM dengan attention, padding di kiri/kanan harus konsisten

# ✅ Pakai utility HuggingFace
inputs = tokenizer(texts, padding=True, truncation=True,
                   max_length=128, return_tensors="pt")
# Otomatis pad ke max length di batch

4. Tidak Handle attention_mask

# ❌ Pad token ikut diproses model = noise
output = model(input_ids)

# ✅
output = model(input_ids, attention_mask=attention_mask)
# Mask 0 = pad, 1 = real token

5. Embedding Layer Tanpa padding_idx

# ❌ Pad token (index 0) ikut di-train → vector pad bergerak random
emb = nn.Embedding(vocab_size, 128)

# ✅ Freeze pad token
emb = nn.Embedding(vocab_size, 128, padding_idx=0)
# Vector pad selalu 0, tidak update

6. Pakai Word2Vec untuk Sentiment Indonesia → Hasil Jelek

# ❌ GloVe pretrained = English-only
model = KeyedVectors.load_word2vec_format("glove.6B.100d.txt")
# Kata Indonesia → tidak ada di vocab

# ✅ Pakai embedding khusus Indonesia
# - fastText Indonesia
# - IndoBERT embedding
# - Atau train Word2Vec sendiri di corpus Indonesia

Bagian 10 — Klasik vs Modern: Kapan Pakai Apa?

Aspek Klasik (BoW/TF-IDF + LR) Modern (Transformer)
Setup time Menit Jam (download model, setup GPU)
Compute CPU cukup GPU rekomendasi
Akurasi sentiment 75-85% 90-95%
Handle context ❌ Tidak ✅ Sangat baik
Handle sarkasme/negasi ❌ Lemah ✅ Kuat
Cocok untuk Baseline, dataset kecil, latency kritis Production NLP modern
Dataset minimum 1k sampel cukup 10k+ ideal (atau fine-tune pretrained)

Strategi praktis: Selalu mulai dengan baseline klasik (TF-IDF + LogReg). Naikkan ke Transformer kalau baseline tidak cukup. Banyak production cuma butuh baseline.


Cek Pemahaman

  • Bisa cleaning text dengan regex?
  • Tahu beda BoW dan TF-IDF?
  • Tahu beda word-level dan subword tokenization?
  • Bisa pakai pretrained Word2Vec?
  • Tahu apa itu embedding layer?

Challenge 6.4

Challenge 1 — Sentiment Analysis Klasik

Dataset IMDB:

  • TF-IDF + Logistic Regression → baseline
  • Word2Vec + LogReg
  • LSTM dengan embedding layer

Bandingkan accuracy.

Challenge 2 — Eksplorasi Embedding

Pakai pretrained Word2Vec/GloVe:

  1. Find similar words untuk 5 kata berbeda
  2. Word analogy task
  3. Visualize 100 kata terpopuler dengan t-SNE

Challenge 3 — Tokenizer Comparison

Pakai HuggingFace tokenizer:

  • BERT
  • GPT-2
  • multilingual BERT

Tokenize teks Indonesia "Indonesia adalah negara kepulauan terbesar". Bandingkan jumlah token.


Selanjutnya: 05-transformer.md