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:
- Input kata
'unbelievable' - Word-level: jadi 1 token tapi out-of-vocab kalau kata baru tidak ada di vocab
- Char-level: jadi 12 token (semua huruf), no OOV tapi sequence super panjang
- BPE: jadi
['un', 'believ', 'able']— 3 token, vocabulary kecil tapi cover semua - 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:
- Tiap kata punya vektor 300-D yang dipelajari Word2Vec/GloVe
- Kata mirip punya vektor mirip (bertetangga di ruang)
- Operasi vektor
raja - pria= mengeluarkan komponen "pria" dari "raja" - Tambah
wanita= injek komponen "wanita" - 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:
- Find similar words untuk 5 kata berbeda
- Word analogy task
- 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