# Deteksi apakah berjalan di Hugging Face Spaces ZeroGPU (Harus di-import paling pertama sebelum torch/transformers) try: import spaces HAS_SPACES = True except ImportError: HAS_SPACES = False # Jika tidak berjalan di ZeroGPU (misal lokal), buat mock decorator if not HAS_SPACES: class spaces: @staticmethod def GPU(duration=None): def decorator(fn): return fn return decorator import gradio as gr from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch import torch.nn.functional as F import os import random import numpy as np import re import unicodedata import string # ========================================== # PENGATURAN EFISIENSI KUOTA HARIANS (ZeroGPU) # ========================================== # Set ke False jika ingin kuota 100% UNLIMITED (Bebas Batasan Harian) # Model BERT berukuran kecil sangat cepat berjalan di CPU (~50-100ms per prediksi). USE_GPU = False # ========================================== # Set seed untuk hasil yang konsisten (Deterministik) SEED = 42 random.seed(SEED) np.random.seed(SEED) torch.manual_seed(SEED) # Load Model & Tokenizer dari Hugging Face Hub secara global MODEL_PATH = "filamss/bert-judol-indonesia" print(f"Loading classification model: {MODEL_PATH}") tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH) # Pola Resmi ZeroGPU: Pindahkan model ke 'cuda' di level global (module level) # ZeroGPU akan meng-intercept pemanggilan ini, menyimpannya di CPU, dan memindahkannya # ke GPU secara otomatis hanya saat masuk ke dalam fungsi yang di-decorate @spaces.GPU. if HAS_SPACES and USE_GPU: print("ZeroGPU detected: Registering model to CUDA (intercepted by ZeroGPU)") model = model.to("cuda") else: print("Running on CPU mode") model = model.to("cpu") model.eval() # Load Sentiment Model & Tokenizer secara manual (Untuk menghindari CUDA init tersembunyi dari pipeline) SENTIMENT_MODEL_PATH = "mdhugol/indonesia-bert-sentiment-classification" print(f"Loading sentiment model (CPU): {SENTIMENT_MODEL_PATH}") sentiment_tokenizer = AutoTokenizer.from_pretrained(SENTIMENT_MODEL_PATH) sentiment_model = AutoModelForSequenceClassification.from_pretrained(SENTIMENT_MODEL_PATH) sentiment_model = sentiment_model.to("cpu") sentiment_model.eval() LABEL_MAP = { 0: "Normal", 1: "Spam" } SENTIMENT_MAP = { 0: "positive", 1: "neutral", 2: "negative" } # --- TEXT CLEANSING UTILITIES FROM COLAB --- LEET_DICT = {"4":"a", "7":"t", "0":"o", "1":"i", "3":"e", "5":"s", "@":"a"} url_re = re.compile(r"(https?://\S+|www\.\S+)", re.IGNORECASE) DOMAIN_EXCLUDE = re.compile(r"\.(com|net|org|id|xyz|biz|info|io|gov|edu)\b", re.IGNORECASE) def fold_spaced_words(text): return re.sub(r"\b(?:[a-z]\s+){2,}[a-z]\b", lambda m: m.group(0).replace(" ", ""), text) def selective_leet_fix(match): word = match.group(0) if DOMAIN_EXCLUDE.search(word): return word if re.search(r'[a-z]', word): suffix_digits = re.search(r'\d+\b$', word) safe_index = suffix_digits.start() if suffix_digits else len(word) new_word = "" for i, char in enumerate(word): if char in LEET_DICT and i < safe_index: new_word += LEET_DICT[char] else: new_word += char return new_word return word def clean_text(text): if not isinstance(text, str): return "" t = unicodedata.normalize("NFKC", text).lower() t = re.sub(r'\b[a-z0-9@.]+\b', selective_leet_fix, t) t = fold_spaced_words(t) t = re.sub(r'([a-z])\1{2,}', r'\1\1', t) t = t.encode('ascii', 'ignore').decode('ascii') t = t.translate(str.maketrans('', '', string.punctuation)) t = re.sub(r"\s+", " ", t).strip() return t # ------------------------------------------- # FUNGSI INFERENSI KHUSUS GPU (Hanya memakan kuota GPU selama proses kalkulasi model matematika) # Dibatasi maksimum 2 detik per panggilan untuk efisiensi ekstrim @spaces.GPU(duration=2) def predict_spam_gpu(input_ids, attention_mask): # Set seed GPU di dalam container GPU ZeroGPU if torch.cuda.is_available(): torch.cuda.manual_seed_all(SEED) # Model secara otomatis dipindahkan ke CUDA oleh ZeroGPU. # Kita hanya perlu memindahkan input tensor ke CUDA. input_ids = input_ids.to("cuda") attention_mask = attention_mask.to("cuda") with torch.no_grad(): outputs = model(input_ids=input_ids, attention_mask=attention_mask) return outputs.logits.cpu() # FUNGSI INFERENSI KHUSUS CPU (Tidak memakan kuota GPU sama sekali) def predict_spam_cpu(input_ids, attention_mask): with torch.no_grad(): outputs = model(input_ids=input_ids, attention_mask=attention_mask) return outputs.logits def predict(text): if not text or not text.strip(): return generate_result_html("", "", "", 0, "", 0), {} try: # 1. Text Cleansing (Dijalankan di CPU - Hemat Kuota) cleaned_text = clean_text(text) # 2. Tokenization (Dijalankan di CPU - Hemat Kuota) inputs = tokenizer(cleaned_text, return_tensors="pt", truncation=True, padding=True, max_length=128) input_ids = inputs["input_ids"] attention_mask = inputs["attention_mask"] # 3. Model Inference (Pilih GPU atau CPU) if USE_GPU and HAS_SPACES: # Menggunakan GPU dinamis, hanya memakan kuota beberapa milidetik saja logits = predict_spam_gpu(input_ids, attention_mask) else: # Berjalan di CPU secara lokal atau Cloud CPU (Kuota GPU terpakai = 0%) logits = predict_spam_cpu(input_ids, attention_mask) probs = F.softmax(logits, dim=1) confidence, predicted_class_id = torch.max(probs, dim=1) label_id = predicted_class_id.item() label_name = LABEL_MAP.get(label_id, f"LABEL_{label_id}") conf_score = confidence.item() # 4. Sentiment Analysis (Dijalankan di CPU secara manual - 100% Bebas Kuota GPU) sentiment_inputs = sentiment_tokenizer(cleaned_text, return_tensors="pt", truncation=True, padding=True, max_length=128) sentiment_inputs = {k: v.to("cpu") for k, v in sentiment_inputs.items()} with torch.no_grad(): sentiment_outputs = sentiment_model(**sentiment_inputs) sentiment_probs = F.softmax(sentiment_outputs.logits, dim=1) sentiment_confidence, sentiment_predicted_class_id = torch.max(sentiment_probs, dim=1) sentiment_id = sentiment_predicted_class_id.item() sentiment_label = SENTIMENT_MAP.get(sentiment_id, 'neutral') sentiment_score = sentiment_confidence.item() # Generate HTML Report & JSON result_html = generate_result_html(text, cleaned_text, label_name, conf_score, sentiment_label, sentiment_score) raw_json = { "input_original": text, "input_cleaned": cleaned_text, "classification": { "label": label_name, "label_id": label_id, "confidence": conf_score }, "sentiment": { "label": sentiment_label, "score": sentiment_score } } return result_html, raw_json except Exception as e: import traceback tb = traceback.format_exc() error_html = f"""
{str(e)}
{tb}
Siap Menganalisis
Masukkan teks di sebelah kiri lalu klik tombol Analyze Text ๐ก๏ธ
"{text}"
"{cleaned_text}"
Indonesian Text Spam & Sentiment Analyzer Dashboard (Thesis Demo)
Masukkan teks SMS, chat, atau postingan sosial media berbahasa Indonesia.
Aplikasi Pendukung Skripsi / Tugas Akhir
Model Klasifikasi: BERT (filamss/bert-judol-indonesia) | Analisis Sentimen: IndoBERT Sentiment