top of page

Sentimen Analisis Deteksi Emosi Menggunakan CNN

Writer's picture: Adib Ahmad IstiqlalAdib Ahmad Istiqlal

Overview

Sentimen analisis deteksi emosi bertujuan untuk mendeteksi emosi, seperti kebahagiaan, frustrasi, kemarahan, kesedihan, dan sebagainya. Chaudhuri (2006) dalam mengatakan bahwa emosi sangat terkait dengan sentimen. Kekuatan sentimen sangat terhubung pada intensitas emosi. Misalnya, emosi ‘sangat bahagia’ menunjukkan sentimen sangat positif. Emosi sangat marah menunjukkan sentimen sangat negatif. Analisis emosi dalam teks, dari berita hingga postingan media sosial bermanfat untuk mengetahui aspirasi emosi pengguna media sosial pada sebuah topik percakapan dan akan memahamkan bagaimana emosi membentuk perilaku manusia. Pada kali ini akan dilakukan klasifikasi teks menggunakan CNN dengan 4 layers yaitu Embedding, Conv1D, Global Max Pooling, dan Dense dengan aktifasi relu dan softmax.



Business Understanding

Pada pernyataan yang telah dijelaskan, sehingga masalah yang diangkat adalah

  1. Bagaimana peforma CNN dengan 4 layer untuk melakukan klasifikasi teks?

Tujuan dari masalah yang diangkat adalah

  1. Mengetahui tingkat keakuratan model yang dihasilkan


Data Understanding

Dataset yang digunakan berasal dari IndoLU dengan memanfaatkan API yang tersedia dengan total 300 dataset. total tersebut digabungkan dengan 3 API (train, valid, test) yang nantinya total ini akan dipisahkan menjadi train dan valid. Untuk test sendiri dilakukan secara manual. Berikut proses extract data dari JSON menjadi data frame.


API

api_1 = 'https://datasets-server.huggingface.co/first-rows?dataset=indonlu&config=emot&split=train'
api_2 = 'https://datasets-server.huggingface.co/first-rows?dataset=indonlu&config=emot&split=validation'
api_3 = "https://datasets-server.huggingface.co/first-rows?dataset=indonlu&config=emot&split=test"
get_data = requests.get(api_1)
data = get_data.json()

Extract Data

tweet = []
label = []

#Api_1
for i in range(len(data['rows'])):
    tweet.append(data['rows'][i]['row']['tweet'])
    label.append(data['rows'][i]['row']['label'])
    
#Api_2
for j in range(len(data['rows'])):
    tweet.append(data['rows'][j]['row']['tweet'])
    label.append(data['rows'][j]['row']['label'])

#Api_3
for z in range(len(data['rows'])):
    tweet.append(data['rows'][z]['row']['tweet'])
    label.append(data['rows'][z]['row']['label'])

Data Frame

tweet = np.array(tweet)
label = np.array(label)
df = pd.DataFrame(data = [tweet, labelt]).T
df.columns = ['tweet','label']

Terlihat bahwasanya dataset terdiri dari 300 baris dan 2 kolom. Pada kolom label sendiri terdapat 5 kategori yaitu

  • 0 = Sadness

  • 1 = Anger

  • 2 = Love

  • 3 = Fear

  • 4 = Happy

Total masing-masing dari kategori tersebut ialah

Visualisasi di atas menyatakan bahwa kategori sadness merupakan kategori terbanyak dari kategori yang lain. Pada kategori 4 dan 2 memiliki frekuensi yang sama.


Data Preparation

Pada tahapan ini akan dilakukan cleansing data, stopword removal, stemming, tokenizing dan padded sequences.


Pada cleansing data akan dilakukan penghapusan tanda baca (punctuation), angka, dan multi whitespace (spasi yang berlebih).


Cleansing data

def hapus_punct(text):
    trans = str.maketrans("", "", string.punctuation)
    return text.translate(trans)

def hapus_angka(text):
    return re.sub(r"\d+","",text)

def hapus_multi_whitespace(text):
    return re.sub('\s+', ' ', text)

Proses

df['preprocessing'] = df['tweet'].apply(hapus_punct)
df['preprocessing'] = df['preprocessing'].apply(hapus_angka)
df['preprocessing'] = df['preprocessing'].apply(hapus_multi_whitespace)

Proses di atas menghasilkan kolom baru 'preprocessing' dengan apply pada fungsi yang sebelumnya sudah ditentukan. kolom preprocessing ini akan digunakan untuk melatih model nantinya dengan melewati serangkaian proses text preprocessing.

Jika diperhatikan pada baris kedua yang sebelumnya kata USERNAME diapit oleh tanda baca, saat ini sudah terhapus.


Selanjutnya akan dilakukan proses stopword. Proses ini akan menghilangkan semua kata yang terdaftar pada kamus stopwords Indonesia menggunakan library NLTK Corpus.


Stopwords

import nltk
from nltk.corpus import stopwords
stoplist = set(stopwords.words("indonesian"))
def remove_stopwords(text):
    return [teks.lower() for teks in text.split() if teks.lower() not in stoplist]

Proses

df['preprocessing'] = df.preprocessing.apply(remove_stopwords)

Hasil yang didapatkan nantinya berupa array yang berisi sekumpulan kata. Berikut hasil yang didapatkan

Perhatikan pada baris pertama, bahwasanya terdapat beberapa kata yang menghilang seperti kata "ini, adalah, hal" dan seterusnya.


Proses selanjutnya adalah stemming dimana proses akan melakukan pengambilan kata dasar yang berasal dari kamus dengan bantuan library Sastrawi. Proses stemming ini akan memanfaatkan hasil array yang didapatkan dari stopwords yang nantinya akan dipetakan kembali tanpa array.


Stemming

from Sastrawi.Stemmer.StemmerFactory import StemmerFactory

feature = StemmerFactory()
stemmer = feature.create_stemmer()

def stemmed_wrapper(term):
    return stemmer.stem(term)

term_dict = {}

for dokumen in df['preprocessing']:
    for term in dokumen:
        if term not in term_dict:
            term_dict[term] = ' '

for term in term_dict:
    term_dict[term] = stemmed_wrapper(term)

def get_stemmed_term(dokumen):
    return [term_dict[term] for term in dokumen]

def remove_array(dokumen):
    d = [word for word in dokumen]
    return " ".join(d)

Proses

df['preprocessing'] = df['preprocessing'].apply(get_stemmed_term)
df['preprocessing'] = df['preprocessing'].map(remove_array)

Pemetaan penghapusan array menggunakan sebuah fungsi remove_array dengan mengembalikkan kumpulan kata menjadi menggunakan join. Hasil yang didapatkan seperti berikut


Pada baris satu kata "membahagiakan" berubah menjadi "bahagia". Sampai proses ini akan dilanjutkan proses tokenizing dan pad sequences menggunakan Tensorflow dan Keras.

Sebelum itu, saya akan menambahkan sebuah kolom bernama panjang dokumen dimana 1 baris dianggap 1 dokumen.

df['panjang_dokumen'] = [len(dokumen) for dokumen in df['preprocessing']]

Proses yang dilakukan untuk melakukan tokenizing adalah menghitung total kosakata. Total ini akan digunakan sebagai total vocab pada hasil tokenizing.

def counter_word(text):
    count = Counter()
    for teks in text.values:
        for word in teks.split():
            count[word] += 1
    return count

counter = counter_word(df['preprocessing'])
panjang_token = len(counter)

Hasil panjang token ini adalah 1052. Hal ini berarti bahwa dari 300 dokumen setelah melewati tahapan cleansing, stopword, dan stemming menghasilkan 1052 vocab.


Setelah mengetahui total vocab, akan dilakukan tahapan split dataset menjadi train dan validasi. Total data train adalah 90% dari total dataset.

train_size = int(df.shape[0] * 0.9)
train_df = df[:train_size]
val_df = df[train_size:]

train_sentences = train_df.preprocessing.to_numpy()
val_sentences = val_df.preprocessing.to_numpy()

train_label = train_df.label.to_numpy().astype(np.int64)
val_label = val_df.label.to_numpy().astype(np.int64)

kolom preprocessing akan diubah menjadi array untuk setiap satu baris pada setiap 1 array dan pada kolom label akan dirubah menjadi array dengan tipe int 64, hal ini dikarenakan model tidak dapat menerima int 32. Setelah ini, data siap dilakukan tokenizing.

from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer(num_words = panjang_token)
tokenizer.fit_on_texts(train_sentences)

Dapat dilihat bahwa num_words = panjang_token dimana panjang_token ini didapatkan sebelumnya dan tokenizer dilakukan hanya pada data train. Hal ini bertujuan untuk tidak melakukan proses compile pada validasi yang nantinya berguna untuk menguji apakah data overfit atau underfit. Selanjutnya dilakukan pengubahan hasil tokenizing menjadi array dengan sequences.

train_sequences = tokenizer.texts_to_sequences(train_sentences)
val_sequences = tokenizer.texts_to_sequences(val_sentences)

Setelah merubah, kita perlu menyamakan panjang array masing-masing dokumen. Hal ini bertujuan agar model dapat melakukan prediksi dan setiap dokumen juga memiliki panjang yang beda-beda. Menyamakan panjang ini akan dilakukan dengan post, proses post ini bertujuan untuk menggantikan persamaan panjangnya nanti menggunakan angka 0 (nol).

from tensorflow.keras.preprocessing.sequence import pad_sequences
max_length = max(df['panjang_dokumen'])

train_padd = pad_sequences(train_sequences, maxlen = max_length, padding = 'post', truncating = 'post')
val_padd = pad_sequences(val_sequences, maxlen = max_length, padding = 'post', truncating = 'post')

Contoh hasilnya kurang lebih seperti berikut, dengan mengambil contoh dokumen pertama / baris pertama.

print(train_sentences[0])
print(train_sequences[0])
print(train_padd[0])

Modeling

Proses modeling akan menggunakan 4 layers dari CNN dengan layer pertama yaitu embedding, selanjutnya Conv1D, GlobalMaxpooling, dan Dense.

from tensorflow.keras import layers

model = keras.models.Sequential()
model.add(layers.Embedding(panjang_token, 32, input_length = max_length))
model.add(layers.Conv1D(filters = 16, kernel_size = 5, activation = 'relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(5, activation = 'softmax'))
model.summary()

Hasil dari model tersebut seperti berikut.


Setelah membentuk model, akan dilakukan callbacks dimana fungsi sebagai penstop model jika akurasi dan validasi akurasi mencapai target yang ditentukan (disini akan ditentukan akurasi dan validasi adalah 95%) dan ketika model mencapai target yang ditentukan, saya melakukan sebuah print perintah "BINGO".

class myCallback(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs={}):
        if(logs.get('accuracy') > 0.95 and logs.get('val_accuracy') > 0.95):
            print("\n\n BINGO")
            self.model.stop_training = True

callbacks = myCallback()

Setelah membuat fungis callbacks, selanjutnya proses training. Proses training menggunakan sparse_categorical_crossentropy sebagai loss dikarenakan memiliki label lebih dari 2, optimizer yang digunakan adam dan metrics yang digunakan adalah akurasi dikarenakan klasifikasi, jika regresi biasanya mse. Setelah melakukan compile, selanjutnya fit dengan verbose (kekuatan CPU yang digunakan adalah = 1 core) dan batch_size = 1, dimana setiap 1 epoch melatih semua data train.

model.compile(loss = 'sparse_categorical_crossentropy', optimizer='adam', metrics = ['accuracy'])
model.fit(train_padd, train_label, 
          epochs = 15, 
          validation_data = (val_padd, val_label), 
          verbose = 1, 
          batch_size = 1, 
          callbacks=[callbacks])

Dapat dilihat bahwa kemampuan model yang dibentuk dapat menghasil akurasi yang meningkat dari setiap epoch hingga mencapai 100 persen dari sisi training dan validasi pada epoch ketiga dan model terhenti (inilah fungsi dari callbacks).

Sekarang, saya akan mencoba dataset pelatihan dari dokumen ke 10 hingga ke 14.

print(train_sentences[10:15])
print(train_label[10:15])
print(np.array(tf.argmax(model.predict(train_padd[10:15]), axis = 1)))

Hasil didapatkan ternyata tepat dari kelima dataset yang digunakan sebagai ujicoba dan Fungsi tf.argmax ini digunakan untuk mengambil nilai probabilitas tertinggi dari kelima label, dimana jika tidak menggunakan tf.argmax akan menghasilkan sebuah 5 array yang setiap 1 array terdiri dari 5 nilai.


Evaluation

Pada proses ini akan dilakukan sebuah kalimat yang ditentukan dari user (misalnya :v) dan model akan memprediksi kalimat tersebut. Proses prediksi harus melalui tokenizing dan padded_sequences terlebih dahulu, hal ini bertujuan untuk membuat kalimat yang ditentukan sesuai dengan input length yang dimasukkan kedalam model (yaitu 210 dari max(df['preprocessing']).

teks_test = ['berat rindu jadi dibuatnya']
token_teks_test = tokenizer.texts_to_sequences(teks_test)
teks_test_ready = pad_sequences(token_teks_test, maxlen = max_length, padding = 'post', truncating = 'post')
hasil = np.array(tf.argmax(model.predict(teks_test_ready), axis = 1))
if hasil == 0:
    print("Prediksi menghasilkan = Sadness")
elif hasil == 1:
    print("Prediksi menghasilkan = Anger")
elif hasil == 2:
    print("Prediksi menghasilkan = Lover")
elif hasil == 3:
    print("Prediksi menghasilkan = Fear")
else:
    print("Prediksi menghasilkan = Happy")

Hasil dari kalimat yang dimasukkan adalah Sadness.




13 views0 comments

Recent Posts

See All

Comments


bottom of page