Metin İşleme 1 — Eski Tarz Yöntemler (Bag of Words ve TFxIDF)

Geleneksel Metin İşleme Yöntemleri

Metin İçerikli Veriler

Günümüz teknolojilerinin ve İnternet altyapılarının geldiği noktada; yapısal olmayan metin içerikli veri miktarı her geçen gün artmaktadır. Yapısal olmayan bu veriler üzerinde Makine Öğrenmesi modellerini ve algoritmalarını uygulayabilmemiz için öncelikle metinlerin işlenmesi gerekmektedir. Kabaca adımlar şu şekildedir: “metin verileri” → “özniteliklere” (yani yapısal bir formata), öznitelikler → “vektörlere” çevrilir.

Makalede, biraz daha eski moda olarak tabir edebileceğimiz yöntemlere değineceğiz. Bu bağlamda, Bilgi Getirimi (BG) ve Makine Öğrenmesi alanlarında yıllardır kullanılan; (i) İki adet öznitelik temsili ve skorlama yöntemi (Terim Sayma, TFxIDF), (ii)Doğal Dil İşleme (DDİ) destekli öznitelik çıkarma teknikleri ve (iii) bir adet konu modelleme (Topic Modeling-LDA) yöntemi tanıtılacaktır. Elde edilen öznitelikler ve vektörler daha sonra Makine Öğrenmesi ve Derin Öğrenme modellerinin tasarımında rahatlıkla kullanılabilirler.


Veri seti

Çalışmada UCI Makine Öğrenimi Havuzunda yer alan TTC-3600 isimli veri setinden yararlanılacaktır. UCI Havuzu, deneysel Makine Öğrenmesi çalışmalarında kullanılmak üzere birçok veritabanını, veri setini, veri üreticilerini ve alan teorilerini bünyesinde barındırmaktadır.

Şekil 1. UCI Makine Öğrenimi Havuzu

Orijinal veri seti; 6 kategoriyi (ekonomi, kültür-sanat, sağlık, siyaset, spor ve teknoloji) içerip her kategoride 600 haber olmak üzere toplam 3600 dokümandan oluşmaktadır. TTC-3600 veri setindeki bu haberler, Mayıs-Temmuz 2015 tarihleri arasında, 6 tanınmış haber portalının (Hürriyet, Posta, İha, HaberTürk, Zaman ve Radikal) Rich Site Summary (RSS) özet akışlarından elde edilmiştir. Örnekler, 8 ünlü (a, e, ı, i, o, ö, u, ü) ve 21 ünsüz (b, c, ç, d, f, g, ğ, h, j, k, l, m, n, p, r, s, ş, t, v, y, z) harften oluşan Latin alfabesinden türetilmiş Türkçe dilindedir. Bu bağlantıyı kullanarak veri setini indirebilirsiniz.

Tablo 1'de görüldüğü üzere, bu makalede kullanılmak üzere, TTC-3600 veri setinden 8 tane örnek doküman parçası seçilmiştir.

Tablo 1. TTC-3600 Örnek Dokümanlar

Şimdi biraz kodlayalım…

İlk adımda, gerekli olan paketleri yükleyerek başlıyoruz: nltk, re, pandas ve numpy. Bazıları hakkında kısa bilgi verecek olursak:

  • NLTK — (Natural Language Toolkit) DDİ alanında araştırma ve geliştirme yapmak için açık kaynak kodlu Python modüllerini ve veri setlerini içeren bir kütüphanedir.
  • Re —(regular expression) düzenli ifadelerle çalışmayı sağlayan bir kütühanedir.
import re
import nltk
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
pd.options.display.max_colwidth = 8000
nltk.download('stopwords')

Ardından, makalede kullanacağımız örnek dokümanlarımızı içeren docs isimli bir doküman dizisi oluşturuyoruz.

# TR: Örnek Türkçe dokümanlar 
# EN: Sample documents in Turkish
docs = ['Açıklama projenin ortaklarından Rus enerji devi Gazprom dan geldi. Yıllık 63 milyar metreküp enerji',
'ilk günündeki 20 yarış heyecanlıydı, 109 puan toplayan Türkiye, 12 ülke arasında 9. oldu ve yarış tamamlandı',
'Cortananın yeni işletim sistemi Windows 10 un önemli bir parçası olduğunu belirten Microsoft ; Google Android ve iOS cihazlarındaki Dijital',
'Teknoloji devi Google, Android in MMM sürümüyle birlikte bir çok sistemsel hatasının düzeltileceğini',
'Siroz hastalığı ile ilgili detaylara dikkat çekerek, sağlıklı bir karaciğere sahip olmak hastalık için',
'Hastalık çoğu kez yıllarca doğru tanı konmaması veya ciddiye alınmaması sebebi ile kısırlaştırıcı etki yapabiliyor, kronik ağrı,',
'ilk 4 etaptan galibiyetle ayrılan 18 yaşındaki Razgatlıoğlu, Almanya daki yarışta 3. sırayı alarak ',
'Helal gıda pazarı sanki 860 milyar doların üzerinde'
]
# TR: Dokümanların sınıfları
# EN: Classes of documents
classes = ['ekonomi', 'spor', 'teknoloji', 'teknoloji', 'saglik', 'saglik', 'spor', 'ekonomi']

Doküman dizinin oluşturulmasından sonra pandas kütüphanesinin en önemli veri yapılarından birisi olan 2 boyutlu etiketlenmiş veri çerçevesi (data frame) oluşturuyoruz.

docs = np.array(docs)
df_docs = pd.DataFrame({'Dokuman': docs,
'Sinif': classes})
df_docs = df_docs[['Dokuman', 'Sinif']]
#df_docs
Şekil 2. df_docs değişkenin ekran çıktısı

Ön işleme (Pre-processing)

Ön işleme, “herhangi bir Makine Öğrenmesi veya Derin Öğrenme görevinden önce” metin içerikli dokümanların hazırlanmasında en kritik adımıdır. Tokenlara ayırma, gereksiz sık kullanılan kelimelerin (stop-words) atılması ve kelime köklerini bulma(stemming) en yaygın kullanılan ön işleme yöntemlerindendir.

  • Bir metin dokümanını analiz etmek için, ilk olarak tokenlara ayırma işlemi yapılmalı ve kelime grupları elde edilmelidir.
  • Tüm ortak ayırıcılar, işleçler, noktalama işaretleri ve yazdırılamayan karakterler kaldırılır.
  • Daha sonra, en sık kullanılan kelimeleri filtrelemeyi amaçlayan stop-words filtreleme gerçekleştirilir. Örnekler: “ama, belki, acaba”.
  • Son olarak, kelime hakkında dil-bilgisel veya sözcüksel bilgiler sunan son-eklerin çıkarılmasıyla morfolojik kökün elde edilmesini amaçlayan stemming ve / veya lemmatization uygulanır. Makalede, bu adımlar atlanmıştır.

Ön işlem ile ilgili kod bloku aşağıda gösterilmektedir. norm_doc fonksiyonu girdi olarak bir doküman alır ve yukarıda bahsedilen ön işleme adımlarını uygular.

WPT = nltk.WordPunctTokenizer()
stop_word_list = nltk.corpus.stopwords.words('turkish')
def norm_doc(single_doc):
# TR: Dokümandan belirlenen özel karakterleri ve sayıları at
# EN: Remove special characters and numbers
single_doc = re.sub(" \d+", " ", single_doc)
pattern = r"[{}]".format(",.;")
single_doc = re.sub(pattern, "", single_doc)
# TR: Dokümanı küçük harflere çevir
# EN: Convert document to lowercase
single_doc = single_doc.lower()
single_doc = single_doc.strip()
# TR: Dokümanı token'larına ayır
# EN: Tokenize documents
tokens = WPT.tokenize(single_doc)
# TR: Stop-word listesindeki kelimeler hariç al
# EN: Filter out the stop-words
filtered_tokens = [token for token in tokens if token not in stop_word_list]
# TR: Dokümanı tekrar oluştur
# EN: Reconstruct the document
single_doc = ' '.join(filtered_tokens)
return single_doc
norm_docs = np.vectorize(norm_doc) #like magic :)
normalized_documents = norm_docs(docs)
print(normalized_documents)

Şekil 3, ön işlemden sonra dokümanların nasıl değiştiğini göstermektedir. Noktalama işaretleri, özel karakterler ve sayılar kaldırılmıştır. Tüm kelimeler küçük harfe dönüştürülmüş ve boşluk ayraçları ile kelimeler belirlenmiştir (tokenization). NLTK kütüphanesinde tanımlanan Türkçe stop-word’ler kaldırılmıştır. Örneğin, son dokümandaki “sanki” sözcüğü filtrelenmiş ve çıkartılmıştır.

Şekil 3. Ön işlemden geçirilmiş dokümanlar

Öznitelik/Terim Temsili ve BoW Modeli

Kelime/Sözcük Çantası (BoW) modeli, bir dokümandaki terimlerin oluşum şeklini (Örneğin: terim sayılarını) belirten metnin temsil biçimidir. Bu modelde; terim pozisyonu ve sözcük sıralaması dikkate alınmaz.

Vektör Uzay Modeli (VUM), her bir metin dokümanının bir vektör olarak temsil edildiği gelişmiş bir BoW sürümüdür ve her bir boyut ayrı bir terime (özniteliğe) karşılık gelir. Dokümanda bir terim yer almışsa, ilgili doküman vektöründe terimin değeri sıfırdan farklı olur.

Makalede, BoW yaklaşımında terim-sayma amacıyla; bir metin dokümanı koleksiyonunu terim sayısı matrisine dönüştüren CountVectorizer sınıfını kullanacağız.

# TR: 1.Terim Sayma Adımları
# EN: 1.Term Counting Steps
from sklearn.feature_extraction.text import CountVectorizer
BoW_Vector = CountVectorizer(min_df = 0., max_df = 1.)
BoW_Matrix = BoW_Vector.fit_transform(normalized_documents)
print (BoW_Matrix)

BoW_Matrix için ekran çıktısının bazı seçilmiş bölümleri aşağıda gösterilmiştir.

Şekil 4. Dokümanlar ve terimleri

Şekil 4, iki dokümanı (Doc-0, Doc-1), terimlerini ve karşılık gelen terim sayılarını göstermektedir.

Doc-0'ın ilk iki satırı, 46 ve 48 numaralı indeksleri olan terimlerin bu belgeye ait olduğunu ve terim sayılarının 1 olduğunu belirtir.

Bu terimlerin değerlerini görmek için aşağıdaki kod blokunu çalıştırmamız gerekmektedir.

# TR: BoW_Vector içerisindeki tüm öznitelikleri al
# EN: Fetch al features in BoW_Vector
features = BoW_Transformer.get_feature_names()
print ("features[46]:" + features[46])
print ("features[48]:" +features[48])

Çıktılar aşağıdaki gibidir:

features[46]:metreküp
features[48]:milyar

Eğer bir veri çerçevesi (data frame) oluşturmak istersek; aşağıdaki kod blokunda da görüldüğü üzere, önce BoW_Matrix’i bir diziye dönüştürmeli ve onu da DataFrame’in girdisi olarak kullanmalıyız (terimlerin / özniteliklerin isimleriyle).

BoW_Matrix = BoW_Matrix.toarray()
# TR: Doküman -öznitelik matrisini göster
# EN: Print document by term matrice
BoW_df = pd.DataFrame(BoW_Matrix, columns = features)
BoW_df

Şekil 5, BoW_df isimli data_frame’in ekran çıktısını göstermektedir.

Şekil 5. BoW_df çıktısı

Veri çerçevesi hakkında daha detaylı bilgi almak için “info()” yöntemini de kullanmamız mümkündür.

print(BoW_df.info())

TF x IDF Skorlama Modeli

Terim sıklıklarının sayılması ile ilgili en önemli sorun, sık kullanılan terimlerin dokümanda baskın olmaları ve artık dokümanı temsil eder hale gelmeleridir.

Bu terimler çok değerli bilgi içermeseler bile, özellik kümesindeki diğer terimlerin etkisiz hale gelmesine neden olurlar.

Bu problemi çözmek için, “Terim Frekansı x Ters Belge Frekansı” anlamına gelen “TF x IDF” modelini ve skorlama yöntemini kullanabiliriz. Hesaplamada iki ölçüt kullanır: terim sıklığı (tf) ve ters belge sıklığı (idf). TF x IDF’nin matematiksel denklemleri şöyledir:

j’ninci dokümandaki i’ninci terim için TF x IDF skoru = TF(i, j) * IDF(i)
TF(i, j) = (Dokümandaki i’ninci terimin sıklığı) / (Dokümandaki toplam terim sayısı)
IDF(i) = log2(Toplam doküman sayısı/i’ninci terimi içeren doküman sayısı)

Kodlama adımında dokümanlarımızı bir TFxIDF özellik matrisine dönüştürebilmek için TfidfVectorizer sınıfını kullanacağız.

# TR: 2.TFxIdf Hesaplama Adımları
# EN: 2.TFxIdf Calculation Steps
from sklearn.feature_extraction.text import TfidfVectorizer
Tfidf_Vector = TfidfVectorizer(min_df = 0., max_df = 1., use_idf = True)
Tfidf_Matrix = Tfidf_Vector.fit_transform(normalized_documents)
Tfidf_Matrix = Tfidf_Matrix.toarray()
print(np.round(Tfidf_Matrix, 3))
# TR: Tfidf_Vector içerisindeki tüm öznitelikleri al
# EN: Fetch al features in Tfidf_Vector
features = Tfidf_Vector.get_feature_names()
# TR: Doküman - öznitelik matrisini göster
# EN: Print document by term matrice
Tfidf_df = pd.DataFrame(np.round(Tfidf_Matrix, 3), columns = features)
Tfidf_df

Yukarıda belirtilen terim temsili skorlama modellerinin farkını görmek için, Şekil 6'daki Tfidf_df çıktısını basitçe yorumlamamız yeterli olacaktır.

Örneğin, 2 indeksine sahip dokümanda;

  • android teriminin skor değeri, terim-sıklığı skorlama yönteminde 1 iken, TFxIDF skorlama yönteminde 0.210'dur.
  • yeni teriminin skor değeri, terim-sıklığı skorlama yönteminde yine 1 iken, TFxIDF skorlama yönteminde bu değer 0.251'dir.
Şekil 6. Tfidf_df çıktısı

Konu Modelleri (Topic Models)

Konu modelleri, bir doküman koleksiyonunda bulunup-çıkartılan anahtar kavramlar olarak adlandırılabilirler. Çıkartılan konular bir terim koleksiyonu olarak temsil edilirler. Metin dokümanlarının büyük bir kısmını özetlemek için çok değerlidirler, dahası, verilerde gizli kalıpları/anlamsallığı ortaya çıkarırlar. Latent Dirichlet Allocation (LDA), bir konu modeli örneği olup, 2003 yılında David Blei, Andrew Ng ve Michael I. Jordan tarafından tanıtılmıştır. Matematik ve İstatistik detayına bu makalede girmeye cesaret edemeyeceğim ama en azından bir tanım yapmaya çalışacak olursak; LDA, verilerdeki bazı bölümlerin neden benzer olduğunu tarif etmek amacıyla, gözlem gruplarının gözlemlenmemiş gruplar tarafından açıklanmasına izin veren bir üretken istatistik modelidir :)

Şekil 7. LDA konu modeli temsili gösterimi

Python’da gensim ve sklearn kütüphanelerini kullanarak LDA modelinin oluşturulması mümkündür. Yazımızda biz sklearn kullanacağız. LDA modeli, konuların sayısını otomatik olarak belirleyemediği için number_of_topics (n_components) değeri elle girilmelidir.

LDA modelinin diğer parametrelerini incelemek isterseniz bu linki kullanabilirsiniz. Aşağıdaki kod blokunda gösterildiği gibi LDA modeli girdi olarak BoW_Matrix alır ve bu mtarisi kullanarak daha küçük matrisler üretir.

#LDA: Topic Modeling
from sklearn.decomposition import LatentDirichletAllocation
number_of_topics = 4
BoW_Matrix = BoW_Vector.fit_transform(normalized_documents)
LDA = LatentDirichletAllocation(n_components = number_of_topics,
max_iter = 10,
learning_offset = 50.,
random_state = 0,
learning_method = 'online').fit(BoW_Matrix)
features = BoW_Vector.get_feature_names()
for t_id, topic in enumerate(LDA.components_):
print ("Topic %d:" % (t_id))
print (" ".join([features[i]
for i in topic.argsort()[:-number_of_topics - 1:-1]]))

Yukarıdaki kod bloku çalıştırılarak, belirlenen 4 konu için en ilgili 4 terim Şekil 8'de gösterilmiştir. Veri setimizdeki doküman sayımızın az olması bazı konu gruplarını anlamsızlaştırmıştır.

Şekil 8. Konular ve en ilgili 4 terimleri

Sonuç ve Tartışma

BoW (terim sayma, TFxIDF skorlama vb.) ve konu modelleri, metin sınıflandırması ve duyarlılık analizi (sentiment analysis) gibi birçok Makine Öğrenimi görevinde kullanılmaktadır. Anlaşılmaları ve uygulanmaları kolaydır. Ayrıca, DDİ tekniklerini uygulayan metinlerle çalışmak eğlencelidir. Bu avantajlara rağmen, modellerin bazı eksiklikleri bulunmaktadır:

  • Seyrek (sparse)gösterimlerin alan ve zaman karmaşıklığından dolayı, modellerin hesaplanması zordur.
  • Dokümandaki terimlerin pozisyonlarını ve sıralamalarını dikkate almazlar, dolayısıyla anlamsallık(semantic)kaybolur. LDA bu soruna bir nebze de olsa çaredir.

Bir sonraki yazımızda, metin işleme ve özellik çıkarma konusunda daha yeni ve gelişmiş yöntemleri (Doc2Vec, Word2Vec, FastText) tanıtmaya çalışacağız.


Not: Bu makalede kullanılan kaynak kod ve veri setine GitHub hesabımdan ulaşabilirsiniz.


Like what you read? Give Deniz KILINÇ a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.