Stream & Graph Mining
Mengolah aliran data real-time dengan sliding/decaying window dan reservoir sampling, mencari dokumen mirip dengan MinHash & LSH, serta meranking simpul graf dengan PageRank.
Notebook lab (pratinjau)
Unduh lalu jalankan di Jupyter, VS Code, atau Google Colab.
Lab Minggu 15 — Stream & Graph Mining
Tujuan lab:
- Mensimulasikan data stream dan menghitung sliding window, decaying window, reservoir sampling.
- Mencari dokumen mirip dengan Jaccard → MinHash → LSH.
- Menghitung PageRank dengan power iteration dan membandingkannya dengan NetworkX.
Baca lesson.md dulu. Jalankan sel satu per satu (Shift+Enter).
import sys
sys.path.insert(0, "..")
import random
from collections import deque, defaultdict
from itertools import combinations
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
from common.datasets import dokumen_teks, jaringan_web, log_transaksi
1. Simulasi data stream
Kita perlakukan log_transaksi() sebagai stream: setiap baris datang satu per satu.
Generator Python cocok untuk meniru stream — data tidak dimuat sekaligus.
def stream_nilai_transaksi():
for baris in log_transaksi():
tgl, kota, produk, jumlah, harga = baris.split(",")
yield int(jumlah) * int(harga)
s = stream_nilai_transaksi()
print([next(s) for _ in range(5)])
2. Sliding window dengan jumlah berjalan
def sliding_mean(stream, w):
jendela, total = deque(maxlen=w), 0.0
for x in stream:
if len(jendela) == w:
total -= jendela[0]
jendela.append(x)
total += x
if len(jendela) == w:
yield total / w
nilai = list(stream_nilai_transaksi())[:1000]
sw = list(sliding_mean(nilai, 50))
3. Exponentially decaying window
Untuk membandingkan dengan rata-rata, kita pakai rata-rata berbobot S_t · c
(karena jumlah bobot ≈ 1/c).
def decaying(stream, c):
s = 0.0
for x in stream:
s = (1 - c) * s + x
yield s
c = 1 / 50
dw = [v * c for v in decaying(nilai, c)]
plt.figure(figsize=(12, 3.5))
plt.plot(nilai, alpha=0.25, label="nilai transaksi")
plt.plot(range(49, 1000), sw, label="sliding window (w=50)")
plt.plot(dw, label="decaying window (c=1/50)")
plt.legend(); plt.title("Ringkasan stream"); plt.show()
Decaying window bereaksi halus dan tidak butuh menyimpan 50 elemen — cukup satu angka.
Trending produk dengan decaying window
Setiap produk punya skor sendiri; skor semua produk meluruh tiap ada transaksi baru.
def trending(stream_baris, c=0.01):
skor = defaultdict(float)
for baris in stream_baris:
produk = baris.split(",")[2]
for p in skor:
skor[p] *= (1 - c)
skor[produk] += 1
return pd.Series(skor).sort_values(ascending=False)
trending(log_transaksi()).head(5)
4. Reservoir sampling
Ambil 5 transaksi acak seragam tanpa tahu panjang stream.
def reservoir(stream, k, seed=0):
rng = random.Random(seed)
res = []
for i, x in enumerate(stream):
if i < k:
res.append(x)
else:
j = rng.randint(0, i)
if j < k:
res[j] = x
return res
reservoir(log_transaksi(), 5, seed=42)
Bukti empiris keseragaman: ulangi 3000 kali pada stream 0..19, setiap angka mestinya terpilih ≈ k/n = 25%.
hitung = np.zeros(20)
for s_ in range(3000):
for x in reservoir(range(20), 5, seed=s_):
hitung[x] += 1
plt.bar(range(20), hitung / 3000); plt.axhline(0.25, color="r", ls="--")
plt.title("Peluang terpilih tiap elemen"); plt.show()
5. Kemiripan dokumen: Jaccard
docs = dokumen_teks()
himp = [set(t.lower().split()) for t in docs]
def jaccard(a, b):
return len(a & b) / len(a | b)
n = len(docs)
J = np.array([[jaccard(himp[i], himp[j]) for j in range(n)] for i in range(n)])
pd.DataFrame(J).round(2)
6. MinHash signature
P = 2_147_483_647
vocab = sorted(set().union(*himp))
idx = {v: i for i, v in enumerate(vocab)}
def minhash(himpunan, n_hash=100, seed=0):
rng = np.random.default_rng(seed)
a = rng.integers(1, P, n_hash, dtype=np.int64)
b = rng.integers(0, P, n_hash, dtype=np.int64)
sig = np.zeros((n_hash, len(himpunan)), dtype=np.int64)
for d, h in enumerate(himpunan):
ids = np.array([idx[x] for x in h], dtype=np.int64)
sig[:, d] = ((a[:, None] * ids[None, :] + b[:, None]) % P).min(axis=1)
return sig
sig = minhash(himp, 200, seed=1)
print("ukuran signature:", sig.shape)
est = np.array([[np.mean(sig[:, i] == sig[:, j]) for j in range(n)] for i in range(n)])
iu = np.triu_indices(n, 1)
plt.scatter(J[iu], est[iu]); plt.plot([0, 1], [0, 1], "r--")
plt.xlabel("Jaccard asli"); plt.ylabel("Estimasi MinHash"); plt.show()
7. LSH banding
def lsh(sig, bands):
r = sig.shape[0] // bands
kandidat = set()
for b in range(bands):
bucket = defaultdict(list)
for d in range(sig.shape[1]):
bucket[tuple(sig[b * r:(b + 1) * r, d])].append(d)
for anggota in bucket.values():
kandidat.update(combinations(anggota, 2))
return kandidat
kand = lsh(sig, bands=50) # r = 4
print(f"Kandidat: {len(kand)} dari {n*(n-1)//2} pasangan")
for i, j in sorted(kand):
print(f"({i},{j}) J={J[i,j]:.2f} | {docs[i][:40]}… ~ {docs[j][:40]}…")
Kurva-S LSH
s_ = np.linspace(0, 1, 200)
for b, r in [(50, 4), (20, 10), (100, 2)]:
plt.plot(s_, 1 - (1 - s_ ** r) ** b, label=f"b={b}, r={r}, ambang≈{(1/b)**(1/r):.2f}")
plt.xlabel("kemiripan s"); plt.ylabel("P(kandidat)"); plt.legend(); plt.show()
8. PageRank
edges = jaringan_web()
G = nx.DiGraph(edges)
nx.draw_networkx(G, pos=nx.spring_layout(G, seed=1), node_color="#9ecae1", arrows=True)
plt.axis("off"); plt.show()
def pagerank(edges, d=0.85, tol=1e-10, max_iter=200):
simpul = sorted({u for e in edges for u in e})
N = len(simpul)
keluar = defaultdict(list)
for u, v in edges:
keluar[u].append(v)
r = {u: 1 / N for u in simpul}
for it in range(max_iter):
bocor = sum(r[u] for u in simpul if u not in keluar)
baru = {u: (1 - d) / N + d * bocor / N for u in simpul}
for u, tujuan in keluar.items():
for v in tujuan:
baru[v] += d * r[u] / len(tujuan)
selisih = sum(abs(baru[u] - r[u]) for u in simpul)
r = baru
if selisih < tol:
break
print("konvergen setelah", it + 1, "iterasi")
return r
pr = pagerank(edges)
pd.DataFrame({"manual": pr, "networkx": nx.pagerank(G, alpha=0.85)}).sort_values("manual", ascending=False).round(4)
Bentuk matriks: r = (1-d)/N + d·M·r. Matriks transisi M (kolom = sumber):
nodes = sorted(G.nodes)
M = nx.to_numpy_array(G, nodelist=nodes).T
M = M / M.sum(axis=0, keepdims=True)
pd.DataFrame(M, index=nodes, columns=nodes).round(2)
✍️ Coba sendiri
- Ubah
wsliding window menjadi 10 dan 200. Apa efeknya pada kehalusan kurva? - Tambahkan dokumen baru yang hampir sama dengan dokumen ke-3, lalu jalankan MinHash + LSH. Apakah pasangan barunya tertangkap?
- Coba
bands=10(r = 20). Berapa kandidat yang tersisa? Jelaskan dengan kurva-S. - Tambahkan simpul dangling
H(C→H) ke graf. Bagaimana PageRank berubah? Bandingkan dengan NetworkX. - Ubah damping d = 0.5 dan d = 0.99. Simpul mana yang paling terpengaruh?
# 1.
# 2.
# 3.
# 4.
Jawaban no. 5:
- ...
Selanjutnya
Kerjakan kuis dan exercise.py, lalu jalankan python check.py 15 dari folder utama.