Python — FuzzyWuzzy Kütüphanesi ile String Eşleme
Bazı projelerde elimizdeki string ifadelerin birbirleriyle olan benzerliklerini anlamak bizim için önem arz eder. Bu tarz durumlar için farklı çözüm yöntemleri mevcuttur. Bu yazıda bu çözümlerden birisi olan FuzzyWuzzy kütüphanesi açıklanmıştır.
Öncelikle bu kütüphanenin temel aldığı Levenshtein Mesafe (Levenshtein Distance) Algoritmasını kısaca inceleyelim. Levenshtein algoritması Vladimir Levenshtein tarafından geliştirilmiştir. Kelimeler veya cümleler arasındaki benzerliklerin derecesini elde etmemizi sağlayan bu algoritma günümüzde özellikle doğal dil işleme problemlerinde sıkça kullanılmaktadır.
Levenshtein Algoritmasında 3 temel işlem vardır.
- Replace (Değiştirme)
- Insert (Eklemek)
- Delete (Silmek)
Bu 3 işlem kullanılarak iki kelime arasındaki Levenshtein uzaklığı belirlenir. Örnek olarak “Ocak” ve “Mart” kelimeleri arasındaki Levenshtein uzaklığını bulmak isteyelim.
Yukarıdaki tablo incelendiğinde ilk kolonda boş string bir ifadenin seçtiğimiz kelimeye olan uzaklığını görebilir. İkinci kolona geçtiğimizde ise “O” harfinin önce“M” harfine sonra “MA”, “MAR” ve “MART” ifadelerine dönüştürülmesi için gereken minimum işlem sayısı incelenebilir. Her bir temel işlem 1 adım olarak kabul edilir. Bu adımların toplam sayısı o dizilimlerin Levenshtein Mesafesini bize verir. Bu örnekte “Mart” ve “Ocak” kelimelerinin Levenshtein uzaklığını 4 olarak buluruz.
Diğer bir örnek olarak birbirine çok daha yakın iki kelime olan “KAYAK” ve “KAVAK” ifadelerinin Levenshtein mesafesine baktığımızda uzaklığın 1 olduğunu görebiliriz.
FuzzyWuzzy
Yazının esas konusu olan FuzzyWuzzy kütüphanesi yukarıda örneklerle incelenen Levenshtein Distance metriğini temel alarak hazırlanmıştır. Bu uzaklık kullanılarak kelime veya cümle dizilimlerinin benzerlik düzeyleri elde edilir. FuzzyWuzzy kütüphanesinin bazı sık kullanılan fonksiyonlarına göz atacak olursak;
- Ratio
fuzz.ratio("FuzzyWuzzy", "fuzzywuzzy")>>>80
fuzz.ratio("FuzzyWuzzy", "Wuzzy")>>>67
Bu fonksiyon büyük-küçük harf duyarlılığına sahip olduğu için aynı harfleri kullansakta bize sonuç olarak 80 değerini döndürdü.
- Partial Ratio
fuzz.partial_ratio("FuzzyWuzzy", "fuzzywuzzy")>>>80
fuzz.partial_ratio("FuzzyWuzzy", "Wuzzy")>>>100
Partial Ratio fonksiyonu Ratio fonksiyonuna benzemekle beraber eğer bir dizilimin diğer bir dizilim içerisinde geçmesi durumunun kritik olduğu problemlerde tercih edilebilir.
- Token Sort Ratio
fuzz.token_sort_ratio("Python'da FuzzyWuzzy kütüphanesi",
"FuzzyWuzzy kütüphanesi Python'da")>>>100fuzz.token_sort_ratio("Python'da FuzzyWuzzy kütüphanesi",
"FuzzyWuzzy kütüphanesi Python'da FuzzyWuzzy")>>>85
Token Sort Ratio fonksiyonu kelimeleri sıralayıp sonra benzerliklerini kontrol eden bir yapıya sahiptir. Yukarıdaki örnekte görüldüğü gibi cümledeki kelimelerin yerlerinin değiştirilmesine rağmen benzerlik oranı 100 çıkmıştır. Ancak aynı kelime cümleden birden fazla kez kullanıldığında bu oran düşmüştür.
- Token Set Ratio
fuzz.token_set_ratio("Python'da FuzzyWuzzy kütüphanesi",
"FuzzyWuzzy kütüphanesi Python'da FuzzyWuzzy")>>>100fuzz.token_set_ratio("Python'da FuzzyWuzzy incelemesi",
"FuzzyWuzzy kütüphanesi Python'da")>>>82
Token Set Ratio fonksiyonu Sort Ratio fonksiyonunun aksine unique kelimeleri baz alarak işlem yapar. “Python” kelimesinin diğer cümlede kaç defa geçtiğinin bir etkisi yoktur. En az bir defa geçmesi yeterlidir. Token Sort Ratio fonksiyonunda olduğu gibi sıralamanın burda da bir önemi yoktur.
- Extract
new_list = ["Fuzzy", "FuzzyWuzzy", "Python'da Fuzzy","Wuzzy"]
process.extract("fuzzy",new_list)>>>[('Fuzzy', 100), ('FuzzyWuzzy', 90), ("Python'da Fuzzy", 90), ('Wuzzy', 80)]
Elimizde benzerliğini kontrol etmek istediğimiz kelimenin karşısında tek bir cümle değil de birden fazla kelime veya cümlenin bulunması durumunda Extract fonksiyonunu kullanabiliriz. “fuzzy” kelimesi ile “new_list” listesi içerisindeki her bir elemanın benzerlikleri hesaplanmıştır.
- ExtractOne
process.extractOne("fuzzy", new_list)>>>('Fuzzy', 100)
Extract fonksiyonunun yaptığı işlemleri gerçekleştirir. Farklı olarak yalnızca en iyi sonucu veren dizilimi ve benzerlik oranını verir.
Kütüphanenin en çok kullanılan fonksiyonlarını tanıdık. Şimdi ise konunun daha iyi anlaşılması için örnek bir proje üzerinden gitmek daha doğru olacaktır. Projemizde bir kolonda float türünde ücretler yer alırken diğer bir kolonda ise bu ücretleri tanımlayan “KDV” ,“Toplam Tutar”, “Brüt Tutar”ve “Matrah” bilgileri yer almaktadır. Ancak bu bilgilerin düzenli bir veritabanından değil de günlük hayattan dinamik bir şekilde, insanlar tarafından yollanan fiş veya faturalardan okunacağını düşünelim. Bu durumda herkes aynı şekilde etiketleme yapmayacaktır. Kimisi “Toplam Tutar” yazarken bir diğer kişi “Top. Tutar”, “Toplam” veya “Top. Tut.” şeklinde yazabilir. Ayrıca alınan fişlerdeki bilgiler OCR taraması sonucunda bilgisayara aktarılacaksa buradaki OCR hatalarını da göz ardı etmemek gerekir. Yine “Toplam Tutar” örneğini göz önüne alırsak bazı harflerin OCR hatası sonucunda okunmama ihtimali (“Tplam Tutr) veya bazı harfilerin sayı olarak görülme ihtimali (“T0p1am Tutar”) olacaktır. Tüm bunlar göz önünde bulundurulduğunda özellikle dinamik olarak gelen verileri statik bir kodla düzenlemenin çözüme ulaştırmayacağı aşikardır. Bu tarz bir soruna sahip projeyi çözüme kavuşturup makine öğrenmesine hazır hale getirmek istersek FuzzyWuzzy kütüphanesi bu noktada bize yardımcı olacaktır.
Kod üzerinde nasıl çalıştığını görecek olursak;
pip install fuzzywuzzy
pip install python-Levenshtein
Öncelikle kütüphanelerin indirilmesi gerekir. Bahsedildiği üzere arka planında Levenshtein metriği kullanıldığı için onun da indirilmesi gerekecektir.
import pandas as pd
import numpy as np
import random
from fuzzywuzzy import fuzz
from fuzzywuzzy import processprice_list = []
for i in range(2000):
price = round(random.uniform(200,700), 2)
price_list.append(price)label_list = []
sample_list = ["Matrah", "Kdv", "Kdv.","Brüt",
"Brüt Tutar", "Toplam", "Toplam Tutar",
"Top.Tutar", "T0p1am Tutar","Matrah Tut."]
for i in range(2000):
index_value = random.randint(0, len(sample_list)-1)
label_list.append(sample_list[index_value])
df = pd.DataFrame({'price':price_list, 'label': label_list})
Gerekli olan kütüphaneler projeye import edildikten sonra bahsi geçen problemi oluşturmak için öncelikle bir fiyat listesi oluşturuldu. Sonrasında örnek olarak verilen etiket tipleri oluşturularak bir liste içerisine atıldı. İşlemlerin sonunda bu iki liste DataFrame formatına çevrildi.
Sonuç olarak yukarıdaki gibi bir görüntü elde ederiz. Bu aşamadan sonra FuzzyWuzzy kütüphanesi kullanarak etiketlerin belirlenen anahtar kelimeye olan uzaklık derecelerini hesaplayabiliriz.
key_label = ["Toplam Tutar"]df["leva_ratio"] = 0
for i in range(len(df)):
pred_label1 = fuzz.token_sort_ratio(key_label[0],df["label"].loc[i])
pred_label2 = fuzz.token_set_ratio(key_label[0],df["label"].loc[i])
pred_ort = pred_label1* 0.4 + pred_label2*0.6
df["leva_ratio"][i] = pred_ort
print(i)
Anahtar kelime olarak “Toplam Tutar” ifadesi belirlendi. Ancak istendiği takdirde birden fazla anahtar kelime de listeye eklenebilir. Bu projede Levenshtein değeri, fuzz.token_sort_ratio değerinin % 40'ı ve fuzz.token_set_ratio değerinin ise %60'ı alınarak elde edilmiştir. Projeye ve veri setine göre değişmekle birlikte sadece biri de kullanılabilir.
Tablo incelendiğinde FuzzyWuzzy kütüphanesiyle yapılan işlemlerin farklı etiketleme tiplerini ve OCR hatalarını tolere edebildiğini görmekteyiz. Bu aşamadan sonra elde edilen oranlar doğrudan makine öğrenmesi modellerinde kullanılabileceği gibi istenirse belirle bir threshold değeri baz alınarak (%75 gibi) bu değerin üzerine, anahtar değer ataması yaptığımız “Toplam Tutar” ifadesi yazılarak DataFrame güncellenebilir.