File size: 7,646 Bytes
c5e83c6
 
 
 
 
 
99f3ba3
c5e83c6
 
99f3ba3
c5e83c6
 
87defe8
 
 
 
 
c5e83c6
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
c5e83c6
 
87defe8
c5e83c6
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
 
c5e83c6
 
 
87defe8
c5e83c6
87defe8
c5e83c6
87defe8
c5e83c6
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
c5e83c6
87defe8
c5e83c6
87defe8
c5e83c6
 
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
c5e83c6
87defe8
 
c5e83c6
87defe8
 
c5e83c6
87defe8
 
c5e83c6
 
 
 
 
 
 
 
f165c76
c5e83c6
f165c76
c5e83c6
 
 
 
 
 
 
99f3ba3
c5e83c6
f165c76
 
 
 
 
 
c5e83c6
 
f165c76
c5e83c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99f3ba3
 
 
 
 
 
 
c5e83c6
87defe8
c5e83c6
 
f165c76
c5e83c6
 
99f3ba3
c5e83c6
 
 
 
 
 
 
 
 
f165c76
 
 
 
 
 
99f3ba3
 
 
 
 
 
 
 
87defe8
99f3ba3
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import torch
import emoji
import re
from transformers import BertTokenizer, BertForSequenceClassification
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Dict, List

# ====================================================================
# 1. KELAS LOGIKA ANDA (Tidak ada perubahan)
# ====================================================================

# Impor library yang dibutuhkan
import re # Untuk operasi regular expression
import emoji # Untuk menangani emoji

# Definisikan sebuah kelas untuk mengelompokkan semua fungsi pembersihan teks
class TextCleaner:
    # Metode constructor, dieksekusi saat objek TextCleaner dibuat
    def __init__(self):
        # Inisialisasi daftar karakter yang akan diperiksa untuk pengulangan.
        # Daftar ini berisi berbagai tanda baca dan simbol.
        self.character = ['.', ',', ';', ':', '?', '!', '(', ')', '[', ']', '{', '}', '<', '>', '"', '/', '\'', '-', '@']
        # Tambahkan semua huruf abjad (a-z) ke dalam daftar karakter di atas.
        self.character.extend([chr(i) for i in range(ord('a'), ord('z') + 1)])

    # Metode untuk membersihkan karakter yang berulang lebih dari 2 kali (misal: "haaiiii" -> "haai").
    def repeatcharClean(self, text):
        # Ulangi untuk setiap karakter dalam daftar 'self.character'
        for char_to_clean in self.character:
            # Buat pola regex untuk menemukan karakter yang berulang 3 kali atau lebih secara berurutan.
            # Contoh: jika char_to_clean adalah 'a', polanya akan mencari 'aaa' atau 'aaaa', dst.
            pattern = re.compile(re.escape(char_to_clean) + r'{3,}')
            # Ganti urutan karakter yang berulang tersebut dengan satu karakter saja.
            # Contoh: "good moooorning" menjadi "good morning".
            text = pattern.sub(char_to_clean, text)
        return text

    # Metode utama untuk menjalankan seluruh proses pembersihan teks
    def clean_review(self, text):
        # 1. Ubah semua teks menjadi huruf kecil (lowercase) untuk konsistensi.
        text = text.lower()
        # 2. Ganti spasi, tab, atau baris baru yang berlebih dengan satu spasi saja.
        text = re.sub(r'\s+', ' ', text)
        # 3. Hapus karakter non-ASCII (seperti karakter Cina, Arab, atau beberapa emoji kompleks).
        text = re.sub(r'[^\x00-\x7F]+', ' ', text)

        # 4. Ganti @mention dan URL dengan token placeholder.
        new_text = []
        # Pecah teks menjadi kata-kata
        for word in text.split(" "):
            # Jika kata diawali dengan '@' dan lebih dari 1 karakter, ganti dengan '@USER'.
            word = '@USER' if word.startswith('@') and len(word) > 1 else word
            # Jika kata diawali dengan 'http', ganti dengan 'HTTPURL'.
            word = 'HTTPURL' if word.startswith('http') else word
            new_text.append(word)
        # Gabungkan kembali kata-kata menjadi satu kalimat.
        text = " ".join(new_text)

        # 5. Ubah emoji menjadi representasi teksnya (misal: 😊 -> ':smiling_face:').
        text = emoji.demojize(text)
        # 6. Hapus representasi teks emoji yang polanya seperti ':nama_emoji:'.
        text = re.sub(r':[A-Za-z_-]+:', ' ', text)

        # 7. Hapus emoticon umum berbasis teks seperti :), :D, :(, xD, dll.
        text = re.sub(r"([xX;:]'?[dDpPvVoO3)(])", ' ', text)
        # 8. Hapus semua tanda baca dan simbol yang tersisa.
        text = re.sub(r'["#$%&()*+,./:;<=>\[\]\\^_`{|}~]', ' ', text)
        
        # 9. Panggil metode 'repeatcharClean' untuk membersihkan karakter yang berulang.
        text = self.repeatcharClean(text)
        
        # 10. Lakukan pembersihan spasi terakhir dan hapus spasi di awal/akhir kalimat.
        text = re.sub(r'\s+', ' ', text).strip()
        
        # Kembalikan teks yang sudah bersih
        return text

class SentimentPredictor:
    def __init__(self, tokenizer, model):
        self.tokenizer = tokenizer
        self.model = model
        self.device = torch.device("cpu")
        self.model.to(self.device)
        self.label_mapping = {0: 'Positif', 1: 'Netral', 2: 'Negatif'}

    def predict(self, text: str) -> (str, float, Dict[str, float]):
        inputs = self.tokenizer(text, return_tensors='pt', truncation=True, padding=True, max_length=280)
        inputs = {k: v.to(self.device) for k, v in inputs.items()}
        
        with torch.no_grad():
            outputs = self.model(**inputs)
        
        logits = outputs.logits
        probabilities = torch.softmax(logits, dim=1)[0]
        
        confidence_score = probabilities.max().item()
        predicted_label_id = probabilities.argmax().item()
        sentiment = self.label_mapping[predicted_label_id]
        
        all_scores = {self.label_mapping[i]: prob.item() for i, prob in enumerate(probabilities)}
        return sentiment, confidence_score, all_scores

# ====================================================================
# 2. INISIALISASI MODEL & APLIKASI FASTAPI (Tidak ada perubahan)
# ====================================================================

print("Memuat model dan tokenizer...")
tokenizer = BertTokenizer.from_pretrained('indolem/indobertweet-base-uncased')
model = BertForSequenceClassification.from_pretrained('indolem/indobertweet-base-uncased', num_labels=3)
model_path = 'model_indoBERTweet_100Epochs_sentiment.pth'
state_dict = torch.load(model_path, map_location=torch.device('cpu'))
model.load_state_dict(state_dict, strict=False)
model.eval()
print("Model berhasil dimuat.")

text_cleaner = TextCleaner()
sentiment_predictor = SentimentPredictor(tokenizer, model)

app = FastAPI(
    title="API Klasifikasi Sentimen",
    description="Sebuah API untuk menganalisis sentimen teks Bahasa Indonesia."
)

# ====================================================================
# 3. DEFINISIKAN MODEL INPUT & OUTPUT API
# ====================================================================

class TextInput(BaseModel):
    text: str

# --- [PERBAIKAN] --- Menambahkan definisi BatchTextInput ---
# Model ini memberitahu FastAPI bahwa endpoint batch akan menerima
# sebuah objek JSON dengan satu key "texts" yang berisi daftar string.
class BatchTextInput(BaseModel):
    texts: List[str]
# -----------------------------------------------------------

class PredictionOutput(BaseModel):
    cleaned_text: str = None  # Optional, hanya diisi pada batch
    sentiment: str
    confidence: float
    all_scores: Dict[str, float]

# ====================================================================
# 4. BUAT ENDPOINT (Tidak ada perubahan logika)
# ====================================================================

@app.get("/")
def read_root():
    return {"message": "Selamat datang di API Klasifikasi Sentimen"}

@app.post("/predict", response_model=PredictionOutput)
def predict_sentiment(request: TextInput):
    cleaned_text = text_cleaner.clean_review(request.text)
    sentiment, confidence, all_scores = sentiment_predictor.predict(cleaned_text)
    return PredictionOutput(
        sentiment=sentiment, 
        confidence=confidence, 
        all_scores=all_scores
    )

@app.post("/predict-batch", response_model=List[PredictionOutput])
def predict_sentiment_batch(request: BatchTextInput):
    results = []
    for text in request.texts:
        cleaned_text = text_cleaner.clean_review(text)
        sentiment, confidence, all_scores = sentiment_predictor.predict(cleaned_text)
        results.append(PredictionOutput(
            cleaned_text=cleaned_text,
            sentiment=sentiment, 
            confidence=confidence, 
            all_scores=all_scores
        ))
    return results