Memproses teks menjadi angka: tokenization, bag-of-words dan TF-IDF, hingga embeddings word2vec/fastText, lalu membangun pipeline klasifikasi teks untuk spam detection dan sentiment analysis memakai scikit-learn.

Sejauh ini kita hanya bekerja dengan angka dan kategori. Pada episode ini tantangannya berbeda: data teks. Kalian punya ribuan email, ulasan, atau tweet — dan perlu memutuskan mana spam, atau seberapa positif sentimennya.
Mengapa teks sulit? Karena model ML tidak membaca bahasa — ia membaca angka. Tugas kita mengubah teks menjadi representasi numerik tanpa kehilangan makna. Episode ini membangun fondasi NLP klasik yang tetap menjadi basis banyak pipeline 2026, sebelum nantinya bertemu embeddings modern dan LLM di episode 23.
Tokenization memecah teks menjadi unit terkecil (token — biasanya kata). Sebelumnya, teks perlu dibersihkan: lowercase, hilangkan tanda baca, dan buang stopwords (kata seperti "yang", "dan", "di" yang tidak bermakna).
import re
def clean_text(text):
text = text.lower()
text = re.sub(r"[^a-z0-9\s]", "", text)
return text.split()
tokens = clean_text("PENAWARAN MENARIK! Dapatkan diskon 50% hari ini!")
print(tokens) # ['penawaran', 'menarik', 'dapatkan', 'diskon', '50', 'hari', 'ini']Dengan scikit-learn, seluruh proses ini diotomatisasi TfidfVectorizer — ia melakukan tokenization, stopword removal, dan konversi ke angka sekaligus.
Bag-of-Words (BoW) adalah representasi paling sederhana: setiap dokumen menjadi vektor berisi jumlah kemunculan tiap kata di kamus.
| Dokumen | "promo" | "makan" | "murah" | "enak" |
|---|---|---|---|---|
| "promo makan murah" | 1 | 1 | 1 | 0 |
| "makan enak enak" | 0 | 1 | 0 | 2 |
Masalahnya: kata yang sering muncul di semua dokumen (misal "makan") tidak informatif, tapi bobotnya besar.
TF-IDF (Term Frequency - Inverse Document Frequency) memperbaiki kelemahan BoW: kata yang muncul di banyak dokumen mendapat bobot lebih kecil (kurang informatif), kata yang langka mendapat bobot lebih besar.
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(
max_features=5000,
stop_words="english",
ngram_range=(1, 2),
)
X_train = vectorizer.fit_transform(corpus_train)
X_test = vectorizer.transform(corpus_test)
print(X_train.shape) # (n_doc, 5000) matriks sparsemax_features=5000: batasi kamus agar tidak meledak.ngram_range=(1, 2): selain kata tunggal, sertakan frasa 2 kata ("not good") yang maknanya berbeda dari kata terpisah.Perhatikan pola fit_transform di train dan transform di test — vectorizer belajar kamus dari train saja, persis seperti scaler di episode 4.
Note
Output TfidfVectorizer adalah matriks sparse — hampir semua entrinya nol. Jangan konversi ke dense array untuk dataset besar; model seperti LogisticRegression dan tree-based menerima sparse secara langsung.
Gabungkan vectorizer + classifier dalam satu Pipeline (kebiasaan kita sejak episode 4):
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
text_pipeline = Pipeline([
("tfidf", TfidfVectorizer(max_features=5000, ngram_range=(1, 2))),
("clf", LogisticRegression(max_iter=1000)),
])
text_pipeline.fit(X_train_text, y_train_text)
print(text_pipeline.score(X_test_text, y_test_text))LogisticRegression + TF-IDF adalah baseline yang sangat kuat untuk klasifikasi teks — sering mengalahkan model rumit pada data teks pendek. Mulailah dari sini sebelum mempertimbangkan pendekatan lain.
Mari klasifikasikan ulasan menjadi positif/negatif:
from sklearn.metrics import classification_report
pipeline = Pipeline([
("tfidf", TfidfVectorizer(max_features=10000, ngram_range=(1, 2))),
("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
])
pipeline.fit(ulasan_train, label_train)
print(classification_report(ulasan_test, label_test))class_weight="balanced" membantu jika ulasan negatif jauh lebih sedikit dari positif — ingat pelajaran episode 6.
Kita juga bisa menginspeksi kata apa yang paling memengaruhi keputusan model:
import numpy as np
coef = pipeline.named_steps["clf"].coef_[0]
names = pipeline.named_steps["tfidf"].get_feature_names_out()
top_pos = names[np.argsort(coef)[-10:]]
top_neg = names[np.argsort(coef)[:10]]
print("Kata paling positif:", top_pos)
print("Kata paling negatif:", top_neg)Ini bentuk awal interpretabilitas — kita akan membahas versi lanjutannya dengan SHAP di episode 20.
Kelemahan TF-IDF: tidak menangkap kemiripan makna. Kata "bagus" dan "hebat" dianggap berbeda total karena tidak berbagi karakter. Word embeddings mengatasinya: setiap kata direpresentasikan sebagai vektor numerik (misal 100 dimensi) di mana kata bermakna serupa punya vektor berdekatan.
Dua pendekatan embedding klasik:
Untuk pipeline sklearn, embed dokumen dengan menggabungkan vektor kata (rata-rata) sehingga setiap dokumen punya satu vektor:
import fasttext
import numpy as np
ft = fasttext.load_model("lid.176.bin") # model multilingual ringan
def embed_doc(text):
words = clean_text(text)
vectors = [ft.get_word_vector(w) for w in words if w in ft]
return np.mean(vectors, axis=0) if vectors else np.zeros(ft.get_dimension())
X_embed = np.array([embed_doc(doc) for doc in texts])Setelah dokumen menjadi vektor, kalian bebas memakai classifier mana pun — LogisticRegression, tree-based, atau kNN. Untuk pilihan 2026, embeddings transformer modern (dari model seperti Sentence-BERT) menjadi standar — kita sentuh di episode 23 bersama LLM.
fit_transform di train, transform di test.max_features besar — sparse dan rawan overfit; kurangi kamus.class_weight pada sentimen yang tidak seimbang.Pada episode 13 ini, kalian telah menguasai dasar-dasar NLP.
Inti yang harus dibawa pulang:
Di episode 14 selanjutnya kita akan membahas imbalanced data & anomaly detection — resampling dan cost-sensitive learning, metrik yang tepat (PR-AUC, F2), lalu Isolation Forest dan One-Class SVM untuk deteksi fraud dan anomaly. Sampai jumpa di episode 14!