import os os.environ["TF_USE_LEGACY_KERAS"] = "1" # Bypass Keras 3 & optree 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 import tf_keras as keras from tf_keras import layers from tf_keras.callbacks import EarlyStopping from tf_keras.preprocessing.text import Tokenizer # type: ignore from tf_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 = "results/figures" REPORTS_DIR = "results/reports" MODELS_DIR = "results/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="") 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, input_length=MAX_LEN), 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/")