refactor: restrukturisasi folder eksperimen ke tasks modular

This commit is contained in:
matthewflrnt
2026-09-27 18:50:08 +07:00
parent e827aa0e96
commit fe63a50997
9 changed files with 112 additions and 60 deletions
+288
View File
@@ -0,0 +1,288 @@
import os
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datasets import load_dataset
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
classification_report,
confusion_matrix,
ConfusionMatrixDisplay
)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.preprocessing.text import Tokenizer # type: ignore
from tensorflow.keras.preprocessing.sequence import pad_sequences
# ============================================================
# KONFIGURASI DIREKTORI PENYIMPANAN
# ============================================================
VOCAB_SIZE = 10000
MAX_LEN = 60
EMBED_DIM = 64
EPOCHS = 20
BATCH_SIZE = 64
RANDOM_STATE = 42
FIGURES_DIR = os.path.join("results", "03_nlp_sentiment", "figures")
REPORTS_DIR = os.path.join("results", "03_nlp_sentiment", "reports")
MODELS_DIR = os.path.join("results", "03_nlp_sentiment", "models")
os.makedirs(FIGURES_DIR, exist_ok=True)
os.makedirs(REPORTS_DIR, exist_ok=True)
os.makedirs(MODELS_DIR, exist_ok=True)
# ============================================================
# SEL 7 — LOAD DATASET TOKOPEDIA
# ============================================================
print("=" * 60)
print("SEL 7 — LOAD DATASET")
print("=" * 60)
dataset = load_dataset(
"farhamu/tokopedia-product-reviews-2019"
)
df = dataset["train"].to_pandas()
# Buang rating 3
df = df[df["rating"] != 3].copy()
# Rating 4-5 = positif (1), Rating 1-2 = negatif (0)
df["label"] = (df["rating"] >= 4).astype(int)
# Rename text menjadi teks
df = df.rename(
columns={"text": "teks"}
)[["teks", "label"]].reset_index(drop=True)
print(df.head(5))
print(f"\nShape : {df.shape}")
print(f"Kolom : {df.columns.tolist()}")
print(f"\nDistribusi label:\n{df['label'].value_counts().sort_index()}")
print(f"\nNull : {df.isnull().sum().sum()}")
# ============================================================
# DISTRIBUSI LABEL
# ============================================================
label_counts = df["label"].value_counts().sort_index()
plt.figure(figsize=(7, 5))
plt.bar(
["Negatif", "Positif"],
[label_counts.get(0, 0), label_counts.get(1, 0)]
)
plt.title("Distribusi Label Sentimen Tokopedia")
plt.xlabel("Sentimen")
plt.ylabel("Jumlah Review")
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/distribusi_label_sentimen.png")
plt.close()
# ============================================================
# CEK PANJANG TEKS
# ============================================================
df["panjang"] = df["teks"].apply(lambda x: len(str(x).split()))
print(f"\nPanjang teks:\n{df['panjang'].describe()}")
# ============================================================
# SEL 8 — TEXT PREPROCESSING
# ============================================================
print("\n" + "=" * 60)
print("SEL 8 — TEXT PREPROCESSING")
print("=" * 60)
def clean_text(teks):
teks = str(teks).lower()
teks = re.sub(r"[^a-z\s]", "", teks)
teks = re.sub(r"\s+", " ", teks).strip()
return teks
df["teks_bersih"] = df["teks"].apply(clean_text)
# Train/Test Split
X = np.array(df["teks_bersih"].tolist())
y = np.array(df["label"].tolist(), dtype=int)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y,
test_size=0.2,
random_state=RANDOM_STATE,
stratify=y
)
print(f"Training data : {len(X_tr)}")
print(f"Testing data : {len(X_te)}")
# Tokenizer
tokenizer = Tokenizer(num_words=VOCAB_SIZE, oov_token="<OOV>")
tokenizer.fit_on_texts(X_tr)
# Text -> Integer Sequence
X_tr_seq = tokenizer.texts_to_sequences(X_tr)
X_te_seq = tokenizer.texts_to_sequences(X_te)
# Padding
X_tr_pad = pad_sequences(X_tr_seq, maxlen=MAX_LEN, padding="post")
X_te_pad = pad_sequences(X_te_seq, maxlen=MAX_LEN, padding="post")
print(f"\nX_tr_pad shape : {X_tr_pad.shape}")
print(f"X_te_pad shape : {X_te_pad.shape}")
print(f"Vocab size : {len(tokenizer.word_index)}")
# ============================================================
# SEL 9 — MODEL GAP
# ============================================================
print("\n" + "=" * 60)
print("SEL 9 — MODEL GAP")
print("=" * 60)
sent_model = keras.Sequential([
layers.Embedding(input_dim=VOCAB_SIZE, output_dim=EMBED_DIM),
layers.GlobalAveragePooling1D(),
layers.Dense(64, activation="relu"),
layers.Dropout(0.4),
layers.Dense(1, activation="sigmoid")
], name="Sentimen_GAP")
sent_model.compile(
optimizer="adam",
loss="binary_crossentropy",
metrics=["accuracy"]
)
sent_model.summary()
# ============================================================
# TRAINING
# ============================================================
print("\n" + "=" * 60)
print("TRAINING MODEL GAP")
print("=" * 60)
early_stop = EarlyStopping(
monitor="val_loss",
patience=4,
restore_best_weights=True
)
history_sent = sent_model.fit(
X_tr_pad, y_tr,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_split=0.15,
callbacks=[early_stop],
verbose=1
)
# ============================================================
# TEST ACCURACY & HISTORY PLOTS
# ============================================================
loss_s, acc_s = sent_model.evaluate(X_te_pad, y_te, verbose=0)
print(f"\nSentimen GAP → Test accuracy: {acc_s:.4f}")
# Plot Accuracy & Loss
plt.figure(figsize=(8, 5))
plt.plot(history_sent.history["accuracy"], label="Training Accuracy")
plt.plot(history_sent.history["val_accuracy"], label="Validation Accuracy")
plt.title("Training vs Validation Accuracy — GAP")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/histori_gap_sentimen.png")
plt.close()
# ============================================================
# SEL 11 — EVALUATION
# ============================================================
print("\n" + "=" * 60)
print("EVALUATION")
print("=" * 60)
y_pred_prob = sent_model.predict(X_te_pad, verbose=0)
y_pred = (y_pred_prob > 0.5).astype("int").flatten()
report = classification_report(
y_te, y_pred,
target_names=["Negatif (0)", "Positif (1)"]
)
print("\nClassification Report:")
print(report)
# Simpan Classification Report Txt
with open(f"{REPORTS_DIR}/classification_report_gap_sentimen.txt", "w", encoding="utf-8") as f:
f.write(report)
# Confusion Matrix Plot
cm = confusion_matrix(y_te, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Negatif", "Positif"])
fig, ax = plt.subplots(figsize=(6, 6))
disp.plot(ax=ax, cmap="Blues")
plt.title("Confusion Matrix — Model Sentimen GAP")
plt.tight_layout()
plt.savefig(f"{FIGURES_DIR}/confusion_matrix_gap_sentimen.png")
plt.close()
# ============================================================
# SEL 12 — INFERENCE (PREDIKSI 5 ULASAN)
# ============================================================
def prediksi_sentimen(teks, model, tokenizer, maxlen):
teks_bersih = clean_text(teks)
seq = tokenizer.texts_to_sequences([teks_bersih])
padded = pad_sequences(seq, maxlen=maxlen, padding="post")
prob = model.predict(padded, verbose=0)[0][0]
label = "Positif" if prob > 0.5 else "Negatif"
return label, prob
print("\n" + "=" * 60)
print("PREDIKSI 5 ULASAN")
print("=" * 60)
ulasan_uji = [
"Produk berkualitas tinggi, pengiriman cepat, packing aman!",
"Kecewa banget, barang datang rusak dan customer service tidak responsif",
"Lumayan sih, tapi harganya kemahalan untuk kualitas segitu",
"Tidak sesuai deskripsi sama sekali, mau return tapi ribet",
"Barang bagus, sesuai dengan foto dan penjual sangat ramah"
]
for i, ulasan in enumerate(ulasan_uji, start=1):
label, prob = prediksi_sentimen(ulasan, sent_model, tokenizer, MAX_LEN)
print(f"\n{i}. {ulasan}\n Prediksi : {label}\n Confidence: {prob:.3f}")
# ============================================================
# SIMPAN MODEL
# ============================================================
sent_model.save(f"{MODELS_DIR}/model_gap_sentimen.keras")
print("\nModel GAP berhasil disimpan di folder results/models/")