derin-ogrenme-ile-duygu-analizi

Derin Öğrenme ile Duygu Analizi

Merhaba VBO okuyucuları, bir önceki yazımda (buradan erişebilirsiniz) sizlere konu tahminlemesi ile ilgili uygulama yaparak bilgi vermeye çalıştım. Bu yazımda size derin öğrenme ile duygu analizi hakkında bildiklerimi aktarmaya çalışacağım.

Sosyal medyada ya da herhangi bir forumda herhangi bir konu hakkında yorum yazarken o konuya olan yaklaşımımıza duygularımızı da katarız. Konu eğer sevdiğimiz bir konuysa o konuya yorum yazarken mutlu ve sevgi dolu cümleler, değilse o konuyla ilgili daha olumsuz veya hiçbir duygu yükü olmayan cümleler kurarız. Aldığımız ya da ilgilendiğimiz bir ürün hakkında da iyi kötü yorum yaparız ya da filmler hakkında. Peki bilgisayarlar bu yorumların iyi ya da kötü bir yorum olduğunu nasıl anlar?

Duygu analiziyle ilgili ilk başta araştırmalar yapmaya başladığımda kendi kendime hep neden makine öğrenmesi algoritmaları değilde derin öğrenme diye sorardım. Daha sonra makine öğrenmesinin metin sınıflamada kısa süreli bir hafızaya sahip olduğunu bu nedenle ilgili kelimelerin kısa süreli olarak hafızada tutulduğunu keşfettim. Doğal dil işlemede kullanılan özyinelemeli sinir ağları(RNN) algoritmaları ise cümlede yer alan kelimelerin kendisinden bir önceki ve bir sonraki kelimelerle birbirine bağlı olduğunu varsayarak onları birbiri ile ilişkili olduğunu hafızasında tutar. Bu sayede metin sınıflamasında çok daha iyi sonuçlar elde eder.

Özyinelemeli sinir ağları(RNN) uzun cümlelerde ve ifadelerde çok başarılı değildir bu nedenle çoğunlukla LSTM(Long Short Term Memory) algoritması daha çok kullanılmaktadır. Ben de bu çalışmamda Uzun-Kısa Vadeli Bellek(LSTM) algoritmasını kullandım.

Bu çalışma için veri setini kendim hazırladım. Veri setini iki sınıftan oluşturdum, olumlu ve olumsuz olarak. Bunlar veri seti içerisinde 0 ve 1 olarak etiket ismiyle yer alıyor. Veriyi aşağıdaki bağlantıdan indirebilirsiniz.

Uygulamayı Ubuntu 18.04 işletim sistemi üzerinde, Jupyter Notebook geliştirme ortamı ile python 3.6.9 ve tensorflow 1.14.0 versiyonlarını kullanarak geliştirdim.

Veri Ön İşleme

import numpy as np
import pandas as pd
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, Embedding,LSTM
from tensorflow.python.keras.optimizers import Adam
from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.models import load_model
from sklearn.model_selection import train_test_split
import re
import nltk
nltk.download('punkt')
WPT = nltk.WordPunctTokenizer()
stop_word_list = nltk.corpus.stopwords.words('turkish')
stop_word_list
['acaba', 'ama', 'aslında', 'az', 'bazı', 'belki', 'biri', 'birkaç', 'birşey', 'biz', 'bu', 'çok', 'çünkü', 'da', 'daha', 'de', 'defa', 'diye', 'eğer', 'en', 'gibi', 'hem', 'hep', 'hepsi', 'her', 'hiç', 'için', 'ile', 'ise', 'kez', 'ki', 'kim', 'mı', 'mu', 'mü', 'nasıl', 'ne', 'neden', 'nerde', 'nerede', 'nereye', 'niçin', 'niye', 'o', 'sanki', 'şey', 'siz', 'şu', 'tüm', 've', 'veya', 'ya', 'yani']
dataset = pd.read_excel('sentiment_analysis.xlsx' , sheet_name = 'Sheet1')
dataset.head()
Text Sentiment
0 bana beklediğim cevapları vermiyorsun 0
1 senden istediğim cevaplar bunlar değil 0
2 verdiğin yanıtlar doğru değil 0
3 duymak istediğim cevaplar bunlar değil 0
4 seni seviyorum bro 1

Öncelikle nltk kütüphanesi içerisinde bulunan Türkçe stopword’leri yükleyip verinin ilk halini inceliyoruz. Daha sonra veriyi temizleyerek hem varsa noktalama işaretlerinden arındırmak hem de stopword’lerden arındırma işlemlerini gerçekleştiriyoruz.

#verimizde bulunan noktalama işaretlerinin temizlenme işlemi
dataset['Text'] = dataset['Text'].apply(lambda x: re.sub('[,\.!?:()"]', '', x))

#büyük harflerin küçük harfe çevrilmesi
dataset['Text'] = dataset['Text'].apply(lambda x: x.lower())

#fazladan boşlukların temizlenmesi
dataset['Text'] = dataset['Text'].apply(lambda x: x.strip())

#cümleler içerisinde bulunan stopword'lerin kaldırılması
def token(values):
    words = nltk.tokenize.word_tokenize(values)
    filtered_words = [word for word in words if word not in stopwordlist]
    not_stopword_doc = " ".join(filtered_words)
    return not_stopword_doc

dataset['Text'] = dataset['Text'].apply(lambda x: token(x))

Gelin verimizin son haline bir bakalım.

dataset['Text']
0 bana beklediğim cevapları vermiyorsun
1 senden istediğim cevaplar bunlar değil
2 verdiğin yanıtlar doğru değil
3 duymak istediğim cevaplar bunlar değil
4 seni seviyorum bro
…
19018 j7 pro cihazı geldi fakat faturası gelmedi
19019 müşteri hizmetlerine ulaşamama sorunu
19020 para i̇adesi sorunu
19021 mağdur ediyor
19022 ürünü aldığı halde parayı i̇ade etmedi
Name: Text, Length: 19023, dtype: object

Artık verimizi temizlediğimize göre üzerinde işlem yapmaya başlayabiliriz. Verideki etiketlerle yorumlarımı birer değişkene atayarak eğitim ve test için veriyi ayırıyoruz.

data = dataset['Text'].values.tolist()
sentiment = dataset['Sentiment'].values.tolist()

x_train, x_test, y_train, y_test = train_test_split(data,sentiment,test_size = 0.2, random_state = 42)

Daha sonra verinin içerisinden en çok kullanılan 10000 kelimeye göre bir sözlük oluşturuyoruz. Tabi bu sayıyı belirlemek size kalmış veriye göre değişkenlik gösterebilir.

tokenizer = Tokenizer(num_words = 10000)
tokenizer.fit_on_texts(data)
tokenizer.word_index
{'bir': 1, 'ürün': 2, 'iyi': 3, 'güzel': 4, 'tavsiye': 5, 'gayet': 6, 'ederim': 7, 'hızlı': 8, 'aldım': 9, 'yok': 10, 'telefon': 11, 'olarak': 12, 'ürünü': 13, 'göre': 14, 'kadar': 15, ...

Burada her bir kelime kullanım sırasına göre numaralandırılıyor. Biliyoruzki cümlelerimizin uzunlukları birbirinden farklı fakat RNN algoritmaları için farklı uzunluklarda olan cümlelerle eğitemiyoruz ki farklı uzunluklarda cümleleri tahmin etmesi olanaksızlaşıyor. Bu nedenle veri setimizde bulunan bütün cümleleri aynı boyuta getirmemiz gerekiyor.

#her bir yorumu aynı boyuta getirmek gerekiyor RNN böyle çalışıyor.
x_train_tokens = tokenizer.texts_to_sequences(x_train)
x_test_tokens = tokenizer.texts_to_sequences(x_test)

Önce cümlelerimizdeki kelimeleri, yukarıda oluşturmuş olduğumuz sözlükte karşılığında hangi index’te yer alıyorsa onunla değiştiriyoruz.

num_tokens = [len(tokens) for tokens in x_train_tokens + x_test_tokens]
num_tokens = np.array(num_tokens)

Daha sonra verimizdeki cümlelerimizin her birinin kelime sayısını alıp birer liste oluşturuyoruz.

#burada token sayısı ayarlanırken ortalama etrafındaki değişkenlik dikkate alınarak bir sayı belirlenir
max_tokens = np.mean(num_tokens) + 2 * np.std(num_tokens)
max_tokens = int(max_tokens)

Sonra oluşturmuş olduğumuz listenin ortalamasını alıp daha sonra standart sapmasını 2 ile çarpıp bir değer elde ediyoruz. Bu değer bize verimizdeki cümlelerimizin dağılımı ile varsa aykırı uzunluğa sahip cümleleri ortalamaya indirgememize sağlayacak.

#belirlenen bu sayı verinin yüzde kaçını kapsadığına bakılır.
np.sum(num_tokens < max_tokens) / len(num_tokens)

Buradaki hesapla elde ettiğimiz değerin verimizdeki cümlelerin yüzde kaçını kapsadığını görebiliriz.

#veriler belirlenen token sayısına göre ayarlanır
x_train_pad = pad_sequences(x_train_tokens, maxlen=max_tokens)
x_test_pad = pad_sequences(x_test_tokens, maxlen=max_tokens)

Daha sonra cümlelerimizi yukarıda elde ettiğimiz değer kadar optimize ediyoruz ve eksik kalan kelime sayısı kadar 0 ekliyoruz. Örnek olarak aşağıdaki çıktıyı inceleyebilirsiniz.

x_train_pad[3027]
array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 545, 10, 1834, 15, 9899, 3943, 228, 1817, 195, 112, 6740, 179, 690, 644, 55, 10, 2689], dtype=int32)

Daha sonra token haline getirdiğimiz cümleleri tekrar cümle haline getirmek için bir fonksiyon yazmamız gerekiyor.

#tokenlaştırılan kelimeler tekrar string hale geitirilmek için bir fonksiyon yazılması gerekiyor.
idx = tokenizer.word_index
inverse_map = dict(zip(idx.values(), idx.keys()))

#tokenlaştırılan cümleyi tekrar string hale getirmek
def tokens_to_string(tokens):
    words = [inverse_map[token] for token in tokens if token!=0]
    text = ' '.join(words)
    return text

Burada daha önce verimizdeki kelimelerden oluşturmuş olduğumuz sözlüğümüzü tekrar kelime ve index’ini birleştirerek bir python sözlüğü haline getiriyoruz. Daha sonra oluşturmuş olduğumuz fonksiyonda sayısal olarak gelen cümlenin içerisindeki 0 değerleri atıp kelimeleri index’leriyle yer değiştiriyoruz. Sonrada boşlukla birleştirip tekrar cümle olarak geri döndürüyoruz.

Model Oluşturma

Artık verimizi modelimizde kullanmak için hazır. Sinir ağımızı oluşturmaya geçebiliriz.

#ardışık bir model
model = Sequential()

#her kelimeye karşılık gelen 50 uzunluğunda bir vektör oluşturulur. (Embedding matrisi)
embedding_size = 50

#matris kelime sayısı ve embedding büyüklüğünde olacak, yani 10bine 50 uzunluğunda. Buna da bir isim veriliyor name değişkeniyle.
model.add(Embedding(input_dim=10000,
                    output_dim=embedding_size,
                    input_length=max_tokens,
                    name='embedding_layer'))

#LSTM layerlerinin eklenmesi
## 16 nöronlu LSTM (16 outputlu , return_sequences=True demek output'un tamamını ver demek)
model.add(LSTM(units=16, return_sequences=True))
## 8 nöronlu LSTM (8 outputlu , return_sequences=True demek output'un tamamını ver demek)
model.add(LSTM(units=8, return_sequences=True))
## 4 nöronlu LSTM (4 outputlu , return_sequences=False yani default değer, tek bir output verecek)
model.add(LSTM(units=4))
## output layer'ı , görsel olarak gösterilirken dense layer kullanılır.  Tek bir nörondan oluştuğu için 1 yazılır.
model.add(Dense(1, activation='sigmoid'))

#optimizasyon algoritması, 1e-3 = 0.001 demek.
optimizer = Adam(lr=1e-3)

#modeli derlemek, loss fonksiyonu binary_crossentropy -> sadece 2 sınıf ama daha fazla sınıflar için categorical_crossentropy kullanılır.
#metrics -> modelin başarısını görmek için.
model.compile(loss='binary_crossentropy',
              optimizer=optimizer,
              metrics=['accuracy'])

Burada ardışık bir model oluşturarak içerisine bir embedding katmanı, 3 adet LSTM katmanı ve bir çıkış katmanı ekledim. Çıkış katmanımda aktivasyon fonksiyonu olarak sigmoid fonksiyonunu kullandım çünkü iki sınıflı bir veri setim var. Modelin özetine bakarsak;

model.summary()

Model: “sequential” _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_layer (Embedding) (None, 61, 50) 500000 _________________________________________________________________ lstm (LSTM) (None, 61, 16) 4288 _________________________________________________________________ lstm_1 (LSTM) (None, 61, 8) 800 _________________________________________________________________ lstm_2 (LSTM) (None, 4) 208 _________________________________________________________________ dense (Dense) (None, 1) 5 ================================================================= Total params: 505,301 Trainable params: 505,301 Non-trainable params: 0

Sinir ağımızı oluşturduğumuza göre artık eğitme işlemine geçebiliriz. Ben 5 kez eğitilmesini ve modelimin 256’şar şekilde beslenmesini istedim. Eğitim verimi tekrar 0.25 oranında bölerek test etmesini istedim.

#model eğitimi, bir defa eğitimden geçmesi -> epoch , batch_size -> 256'şar 256'şar beslenecek.
history = model.fit(x_train_pad, y_train,validation_split=0.25, epochs=5, batch_size=256)

Train on 11413 samples, validate on 3805 samples Epoch 1/5 11413/11413 [==============================] – 7s 581us/sample – loss: 0.6109 – acc: 0.7108 – val_loss: 0.5099 – val_acc: 0.7556 Epoch 2/5 11413/11413 [==============================] – 5s 425us/sample – loss: 0.4399 – acc: 0.8333 – val_loss: 0.3742 – val_acc: 0.8649 Epoch 3/5 11413/11413 [==============================] – 6s 503us/sample – loss: 0.2953 – acc: 0.9148 – val_loss: 0.2884 – val_acc: 0.9038 Epoch 4/5 11413/11413 [==============================] – 5s 433us/sample – loss: 0.2119 – acc: 0.9452 – val_loss: 0.2581 – val_acc: 0.9180 Epoch 5/5 11413/11413 [==============================] – 5s 413us/sample – loss: 0.1597 – acc: 0.9633 – val_loss: 0.2442 – val_acc: 0.9275

Gördüğünüz gibi oluşturmuş olduğumuz modelimizin train datasında başarısı bir hayli iyi. Bir de test datasına bakalım.

result = model.evaluate(x_test_pad, y_test)
3805/3805 [==============================] - 4s 927us/sample - loss: 0.2488 - acc: 0.9235

Modelimizi oluşturduk ve iki sınıflı duygu analizimizde test datamızda da güzel bir sonuç aldık. Gelin bir de sonuçlarımızı görselleştirerek bakalım.

Şekil 1 : Model Accuracy – Epoch

Görsele gördüğümüz gibi epoch sayımız 3 e geldiğinde modelimiz en iyi sonuçları vermektedir. Epoch sayısı arttıkça modelimizin eğitim verisinde başarısı artsada test verisinde artış giderek azalmakta.

Şekil 2 : Model Loss – Epoch

Buradaki görselde de modelimizin epoch sayısı 3’e geldiğinde test verisinin artık loss değerinin azalmadığını görüyoruz. Bu nedenle bu modelimiz için en iyi eğitim süresi 3 diyebiliriz.

Github hesabımdan kodların tamamına erişebilirsiniz.
https://github.com/serkanars/turkishsentimentanalysis

Bir yazının daha sonuna gelmiş bulunuyoruz, okuduğunuz için teşekkür ederim. Bir sonraki yazıda görüşmek üzere, veriyle kalın.

Yazar Hakkında
Toplam 7 yazı
Serkan Arslan
Serkan Arslan
Eskişehir Osmangazi Üniversitesi İstatistik bölümü mezunuyum , chatbot yapan bir şirkette Veri Analisti olarak çalışmaktayım, NLP üzerine araştırma ve çalışmalar yapmaktayım.
Yorumlar (3 yorum)
Onur
Onur Yanıtla
- 13:11

Hocam Merhaba,

Öncelikle emeğine sağlık.Ben sizin yukarıdaki datasetinizle çalışıp aynı adımları yaptım ancak aşağıdaki aşamaya geldiğimde hata alıyorum.

#model eğitimi, bir defa eğitimden geçmesi -> epoch , batch_size -> 256’şar 256’şar beslenecek.
history = model.fit(x_train_pad, y_train,validation_split=0.25, epochs=5, batch_size=256)

Konu hakkında yardımcı olabilir misiniz ?
İyi Çalışmalar.

haktan
haktan Yanıtla
- 23:51

history model fitte hata alanlar bir üst satırda
x_train_pad = np.array(x_train_pad)
y_train = np.array(y_train)
x_test_pad = np.array(x_test_pad)
y_test = np.array(y_test)
kodlarını çalıştırsın

Nurgül Aydın
Nurgül Aydın Yanıtla
- 03:44

Merhabalar çalışma için çok teşekkür ediyorum benim için çok faydalı oldu.
Ancak şöyle bir durum var verileri çoğalttım ve toplam 28000 verim var. Bunların 21000 eğitim ve 7000 test verisi olarak kullandım.
Epoch 5/5
64/64 [==============================] – 8s 127ms/step – loss: 0.1414 – accuracy: 0.9689 – val_loss: 0.3127 – val_accuracy: 0.8894
170/170 [==============================] – 2s 14ms/step – loss: 0.3138 – accuracy: 0.8892
eğitim yukarıdaki şekilde oluyor. Neden 64 görünüyor 21000 olması gerekmiyor mu veya 170 yerine 7000 olması gerekmiyor mu?
Yardımcı olursanız çok mutlu olurum.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

×

Bir Şeyler Ara