Sessi 4 — Representasi BoW, n‑gram, dan TF–IDF

Tujuan: memahami model ruang vektor (BoW), memperkaya konteks dengan n‑gram, dan membobotkan fitur dengan TF–IDF; pratinjau reduksi dimensi (SVD/PCA) untuk mengurangi sparsity dan meningkatkan efisiensi.

Learning Outcomes: (1) Menerapkan BoW/TF–IDF dengan scikit-learn; (2) Mengonfigurasi ngram_range, min_df/max_df, sublinear_tf, norm; (3) Mengevaluasi dampak representasi pada retrieval & visualisasi; (4) Pratinjau Latent Semantic Analysis via TruncatedSVD.

1) Konsep Inti

  • Bag‑of‑Words (BoW): representasi frekuensi token tanpa urutan.
  • n‑gram: fitur berurutan (kata/karakter) untuk konteks lokal: mis. (1,2) = unigram+bigram.
  • TF–IDF: TF menekankan istilah sering; IDF menurunkan istilah umum. Sublinear TF (log scaling) untuk menekan efek frekuensi ekstrem.
\(\text{tfidf}(t,d) = \text{tf}(t,d) \cdot\big(\log\frac{N+1}{\text{df}(t)+1} + 1\big)\) dengan normalisasi L2 per dokumen.

2) Praktik Google Colab — BoW, n‑gram, TF–IDF

Gunakan korpus bersih dari sessi2 atau varian v1/v2 dari sessi3.

A. Setup & Data

!pip -q install pandas numpy scikit-learn matplotlib

import numpy as np, pandas as pd
import matplotlib.pyplot as plt
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# Muat data
try:
    df = pd.read_csv('corpus_sessi3_variants.csv')
    corpus = df['v2_stop_stemID'].fillna('').astype(str).tolist()  # contoh: pakai varian stop+stem ID
    print('Loaded corpus_sessi3_variants.csv (v2_stop_stemID):', len(corpus))
except:
    df = pd.read_csv('corpus_sessi2_normalized.csv')
    corpus = df['text'].fillna('').astype(str).tolist()
    print('Loaded corpus_sessi2_normalized.csv:', len(corpus))

print('Contoh dokumen:', corpus[0][:120])

B. BoW vs n‑gram

# Unigram BoW
cv1 = CountVectorizer(ngram_range=(1,1), min_df=1)
X1 = cv1.fit_transform(corpus)

# Unigram+Bigram BoW
cv2 = CountVectorizer(ngram_range=(1,2), min_df=1)
X2 = cv2.fit_transform(corpus)

print('Unigram shape:', X1.shape, ' | Vocab size:', len(cv1.get_feature_names_out()))
print('Uni+Bi shape:', X2.shape, ' | Vocab size:', len(cv2.get_feature_names_out()))

# Periksa top 20 n‑gram teratas (berdasarkan frekuensi total)
import numpy as np
sumX2 = np.asarray(X2.sum(axis=0)).ravel()
idx = sumX2.argsort()[::-1][:20]
terms = cv2.get_feature_names_out()[idx]
counts = sumX2[idx]
for t,c in zip(terms, counts):
    print(f'{t:25s} {int(c)}')

C. TF–IDF & Pengaturan Penting

# TF–IDF dengan opsi umum
vec = TfidfVectorizer(
    ngram_range=(1,2),
    min_df=2,          # buang istilah sangat langka
    max_df=0.9,        # buang istilah terlalu umum
    sublinear_tf=True, # log(1+tf)
    norm='l2'
)
X = vec.fit_transform(corpus)
print('TF–IDF shape:', X.shape)

# Lihat beberapa fitur teratas menurut IDF terendah (paling umum)
idf = vec.idf_
feat = vec.get_feature_names_out()
ord = np.argsort(idf)[:20]
for i in ord:
    print(f'{feat[i]:30s} idf={idf[i]:.3f}')

D. Retrieval dengan Cosine Similarity

def search(query, topk=5):
    q = vec.transform([query])
    sims = cosine_similarity(X, q).ravel()
    rank = sims.argsort()[::-1][:topk]
    return [(float(sims[i]), corpus[i]) for i in rank]

queries = [
  'pengiriman cepat kualitas bagus',
  'screen glare outdoors',
  'refund proses cepat',
  'bluetooth stabil',
  'login delay'
]

for q in queries:
    print('\nQUERY:', q)
    for s,doc in search(q, topk=3):
        print(f'  sim={s:.3f}  {doc[:100]}')

E. Pratinjau Reduksi Dimensi — TruncatedSVD (LSA)

from sklearn.decomposition import TruncatedSVD

k = 100 if X.shape[1] > 100 else max(2, X.shape[1]//2)
svd = TruncatedSVD(n_components=k, random_state=42)
Xk = svd.fit_transform(X)
print('Reduced shape:', Xk.shape)
print('Explained variance (sum top‑k):', svd.explained_variance_ratio_.sum().round(3))

# Visualisasi 2D pertama (komponen 1 vs 2)
import matplotlib.pyplot as plt
plt.figure(figsize=(6,5))
plt.scatter(Xk[:,0], Xk[:,1], s=18)
plt.title('Proyeksi LSA: Komponen 1 vs 2')
plt.xlabel('SVD-1'); plt.ylabel('SVD-2'); plt.tight_layout(); plt.show()

F. Perbandingan Karakter n‑gram (Opsional)

# Karakter n‑gram berguna untuk bahasa campuran/typo
char_vec = TfidfVectorizer(analyzer='char', ngram_range=(3,5), min_df=2)
Xc = char_vec.fit_transform(corpus)
print('Char TF–IDF shape:', Xc.shape)

G. Ekspor Artefak

import joblib
joblib.dump(vec, 'tfidf_vec_sessi4.joblib')
joblib.dump(svd, 'svd_sessi4.joblib')
print('Tersimpan: tfidf_vec_sessi4.joblib, svd_sessi4.joblib')

3) Studi Kasus & Analisis

KasusRepresentasi DisarankanAlasan
Pencarian dokumen kelasTF–IDF unigram+bigramKonteks frasa pendek ditangkap; cocok untuk cosine
Ulasan produk campuran ID/ENTF–IDF + char n‑gramRobust terhadap typo/variasi ejaan
Ringkas fitur untuk model cepatTF–IDF → LSA (SVD 100D)Kurangi sparsity & dimensi; akselerasi model downstream

4) Tugas Mini (Dinilai)

  1. Bandingkan unigram vs uni+bi pada metrik: retrieval@5 untuk 5 query Anda.
  2. Uji min_df ∈ {1,2,3} dan max_df ∈ {0.85,0.9,0.95}; laporkan perubahan ukuran kosakata & kinerja retrieval.
  3. Gunakan LSA (SVD) → ukur perubahan waktu komputasi dan kualitas retrieval.