Latent Dirichlet Allocation(LDA) Kullanarak Nasıl Topic Modeling Yapılır?

Büşra Gökmen
10 min readMay 10, 2020

--

Topic Modeling(Konu Modelleme) Nedir?

Topic Modeling, bir metin belgesinde “topics(konular)” adı verilen kelime gruplarını bulmak için kullanılan denetimsiz(unsupervised) bir yaklaşımdır. Bu konular, sık sık birlikte ortaya çıkan ve genellikle ortak bir temayı paylaşan kelimelerden oluşur. Bu nedenle, önceden tanımlanmış kelime kümesiyle bu konular, belgenin tamamını en iyi şekilde tanımlamak için kelime grubu olarak kullanılabilir.

Topic Modeling, bize büyük metin verisi koleksiyonlarını organize etme, anlama ve özetleme yöntemleri sunar.

Metin belgesinden çıkarım yapmak için birçok yaklaşım vardır. Bu yazımda, Latent Dirichlet Allocation (LDA) adı verilen ve yaygın olarak kullanılan topic modellerinden birini de açıklayacağım.

Topic Modeling’in Kullanım Alanları Neler?

Bilgi çağında, her gün karşılaştığımız yazılı materyal miktarı, işleme kapasitemizin çok ötesindedir. Topic modeling, yapılandırılmamış(unstructured) metin gövdelerinin büyük koleksiyonlarını anlamamıza yardımcı olarak bilgileri düzenlememize ve sunmamıza yardımcı olur. Başlangıçta bir metin madenciliği aracı olarak geliştirilen topic modeling, genetik bilgi, görüntüler(image) ve ağlar(network) gibi verilerdeki öğretici yapıları tespit etmek için kullanılmış. Ayrıca biyoinformatik, NLP çalışmaları, chatbot çalışmaları ve akademik araştırmalar için de kullanılıyor. Hatta bu yıl AllenNLP’nin Kaggle’da düzenlediği COVID-19 Open Research Dataset Challenge için yazılan en iyi notebooklardan birinde COVID-19 literatür verilerini gruplamak için topic modeling kullanıldı.

Eğer isterseniz sevdiğiniz bir yazarın metinlerini de sınıflandırabilirsiniz.

LDA(Latent Dirichlet Allocation) Nedir?

Latent Dirichlet Allocation (LDA), her belgenin bir konu koleksiyonu olarak kabul edildiği ve belgedeki her kelimenin konulardan birine karşılık geldiği bir topic modeling örneğidir.

Dolayısıyla, bir belge(text data) verildiğinde LDA, belgeyi temel alarak her konu grubunu o grubu en iyi açıklayan bir dizi kelimenin olduğu konu gruplarına kümeler.

Örneğin, aşağıdaki ürün yorumlarını incelersek:

Yorum 1: A Five Star Book: Yeni okumayı bitirdim. Ortalama bir romantizm okumasını bekliyordum, ama bunun yerine tüm zamanların en sevdiğim kitaplarından birini buldum. Eğer romantizm romanlarının sevgilisi iseniz o zaman bunu okumak bir zorunluluktur.

Yorum 2: Lezzetli Kurabiye Karışımı: İlk defa bir kurabiye karışımı ile pişirmeyi denedim. Hamuru karıştırmak ÇOK zor olabilir. Bununla birlikte, böyle bir karışımda bileşenlerin oranında çok fazla esnekliğe sahipsiniz (biraz ekstra tereyağı eklemeyi seviyorum) .

Yorum 3: Bu kitabı okumaktan zevk aldım. Steven Wardell, yetenekli bir genç yazar ve Japonya’daki aile hayatını içeriden dışarıya çok güzel anlatmış. Harika bir kitap!

Bu durumda LDA her yorumu bir belge olarak görür ve bu belgelere karşılık gelen konuları bulur. Her konunun grubu, konuya yüzde katkısı ile birlikte bir dizi kelime içerir.

Yukarıdaki yorumlara kullanarak LDA konuları ve konulara ait kelimeleri yüzdeleriyle birlikte gruplar ve kelimelerin yüzdelik ilişkilerini belirtir:

Topic 1: 40% kitap, 30% okumak, 20% romantizm

Topic 2: 45% japonya, 30% okumak, 20% yazar

Topic 3: 30% kurabiye, 30% karışım, 20% lezzetli

Yukarıdan, Topic 3'ün yorum 2 ile, Topic 1 ve 2'nin ise Yorum 1 ve 3 ile kısmen ilişkili olduğunu söyleyebiliriz.

Daha iyi anlayabilmek için python ile LDA implementasyonuna bakalım.

Python ile LDA İmplementasyonu

  1. Verisetini import edelim
  2. Verisetindeki text datalara preprocess uygulayalım
  3. Gensim dictionary ve corpus oluşturalım
  4. Topic Model’i oluşturalım
  5. Sonuçları analiz edelim
  6. PyLDAvis ile topic gruplarını görselleştirelim
  7. Dokümanların(text data) dominant konularını belirleyelim

Verisetini import edelim:

Burada farklı müşterilerin ürünler hakkındaki yorumlarını içeren Reviews veri kümesini kullanacağız. Verisetimizden 8000 verilik bir dataframe oluşturabiliriz.

import pandas as pd
import numpy as np
#read the csv file
query_df = pd.read_csv(datafile,error_bad_lines=False)
query_df = query_df.iloc[:8000]#subset of dataframe
query_df['Review'] = query_df['Review'].astype(str)
query_df['Review'].head()
0                  3 yıldır kullanıyorum ve memnunum:)
1 3 yıldır kullanıyorum müthiş
2 Ürün bugün elime geçti çok fazla inceleme fırs...
3 Almaya karar verdim. Hemencecik geldi. Keyifle...
4 Günlük kullanımınızı çok çok iyi karsılıyor kı...
Name: Review, dtype: object

Verisetindeki text verilerine preprocess uygulayalım:

Preprocess için gerekli kütüphaneleri import edelim.

#text processing(text verisini normalize etmek)
import re
import string
import nltk
from gensim import corpora, models, similarities
from nltk.corpus import stopwords

Text verisini preprocessten geçirmek için yardımcı fonksiyonlar tanımlayabiliriz.

Text verisi üzerindeki noktalama işaretlerini temizlemek (punctuations) ve büyük harfleri küçük harflere çevirmek için initial_clean fonksiyonunu tanımlayabiliriz. Ve word_tokenize ile text verisindeki kelimeler ve semboller boşluğa göre ayrılır.

Eğer kelimelerin kök hallerini alıp bunları gruplandırmak istiyorsanız stemmer da kullanabilirsiniz.

def word_tokenize(sentence):acronym_each_dot = r"(?:[a-zğçşöüı]\.){2,}"
acronym_end_dot = r"\b[a-zğçşöüı]{2,3}\."
suffixes = r"[a-zğçşöüı]{3,}' ?[a-zğçşöüı]{0,3}"
numbers = r"\d+[.,:\d]+"
any_word = r"[a-zğçşöüı]+"
punctuations = r"[a-zğçşöüı]*[.,!?;:]"
word_regex = "|".join([acronym_each_dot,acronym_end_dot,suffixes,numbers,any_word,punctuations])sentence = re.compile("%s"%word_regex, re.I).findall(sentence)return sentence
def initial_clean(text):     text = re.sub("[^a-zA-Z ]", "", text)
text = text.lower() # lower case text
text = nltk.word_tokenize(text)
return text

Fonksiyonumuzun input verisine ve preprocessten sonraki outputuna bakalım.

text = "Son aldığım ürün henüz elime ulaşmadı"initial_clean(text)
['son', 'aldığım', 'ürün', 'henüz', 'elime', 'ulaşmadı']

Sonraki fonksiyonumuz remove_stop_words (), metin verilerinden tüm stopwords sözcüklerini kaldırır. Stopwords listesi temel olarak bir dilde en yaygın kullanılan kelimelerden oluşur.

Metin işleme algoritmalarında kullanıldıklarında gürültü veya dikkat dağıtıcı özellikler olarak değerlendirilebileceklerinden, bu kelimelerin metin verilerinden kaldırılması yaygındır.

#Türkçe için stopwords listesi
with open('stopwords-tr.txt', 'r') as f:
myList = [line.strip() for line in f]
def remove_stop_words(text):stop_words = myList 
return [word for word in text if word not in stop_words]

Yukarıdaki yardımcı fonksiyonların hepsini çağırdığımız apply_all() fonksiyonu ile verisetimizin üzerinde preprocess uygulayalım.

def apply_all(text):return remove_stop_words(initial_clean(deEmoji(text)))
import timet1 = time.time()
query_df['tokenized_texts'] = query_df['Text'].apply(apply_all) #kirli veri seti ->>>> normalize edilmiş veri seti
t2 = time.time()
print("prerocess ve tokenize için geçen süre", len(query_df), "texts:", (t2-t1)/60, "min") Preprocess ve tokenize uyguladığımız verilerimize bir bakalım.

Preprocess ve tokenize uyguladığımız verilerimize bir bakalım.

Gensim dictionary ve corpus oluşturalım:

LDA Gensim kütüphanelerini import edelim.

#LDA
import gensim
import pyLDAvis.gensim

LDA modeli oluştururken input olarak dictionary ve corpusa ihtiyacımız var. Dictionary ve corpusu oluştururken Gensimin LDA kütüphanesini kullanacağız.

Gensim kütüphanesi text verisindeki kelimeleri token olarak ifade eder ve her token için index numarası oluşturur ve dictionaryi içinde bu indexler “id” olarak yer alır. Yani dictionary kelimeler ve onlara ait “id” numaralarını barındıran bir sözlük yapısıdır. Corpus ise bu kelimelerin id’lerinden ve text verisi içindeki frekanslarından oluşur.

tokenized = query_df['tokenized_texts']dictionary = corpora.Dictionary(tokenized)dictionary.filter_extremes(no_below=1, no_above=0.8)corpus = [dictionary.doc2bow(tokens) for tokens in tokenized]print(corpus[:1])

corpusun ilk indexindeki (0,1)’de ifade edilmek istenen, word_id’si 0 olan kelimenin ilk text verisinde kaç kere tekrarlamış olduğu. Corpusun olayı kelime-frekans mapping yapması aslında.

Hadi corpusumuzun ilk indexine bir de kelimeli hali ile bakalım.

[[(dictionary[id], freq) for id, freq in cp] for cp in corpus[:1]]

output:

[[('kullanıyorum', 1), ('memnunum', 1), ('yıldır', 1)]]

Bu corpus outputuna Document Term Matrix(Doküman Terim Matrixi) de deniyor ve LDA topic modelimiz için input matrixi olacak.

Topic Model’i Oluşturalım:

#LDA
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics = 8, id2word=dictionary, passes=15)
ldamodel.save('mOdel.gensim')topics = ldamodel.print_topics(num_words=30)

Modelimizi oluştururken üstte belirttiğimiz gibi dictionary, num_topics (tüm corpus için oluşturduğumuz toplam topic sayısı) ve corpusu input olarak aldık. Daha sonra modeli mOdel.gensim olarak kaydettik. Her topic için bulunma oranı en fazla olan kelimeleri num_words parametresi ile yazdırabiliriz.

Sonuçları analiz edelim:

for topic in topics:
print(topic)

LDA algoritması Doküman-Topic matrixi ve Topic-Kelime matrixi oluşturur. Topic-Kelime matrixi kelimelerin topiclere dağılım olasılıklarını tutar.

Kelime gruplarımızın outputunu alalım.

(0, '0.024*"hızı" + 0.012*"usb" + 0.011*"yazma" + 0.009*"okuma" + 0.008*"ürün" + 0.007*"yeterli" + 0.007*"aldım" + 0.007*"veri" + 0.007*"aktarım" + 0.006*"sinyal" + 0.006*"mb" + 0.006*"gb" + 0.006*"30" + 0.005*"küçük" + 0.005*"tl" + 0.004*"yavaş" + 0.004*"bi" + 0.004*"güzel" + 0.004*"hız" + 0.004*"20" + 0.004*"normal" + 0.004*"ürünü" + 0.004*"kablosu" + 0.004*"mbs" + 0.004*"bellek" + 0.004*"dk" + 0.003*"bence" + 0.003*"16" + 0.003*"tercih" + 0.003*"hızlı"') (1, '0.008*"ssd" + 0.006*"cok" + 0.004*"tesekkurler" + 0.004*"ses" + 0.004*"urun" + 0.004*"bilgisayar" + 0.003*"eski" + 0.003*"pc" + 0.003*"usb" + 0.003*"kablo" + 0.003*"yeni" + 0.003*"karşılıyor" + 0.002*"işletim" + 0.002*"hızlandı" + 0.002*"d" + 0.002*"veriyor" + 0.002*"idare" + 0.002*"eder" + 0.002*"degil" + 0.002*"bi" + 0.002*"guzel" + 0.002*"laptop" + 0.002*"icin" + 0.002*"yorumları" + 0.002*"kullaniyorum" + 0.002*"gözle" + 0.002*"bilgisayarımın" + 0.002*"icinde" + 0.002*"uyumlu" + 0.002*"sistemi"')(2, '0.009*"aldım" + 0.008*"10" + 0.008*"kurulumu" + 0.006*"basit" + 0.006*"kolay" + 0.006*"sn" + 0.005*"sorun" + 0.005*"windows" + 0.005*"evin" + 0.005*"kurulum" + 0.005*"saniyede" + 0.005*"kulaklık" + 0.004*"modem" + 0.004*"ssd" + 0.004*"memnun" + 0.004*"aynı" + 0.004*"wifi" + 0.004*"ürünü" + 0.004*"oda" + 0.003*"kalitesi" + 0.003*"bağlantı" + 0.003*"başka" + 0.003*"yaptım" + 0.003*"pc" + 0.003*"duvar" + 0.003*"tane" + 0.003*"kablosu" + 0.003*"taktım" + 0.003*"kat" + 0.003*"tavsiye"') (3, '0.041*"hızlı" + 0.035*"ürün" + 0.034*"teşekkürler" + 0.025*"kargo" + 0.021*"cok" + 0.014*"elime" + 0.013*"hepsiburada" + 0.013*"güzel" + 0.013*"geldi" + 0.009*"memnun" + 0.008*"urun" + 0.008*"kaliteli" + 0.008*"uygun" + 0.007*"sipariş" + 0.007*"ulaştı" + 0.007*"teslimat" + 0.007*"kaldım" + 0.007*"sorunsuz" + 0.006*"fiyat" + 0.006*"süper" + 0.006*"teşekkür" + 0.005*"gönderi" + 0.005*"guzel" + 0.005*"günde" + 0.005*"hizli" + 0.004*"hepsiburadacom" + 0.004*"verdim" + 0.004*"kısa" + 0.004*"net" + 0.004*"icin"')(4, '0.016*"bi" + 0.016*"m" + 0.009*"internet" + 0.009*"r" + 0.008*"çekim" + 0.007*"çekiyor" + 0.007*"n" + 0.006*"gücü" + 0.006*"modem" + 0.006*"kurulum" + 0.006*"full" + 0.005*"kurulumu" + 0.005*"fi" + 0.005*"cihaz" + 0.005*"tavsi" + 0.005*"diş" + 0.005*"ürün" + 0.005*"li" + 0.005*"çi" + 0.005*"ederi" + 0.005*"kolay" + 0.004*"si" + 0.004*"wifi" + 0.003*"ni" + 0.003*"katta" + 0.003*"ş" + 0.003*"mbps" + 0.003*"k" + 0.003*"wps" + 0.003*"kablosuz"') (5, '0.014*"gb" + 0.008*"ssd" + 0.006*"ürünü" + 0.005*"eski" + 0.004*"aldım" + 0.004*"16" + 0.004*"disk" + 0.004*"pişman" + 0.004*"hdd" + 0.004*"10" + 0.004*"bilgisayarım" + 0.003*"yıllık" + 0.003*"32" + 0.003*"görür" + 0.003*"başladı" + 0.003*"bilgisayar" + 0.003*"alın" + 0.003*"başka" + 0.003*"kutu" + 0.003*"fazla" + 0.002*"yeni" + 0.002*"arkadaşlar" + 0.002*"etmem" + 0.002*"toshiba" + 0.002*"cihazın" + 0.002*"olmazsınız" + 0.002*"performans" + 0.002*"12" + 0.002*"ay" + 0.002*"sata"') (6, '0.047*"ürün" + 0.040*"tavsiye" + 0.038*"ederim" + 0.025*"güzel" + 0.018*"elime" + 0.014*"kullanışlı" + 0.013*"ürünü" + 0.013*"gün" + 0.012*"ulaştı" + 0.011*"hızlı" + 0.011*"aldım" + 0.009*"kullanıyorum" + 0.008*"kesinlikle" + 0.007*"kaliteli" + 0.007*"fiyatına" + 0.007*"küçük" + 0.006*"mükemmel" + 0.006*"sipariş" + 0.006*"gerçekten" + 0.006*"başarılı" + 0.005*"teşekkürler" + 0.005*"hemen" + 0.005*"teşekkür" + 0.005*"şık" + 0.005*"kısa" + 0.005*"fiyata" + 0.005*"kargo" + 0.005*"ediyorum" + 0.005*"fiyatı" + 0.004*"geldi"') (7, '0.031*"ürün" + 0.016*"tavsiye" + 0.016*"güzel" + 0.014*"mouse" + 0.014*"uygun" + 0.013*"ederim" + 0.013*"fiyata" + 0.012*"fiyat" + 0.009*"kaliteli" + 0.009*"küçük" + 0.008*"aldım" + 0.008*"ses" + 0.008*"kullanışlı" + 0.008*"kalitesi" + 0.008*"ürünü" + 0.008*"uzun" + 0.007*"fiyatı" + 0.007*"kullanıyorum" + 0.007*"pil" + 0.006*"performans" + 0.006*"gerçekten" + 0.006*"bence" + 0.006*"harika" + 0.005*"kesinlikle" + 0.005*"hızlı" + 0.005*"büyük" + 0.005*"kullanımı" + 0.005*"rahat" + 0.005*"fazla" + 0.004*"mükemmel"')

Outputtan gördüğümüz üzere Topic-Kelime matrixinde 8 topic ve her topicte onu en iyi tanımlayan 30 kelime var. Buna bakarak her topicte ona bağlı olan kelimelerle ilgili çıkarım yapabiliriz. Örneğin, Topic 4 internet, wifi ve kurulum ile ilgiliyken Topic 5 ssd, hdd ve bilgisayar ile ilgili diyebiliriz.

Doküman-Topic matrixi dokümanların topiclere dağılım olasılığını içeriyor. Hadi bu matrixi hangi dökümanın hangi topice ait olduğunu bulmak için kullanalım.

get_document_topics = ldamodel.get_document_topics(corpus[0])
print(get_document_topics)

Corpusumuzdaki ilk verimizi en iyi ifade eden topic grubu ve verinin bu gruba yakınlığını belirten orana bakalım:

[(0, 0.03126284), (1, 0.031250004), (2, 0.03126983), (3, 0.03126666), (4, 0.031250667), (5, 0.031258944), (6, 0.7811505), (7, 0.03129058)]

6. topic grubu 0.78'lik bir oranla verimiz için en uygun grup diyebiliriz.

PyLDAvis ile topic gruplarını görselleştirelim:

PyLDAvis, kullanıcıların bir topic modelindeki konuları bir metin verisi topluluğuna uygun olarak yorumlamalarına yardımcı olmak için tasarlanmış. LDA topic modelinden aldığı bilgilerle etkileşimli bir web tabanlı görselleştirme sunar.

Bu görselleştirmeyi python notebook ile kullanabileceğiniz gibi HTML dosyası olarak kaydedip paylaşabilirsiniz.

Gensim ve pyLDAvis ile lda modelini görselleştirebiliz:

#visualizing topics
lda_viz = gensim.models.ldamodel.LdaModel.load('mOdel.gensim')#load lda model
lda_display = pyLDAvis.gensim.prepare(lda_viz, corpus, dictionary, sort_topics=True)
pyLDAvis.display(lda_display)

Selected Topic kısmı ile incelemek istediğiniz topic grubunun numarasını girerek inceleyebilirsiniz. İlgili grubu en iyi ifade eden 30 kelimenin topiclerle arasındaki dağılımı gözlemleyebilirsiniz.

Dokümanların(text verisi) dominant konularını belirleyelim:

Şimdi çok daha iyi bir fikir edinmek ve sonuçlarımızı doğrulamak için her bir text verisine en uygun konuyu veren ve topic oranlarını da anahtar kelimeleriyle birlikte göreceğimiz bir output oluşturalım.

def dominant_topic(ldamodel,corpus,content):#Function to find the dominant topic in each query
sent_topics_df = pd.DataFrame()
# Get main topic in each query
for i, row in enumerate(ldamodel[corpus]):
row = sorted(row, key=lambda x: (x[1]), reverse=True)
# Get the Dominant topic, Perc Contribution and Keywords for each query
for j, (topic_num, prop_topic) in enumerate(row):
if j == 0: # => dominant topic
wp = ldamodel.show_topic(topic_num,topn=30)
topic_keywords = ", ".join([word for word, prop in wp])
sent_topics_df = sent_topics_df.append(pd.Series([int(topic_num), round(prop_topic,4), topic_keywords]), ignore_index=True)
else:
break
sent_topics_df.columns = ['Dominant_Topic', 'Perc_Contribution', 'Topic_Keywords']
contents = pd.Series(content)#noisy data
sent_topics_df = pd.concat([sent_topics_df, contents], axis=1)
return(sent_topics_df)
df_dominant_topic = dominant_topic(ldamodel=ldamodel, corpus=corpus, content=query_df['Review'])df_dominant_topic.head(10)

Burdan gördüğümüz üzere review text verisi alakalı topiclere dağılmış durumda.

Bonus:

Eğer modeliniz gpu desteği istiyorsa google colab’ten faydalanabilirsiniz:)

Modelin Colab Kodu için Github Gist:

--

--