-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdata_analysis_sentiment_mypertamina.py
661 lines (508 loc) · 26.1 KB
/
data_analysis_sentiment_mypertamina.py
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# -*- coding: utf-8 -*-
"""data-analysis-sentiment-mypertamina.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/17KOp5ItYxIN4LJqUpNrS3qW8wZZJn_iw
# Analisis Sentimen App MyPertamina
- **Nama:** NANTHA SEUTIA
- **Email:** [email protected]
# Library
"""
# 1. Install library yang dibutuhkan
# !pip install google-play-scraper
# !pip install Sastrawi
# 2. Import Library Utama
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
import re
import nltk
import string
import csv
import requests
import pickle
import os
# 3. Download NLTK resources BEFORE importing word_tokenize
nltk.download('punkt')
nltk.download('punkt_tab')
nltk.download('stopwords')
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
# 4. Import Google Play Scraper untuk mengambil ulasan aplikasi
from google_play_scraper import app, reviews, Sort, reviews_all
# 5. Import Sastrawi untuk Stemming dan Stopword Removal
from Sastrawi.StopWordRemover.StopWordRemoverFactory import StopWordRemoverFactory
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
# 6. Import tambahan untuk pemrosesan teks dan visualisasi
from io import StringIO
from wordcloud import WordCloud
from sklearn.preprocessing import LabelEncoder
# 7. Library untuk TF-IDF dan BoW
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
# 8. Model dan Evaluasi
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score, confusion_matrix
# 9. Word2Vec untuk Representasi Teks
from gensim.models import Word2Vec
# 10. For Downloading file
# from google.colab import files
# 11. Cache for stemming
from functools import lru_cache
# 12. SMOTE
from imblearn.over_sampling import SMOTE
# !pip3 freeze > requirements.txt
"""# Pengumpulan Data
1. Web scraping menggunakan Google Play Scraper.
2. Dataset memiliki minimal 10.000 sampel.
3. Pemberian label kelas sentimen berdasarkan leksikon Bahasa Indonesia (positif, negatif, netral).
"""
# 2. Data Gathering
# URL file CSV di GitHub
github_url = "https://raw.githubusercontent.com/esnanta/data-analysis-sentiment-mypertamina/main/dataset/mypertamina_reviews.csv"
# Membaca CSV langsung dari URL menjadi DataFrame
reviews_df = pd.read_csv(github_url, encoding="utf-8")
# Tampilkan beberapa baris pertama untuk verifikasi
reviews_df.head()
# Tampilkan info data
reviews_df.info()
# Hapus baris yang memiliki nilai yang hilang
clean_review_df = reviews_df.dropna()
# Hapus baris duplikat (menggunakan hasil dropna sebelumnya)
clean_review_df = clean_review_df.drop_duplicates()
# Tampilkan informasi dataset setelah dibersihkan
clean_review_df.info()
"""# Preprocessing Data
1. Cleaning data
2. Case folding (lowercase)
3. Tokenization
4. Stopword Removal
"""
# 3. Processing Data
def cleaningText(text):
text = re.sub(r'@[A-Za-z0-9]+', '', text) # menghapus mention
text = re.sub(r'#[A-Za-z0-9]+', '', text) # menghapus hashtag
text = re.sub(r'RT[\s]', '', text) # menghapus RT
text = re.sub(r"http\S+", '', text) # menghapus link
text = re.sub(r'[0-9]+', '', text) # menghapus angka
text = re.sub(r'[^\w\s]', '', text) # menghapus karakter selain huruf dan angka
text = re.sub(r'(.)\1+', r'\1', text) # Hilangkan karakter berulang (contoh: "baguuuuus" -> "bagus"),
text = text.replace('\n', ' ') # mengganti baris baru dengan spasi
text = text.translate(str.maketrans('', '', string.punctuation)) # menghapus semua tanda baca
text = text.strip(' ') # menghapus karakter spasi dari kiri dan kanan teks
return text
def casefoldingText(text): # Mengubah semua karakter dalam teks menjadi huruf kecil
text = text.lower()
return text
def tokenizingText(text): # Memecah atau membagi string, teks menjadi daftar token
text = word_tokenize(text)
return text
def filteringText(text):
# Menggabungkan stopwords Bahasa Indonesia dan Bahasa Inggris
listStopwords = set(stopwords.words('indonesian'))
listStopwords_en = set(stopwords.words('english'))
listStopwords.update(listStopwords_en)
# Tambahan stopwords khusus untuk analisis sentimen ulasan MyPertamina
# Alasan:
# - Kata-kata seperti "pertamina", "aplikasi", "mypertamina", dan "update" muncul secara konsisten di setiap ulasan
# sehingga tidak memberikan informasi sentiment spesifik.
# - Ekspresi informal dan filler seperti "iya", "gak", "sih", "dong", "deh", "nih", dll. umumnya tidak
# memberikan konteks emosional yang signifikan.
# - Penambahan beberapa variasi kata negatif/informal seperti "nggak" dan kata penguat umum yang sering muncul.
filler_stopwords = [
"iya", "yaa", "gak", "nggak", "nya", "na", "sih", "ku", "di", "ga", "ya",
"gaa", "loh", "kah", "woi", "woii", "woy", "dong", "deh", "nih", "ap",
"tp", "sy", "sdh", "tdk", "gimana", "kali", "ok", "tolong", "moga", "blm",
"lg", "pa","sat", "min", "mhn", "kartu", "hp", "stnk", "mohon", "cs", "bm",
"klo", "masa", "kok", "knp", "kenapa", "gitu", "liat", "lihat", "dapet",
"dapat", "yah", "gmn", "utk", "trus"
]
general_stopwords = [
"update", "pertamina", "aplikasi", "mypertamina",
"aja", "udah", "belum", "sekali", "pakai", "pake", "data", "buka",
"scan", "upload", "baru", "versi", "spbu", "struk" , "foto", "baca",
"coba", "baru", "daftar", "masuk", "login", "barcode", "undian", "kupon",
"sistem", "akun", "ktp", "bayar", "hasil", "tingkat", "kendaraan",
"transaksi", "energi", "proses", "ulang", "moga", "ternyata", "kode",
"beli", "kalo", "habis", "pakai", "bikin", "mobil", "poin",
"verifikasi", "foto","baca","baru", "baik", "subsidi", "otp",
"download", "unduh", "verifikasi", "info", "informasi",
"nik", "tulis", "nomor", "orang", "koneksi", "bilang", "apk", "server"
]
listStopwords.update(filler_stopwords)
listStopwords.update(general_stopwords)
filtered = [word for word in text if word not in listStopwords]
return filtered
# Membuat objek stemmer hanya sekali
factory = StemmerFactory()
stemmer = factory.create_stemmer()
@lru_cache(maxsize=10000) # Cache untuk 10.000 kata unik
def cached_stem(word: str) -> str:
return stemmer.stem(word)
def stemmingText(words: list) -> list:
return [cached_stem(word) for word in words] # Proses setiap kata dengan caching
def toSentence(list_words): # Mengubah daftar kata menjadi kalimat
sentence = ' '.join(word for word in list_words)
return sentence
slangwords = {"@": "di", "abis": "habis", "wtb": "beli", "masi": "masih",
"wts": "jual", "wtt": "tukar", "bgt": "banget",
"maks": "maksimal", "aja":"saja", "yg":"yang",
"gak":"tidak","gk":"tidak", "ngak":"tidak", "maf":"maaf",
"sesuai banget":"sangat sesuai", "pas":"sesuai"}
def fix_slangwords(text):
words = text.split()
fixed_words = []
for word in words:
if word.lower() in slangwords:
fixed_words.append(slangwords[word.lower()])
else:
fixed_words.append(word)
fixed_text = ' '.join(fixed_words)
return fixed_text
"""# Preprocessing
1. Gunakan 'text_final' untuk model pra-latih seperti IndoBERT.
2. Gunakan 'text_akhir' untuk proses lexicon-based sentiment analysis dan model klasik.
3. Stemming bisa diabaikan jika menggunakan lexicon-based sentiment analysis dengan kamus yang tidak berbasis kata dasar.
"""
# 1. Cleaning: Hapus karakter khusus, URL, angka, dsb.
clean_review_df['text_clean'] = clean_review_df['content'].apply(cleaningText)
# 2. Normalisasi: Ubah semua teks menjadi huruf kecil
clean_review_df['text_casefolding'] = clean_review_df['text_clean'].apply(casefoldingText)
# 3. Perbaikan Slang: Ganti kata-kata slang dengan bentuk standarnya
clean_review_df['text_slangwords'] = clean_review_df['text_casefolding'].apply(fix_slangwords)
# --- Persiapan Teks untuk Lexicon-Based & Model Klasik ---
# 4. Tokenisasi: Memecah teks menjadi token (kata)
clean_review_df['text_tokenized'] = clean_review_df['text_slangwords'].apply(tokenizingText)
# 5. Filtering: Hapus stopwords menggunakan fungsi filteringText
clean_review_df['text_filtered'] = clean_review_df['text_tokenized'].apply(filteringText)
# 6. (Opsional) Stemming: Hanya jika ingin digunakan untuk model klasik seperti SVM/RandomForest
# clean_review_df['text_stemmed'] = clean_review_df['text_filtered'].apply(stemmingText)
# 7. Gabungkan token menjadi kalimat kembali
# Kolom untuk lexicon-based labeling atau model klasik (misalnya menggunakan TF-IDF)
clean_review_df['text_akhir'] = clean_review_df['text_filtered'].apply(toSentence)
"""# Pelabelan Data
1. Gunakan leksikon data positif dan negatif untuk Bahasa Indonesia
2. Kolom sasaran adalah text_akhir
3. Distribusi Sentimen
"""
# 4. Data Labelling
# Membaca data kamus kata-kata positif dari GitHub
lexicon_positive = dict()
response = requests.get('https://raw.githubusercontent.com/angelmetanosaa/dataset/main/lexicon_positive.csv')
# Mengirim permintaan HTTP untuk mendapatkan file CSV dari GitHub
if response.status_code == 200:
# Jika permintaan berhasil
reader = csv.reader(StringIO(response.text), delimiter=',')
# Membaca teks respons sebagai file CSV menggunakan pembaca CSV dengan pemisah koma
for row in reader:
# Mengulangi setiap baris dalam file CSV
lexicon_positive[row[0]] = int(row[1])
# Menambahkan kata-kata positif dan skornya ke dalam kamus lexicon_positive
else:
print("Failed to fetch positive lexicon data")
# Membaca data kamus kata-kata negatif dari GitHub
lexicon_negative = dict()
response = requests.get('https://raw.githubusercontent.com/angelmetanosaa/dataset/main/lexicon_negative.csv')
# Mengirim permintaan HTTP untuk mendapatkan file CSV dari GitHub
if response.status_code == 200:
# Jika permintaan berhasil
reader = csv.reader(StringIO(response.text), delimiter=',')
# Membaca teks respons sebagai file CSV menggunakan pembaca CSV dengan pemisah koma
for row in reader:
# Mengulangi setiap baris dalam file CSV
lexicon_negative[row[0]] = int(row[1])
# Menambahkan kata-kata negatif dan skornya dalam kamus lexicon_negative
else:
print("Failed to fetch negative lexicon data")
def sentiment_analysis_lexicon_indonesia(text):
score = 0
words = text.split() # Split the text into words!
for word in words:
if word in lexicon_positive:
score = score + lexicon_positive[word]
for word in words:
if word in lexicon_negative:
score = score + lexicon_negative[word]
polarity = ''
if score > 0: # Corrected the condition here (no need for >= 0)
polarity = 'positive'
elif score < 0:
polarity = 'negative'
else:
polarity = 'neutral' # Explicitly set neutral
return score, polarity
results = clean_review_df['text_akhir'].apply(sentiment_analysis_lexicon_indonesia)
results = list(zip(*results))
clean_review_df['polarity_score'] = results[0]
clean_review_df['polarity'] = results[1]
clean_review_df['polarity'].value_counts()
label_encoder = LabelEncoder()
# Fit dan transform kolom 'polarity' menjadi numerik
clean_review_df['label'] = label_encoder.fit_transform(clean_review_df['polarity'])
# Tampilkan mapping label
print(dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
# Tampilkan 5 baris teratas dengan label
clean_review_df[['content','polarity', 'label']].head()
# Calculate value counts of 'polarity' column
polarity_counts = clean_review_df['polarity'].value_counts()
# Create a DataFrame from the value counts (optional, but good practice)
df = pd.DataFrame({'polarity': polarity_counts.index, 'count': polarity_counts.values})
# Plotting the pie chart
plt.figure(figsize=(4, 3)) # Adjust figure size for better readability
# Define colors
colors = ['red', 'green', 'gray']
# Create the pie chart
plt.pie(df['count'], labels=df['polarity'], colors=colors,
autopct='%1.1f%%', startangle=140)
# Add a title
plt.title('Distribution of Sentiment Polarity in MyPertamina Reviews', fontsize=14)
# Ensure the circle's proportion
plt.axis('equal')
# Show the plot
plt.show()
"""Distribusi menunjukkan bahwa persepsi pengguna terhadap MyPertamina cenderung beragam, dengan mayoritas ulasan bersifat netral hingga negatif. Hal ini bisa menjadi masukan bagi pengembang aplikasi untuk meningkatkan pengalaman pengguna agar lebih banyak ulasan positif di masa mendatang."""
# Function to generate word cloud (no display inside the function)
def generate_wordcloud(text):
wordcloud = WordCloud(width=400, height=200, # Smaller size
background_color='white',
max_words=100, # Fewer words for clarity
min_font_size=8).generate(' '.join(text))
return wordcloud
# Separate text by polarity
positive_text = clean_review_df[clean_review_df['polarity'] == 'positive']['text_akhir']
negative_text = clean_review_df[clean_review_df['polarity'] == 'negative']['text_akhir']
neutral_text = clean_review_df[clean_review_df['polarity'] == 'neutral']['text_akhir']
# Generate word clouds
positive_wc = generate_wordcloud(positive_text)
negative_wc = generate_wordcloud(negative_text)
neutral_wc = generate_wordcloud(neutral_text)
# Create a figure with subplots
fig = plt.figure(figsize=(12, 6)) # Adjust figure size
# Create a gridspec layout for better spacing
gs = gridspec.GridSpec(1, 3, width_ratios=[1, 1, 1]) # 1 row, 3 columns
# Plotting the word clouds
ax2 = plt.subplot(gs[0])
ax2.imshow(negative_wc, interpolation='bilinear')
ax2.set_title('Negative', fontsize=12)
ax2.axis("off")
ax3 = plt.subplot(gs[1])
ax3.imshow(neutral_wc, interpolation='bilinear')
ax3.set_title('Neutral', fontsize=12)
ax3.axis("off")
ax1 = plt.subplot(gs[2])
ax1.imshow(positive_wc, interpolation='bilinear')
ax1.set_title('Positive', fontsize=12)
ax1.axis("off")
plt.tight_layout() # Adjust layout to prevent labels from overlapping
plt.suptitle("Sentiment Analysis Word Clouds", fontsize=16) # Overall title
plt.show()
"""# Pelatihan Machine Learning
1. Ekstraksi Fitur: Term Frequency-Inverse Document Frequency (TF-IDF)
"""
# 1. Ekstraksi Fitur dengan TF-IDF dengan parameter yang diperbarui
vectorizer = TfidfVectorizer(ngram_range=(1, 2), max_features=5000, min_df=5)
X = vectorizer.fit_transform(clean_review_df['text_akhir'])
y = clean_review_df['label']
# 2. Bagi dataset menjadi training dan testing
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. Karena X_train berupa sparse matrix, konversi ke dense untuk SMOTE
X_train_dense = X_train.toarray()
# 4. Terapkan SMOTE pada data training
smote = SMOTE(random_state=42)
X_train_res, y_train_res = smote.fit_resample(X_train_dense, y_train)
# 5. Ubah X_test menjadi dense untuk konsistensi
X_test_dense = X_test.toarray()
# --- Evaluasi Model SVM ---
# 6. Model SVM dengan class_weight='balanced'
svm_model_tfidf = SVC(kernel='linear', class_weight='balanced', random_state=42)
svm_model_tfidf.fit(X_train_res, y_train_res)
# Evaluasi pada Training Dataset
y_pred_svm_train = svm_model_tfidf.predict(X_train_res)
accuracy_svm_train = accuracy_score(y_train_res, y_pred_svm_train)
print("Akurasi Model (SVM) pada Training dengan SMOTE:", accuracy_svm_train)
print("\nLaporan Klasifikasi (SVM) pada Training dengan SMOTE:\n",
classification_report(y_train_res, y_pred_svm_train))
# Evaluasi pada Testing Dataset
y_pred_svm_test = svm_model_tfidf.predict(X_test_dense)
accuracy_svm_test = accuracy_score(y_test, y_pred_svm_test)
print("Akurasi Model (SVM) pada Testing dengan SMOTE:", accuracy_svm_test)
print("\nLaporan Klasifikasi (SVM) pada Testing dengan SMOTE:\n",
classification_report(y_test, y_pred_svm_test))
# --- Evaluasi Model RandomForest ---
# 7. Model RandomForest dengan class_weight='balanced'
rf_model_tfidf = RandomForestClassifier(n_estimators=400, random_state=42, class_weight='balanced')
rf_model_tfidf.fit(X_train_res, y_train_res)
# Evaluasi pada Training Dataset
y_pred_rf_train = rf_model_tfidf.predict(X_train_res)
accuracy_rf_train = accuracy_score(y_train_res, y_pred_rf_train)
print("Akurasi Model (RandomForest) pada Training dengan SMOTE:", accuracy_rf_train)
print("\nLaporan Klasifikasi (RandomForest) pada Training dengan SMOTE:\n",
classification_report(y_train_res, y_pred_rf_train))
# Evaluasi pada Testing Dataset
y_pred_rf_test = rf_model_tfidf.predict(X_test_dense)
accuracy_rf_test = accuracy_score(y_test, y_pred_rf_test)
print("Akurasi Model (RandomForest) pada Testing dengan SMOTE:", accuracy_rf_test)
print("\nLaporan Klasifikasi (RandomForest) pada Testing dengan SMOTE:\n",
classification_report(y_test, y_pred_rf_test))
# --- Menampilkan Confusion Matrix secara berdampingan untuk Testing Dataset ---
cm_svm = confusion_matrix(y_test, y_pred_svm_test)
cm_rf = confusion_matrix(y_test, y_pred_rf_test)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Confusion Matrix untuk Model SVM pada Testing Dataset
sns.heatmap(cm_svm, annot=True, fmt='d', cmap='Blues', ax=axes[0])
axes[0].set_title("Confusion Matrix - SVM + TF-IDF (Testing)")
axes[0].set_xlabel("Prediksi")
axes[0].set_ylabel("Aktual")
# Confusion Matrix untuk Model RandomForest pada Testing Dataset
sns.heatmap(cm_rf, annot=True, fmt='d', cmap='Oranges', ax=axes[1])
axes[1].set_title("Confusion Matrix - RandomForest + TF-IDF (Testing)")
axes[1].set_xlabel("Prediksi")
axes[1].set_ylabel("Aktual")
plt.tight_layout() # Agar plot tidak saling tumpang tindih
plt.show()
"""2. Ekstraksi Fitur: Word2Vec"""
# 1. Ekstraksi Fitur dengan Word2Vec
sentences = [text.split() for text in clean_review_df['text_akhir']]
w2v_model = Word2Vec(sentences, vector_size=100, window=5, min_count=2, workers=4)
def get_word2vec_features(text):
words = text.split()
word_vectors = [w2v_model.wv[word] for word in words if word in w2v_model.wv]
if not word_vectors:
return np.zeros(100)
return np.mean(word_vectors, axis=0)
features_list = [get_word2vec_features(text) for text in clean_review_df['text_akhir']]
X = np.vstack(features_list)
y = clean_review_df['label']
# 2. Bagi dataset menjadi training dan testing
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. Terapkan SMOTE pada data training (Word2Vec sudah berupa data dense)
smote = SMOTE(random_state=42)
X_train_res, y_train_res = smote.fit_resample(X_train, y_train)
# 4. Model RandomForest dengan class_weight='balanced'
rf_model_word2vec = RandomForestClassifier(n_estimators=100, random_state=42, class_weight='balanced')
rf_model_word2vec.fit(X_train_res, y_train_res)
# --- Evaluasi pada Training Dataset ---
y_pred_rf_train = rf_model_word2vec.predict(X_train_res)
accuracy_rf_train = accuracy_score(y_train_res, y_pred_rf_train)
print("Akurasi Model (RandomForest dengan Word2Vec & SMOTE) pada Training:", accuracy_rf_train)
print("\nLaporan Klasifikasi (Training):\n", classification_report(y_train_res, y_pred_rf_train))
# --- Evaluasi pada Testing Dataset ---
y_pred_rf_test = rf_model_word2vec.predict(X_test)
accuracy_rf_test = accuracy_score(y_test, y_pred_rf_test)
print("Akurasi Model (RandomForest dengan Word2Vec & SMOTE) pada Testing:", accuracy_rf_test)
print("\nLaporan Klasifikasi (Testing):\n", classification_report(y_test, y_pred_rf_test))
# --- Confusion Matrix untuk Testing Dataset ---
cm_rf = confusion_matrix(y_test, y_pred_rf_test)
plt.figure(figsize=(6, 5))
sns.heatmap(cm_rf, annot=True, fmt='d', cmap='Oranges')
plt.title("Confusion Matrix - RandomForest + Word2Vec (Testing)")
plt.xlabel("Prediksi")
plt.ylabel("Aktual")
plt.show()
"""# Testing
Output berupa kelas kategorikal (contoh: negatif, netral, dan positif).
"""
# Simpan model SVM dengan TF-IDF
with open('svm_model_tfidf.pkl', 'wb') as file:
pickle.dump(svm_model_tfidf, file)
# Simpan model Random Forest dengan TF-IDF
with open('rf_model_tfidf.pkl', 'wb') as file:
pickle.dump(rf_model_tfidf, file)
# Simpan model Random Forest dengan Word2Vec
with open('rf_model_word2vec.pkl', 'wb') as file:
pickle.dump(rf_model_word2vec, file)
# Simpan TF-IDF Vectorizer
with open('tfidf_vectorizer.pkl', 'wb') as vectorizer_file:
pickle.dump(vectorizer, vectorizer_file)
# Simpan Word2Vec model
w2v_model.save("word2vec_model.bin")
print("Semua model telah disimpan!\n")
# ========================
# MEMUAT KEMBALI MODEL
# ========================
# Load model SVM TF-IDF
with open('svm_model_tfidf.pkl', 'rb') as file:
loaded_svm_model_tfidf = pickle.load(file)
# Load model Random Forest TF-IDF
with open('rf_model_tfidf.pkl', 'rb') as file:
loaded_rf_model_tfidf = pickle.load(file)
# Load model Random Forest Word2Vec
with open('rf_model_word2vec.pkl', 'rb') as file:
loaded_rf_model_word2vec = pickle.load(file)
# Load TF-IDF Vectorizer
with open('tfidf_vectorizer.pkl', 'rb') as vectorizer_file:
loaded_vectorizer = pickle.load(vectorizer_file)
# Load Word2Vec model
loaded_w2v_model = Word2Vec.load("word2vec_model.bin")
# ========================
# INFERENCE (PREDIKSI)
# ========================
# Contoh review baru yang akan diprediksi
new_reviews = [
"Aplikasi ini sangat jelek, banyak bug", # Negatif
"Saya sangat suka aplikasi ini, keren!", # Positif
"Aplikasinya biasa saja, tidak buruk", # Netral
"Banyak fitur yang tidak berfungsi", # Negatif
"Aplikasi sangat membantu pekerjaan saya", # Positif
"Tidak terlalu bagus tapi bisa digunakan" # Netral
]
# Transformasi review baru dengan TF-IDF dan konversi ke dense array
X_new_tfidf = loaded_vectorizer.transform(new_reviews).toarray()
# 1. Prediksi menggunakan TF-IDF + SVM
pred_svm_tfidf = loaded_svm_model_tfidf.predict(X_new_tfidf)
# 2. Prediksi menggunakan TF-IDF + RandomForest
pred_rf_tfidf = loaded_rf_model_tfidf.predict(X_new_tfidf)
# 3. Prediksi menggunakan Word2Vec + RandomForest
def get_word2vec_features(text):
words = text.split()
word_vectors = [loaded_w2v_model.wv[word] for word in words if word in loaded_w2v_model.wv]
if len(word_vectors) == 0:
return np.zeros(100) # Sesuaikan dengan vector_size=100
return np.mean(word_vectors, axis=0)
# Ekstraksi fitur Word2Vec untuk review baru
X_new_word2vec = np.vstack([get_word2vec_features(text) for text in new_reviews])
# Prediksi menggunakan RandomForest dengan Word2Vec
pred_rf_word2vec = loaded_rf_model_word2vec.predict(X_new_word2vec)
# Mapping Label Sentimen
label_mapping = {0: "Negatif", 1: "Netral", 2: "Positif"}
# ========================
# MENAMPILKAN HASIL PREDIKSI
# ========================
for review, label_svm, label_rf_tfidf, label_rf_word2vec in zip(new_reviews, pred_svm_tfidf, pred_rf_tfidf, pred_rf_word2vec):
print(f"Review: {review}")
print(f" Prediksi SVM + TF-IDF: {label_mapping[label_svm]}")
print(f" Prediksi Random Forest + TF-IDF: {label_mapping[label_rf_tfidf]}")
print(f" Prediksi Random Forest + Word2Vec: {label_mapping[label_rf_word2vec]}\n")
"""# Summary
1. SVM dengan TF-IDF + SMOTE
* Akurasi tertinggi di antara ketiga model, yaitu 92,26%.
* Performa precision, recall, dan f1-score yang merata di atas 0,90 untuk setiap kelas. Kelas 1 memiliki recall tertinggi (0,95), sedangkan kelas 2 memiliki precision tertinggi (0,94).
* Model ini menunjukkan kemampuan klasifikasi yang kuat dan seimbang, sehingga sangat efektif untuk memisahkan ketiga kelas sentimen.
* Berdasarkan diagram confusion matrix, jumlah misclassification cenderung sedikit salah memprediksi pada setiap kelas, khususnya kelas 0 dan kelas 2, sehingga menghasilkan akurasi keseluruhan yang lebih tinggi.
2. RandomForest dengan TF-IDF + SMOTE
* Mencapai akurasi 91,46%, sedikit di bawah SVM namun masih tergolong tinggi.
* Kelas 1 memiliki kinerja yang sangat baik dengan recall sebesar 0,96, menandakan model sangat handal dalam menangkap data pada kelas tersebut.
* Kelas 2 juga terklasifikasi cukup baik dengan precision 0,93, menandakan tingkat kesalahan prediksi yang rendah saat model memprediksi kelas 2.
3. Confusion Matrix TF-IDF
* Dari segi jumlah misclassification, SVM cenderung lebih sedikit salah memprediksi pada setiap kelas, khususnya kelas 0 dan kelas 2, sehingga menghasilkan akurasi keseluruhan yang lebih tinggi.
* Meskipun kinerja SVM lebih baik di beberapa kelas, RandomForest terlihat cukup akurat dalam memprediksi kelas 1 (dengan sedikit kesalahan memprediksi ke kelas 0 atau 2).
* Namun, RandomForest mengalami lebih banyak misclassification pada kelas 0 dan kelas 2 dibandingkan SVM.
* Pada kedua model, kesalahan prediksi paling sering terjadi antara kelas 0 ↔ 1 dan 1 ↔ 2. Hal ini bisa menandakan bahwa teks pada kelas-kelas tersebut memiliki kemiripan tertentu (misalnya ekspresi netral dan positif, atau netral dan negatif), sehingga model terkadang kesulitan membedakannya.
4. RandomForest dengan Word2Vec + SMOTE
* Akurasi 78,93%, lebih rendah dibandingkan dua model berbasis TF-IDF.
* Kelas 1 menunjukkan precision tertinggi (0,92), sementara kelas 0 memiliki recall tertinggi (0,85). Hal ini menunjukkan masih ada tantangan dalam menyeimbangkan performa antarkelas.
* Penggunaan Word2Vec mungkin memerlukan penyesuaian atau fine-tuning lebih lanjut agar dapat menangkap konteks dan karakteristik teks secara lebih optimal, khususnya dibandingkan pendekatan TF-IDF.
5. Confusion Matrix Word2Vec
* Model bisa mengklasifikasikan sebagian besar sampel dengan benar, tetapi masih terdapat cukup banyak kesalahan prediksi, terutama untuk kelas 2.
* Model sering salah mengklasifikasikan kelas 2 sebagai kelas 0 (199 kasus) atau kelas 1 (40 kasus).
* Hal ini menunjukkan bahwa representasi fitur Word2Vec mungkin belum cukup membedakan teks di kelas 2 dengan kelas lain.
* Kelas 0 memiliki 897 prediksi benar, tetapi ada 121 kasus yang salah diklasifikasikan sebagai kelas 2.
* Ini menunjukkan kemungkinan beberapa teks di kelas 0 memiliki kemiripan dengan teks di kelas 2 menurut Word2Vec.
* Kelas 1 memiliki 156 kasus salah diprediksi sebagai kelas 0 dan 101 kasus salah diprediksi sebagai kelas 2.
* Ini menunjukkan bahwa Word2Vec mungkin masih mengalami kesulitan dalam membedakan nuansa dari kelas netral (1) dengan kelas lain.
"""