2 Veri Olarak Metin: Token’lar, Normalleştirme, Zipf
Öğrenme hedefleri. Bu bölümün sonunda:
- ham metni modele hazır birimlere dönüştürebilir ve her ön işleme kararını savunabilirsiniz;
- Zipf yasasıyla kelime sıklıklarının dağılımını, Heaps yasasıyla derlem büyüdükçe farklı kelime sayısının nasıl arttığını açıklayabilir; bu örüntülerin sözlük oluşturma ve nadir kelimeleri temsil etme açısından sonuçlarını tartışabilirsiniz;
- bir TF-IDF vektörünü elle türetip
scikit-learnile aynı sayıları elde edebilirsiniz; - bag-of-words (kelime torbası) temsilinin koruduğu ve kaybettiği bilgileri örneklerle açıklayabilir, bu temsilin hangi görevlerde uygun olduğunu bir mülakatta gerekçelendirebilirsiniz;
- Türkçe küçük harf dönüşümü ve eklemeli yapı gibi dile özgü özelliklerin normalleştirme kararlarını ve sözlük kapsamını nasıl etkilediğini açıklayabilirsiniz.
2.1 Problem, tek cümlede
Bu bölümde kullanacağımız modeller sayısal girdilerle çalışır. Metni bu modellere verebilmek için karakter dizilerini sayısal bir temsile dönüştürmemiz gerekir. Sayım temelli temsilleri iki temel karar üzerinden kuracağız: metni hangi birimlere ayıracağımızı belirlemek (tokenleştirme, tokenization) ve bu birimlerin belge vektöründeki değerlerini hesaplamak (ağırlıklandırma, weighting).
Bu yaklaşımı bag-of-words (kelime torbası) ve TF-IDF üzerinden inceleyeceğiz. Bu yöntemler, daha karmaşık metin modellerini değerlendirmek için de yararlı bir başlangıç noktasıdır.
Bu bölümde seyrek (sparse) metin temsilleri kuracağız. Büyük bir derlemden oluşturulan sözlüğün genellikle yalnızca küçük bir bölümü belirli bir belgede geçer. Bu nedenle belge vektöründeki bileşenlerin çoğu sıfır olur. Bölüm 3’te ise her kelimeyi daha az sayıda ve çoğu sıfır olmayan koordinatla temsil eden yoğun (dense) vektörlere geçeceğiz. Seyrek temsilleri kullanan basit modeller; etiketli örnek sayısının az olduğu, uzun belgelerin konularına göre sınıflandırıldığı veya birebir kelime eşleşmesinin önemli olduğu görevlerde güçlü bir baseline (karşılaştırma modeli) olabilir. Böyle bir model, daha karmaşık yöntemlerin sağladığı kazancı ek maliyetleriyle birlikte değerlendirmemize yardımcı olur.
Bu temsilleri kurarken metni hangi birimlere ayıracağımıza, hangi farkları koruyacağımıza ve hangi birimleri sayacağımıza karar veririz. Büyük dil modellerinde de benzer tasarım kararları vardır; ancak ön eğitimli bir model kullanırken bunların önemli bir bölümünü modelle birlikte gelen tokenizer’dan devralırız. Bu bölümde seçenekleri ve kütüphane varsayımlarını inceleyerek her kararın temsili nasıl etkilediğini göreceğiz.
Neye bakacağız. Önce metnin nasıl token’lara ayrıldığını ve normalleştirme kararlarının neyi birleştirip neyi sildiğini inceleyeceğiz. Ardından kelime sıklıklarının dağılımını Zipf yasasıyla, derlem büyüdükçe farklı kelime sayısının artışını ise Heaps yasasıyla ele alacağız. Sonra dört kısa cümleyi altı adımda belge vektörlerine dönüştürecek ve aralarındaki benzerliği hesaplayacağız; hesaplamaların ara sonuçlarını da göstereceğiz. Son olarak bu temsilin hangi bilgileri kaybettiğini ve bir sonraki bölümde neden başka temsillere geçtiğimizi tartışacağız. İlk okumada açılıp kapanan “Derinleşme” kutularını atlayabilir, daha sonra bu kutulara dönebilirsiniz.
Ön işleme, hangi bilginin modele ulaşacağını belirlediği için modelleme sürecinin bir parçasıdır. Bu bölümdeki seçimleri birer modelleme kararı olarak değerlendirin ve gerekçelerini kaydedin. Çalışmanızı bir makalede sunuyorsanız, sonuçları etkileyen ön işleme adımlarını yöntem bölümünde belirtin.
2.2 Karakterlerden token’lara
tokenleştirme (tokenization; bölütleme de denir) — metni, modelin ayrı birimler olarak işleyeceği token’lara ayırma işlemi. Bu kitapta “token” ve “tokenleştirme” terimlerini kullanıyoruz; bu işlemi yapan araca “tokenizer” diyoruz. Birimlerin sınırlarını kullanılan yöntem ve kurallar belirler. Dilin yapısı, bu seçimlerin ne kadar uygun ve verimli olduğunu etkiler. ↩︎
token — modelin ayrı birim olarak işlediği öğe. Kullanılan tokenizer’a göre bir kelimeyi, kelime parçasını, noktalama işaretini, boşluğu veya bir bayt dizisini temsil edebilir; her zaman tek başına okunabilir bir metin parçası değildir. Dizi başlangıcı ya da sonu gibi işlevler için kullanılan özel token’lar da vardır. Token başına ücretlendirilen bir hizmette sayılan birimleri, kullanılan modelin tokenizer’ı belirler. ↩︎
Metni temsil etmeye başlamadan önce hangi birimleri kullanacağımızı belirlememiz gerekir; bu işleme tokenleştirme denir. Bu seçim; sözlük büyüklüğünü, yeni kelimelerin nasıl temsil edileceğini ve bir metnin kaç token’a ayrılacağını etkiler. Token başına ücretlendirilen hizmetlerde token sayısı maliyeti de etkiler. Kendi işlem hattınızı (pipeline), yani ham metni modelin girdisine dönüştüren adımların sırasını kurarken bu seçenekleri belirleyebilirsiniz. Ön eğitimli bir model kullanırken ise genellikle modelin tokenizer’ını devralırsınız. Her iki durumda da kütüphanenin hangi işlemleri yaptığını bilmek gerekir.
Basit bir başlangıç, metni boşluk, sekme ve satır sonu gibi boşluk karakterlerinden bölmektir. Python’da parametresiz str.split() bunu yapar. Ancak bu işlem noktalama işaretlerini, büyük/küçük harf farklarını ve ekleri ayrıca ele almaz. Aşağıdaki örnek, görevimize göre hangi ek kararlara ihtiyaç duyabileceğimizi gösteriyor.
text = "P-101'in pompası arızalandı; sorun salmastrada (yine!). Pompa değiştirildi."
print(text.split())["P-101'in", 'pompası', 'arızalandı;', 'sorun', 'salmastrada',
'(yine!).', 'Pompa', 'değiştirildi.']
Çıktıda arızalandı;, Pompa ve P-101'in ayrı birimler olarak korunuyor. Aynı yöntemle işlenen başka bir metinde arızalandı, pompa veya P-101 geçseydi, bunlar ilk biçimlerden farklı token’lar olarak sayılacaktı. Bu biçimlerin ayrı mı sayılacağı, yoksa ortak bir biçime mi dönüştürüleceği göreve bağlıdır. Karar verirken hangi ayrımların anlam taşıdığını ve hangilerinin aynı bilgi için gereksiz sayıda farklı girdi oluşturduğunu değerlendirmek gerekir.
Bu örnek, yalnızca boşluklara göre bölmenin sınırlılıklarını gösteriyor. Kelime düzeyinde çalışan bir tokenizer; noktalama işaretleri, kesme işaretleri, URL’ler, sayılar ve kısaltmalar için ayrı kurallar kullanabilir. Bununla birlikte, Pompa ile pompayı aynı biçime dönüştürmek gibi normalleştirme kararları, token sınırlarını belirlemekten ayrı işlemlerdir.
Kelime düzeyinde bir sözlüğü sabit tutsanız da yeni metinlerde sözlükte bulunmayan kelimelerle karşılaşabilirsiniz. Önce normalleştirmenin farklı kelime biçimlerini nasıl bir araya getirdiğini inceleyeceğiz. Ardından kelime düzeyinin sınırına ve bugünün modellerinin bu sınırı nasıl aştığına kısaca değineceğiz (Bölüm 2.2.2).
2.2.1 Normalleştirme merdiveni
stop word — bazı metin işleme görevlerinde, çok sık geçtiği ve ayırt edici bilgi taşımadığı varsayılarak sayım öncesinde atılan sözcük. The, of, is. Bu varsayımın doğru olup olmadığı tamamen göreve bağlıdır; etkisiz kelime adlandırması bu yüzden yanıltıcıdır. ↩︎
normalleştirme (normalization) — bu bölümde, görev açısından önemsiz sayılan yazımsal veya biçimbilimsel (morphological) farklılıkları azaltan işlemlerin genel adı. Büyük/küçük harf farkını kaldırmak (Pompa → pompa) ve çekimli biçimleri ortak bir gövdeye veya sözlük biçimine dönüştürmek (pompalar → pompa) bu işlemlere örnektir. Gövdeleme sonucunun dilbilimsel kökle aynı olması gerekmez. Unicode normalleştirmesi ise ayrı bir işlemdir: aynı görünen karakter dizilerini standart bir biçime getirir. ↩︎
Yukarıdaki örnek üç farklı karar gerektiriyor. Pompa ile pompa aynı biçime mi dönüştürülsün? arızalandı; içindeki noktalı virgül ayrı bir token olarak mı korunsun, yoksa çıkarılsın mı? P-101'in içindeki ek korunsun mu, yoksa ekipman adı P-101 ile mi eşleştirilsin? Bu kararlar, sırasıyla büyük/küçük harf, noktalama ve dilbilgisel ek bilgisini etkiler. Normalleştirme bazı biçimlerin sayımlarını birleştirerek sözlüğü küçültebilir. Ancak yararlı bir ayrımı silip silmediğini göreve göre değerlendirmek gerekir.
Bu işlemler yararlı bilgileri de silebilir. Hangi ayrımın kaldırıldığını işlemin tanımından görebiliriz; bunun görev üzerindeki etkisini ise değerlendirmemiz gerekir. Aşağıdaki tablo, yaygın normalleştirme ve ön işleme seçeneklerini yararları ve olası kayıplarıyla karşılaştırıyor. Tablo zorunlu bir uygulama sırası değildir; gövdeleme ile lemmatizasyon da çoğu durumda birbirinin alternatifidir.
Bu tablo, işlem hattını kendiniz kurduğunuz durumlar içindir. Metni ön eğitimli bir modele verecekseniz, modelin eğitim sırasında kullandığı tokenizer’a ve ön işleme varsayımlarına uymanız gerekir; aşağıdaki işlemleri kendi başınıza eklemek girdinizi modelin beklediği biçimden uzaklaştırır.
| İşlem | Olası yararı | Olası kaybı veya maliyeti | Kullanım önerisi |
|---|---|---|---|
| Küçük harfe çevirme | Pompa = pompa: iki sütun tek sütunda birleşir, sayımları toplanır. |
AŞ (anonim şirket) ile aş (yemek) aynı biçime dönüşür; özel adları ve kısaltmaları ayırt etmeye yardımcı olan büyük harf bilgisi kaybolur. Python’un str.lower() metodu Türkçeye özgü I–ı ve İ–i eşlemelerini uygulamaz. |
Kararı dile ve göreve göre verin. Türkçe yazıma uygun dönüşüm gerektiğinde Türkçeye özgü harf eşlemelerini destekleyen bir yöntem kullanın; ön eğitimli modellerde modelin kendi ön işleme kurallarını izleyin. |
| Noktalama işaretlerini çıkarma | Seçilen işaretleri çıkararak arızalandı; gibi biçimleri arızalandı ile eşleştirebilir. |
İşaretleri ayrım gözetmeden silmek; P-101 gibi tanımlayıcıları, ondalık gösterimleri, URL’leri ve noktalama içeren kimyasal gösterimleri değiştirebilir. |
Toplu silme yerine hangi işaretlerin korunacağını, ayrılacağını veya çıkarılacağını göreve göre belirleyin. |
| Stop word çıkarma | Listedeki kelimeleri sayımın dışında bırakır. Bu bölümde kullanılan İngilizce liste, aşağıda belirtilen derlem ve tokenleştirme kuralıyla bütün token’ların %43,9’unu eler. | Listede not bulunuyorsa olumsuzluk bilgisi kaybolabilir; to be or not to be gibi ifadelerin içeriği de değişebilir. |
Özellikle seyrek sayım temelli yöntemlerde etkisini ölçün. Listenin, görev açısından önemli kelimeleri silmediğini kontrol edin. |
| Gövdeleme (stemming) | Farklı kelime biçimlerini ortak bir çıktıda birleştirebilir. Örneğin Porter gövdeleyicisi replaced kelimesini replac, Snowball Türkçe gövdeleyicisi kitaplarımızdan kelimesini kitap biçimine dönüştürür. |
Farklı kelimeleri yanlışlıkla birleştirebilir veya aynı kelimenin biçimlerini ayrı bırakabilir. Örneğin Porter’da hem university hem universe, univers olur. |
Sözcüksel arama ve seyrek modellerde deneyin; çıktıları ve görev başarısını değerlendirerek karar verin. |
| Lemmatizasyon (lemmatization, sözlük biçimine indirgeme) | Kelimeyi uygun sözlük biçimiyle eşleştirmeyi amaçlar: fiil olarak kullanılan replaced için replace, gidiyorum için gitmek. |
Kullanılan yönteme göre sözlük, kurallar, sözcük türü veya bağlam bilgisi gerektirebilir. Belirsiz biçimlerde hata yapabilir; işlem maliyeti kullanılan araca bağlıdır. | Ön eğitimli transformer modellerine ek bir adım olarak uygulamadan önce modelin beklediği girdiyi kontrol edin. Seyrek modellerde ve dilbilimsel görevlerde yararını değerlendirin. |
Unicode nedir? Bilgisayarlar metni sayılarla kodlayarak saklar. Unicode, çok sayıda yazı sistemindeki karakterlere ortak numaralar veren standarttır; bu numaralara kod noktası denir. Örneğin A 65, ç 231, İ 304, 😀 128512. Ekranda tek bir harf gibi görünen bir işaret bazen birden fazla kod noktasından oluşur. Kod noktalarıyla baytların ilişkisini alt kelime tokenleştirmesiyle birlikte ele alıyoruz (Bölüm 8.3.2).
Tablodaki ilk satırla, küçük harfe çevirmeyle başlayalım: burada dilin harf eşlemelerine dikkat etmek gerekir. Python’un str.lower() metodu Unicode’un varsayılan küçük harf eşlemesini uygular; Türkçe için bir yerel ayar parametresi almaz. Türkçede iki ayrı eşleme vardır: I → ı ve İ → i. Varsayılan dönüşümün bu eşlemelerden nasıl ayrıldığını aşağıdaki örneklerde görebilirsiniz:
"İSTANBUL".lower() # 'i̇stanbul': başta U+0069 + U+0307; toplam 9 kod noktası
"İSTANBUL".lower() == "istanbul" # False
"IĞDIR".lower() # 'iğdir' — 'ığdır' olmalıydıBu dönüşümler hata mesajı üretmez; ancak sonraki işlemlerde beklenmedik sonuçlara yol açabilir. Aynı kelimenin farklı yazımları eşleşmeyebilir veya tokenizer kelimeyi beklenmedik biçimde bölebilir. Örneğin varsayılan CountVectorizer, İSTANBUL girdisinden stanbul token’ını üretir. Bölüm 4 bu sorunu bir derlem üzerinde gösteriyor; Bölüm 16 Türkçeye uygun dönüşümü ele alıyor. Ön işleme düzeltildiğinde sözlüğün ve ona bağlı modellerin de yeniden oluşturulması gerekebilir.
Tablonun “Kullanım önerisi” sütunu bir başlangıç noktasıdır. Her işlem, kaldırdığı ayrımın görev açısından gerekli olmadığı varsayımına dayanır. Örneğin küçük harfe çevirme bazı konu sınıflandırma görevlerinde yararlı olabilir; adlandırılmış varlık tanımada (named entity recognition, NER) ise büyük harflerden gelen bir ipucunu kaldırır. Modelin gerekliliklerini ve verinin özelliklerini dikkate alın; uygun seçenekleri doğrulama verisinde karşılaştırın ve son değerlendirme için test kümesini ayrı tutun.
Gövdeleme ile lemmatizasyon (lemmatization, sözlük biçimine indirgeme), farklı kelime biçimlerini ortak bir biçimde toplamak için kullanılır; ancak aynı işlemi yapmazlar. Porter (Porter 1980) gibi gövdeleyiciler, çekim veya yapım eklerine yönelik kurallar uygular ve sonucun sözlükte bulunan bir kelime olmasını garanti etmez. Lemmatizasyon, kelimeyi sözlükteki temel biçimiyle eşleştirmeyi amaçlar. Bazı yöntemler yalnızca tablo ve kurallardan yararlanır; belirsiz biçimlerde uygun sonucu seçmek için bağlam gerekebilir. Örneğin İngilizcede left, bir cümlede leave fiilinin geçmiş zaman biçimi, başka bir cümlede ise “sol” anlamındaki kelime olabilir.
Türkçede üç şeyi birbirinden ayırmak gerekir: dilbilimsel kök, sözlük biçimi ve bir NLP aracının verdiği çıktı. gidiyorum biçiminin kökü git-tir. Sözlük biçimi, yani bu fiili bir sözlükte aradığınızda bulacağınız biçim ise gitmektir; fiiller sözlükte mastar hâliyle, isimler ek almamış yalın hâliyle (ev) yer alır. Bir gövdeleyicinin çıktısı bu biçimlerden herhangi biriyle aynı olmak zorunda değildir. Bu kitapta kullanılan Snowball Türkçe gövdeleyicisi (snowballstemmer 3.1.1) gidiyorum kelimesini gidiyor biçimine dönüştürür; şimdiki zaman ekini kaldırmaz. Bazı başka fiil biçimlerini ise kısaltır: aynı sürümde geldim → gel, gitti → git. Bu nedenle çıktısını, fiilin kökünü veya sözlük biçimini güvenilir biçimde veren bir çözümleme saymamak gerekir (Bölüm 16).
Biçimbilim çözümleyicileri (morphological analyzer), bir kelimenin olası yapısını belirler; araca göre kök veya gövde, sözlük biçimi, ekler ve sözcük türü gibi bilgiler sunabilir. Türkçe için kullanılabilen araçlardan biri Zemberek’tir (Akın 2019). Örneğin Zemberek’in Python uyarlaması (zemberek-python 0.2.3) gidiyorum için kök olarak git, sözlük biçimi olarak gitmek verir. kullandık için ise altı ayrı çözümleme döndürür: bazıları kullanmak fiiline, bazıları kul ismine dayanır. Bir kelimenin birden fazla çözümlemesi varsa bağlama uygun olanın ayrıca seçilmesi gerekir. Araçlar bu alanları farklı adlarla verebilir; hangi alanın ne anlama geldiğini aracın belgelerinden kontrol edin.
Kök ile gövdeyi de ayırın. Dilbilgisel anlamda kök, daha küçük anlamlı birimlere ayrılamayan tabandır; gövde ise köke yapım eki eklenmesiyle oluşabilir. Örneğin kitaplıklarımızdan biçiminde kök kitap, türemiş gövde kitaplıktır. Bir gövdeleme algoritmasının ürettiği çıktı, bu dilbilgisel gövdeyle aynı olmak zorunda değildir.
Hangi çıktıyı kullanacağınız amacınıza bağlıdır. Sözlük araması için sözlük biçimini kullanabilirsiniz. Kelimenin fiil mi isim mi olduğunu korumanız gerekiyorsa sözlük biçimiyle birlikte sözcük türü bilgisini de saklayın.
Gövdeleme ile lemmatizasyon arasındaki seçim, kullandığınız işlem hattına bağlıdır. Ön eğitimli bir transformer modelinde, modelin tokenizer’ını ve beklediği ön işleme adımlarını kullanın; gövdeleme veya lemmatizasyonu kendiliğinizden eklemeyin. Alt kelime (subword) tokenleştirmesi (Bölüm 8.3.1), nadir veya yeni kelimeleri daha küçük birimlerle temsil etmeye yardımcı olur. Ancak çekimli biçimleri aynı sözlük biçimine dönüştürmez; örneğin kullanıyorum ile kullandık için bir sayım temsilinde kendiliğinden tek bir ortak sütun oluşturmaz.
Çekimli biçimleri ortak bir eşleştirme biriminde toplamak istediğiniz sözcüksel arama veya seyrek sınıflandırma görevlerinde gövdeleme yararlı olabilir. Ancak seçtiğiniz aracın hangi biçimleri birleştirdiğini kontrol edin: bu bölümde kullanılan Snowball sürümü kullanıyorum için kullanıyor, kullandık için kulla üretir; bu iki biçimi birleştirmez.
Bölüm 16’da, hedef belgelerden oluşturulan ve terimlerinin çoğu belgede geçenden farklı çekimli biçimlerde olan sorgularla bir BM25 deneyi yapılıyor. Bu deneyde Snowball gövdeleme, hedef belgenin ilk sırada bulunduğu sorguların oranını (recall@1) %28,0’dan %95,3’e çıkarıyor. Bu sonuç, belirtilen deney düzeninde gövdelemenin sağlayabileceği yararı gösterir; gerçek kullanıcı sorgularında aynı kazanım beklenmemelidir.
Stop word çıkarırken bilgi kaybını değerlendirin. scikit-learn’ün İngilizce stop word listesinde 318 madde bulunur. Bu bölümdeki 20 Newsgroups ölçümünde, kullanılan tokenleştirme kuralıyla bu maddelerin 311’i derlemde görülür. Bunlar 78.649 farklı token türünün yaklaşık %0,4’ünü, derlemdeki bütün token’ların ise %43,9’unu oluşturur. Böylece token’ların neredeyse yarısı sayımın dışında kalır. Ancak bu oran, metnin bayt büyüklüğünün veya seyrek matrisin belleğinin aynı oranda azalacağı anlamına gelmez; bellek kazancı, çıkarılan kelimelerin kaç belgede bulunduğuna da bağlıdır. Ayrıca not gibi görev açısından önemli bir kelime çıkarılırsa, anlamı ayırt etmek için gereken bilgi kaybolabilir.
Türkçe stop word listelerini kullanmadan önce içeriklerini ve işlem hattınızla uyumlarını inceleyin. Listedeki yalın bir biçim, ek almış biçimleri kendiliğinden kapsamaz. Bazı listeler değil, yok ve hiç gibi anlamı etkileyen kelimeleri de içerir; bunları çıkarmanın göreviniz için uygun olup olmadığını değerlendirin. Listenin, kullandığınız tokenleştirme ve normalleştirme kurallarıyla aynı biçimleri kullandığını kontrol edin.
2.2.2 Kelime düzeyinin sınırı
Bu bölümün geri kalanında token, yukarıdaki kurallarla elde edilen normalleştirilmiş bir kelime biçimidir. Kelime torbası ve TF-IDF için bu birim yeterlidir: sözlüğü eğitim metninden kurarız, her belgede hangi kelimenin kaç kez geçtiğini sayarız.
Bu seçimin bir sınırı var ve onu baştan bilmek gerekir. Sözlük sabitlendikten sonra gelen bir metinde, sözlükte bulunmayan kelime biçimleriyle karşılaşabilirsiniz; kelime düzeyinde bir temsil bu biçimlere sütun ayıramaz ve onları sayımın dışında bırakır. Türkçe gibi eklemeli bir dilde aynı gövdenin çok sayıda çekimli biçimi olduğu için bu durum daha sık yaşanır. Sözlüğün veri arttıkça nasıl büyümeye devam ettiğini bir sonraki kısımda Heaps yasasıyla ölçeceğiz (Bölüm 2.3.2).
Bugünün dil modelleri bu sınırı kelimeden küçük birimlerle aşar: sözlüğe kelimeleri değil, veriden öğrenilen kelime parçalarını (alt kelimeleri) koyar ve yeni bir kelimeyi bu parçalarla yazar. Byte-pair encoding (BPE), WordPiece ve benzeri yöntemleri, onları kullanan ön eğitimli modellerle birlikte ele alacağız (Bölüm 8.3). Orada aynı metnin Türkçede neden daha fazla token’a bölündüğünü de ölçeceğiz. Şimdilik kelimelerle devam ediyoruz.
2.3 Zipf ve Heaps: kelimelerin uzun kuyruğu
Buraya kadar metni nasıl böleceğimizi ve her seçimin neye mal olduğunu gördük. Bu kısımda, kelime sıklıklarının dağılımını ve veri arttıkça karşılaşılan farklı kelime biçimlerinin sayısını betimlemek için kullanılan iki ampirik ilişkiyi inceleyeceğiz. İkisi kolayca karıştığı için baştan iki ayrı soru olarak koyalım:
Soru 1. Bir derlemde en sık kelimelerle nadir kelimelerin frekansları nasıl dağılır? Cevap: Zipf yasası.
Soru 2. Derleme daha fazla metin ekledikçe farklı kelime sayısı nasıl büyür? Cevap: Heaps yasası.
Zipf yasası, belirli bir derlemdeki sıklıkların nasıl dağıldığını; Heaps yasası ise derlem büyüdükçe farklı kelime biçimlerinin sayısının nasıl değiştiğini betimler. İkisi de gözleme dayanan yaklaşık ilişkilerdir; tokenleştirmenin nasıl yapılacağını tek başlarına belirlemezler. Aralarında belirli varsayımlar altında bağlantı kurulabilir (Lü vd. 2010). Burada önce sıklık dağılımını, ardından sözlük büyümesini inceleyeceğiz.
2.3.1 Zipf: frekanslar nasıl dağılır?
20 Newsgroups koleksiyonunun (Lang 1995) 11.314 belgelik eğitim bölümünü kullanalım. Başlık, imza ve alıntı temizleme seçeneklerini uyguladıktan sonra metni küçük harfe çeviriyor, [a-z']+ düzenli ifadesinin eşleştirdiği her karakter dizisini bir token sayıyoruz. Bu işlem 2.288.401, yani yaklaşık 2,29 milyon token üretiyor. Buradaki birimler dilbilimsel kelimelerle her zaman örtüşmez.
Bu bölümün derlem ölçümleri (Zipf, Heaps, stop word payı, sınıflandırma deneyi) için tek satırla indirilebilen bir İngilizce veri kümesi seçildi. Aşağıdaki 'ax örneği, veri içeriğinin ve ön işleme kararlarının sonuçları nasıl etkilediğini de gösteriyor. Türkçe uygulamalar için ürün yorumu derlemine (Bölüm 4) ve Türkçe NLP bölümüne (Bölüm 16) bakabilirsiniz. Sayım şöyle:
from collections import Counter
import re
from sklearn.datasets import fetch_20newsgroups
docs = fetch_20newsgroups(subset="train",
remove=("headers", "footers", "quotes")).data
tokens = re.findall(r"[a-z']+", "\n".join(docs).lower())
counts = Counter(tokens)
print("tokens:", len(tokens), " distinct types:", len(counts))tokens: 2288401 distinct types: 78649
Çıktıdaki iki sayıyı ayırmak önemli. Bu sayımda tür (type), seçtiğimiz bölme işleminin ürettiği her farklı karakter dizisidir; token ise bu dizinin metindeki her bir geçişidir. Örneğin pompa yüz kez geçiyorsa bu, bir tür ve yüz token demektir. Tür ve token sayıları, kullanılan normalleştirme ve bölme kurallarına bağlıdır. Şimdi dağılımın biçimine bakalım:
top 10 types -> 20.99% of all tokens
top 100 types -> 46.99% of all tokens
top 1000 types -> 71.04% of all tokens
top 10000 types -> 92.50% of all tokens
types appearing exactly once: 38571 (49.0% of the vocabulary,
1.69% of all tokens)
En sık on tür, bu derlemdeki token’ların yaklaşık beşte birini oluşturuyor. Buna karşılık türlerin yaklaşık %49’u yalnızca bir kez görülüyor. Bu tek geçişli türler (hapax legomena), gözlenen türlerin neredeyse yarısı olmasına rağmen bu derlemdeki token’ların yalnızca %1,7’sine karşılık geliyor. Bunların tamamını model sözlüğünde tutup tutmamak göreve ve sözlük oluşturma yöntemine bağlıdır.
Bu sonuç, gözlenen türlerin büyük bölümünün seyrek olduğunu gösteriyor. Yeni veri eklendiğinde kaç yeni türle karşılaşılacağı ayrı bir sorudur; onu aşağıda Heaps yasasıyla ele alacağız (Bölüm 2.3.2).
Zipf yasası (Zipf 1949), türleri sıklıklarına göre sıraladığımızda frekansın sırayla yaklaşık bir kuvvet ilişkisi göstermesidir:
\[ f(r) \propto r^{-\alpha}. \]
Burada \(r\) türün sıralamadaki konumunu, \(f(r)\) o konumdaki türün geçiş sayısını gösterir. Bu ilişki, iki ekseni de logaritmik olan bir grafikte yaklaşık bir doğru olarak görünür.
Buradaki “yaklaşık” kelimesi önemlidir. Birinci sıradaki the’nin 106.425 geçişini ölçek olarak alıp \(\alpha = 1\) seçersek, onuncu sıra için yaklaşık 10.600 geçiş bekleriz. Gözlenen değer 26.985’tir; bu basit referansın yaklaşık iki buçuk katı. Bu karşılaştırma, ilk sıraların seçtiğimiz \(1/r\) eğrisini tam izlemediğini gösterir.
Zipf–Mandelbrot biçimi,
\[ f(r) \propto (r + b)^{-\alpha}, \]
sıraya eklenen \(b\) parametresiyle dağılımın başındaki biçime daha fazla esneklik tanır. Burada bu parametreleri kestirmiyor, iki biçimin uyumunu da karşılaştırmıyoruz. Doğruyu veriden kestirilmiş bir model olarak değil, dağılımın biçimini özetleyen bir referans olarak alın.
UyarıDerinleşme: Zipf üssünü gerçekten kestirmek
Log-log sıra–frekans grafiğine en küçük karelerle (OLS) bir doğru uydurmak, dağılımın biçimini betimlemek için kullanılabilir. Ancak elde edilen eğim tek başına güvenilir bir kuvvet yasası kestirimi veya uyum kanıtı değildir. Sıralanmış frekansların örnekleme yapısı, gözlemler arasındaki bağımlılık ve seyrek türlerin çokluğu, basit regresyonun belirsizlik hesaplarını yanıltabilir. Uyum için seçilen aralık ve noktaların nasıl ağırlıklandırıldığı da eğimi etkiler. Değişen varyans (heteroskedastisite) tek başına OLS katsayılarının yanlı olduğunu göstermez. Burada üç ayrı soru vardır: katsayının kestirimi, standart hatanın hesabı ve kuvvet yasasının gerçekten uygun bir model olup olmadığı.
Daha güçlü bir inceleme için önce hangi büyüklüğün modellendiğini ve uyum aralığını tanımlamak gerekir. Clauset ve arkadaşlarının yaklaşımı (Clauset vd. 2009), ayrık veya sürekli kuvvet yasası modelini veriden kestirilen bir alt kesim \(x_{\min}\) ile birlikte maksimum olabilirlik yöntemiyle kestirir. Uyumu, \(R^2\) yerine Kolmogorov–Smirnov (KS) uzaklığıyla değerlendirir: verideki uzaklık, modelden üretilmiş sentetik örneklemlerdeki uzaklıklarla karşılaştırılır ve parametreler her sentetik örneklemde yeniden kestirilir. Kuvvet yasası ayrıca alternatif dağılımlarla karşılaştırılır. Bu yöntemin kestirdiği üs, geçiş sayılarının dağılımına aittir; yukarıdaki sıra–frekans üssü \(\alpha\) ile aynı parametre değildir. Bu bölümde böyle bir kestirim yapmıyoruz ve hiçbir sonuç üssün tam değerine bağlı değil.
Bu gözlemler, sözlük tasarımı ve öznitelik seçimi açısından üç pratik noktayı öne çıkarıyor:
- Sabit bir kelime sözlüğü yeni girdileri eksiksiz kapsamayabilir. Özellikle konusu ve kaynağı sınırlanmamış metinlerde yeni adlarla, parça numaralarıyla, yazım biçimleriyle ve bileşik kelimelerle karşılaşılır. Alt kelimeler, bu yeni biçimleri mevcut parçalarla temsil etmeye yardımcı olur.
- Seyrek türler için güvenilir istatistik öğrenmek zordur; yine de nadir bir terim değerli olabilir. Tek bir gözlem genelleme için zayıf bir dayanaktır, ama nadir bir ürün kodu veya özel ad görev açısından belirleyici olabilir.
scikit-learn’ünmin_dfparametresi, az sayıda belgede görülen terimleri sözlükten çıkarır: tam sayı değer belge sayısını, ondalıklı değer belge oranını belirtir. Bu derlemdeCountVectorizer’ın varsayılan ayarlarıylamin_df=2, 101.631 sütunun 62.208’ini (%61) siler; silinen sütunlar token geçişlerinin yalnızca %3,4’ünü taşır. İngilizce stop word listesi ise 309 sütun siler, ama bu sütunlar geçişlerin %43’ünü taşır (varsayılan ayar tek harfli a ve i token’larını üretmediği için sayı, normalleştirme kısmındaki 311’den iki eksiktir). Sayılar bu derleme ve bu eşiğe aittir; filtrenin başarıyı nasıl etkilediği ayrıca ölçülmelidir. - Bir terimin sıklığı, görev açısından ne kadar yararlı olduğunu tek başına belirlemez. Çok sayıda belgede bulunan terimler, belgeleri birbirinden ayırmada daha az işe yarayabilir. Bir sonraki kısımda göreceğimiz IDF bu yaygınlığı hesaba katar. Ancak az belgede geçmek de tek başına anlam veya yararlılık güvencesi değildir; aşağıdaki
'axörneği bunu gösteriyor.
2.3.2 Heaps: sözlük neden büyümeye devam eder?
Şimdi ikinci soruya bakalım: işlenen token sayısı arttıkça kaç farklı türle karşılaşıyoruz? Önce mevcut derlem boyunca birikimli tür sayısını ölçelim, sonra bu eğriye log-log uzayında bir doğru uyduralım. Bu doğru, mevcut belge sırasındaki birikimli büyümeyi özetleyen betimsel bir yaklaşımdır. Aynı token dizisinin giderek uzayan başlangıç parçalarını ölçtüğümüz için noktalar birbirinden bağımsız örneklemler değildir. Elde edilen katsayılar bu sıraya ve bu ölçüm aralığına aittir; güven aralığı veya yeni veride doğrulanmış bir tahmin olarak yorumlanmamalıdır.
import numpy as np
seen, curve = set(), []
for i, tok in enumerate(tokens, 1):
seen.add(tok)
if i % 10_000 == 0:
curve.append((i, len(seen)))
N, V = np.array(curve).T
# Mevcut birikimli eğri için log-log uzayında betimsel doğru uyumu.
beta, logK = np.polyfit(np.log(N), np.log(V), 1)
print(f"|V| ~ {np.exp(logK):.1f} * N^{beta:.3f}")
print("new types in the first 100,000 tokens:", V[9])
print(f"new types in tokens {N[-11] + 1:,}-{N[-1]:,}:", V[-1] - V[-11])|V| ~ 16.5 * N^0.580
new types in the first 100,000 tokens: 13497
new types in tokens 2,180,001-2,280,000: 1814
Heaps yasası (Heaps 1978), işlenen token sayısı \(N\) ile gözlenen farklı tür sayısını yaklaşık olarak
\[ |V(N)| \approx K N^{\beta} \]
biçiminde ilişkilendirir. \(K\) ve \(\beta\), derleme ve ön işleme kararlarına bağlıdır. Bu derlemde ve bu belge sırasında \(K \approx 16{,}5\) ve \(\beta \approx 0{,}58\) bulduk; Reuters-RCV1 derlemi için bildirilen değerler \(K \approx 44\) ve \(\beta \approx 0{,}49\)’dur (Manning vd. 2008).
\(K > 0\) ve \(0 < \beta < 1\) olduğunda bu sürekli model büyür, ancak büyüme hızı azalır:
\[ \frac{\mathrm{d}|V|}{\mathrm{d}N} = K\beta N^{\beta-1} > 0 \qquad (N > 0). \]
Ölçümümüz de aynı şeyi gösteriyor: ilk 100.000 token 13.497 tür getirirken, ölçtüğümüz son 100.000 token yalnızca 1.814 yeni tür getiriyor. Yeni türler gelmeye devam ediyor, ama giderek daha seyrek. Modelde \(\beta > 0\) olduğu için \(N\) sınırsız artırılırsa değer de sınırsız büyür. Bu, modelin bir özelliğidir; sonlu bir veriden, dilin sonsuza kadar yeni tür üreteceğinin kanıtlandığı anlamına gelmez. Ölçülen tür sayısı da basamak basamak artan bir tam sayı dizisidir; kuvvet eğrisi onun yaklaşık bir özetidir.
Birçok doğal dil derleminde veri arttıkça yeni türler görülmeye devam eder; bunların ortaya çıkış hızı derlemin içeriğine ve ön işlemeye göre değişir. Heaps yasası, bu büyümeyi belirli bir aralıkta yaklaşık olarak modelleyen ampirik bir düzenliliktir; kanıtlanmış bir teorem değildir.
Yeni biçimlerle karşılaşmayı sürdürmek, alt kelime temsillerini yararlı kılan etkenlerden biridir. min_df filtresinin etkisi ise Heaps yasasından değil, türlerin kaç belgeye dağıldığından, yani belge frekanslarından çıkar. Yukarıdaki ölçümde çok sayıda seyrek sütun elenirken token geçişlerinin küçük bir bölümü dışarıda kalmıştı; başka bir derlemde bunun böyle olup olmadığı ölçülmelidir. min_df kaynak metni değiştirmez; yalnızca vektörleştirme sözlüğündeki öznitelikleri eler.
Her zaman ilk 50 listenize bakın. Bu sayımda en sık ikinci token 'ax: 62.043 kez geçiyor ve to, a ve of’un önünde. Bu, doğal dilde kullanılan bir kelime değil. Geçişlerin tamamı 11.314 belgenin 13’ünde toplanıyor. Bunların 12’si, roman.bmp adlı bir dosyayı on dört parça hâlinde taşıyan gönderilerin 3–14. parçaları. Gövdelerinde dosyanın metne kodlanmış (uuencode) içeriği var ve art arda gelen aynı baytlar >'AX>'AX>'AX biçiminde tekrarlanan satırlar üretiyor. On üçüncü belge, kodlanmış başka bir dosya içeren ayrı bir gönderi ve yalnızca bir geçiş içeriyor. Tek bir belge, yani 12. parça, 62.043 geçişin 11.147’sini karşılıyor.
Toplam geçiş sayısına göre 'ax bu işlenmiş derlemde ikinci sırada; buna karşılık belgelerin yalnızca %0,1’inde bulunuyor. Bu örnek, derlem toplamındaki sıklık ile belgeler arasındaki yaygınlığın farklı şeyler olduğunu gösteriyor.
TF-IDF bu iki bilgiyi birlikte kullanır, ama doğal dil dışındaki içeriği kendiliğinden tanımaz: az belgede görülen bir gürültü dizisi yüksek IDF alabilir. Bu nedenle en sık token’ların listesine bakmak, ağırlıklandırmadan önce veri ve ön işleme sorunlarını fark etmenin basit bir yoludur.
'ax token’ının başındaki kesme işareti ham veride zaten var: satırlar >'AX>'AX biçiminde. Bizim [a-z']+ düzenli ifademiz bu işareti token’ın içine alıyor; küçük harfe çevirme de AX parçasını ax yapıyor. scikit-learn’ün varsayılan token_pattern ayarı kesme işaretini token’a katmaz ve aynı belgelerden ax token’ını üretir; bu ayarla da ax, 62.387 geçişle the’dan sonra ikinci sıradadır. Kaynak içerik aynı kalsa da token’ın biçimi bir ön işleme kararına bağlıdır.
Bir sonraki kısma geçmeden önce üç sayımı ayıralım: bir terimin belirli bir belgedeki geçiş sayısı, bütün derlemdeki toplam geçiş sayısı ve terimi içeren belge sayısı. 'ax örneğinde derlem toplamı yüksek, terimi içeren belge sayısı ise düşüktür. TF-IDF, her belgedeki terim sıklığını (TF), terimin kaç belgede bulunduğundan hesaplanan IDF ağırlığıyla birleştirir.
2.4 Bir metin nasıl vektöre dönüşür
Bölümün çekirdeği burası. Dört kısa bakım kaydını, karakter dizisi olmaktan çıkarıp üzerinde uzaklık hesaplayabileceğiniz vektörlere kadar götüreceğiz — aradaki hiçbir adımı atlamadan, her ara hesabı tek tek göstererek.
corpus = [
"pompa yine arızalandı", # d1
"pompa yine değiştirildi", # d2
"vana yine arızalandı", # d3
"salmastra yine kaçırdı ve onarıldı", # d4
]Karakter dizisinden vektöre: bu bölümün altı adımı
Bütün bölüm tek bir resimde. 1–3. adımlar sözlüğü ve terim sayımlarını oluşturuyor, kelime sırasını atıyor; vurgulu 4–5. adımlar bu sayımları belge frekanslarıyla ağırlıklandırıp vektör normuna göre ölçekliyor; 6. adım vektörleri karşılaştırıyor. Gösterilen her sayı, kodun d1 = "pompa yine arızalandı" için fiilen ürettiği sayı. Sözlük dizini Unicode kod noktası sırasına göre dizili; dolayısıyla arızalandı 0, pompa 4, yine 8 numaralı sütunda.
2.4.1 1. adım — sözlük sadece bir dizindir
Ön işleme ve tokenleştirmeden sonra elde edilen farklı terimleri sıralıyor, her birini bir sütun numarasına eşliyoruz. Bu numara terimin sözlükteki adresidir; kendi başına anlamsal yakınlık belirtmez. CountVectorizer varsayılan ayarlarıyla metni küçük harfe çevirir ve en az iki karakterden oluşan harf-rakam dizilerini token sayar.
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
X = cv.fit_transform(corpus)
print(cv.get_feature_names_out())['arızalandı' 'değiştirildi' 'kaçırdı' 'onarıldı' 'pompa' 'salmastra'
'vana' 've' 'yine']
Buradaki sıra Türkçe alfabetik sıra değil, Unicode kod noktası sırasıdır. ç, ğ, ı, ö, ş, ü harfleri ASCII’nin dışında kaldığı için z’den sonra gelirler: Python bu kelimeleri ['ağır','vana','zaman','çark','ılık','şalter'] diye sıralar. Sütunları hangi sırayla numaralandırdığımız, eğitimde ve kullanımda aynı eşlemeyi koruduğumuz sürece sonucu değiştirmez. Bu sıralama, get_feature_names_out() çıktısını gözle incelerken önem kazanır.
Dokuz terim var, yani buradan sonraki her vektör \(\mathbb{R}^9\) uzayında yer alıyor. Kelime ya da n-gram tabanlı sistemlerde sözlük boyutu on binlere, hatta yüz binlere ulaşabilir. Bu büyüklük, özellikle seyrek saklama ve az görülen terimlerden güvenilir örüntüler öğrenme açısından önemlidir.
2.4.2 2. adım — one-hot: vektör olarak kelime ve neden yetmediği
Elimizde bir dizin var. Ancak sütun numaraları arasındaki sayısal fark, kelimelerin anlamca ne kadar yakın olduğunu göstermez: 4 numaralı pompa, 5 numaralı salmastra kelimesine 6 numaralı vana kelimesinden daha yakın değildir. Bir kelimeyi, bir modelin hesap yapabileceği bir vektöre nasıl çeviririz?
Tek bir kelimenin akla gelebilecek en basit kodlaması: kelimenin adresinde tek bir 1, geri kalanı sıfır olan bir vektör.
index of 'pompa': 4
one-hot: [0 0 0 0 1 0 0 0 0]
Bu vektör kelimenin kimliğini kodlar; ancak farklı kelimeler arasında anlamsal yakınlık göstermez. Herhangi iki farklı one-hot vektörün iç çarpımını alın, sıfır çıkar — birindeki tek 1 her zaman diğerindeki bir 0’a denk gelir, dolayısıyla toplamdaki her çarpım sıfırdır:
pompa : [0 0 0 0 1 0 0 0 0]
vana : [0 0 0 0 0 0 1 0 0]
------------------- konum konum çarp
[0 0 0 0 0 0 0 0 0] -> toplam = 0
Bu kodlamada pompa, vana’ya tam olarak salmastra’ya olduğu kadar benzerdir. Farklı kelimelerin one-hot vektörleri birbirine diktir. Aynı kelimenin vektörü kendisiyle tam eşleşir (iç çarpım 1); farklı kelimeler arasındaki anlamsal yakınlık ise bu kodlamada yer almaz. pompa kelimesinin vektörü bize kelimenin hangi sütunda tutulduğunu söyler; neye benzediği hakkında bir şey söylemez.
One-hot burada kelime anlamını temsil etmenin nihai yolu olarak değil, bir belge sayım vektörünün nasıl oluştuğunu görmek için ara yapı olarak kullanılıyor; bir sonraki adımda bunu yapacağız. Bu eksikliği gidermek için öğrenilen yoğun vektörlere, yani embedding’lere geçebiliriz. Embedding bölümünde (Bölüm 3), kelimeler arasındaki ilişkilerin vektör uzayına nasıl yansıtıldığını ve bunun sınırlarını inceleyeceğiz.
2.4.3 3. adım — belge, one-hot’larının toplamıdır
Bir kelime, bir vektör. Ama yola kelimeleri karşılaştırmak için çıkmadık — belgeleri karşılaştırmak için çıktık ve bir belge aynı anda birçok kelimedir. Hepsiyle ne yapacağız?
En basit cevap işe yarıyor: toplayın. Bir belgedeki her token’ın one-hot vektörünü toplayın; bir sayım vektörü elde edersiniz. İşte kelime torbası (bag-of-words) budur: token’ların çoklu kümesi, sıra atılmış hâlde. Kelimeleri bir torbaya atıp çalkaladığınızı düşünün; neyin kaç kez geçtiği kalır, hangi sırayla geçtiği kaybolur.
d1 tokens: ['pompa', 'yine', 'arızalandı']
d1 vector: [1 0 0 0 1 0 0 0 1]
Bunu dört belge için de yapın; elinizde klasik NLP’nin merkezî nesnesi olan belge-terim matrisi olur. Standart gösterimde satırlar belge, sütunlar terimdir; matris burada 4 belge × 9 terimdir. Sayfaya sığsın diye aşağıda devriğini gösteriyoruz: satırlar kelime, sütunlar belge.
d1 d2 d3 d4
arızalandı 1 0 1 0
değiştirildi 0 1 0 0
kaçırdı 0 0 0 1
onarıldı 0 0 0 1
pompa 1 1 0 0
salmastra 0 0 0 1
vana 0 0 1 0
ve 0 0 0 1
yine 1 1 1 1
feature (öznitelik) — bir gözlemi betimleyen değişken. Burada her öznitelik, belirli bir terimin belgede kaç kez geçtiğini gösterir; belge-terim matrisinde bir sütunla temsil edilir. İstatistikten geliyorsanız bu, öngörücü değişkenin başka bir adıdır. ↩︎
Bir istatistikçi bu yapıyı tanır: çok sayıda ilişkili ve çoğunlukla sıfır değerli öngörücü içeren bir tasarım matrisi. Her belge bir gözlem, her terimin sayımı bir öznitelik (feature). Düzenlileştirme, öznitelik seçimi ve genelleme sorunlarını burada da dikkate alırız; kullanılacak yöntemin varsayımlarını ayrıca kontrol ederiz.
2.4.4 4. adım — her kelime aynı ağırlığı hak eder mi?
Artık elimizde terim sayımları var. Şimdi belgeleri karşılaştırırken hangi terime ne kadar ağırlık vermek istediğimizi belirleyelim.
yine satırına bakın. Dört belgenin hepsinde geçiyor, yani her karşılaştırmaya katkı veriyor ama hiçbir şeyi ayırt etmiyor. Buna karşılık vana bir kez geçiyor ve d3’ü ayırt eden tek şey o. Ham sayımlar ikisine aynı muameleyi yapar.
Formülden önce iki soru. Bir terime yüksek ağırlık vermek için iki şey isteriz:
- Bu belgede sık geçsin. Bunu ölçen çarpan TF’dir.
- Bütün belgelerde sıradan biçimde geçmesin. Bunu ölçen çarpan IDF’dir.
TF-IDF, bu iki sorunun cevaplarını çarpmaktan ibarettir. Aşağıdaki formüller yalnızca bu iki soruyu sayıya dökmenin belirli bir yoludur.
İstediğimiz ağırlık, bir kelime bu belgede sık ve belgeler arasında nadir olduğunda yüksek olmalı. Bunu iki çarpana ayırmak TF-IDF’i verir.
Terim frekansı, kelimenin bu belgede kaç kez geçtiğidir — zaten elimizdeki ham sayım. scikit-learn bu sayımı belge uzunluğuna bölmez. Sayımları önce belge uzunluğuna bölseydik de aynı IDF ağırlıkları ve 5. adımdaki L2 normalleştirmesiyle aynı vektörü elde ederdik: bir satırın bütün bileşenlerine uygulanan ortak pozitif çarpan son bölmede sadeleşir. Bu, belge uzunluğunun bütün etkilerinin ortadan kalktığı anlamına gelmez.
NotUygulama notu: TF-IDF tek bir formül değildir
Birçok ders kitabı terim frekansı için \(\mathrm{tf} = c(t,d)/|d|\) tanımını ya da logaritmik \(1 + \log c(t,d)\) biçimini kullanır; çıkan sayılar da farklı olur. Bir varyantı adlandırmanın klasik kısaltması, (tf) × (idf) × (normalleştirme) için üç harfli bir kod olan SMART gösterimidir. Bu gösterimde scikit-learn’ün varsayılanı kabaca ntc olarak özetlenir: ham (doğal) tf, idf ve kosinüs normalleştirmesi. Ancak bu kod, IDF düzleştirmesinin ayrıntılarını belirtmez; tam formülü ayrıca yazmak gerekir. Logaritmik tf kullanan karşılığı ltc kodudur ve klasik bilgi erişiminde yaygındır. sublinear_tf=True, TF bileşenini ham sayımdan logaritmik sayım ağırlığına (\(1 + \ln c(t,d)\)) geçirir. Bölüm sonundaki deneyde bu seçeneğin doğruluğu, temel TF-IDF yapılandırmasından biraz daha yüksektir (0,843’e karşı 0,831). Bir sayıyı raporlarken hangi varyantı kullandığınızı yazın; “TF-IDF” tek başına bir şartname değildir.
document frequency (df) (belge frekansı) — bir kelimenin kaç belgede en az bir kez geçtiği; her belgede kaç kez geçtiği önemli değildir. Her belgede geçen bir kelimenin yalnızca varlığı belgeleri ayırt etmez; belgelerdeki kullanım sayıları ise farklı olabilir. ↩︎
Belge frekansı (document frequency, df), \(\mathrm{df}(t)\), kelimenin en az bir kez geçtiği belge sayısıdır. Ağırlığın df arttıkça düşmesini istiyoruz, o hâlde tersini alıyoruz. Ama ham tersleme, yani \(1/\mathrm{df}\), fazla sert, çünkü aralığı çok geniş. Bin belgelik bir koleksiyonda tek belgede geçen bir terim \(1/\mathrm{df} = 1\), her belgede geçen bir terim \(0{,}001\) alır: bin kat fark. Bu kadar büyük bir oran, bazı belgelerde nadir terimlerin ağırlığa aşırı katkı vermesine yol açabilir. Bu terimler arasında ayırt edici teknik adlar kadar yazım hataları da bulunabilir. Logaritma bu aralığı sıkıştırır: \(\log(n/\mathrm{df})\) aynı koleksiyonda \(\ln 1000 \approx 6{,}9\) ile 0 arasında kalır ve belge frekansını her ikiye katlamak, aralığın neresinde olursa olsun aynı miktarı (\(\ln 2 \approx 0{,}69\)) götürür. İkinci gerekçe bilgi kuramından gelir: belgelerin eşit olasılıkla seçildiği bir modelde \(\log(n/\mathrm{df})\), seçilen belgenin terimi içermesi olayının öz bilgisidir (aşağıdaki sezgi kontrolüne bakın) (Spärck Jones 1972). Temel biçim şudur:
\[ \mathrm{idf}(t) = \ln\!\left(\frac{n}{\mathrm{df}(t)}\right) \]
Kütüphaneler bu temel biçimi biraz değiştirir. Eğitim derleminden çıkarılan sözlükte her terim en az bir belgede görülür; önceden belirlenmiş bir sözlükte ise eğitimde hiç görülmeyen, yani \(\mathrm{df} = 0\) olan terimler bulunabilir. scikit-learn, sıfır belge frekansını güvenle işlemek ve her belgede geçen terimleri de korumak için şu düzleştirilmiş biçimi kullanır:
\[ \mathrm{idf}(t) = \ln\!\left(\frac{1 + n}{1 + \mathrm{df}(t)}\right) + 1 \]
Formülü sembol sembol okuyun. \(t\) bir terim, \(n\) derlemdeki belge sayısı ve \(\mathrm{df}(t)\), bunlardan kaçının \(t\)’yi en az bir kez içerdiğini sayıyor. \(1+\) terimleri düzleştirmedir ve net bir yorumu vardır: formül, sanki derlemde her terimi birer kez içeren fazladan bir belge varmış gibi davranır. Bu hayalî belge yorumu, paydadaki \(1 + \mathrm{df}\) değerinin neden hep pozitif kaldığını açıklar. Sabit bir \(n\) için IDF sonludur; \(n\) büyüdükçe bu üst sınır da büyür. Buradaki bire ekleme, dil modelleri bölümünde (Bölüm 6) göreceğimiz Laplace düzleştirmesine benzer bir yaklaşımdır; iki formülün paydaları aynı değildir. Sondaki \(+1\) ise her belgede geçen bir terimin IDF değerini 0 yerine 1 yapar. Böylece bu terim sıfırlanmaz; daha az belgede geçen terimlere göre daha düşük bir IDF çarpanı alır. Bu, scikit-learn’ün birebir formülüdür; başka kütüphaneler ayrıntıda farklılaşır ve “sayılarım neden tutmuyor” sorusunun sık rastlanan kaynaklarından biri budur.
İkisini elle hesaplayın. Derlemimizde \(n = 4\) belge var. pompa kelimesi d1 ve d2’de geçiyor, yani \(\mathrm{df} = 2\):
\[ \mathrm{idf}(\texttt{pompa}) = \ln\!\left(\frac{1+4}{1+2}\right) + 1 = \ln(1{,}667) + 1 = 0{,}511 + 1 = 1{,}511 \]
yine kelimesi dördünde birden geçiyor, yani \(\mathrm{df} = 4\):
\[ \mathrm{idf}(\texttt{yine}) = \ln\!\left(\frac{1+4}{1+4}\right) + 1 = \ln(1) + 1 = 0 + 1 = 1 \]
İkinci hesap tasarımı özetliyor. Her belgede geçen bir kelimenin varlığı, elinizde hangi belgenin olduğu hakkında bilgi vermez; dolayısıyla logaritmanın içindeki oran 1’e, logaritma da 0’a iniyor ve terim 1’lik taban IDF değerinde kalıyor. Terim silinmiyor: pompa 1,51 çarpanını alırken yine 1 çarpanını alıyor.
Kütüphanecinin versiyonu. “Pompalarla ilgili kitabı” isteyin; ilgili kelimesi rafı hiç daraltmaz, pompalar ise bir metrelik rafa indirir. IDF, bir terimin derlemdeki belgeler arasında ne kadar yaygın olduğunu ters yönde ağırlıklandırır: yüksek IDF’li bir terimi içeren belgeler, derlemin daha küçük bir bölümünü oluşturur.
NotSezgi kontrolü: logaritma neden?
Logaritmanın kullanımını iki açıdan açıklayabiliriz: ağırlık aralığını sıkıştırması ve belirli bir olasılık modeli altında öz bilgiyle ilişkilendirilebilmesi.
Aralığı sıkıştırır. Bilgilendiricilik nadirliğin doğrusal fonksiyonu değildir; ama bu tek başına logaritmayı gerektirmez, çünkü \(1/\mathrm{df}\) de azalan ve giderek yassılaşan bir eğridir. Fark ağırlık aralığındadır: 10.000 belgelik bir derlemde \(1/\mathrm{df}\), tek belgede geçen terime her belgede geçenden on bin kat büyük ağırlık verir. Bu, bazı karşılaştırmalarda nadir ortak terimlerin etkisini aşırı büyütebilir. \(\log(n/\mathrm{df})\) ise 9,2 ile 0 arasında kalır ve oranları eşit sayar: df’yi 1’den 10’a çıkarmak da 1.000’den 10.000’e çıkarmak da aynı ağırlığı (\(\ln 10 \approx 2{,}3\)) götürür.
Belgede görülme olayının öz bilgisiyle ilişkisi. Derlemden eşit olasılıkla bir belge seçildiğinde, “bu belge \(t\) terimini içeriyor” olayının olasılığı \(P(t) = \mathrm{df}(t)/n\) olur. Düzleştirilmemiş IDF, bu olayın öz bilgisine (self-information: bir olayın gerçekleşmesinin taşıdığı bilgi miktarı) eşittir: \(\log \frac{n}{\mathrm{df}(t)} = -\log P(t)\). Bu yorum, terimin anlamsal önemini ya da her görevde yararlı olduğunu garanti etmez. Robertson’ın makalesi (Robertson 2004) konuyu titizlikle ele alır. Tarihsel sıra da şöyledir: Spärck Jones IDF’i 1972’de görgül gerekçelerle önerdi; kuramsal yorumlar sonradan geliştirildi.
Zipf ile ilişkisi. Logaritma oranları farklara dönüştürür ve geniş değer aralıklarını karşılaştırmayı kolaylaştırır; gelir ya da şehir nüfusu gibi çarpık dağılımlı büyüklüklerde log dönüşümünün sık kullanılmasının nedeni budur. Zipf türü bir kuvvet yasası log-log eksenlerde yaklaşık bir doğru olarak görünür; ancak bu, her dağılım için geçerli değildir. Üstelik Zipf yasasındaki toplam kullanım sıklığı ile IDF hesabındaki belge frekansı farklı büyüklüklerdir.
Derlemimizin belge frekansları ve IDF değerleri aşağıda; önce elle hesaplandı, sonra kütüphaneyle karşılaştırıldı:
import numpy as np
n = len(corpus)
df = (X.toarray() > 0).sum(axis=0)
idf_manual = np.log((1 + n) / (1 + df)) + 1 df idf
arızalandı 2 1.5108
değiştirildi 1 1.9163
kaçırdı 1 1.9163
onarıldı 1 1.9163
pompa 2 1.5108
salmastra 1 1.9163
vana 1 1.9163
ve 1 1.9163
yine 4 1.0000
sklearn idf_: [1.5108 1.9163 1.9163 1.9163 1.5108 1.9163 1.9163 1.9163 1.]
max abs diff vs manual: 0.0
yine dört belgenin hepsinde geçiyor ve tam olarak 1,0 tabanına oturuyor. Tek bir belgeye özgü her kelime 1,9163 alıyor. Asıl mesele sıralama: ağırlık, kelimenin kaç belgede geçtiğinin monoton azalan bir fonksiyonudur.
Gerçek derlemde yayılım çok daha geniş:
the df= 9477 df/N= 0.838 idf= 1.18
is df= 7034 df/N= 0.622 idf= 1.48
car df= 394 df/N= 0.035 idf= 4.35
encryption df= 169 df/N= 0.015 idf= 5.20
gravitational df= 8 df/N= 0.001 idf= 8.14
2.4.5 5. adım — çarp, sonra normalleştir
Masada iki malzeme var: bir kelimenin burada ne sıklıkta geçtiğini söyleyen bir sayım ve onun her yerde ne kadar olağandışı olduğunu söyleyen bir ağırlık. Onları birleştirmek bu bölümün son aritmetik parçası ve bir çarpmanın ardından gelen bir bölmeden ibaret.
Bölmenin nedenini matematiğe geçmeden bir soruyla koyalım: benzer terimleri benzer oranlarda kullanan, biri 20, diğeri 2.000 kelimelik iki belgeyi, yalnızca biri uzun diye neden birbirinden uzak görmek isteyelim? Daha uzun belgelerde bazı terimlerin sayımları ve vektör normu büyüyebilir; ancak her terimin sayımı mutlaka artmaz. L2 normalleştirmesi, sıfır olmayan vektörün normunu 1’e getirirken bileşenlerin birbirine oranını korur. Böylece karşılaştırmada vektörün mutlak büyüklüğü yerine yönü öne çıkar.
TF-IDF — bir belgedeki her kelimeye burada ne sıklıkta geçtiğine göre puan vermek ve bu puanı, kelime koleksiyonda yaygınlaştıkça azaltmak. Diğer koşullar aynıyken, terimin belge içindeki sayımı arttıkça TF-IDF ağırlığı artar; terimi içeren belge sayısı arttıkça IDF çarpanı azalır. ↩︎
TF-IDF, hücre hücre hesaplanan çarpımdır. d1 (pompa yine arızalandı) için:
from sklearn.feature_extraction.text import TfidfVectorizer
counts = X.toarray()[0]
weighted = counts * idf_manual
norm = np.linalg.norm(weighted)
print("counts :", counts)
print("times idf :", weighted.round(4))
print("l2 norm :", round(norm, 4))
print("normalized :", (weighted / norm).round(4))
print("sklearn row d1:", TfidfVectorizer().fit_transform(corpus).toarray()[0].round(4))counts : [1 0 0 0 1 0 0 0 1]
times idf : [1.5108 0. 0. 0. 1.5108 0. 0. 0. 1. ]
l2 norm : 2.3591
normalized : [0.6404 0. 0. 0. 0.6404 0. 0. 0. 0.4239]
sklearn row d1: [0.6404 0. 0. 0. 0.6404 0. 0. 0. 0.4239]
Son iki satır, elle kurduğumuz hesapla TfidfVectorizer sonucunun gösterilen dört ondalık basamakta uyuştuğunu gösteriyor. Bu örnekte iki sonucu yuvarlamadan karşılaştırdığımızda da en büyük mutlak fark 0,0 çıkıyor. Genel olarak kayan noktalı sonuçları np.allclose gibi bir toleransla karşılaştırmak gerekir. Kütüphane, burada adım adım kurduğumuz hesabın aynısını yapıyor.
Tek bir satırı baştan sona takip edin. d1, pompa yine arızalandı; yani üç konum sıfırdan farklı ve her birinin sayımı 1. Her birini kendi IDF’siyle çarpın; arızalandı için \(1{,}5108\), pompa için \(1{,}5108\), yine için \(1{,}0000\) çıkar. Bu vektörün uzunluğu, dokuz boyuta genişletilmiş sıradan Pisagor uzunluğudur — karelerini alın, toplayın, karekökünü alın:
\[ \lVert v \rVert_2 = \sqrt{1{,}5108^2 + 1{,}5108^2 + 1{,}0000^2} \approx \sqrt{2{,}28259 + 2{,}28259 + 1{,}00000} = \sqrt{5{,}56518} \approx 2{,}3591 \]
Ara değerler gösterim için yuvarlandı; tam hassasiyetle hesaplandığında norm \(2{,}35906509\ldots\) çıkıyor ve kütüphane de bu değeri kullanıyor. Sıfır olan altı bileşen toplamı değiştirmez. Büyük sözlüklerde bu avantajdan hesaplama sırasında yararlanmak için matrisi seyrek biçimde tutarız; bu küçük örnekte ara değerleri göstermek için kullandığımız toarray() dönüşümünü büyük matrislere uygulamayız. Şimdi bölün:
\[ \frac{1{,}5108}{2{,}3591} = 0{,}6404 \qquad \frac{1{,}0000}{2{,}3591} = 0{,}4239 \]
ve bunlar yukarıda TfidfVectorizer’ın bastığı sayılar.
Son adım — vektörü kendi uzunluğuna, \(\lVert v \rVert_2\)’ye bölmek — L2 normalleştirmedir. L2 normalleştirmesi, modelin vektör büyüklüğünü nasıl kullanacağını değiştirir. Bu adım olmadan uzun bir belgenin vektörü çoğu zaman daha büyük normludur ve iç çarpıma ya da Öklid uzaklığına dayanan her karşılaştırma — doğrusal bir sınıflandırıcının puanı dâhil — kısmen bir vektör büyüklüğü karşılaştırmasına dönüşür. (Kosinüs normlara kendisi böldüğü için bundan etkilenmez; aşağıda göreceğiz.) Normalleştirme sıfır olmayan her belge vektörünü birim küre üzerine taşır; böylece karşılaştırmada yalnızca yön, yani ağırlıklı terim bileşimi kalır. Büyüklük etkisini kaldırmak istediğimizde bu yararlıdır; sınıflandırma başarısına etkisini ise doğrulama verisiyle ölçeriz.
Az önce kurduğunuz belirli bileşim — terim frekansı, çarpı ters belge frekansı, sonra uzunluğa göre normalleştirme — denenmiş tek bileşim değil. Salton ve Buckley (Salton ve Buckley 1988), farklı terim ağırlıklandırma şemalarını deneysel olarak karşılaştırdı. Burada kullandığımız ham TF, düzleştirilmiş IDF ve L2 bileşimi scikit-learn’ün varsayılanıdır; başka araçlarda formül ve normalleştirme tercihleri değişebilir.
Bunu, her belgeyi aynı yarıçaplı bir küreye oturtmak olarak düşünün. 50 kelimelik bir rapor da 5.000 kelimelik bir rapor da birim uzunluğa iner — bileşenlerin kendisi değil, karelerinin toplamı 1 olur — dolayısıyla soru artık her birinin ne kadar uzağa uzandığı değil, hangi yönü gösterdiği.
Ön işlemeden ve sözlük süzmesinden sonra hiçbir etkin terimi kalmayan belge sıfır vektörü verir. Bunun nedeni boş metin, yalnızca sözlük dışı terimler veya bütün terimlerin filtrelenmesi olabilir. Böyle bir belgede \(\lVert v \rVert_2 = 0\) olur; sıfır vektörün yönü yoktur ve onunla hesaplanan kosinüs \(0/0\), yani tanımsızdır. scikit-learn sıfır satır ve 0 kosinüs döndürür; bu bir uzlaşımdır. Bu satırları sayın ve ayrı işleyin. Kütüphanenin bunlar için döndürdüğü 0 puanını, iki geçerli belge arasında ölçülmüş anlamsal benzemezlik olarak yorumlamayın.
2.4.6 6. adım — belgeleri kosinüs benzerliği ile karşılaştırmak
Artık her belge \(\mathbb{R}^9\) içinde bir nokta. Bu da bölümün baştan beri yöneldiği soruyu sormamızı sağlıyor: bu iki belgenin ağırlıklı terim bileşimleri ne kadar benziyor?
cosine similarity (kosinüs benzerliği) — iki belgenin ne kadar aynı yöne işaret ettiği; ne kadar uzun oldukları hesaba katılmadan. Bu nedenle kısa bir not ile uzun bir rapor, ağırlıklı terim oranları benzerse yüksek kosinüs puanı alabilir. Puan, tek başına aynı konuyu ya da aynı anlamı taşıdıklarını kanıtlamaz. ↩︎
Aklınızda tutulacak tek cümle şu: kosinüs benzerliği uzunluğu değil yönü karşılaştırır. Normalleştirilmiş iki vektör iç çarpımlarıyla karşılaştırılır; bu da aralarındaki açının kosinüsüdür — yani kosinüs benzerliği (cosine similarity): Buradaki sıfır olmayan ve negatif bileşen içermeyen vektörlerde 1 aynı yönü, 0 ise ortak pozitif ağırlıklı terim bulunmadığını gösterir. Bu yorum, negatif bileşen içerebilen başka temsillere doğrudan taşınamaz. Aşağıdaki kutuda göreceğiniz Öklid uzaklığıyla denklik, bu temel sezginin yerine geçen değil, onun üzerine eklenen bir ayrıntıdır.
\[ \cos(a, b) = \frac{a \cdot b}{\lVert a \rVert \, \lVert b \rVert} = \sum_{i} a_i b_i \quad \text{(ikisi de zaten birim uzunluktaysa)} \]
Bir hücreyi elle hesaplayın. d1 pompa yine arızalandı, d3 ise vana yine arızalandı; arızalandı ile yine ortak, geri kalan her yerde ayrışıyorlar. Toplama yalnızca iki vektörün de sıfırdan farklı olduğu konumlar katkı verebilir. Bu yüzden dokuz boyutlu toplamda sıfırdan farklı tam iki terim kalır:
\[ \cos(\text{d1}, \text{d3}) = \underbrace{0{,}6404 \times 0{,}5729}_{\texttt{arızalandı}} + \underbrace{0{,}4239 \times 0{,}3792}_{\texttt{yine}} = 0{,}3669 + 0{,}1607 = 0{,}528 \]
Bu dağılımın ne kadar dengesiz olduğuna dikkat edin. Benzerliğin %70’ini arızalandı, %30’unu yine sağlıyor — üstelik iki kelime de her iki belgede tam olarak birer kez geçiyor. Ham sayımlarda ikisi eşit katkı verirdi. Ortak kelimelerden hangisinin kanıt sayılacağına ağırlıklandırma karar verir.
from sklearn.metrics.pairwise import cosine_similarity
T = TfidfVectorizer().fit_transform(corpus) # 5. adımdaki TF-IDF satırları
print(cosine_similarity(T).round(3)) # üst panel
print(cosine_similarity(X).round(3)) # alt panel: 1. adımdaki ham sayımlarÇıktıya okunabilsin diye satır ve sütun etiketleri eklendi:
d1 d2 d3 d4 <- TF-IDF
d1 1.000 0.528 0.528 0.107
d2 0.528 1.000 0.144 0.096
d3 0.528 0.144 1.000 0.096
d4 0.107 0.096 0.096 1.000
d1 d2 d3 d4 <- raw counts
d1 1.000 0.667 0.667 0.258
d2 0.667 1.000 0.333 0.258
d3 0.667 0.333 1.000 0.258
d4 0.258 0.258 0.258 1.000
İki paneli karşılaştırın. Ham sayımda d1 ile d4’ün kosinüsü 0,26 — oysa paylaştıkları tek kelime yine. TF-IDF, ortak yine teriminin göreli katkısını azaltarak bu puanı 0,11’e indiriyor. (Kosinüs, iki vektör arasındaki açının kosinüsüdür. Anlamsal benzerliğin kalibre edilmiş bir yüzdesi değildir; oysa onu “%26 benzer” diye raporlamak bunu ima eder.) Bu örnekte farklı belgeler arasındaki bütün puanlar düşüyor; yalnızca yine terimini paylaşan çiftlerde oransal düşüş daha büyük. Her belgenin kendisiyle benzerliği ise 1 olarak kalıyor.
Neden kosinüs, neden Öklid uzaklığı değil
Solda: iki kelime üzerinde ham sayım vektörü olarak üç belge; iki eksen aynı ölçekte çizildi. Bu örnekte B’nin her terim sayımı A’nın tam on katıdır; dolayısıyla A ve B orijinden çıkan aynı ışın üzerindedir. C’de ise terim oranları farklıdır. Ham sayımlarla Öklid uzaklığı A’yı C’ye, B’ye olduğundan altı kattan fazla yakın bulur. Sağda: L2 normalleştirmesinden sonra her vektörün uzunluğu 1; A ile B aynı nokta hâline geliyor ve ölçülecek tek şey olarak yönler arasındaki açı kalıyor. Klasik metin işleme hatlarının karşılaştırmadan önce normalleştirmesinin sebebi bu.
NotSezgi kontrolü: neden kosinüs, neden Öklid uzaklığı değil?
Çünkü normalleştirilmemiş vektörlerde Öklid uzaklığı vektör büyüklüğünden güçlü biçimde etkilenir. Aynı arızayı anlatan iki rapor, benzer terim oranlarına sahipse farklı büyüklüklerde ama yakın yönlerde vektörler üretebilir. Ancak uzun rapora eklenen ayrıntılar vektörün yönünü de değiştirebilir. Kosinüs yalnızca yöne bakar: terimlerin birbirine oranını karşılaştırır, toplam miktarını değil.
Bunu akılda tutmanın en kolay yolu: L2 normalleştirmesinden sonra bütün vektörler birim küre üzerindedir ve orada kosinüs benzerliği ile Öklid uzaklığı monoton biçimde ilişkilidir — \(\lVert a - b \rVert^2 = 2 - 2\cos(a,b)\). Sıfır olmayan vektörler L2 ile normalleştirilmişse, tam en yakın komşu aramasında iki ölçüt aynı sıralamayı verir. Sayısal eşiklerin ise dönüştürülmesi gerekir; bu denklik, bütün kümeleme yöntemlerinin veya yaklaşık arama uygulamalarının aynı sonucu vereceği anlamına gelmez. Normalleştirilmemiş vektörlerde iki ölçüt çok farklı sıralamalar verebilir.
Şekil üzerinden doğrulayın. \(a\) ile \(c\) arasındaki açı \(\cos = 0{,}651\) veriyor; dolayısıyla \(\lVert a - c \rVert^2 = 2 - 2(0{,}651) = 0{,}698\) ve uzaklık \(\sqrt{0{,}698} = 0{,}836\) oluyor. Normalleştirmeden önce aynı çift 3,16 uzaklıktaydı; normalleştirmeden sonra 0,836 ve — asıl önemli kısım burası — artık her çiftin sıralaması kosinüsle uyuşuyor.
Not\(2 - 2\cos\) nereden geliyor?
İki satırlık bir cebir. Biri kosinüs indeksini L2 indeksiyle değiştirip aynı komşuları beklediği her durumda bu özdeşlik Bölüm 3’te yeniden karşınıza çıkacak.
Uzaklığın karesini, \((x-y)^2\)’yi açar gibi açın; tek fark, buradaki çarpımların iç çarpım olması:
\[ \lVert a - b \rVert^2 = (a - b) \cdot (a - b) = a \cdot a - 2\,(a \cdot b) + b \cdot b \]
Şimdi normalleştirmenin bize kazandırdığını kullanın. Bir vektörün kendisiyle iç çarpımı, uzunluğunun karesidir ve normalleştirilmiş her vektörün uzunluğu tam olarak 1’dir; yani \(a \cdot a = 1\) ve \(b \cdot b = 1\). İkisi de birim uzunlukta olduğu için iç çarpımları doğrudan kosinüstür. Yerine koyun:
\[ \lVert a - b \rVert^2 = 1 - 2\cos(a,b) + 1 = 2 - 2\cos(a,b) \]
Uzaklık, benzerliğin kesin azalan bir fonksiyonudur: kosinüs 0’dan 1’e tırmanırken uzaklığın karesi 2’den 0’a düşer. Yani Öklid uzaklığına göre yapılan bir en yakın komşu araması ile kosinüse göre yapılan arama aynı sıralı listeyi döndürür — vektörler önceden normalleştirilmiş olmak şartıyla. Normalleştirme yapılmazsa bu sıralama denkliği ortadan kalkar. L2 uzaklığı hem vektör büyüklüğünden hem yönünden etkilenir; bu nedenle terim bileşimi benzer belgeler, sayım ölçekleri farklı olduğu için uzak görünebilir.
2.5 Kelime torbasının yapamadıkları
Şimdi bu temsilin, daha vektör kurulurken hangi bilgileri kaybettiğine bakalım. Bu kayıplar, sonraki bölümlerde inceleyeceğimiz sıra ve bağlam temelli temsillere neden ihtiyaç duyulduğunu açıklıyor.
Kelime sırasını temsil edemez. “Kötü temsil eder” değil; hiç temsil edemez:
pair = ["pompa arızalandı ve vana sağlam",
"vana arızalandı ve pompa sağlam"]vocabulary: ['arızalandı','pompa','sağlam','vana','ve']
row 1: [1 1 1 1 1]
row 2: [1 1 1 1 1]
vectors identical: True
cosine similarity: 1.0
Hangi ekipmanın arızalandığı konusunda birbirinin tam tersini söyleyen iki rapor, uzayda aynı noktadır. Ne kadar iyi olursa olsun hiçbir sınıflandırıcı bunları ayıramaz, çünkü bilgi, sınıflandırıcı onu görmeden önce yok edilmiştir. Temsil aşamasında atılan bilgi, daha güçlü bir sınıflandırıcıyla geri getirilemez.
n-gram’lar bu bilginin bir kısmını geri kazandırır. Tek kelime yerine komşu kelime çiftleri kullanınca:
bigram vocabulary: ['arızalandı ve','pompa arızalandı','pompa sağlam',
'vana arızalandı','vana sağlam','ve pompa','ve vana']
cosine similarity: 0.25
vocabulary size: unigrams 5 -> bigrams 7
Artık ayırt edilebiliyorlar. Bu kazancın bir maliyeti vardır: n-gram sözlüğü gerçek derlemlerde çok büyüyebilir. Büyüme, derlemin özelliklerine, n-gram aralığına ve uygulanan filtrelere bağlıdır. Aşağıdaki deneyde ikili n-gram (bigram) eklemek öznitelik sayısını 32.318’den 249.522’ye, yani 7,7 katına çıkardı ve doğruluk düştü. n-gram’lar yerel sıra bilgisinin bir kısmını geri kazandırır ve dil tanıma, spam süzme ya da kısa metin sınıflandırma gibi işlerde tek başlarına yeterli olabilir. Ama bağlam uzunluğu arttıkça öznitelik uzayı hızla büyür; uzun menzilli ve bağlama duyarlı ilişkiler için daha genel temsillere ihtiyaç duyarsınız. Bölüm 7 bunun konusu.
Olumsuzlamanın kapsamını temsil edemez. değil de diğerleri gibi bir özniteliktir ve bir sınıflandırıcı, bu kelimenin varlığının olasılıkları kaydırdığını öğrenebilir. Kelime torbasının geri getiremediği şey ise neyin olumsuzlandığıdır. Aşağıdaki örnek bunu gösteriyor:
"sızıntı tespit edilmiş değil" vs "sızıntı tespit edilmiş"
vocabulary: ['değil', 'edilmiş', 'sızıntı', 'tespit']
raw counts -> cosine 0.866
TF-IDF -> cosine 0.777
Operasyonel anlamı taban tabana zıt iki cümle, ağırlıklandırmaya göre 0,78 ile 0,87 arasında bir kosinüs alıyor. Bu örnek, anlamca karşıt cümlelerin yüksek kosinüs alabileceğini gösteriyor. değil korunursa vektörler farklıdır ve bir sınıflandırıcı bu farkı kullanabilir. Olumsuzlamanın hangi ifadeye bağlandığını ise tek kelimelik sayımlar doğrudan kodlamaz. Stop word listenizde değil sözcüğü varsa — ki bazı hazır Türkçe listelerde bulunur — iki cümle arasındaki farkı daha vektörü kurmadan silersiniz ve iki vektör birebir aynı hâle gelir (kosinüs 1). Bu durumda yalnızca bu vektörleri kullanan bir model iki cümleyi ayırt edemez. Olumsuzluğun kararı değiştirdiği görevlerde stop word listesini bu nedenle ayrıca denetlemek gerekir.
Türkçede olumsuzluğun ifade edilişine burada iki örnek verelim: ayrı yazılan değil ve fiile eklenen -me-/-ma-. Başka yollar da vardır; örneğin yok ve ne … ne yapıları. değil ayrı bir kelimedir; yukarıdaki örnekteki gibi, bir stop word listesi onu silebilir. -me-/-ma- ise gövdeye eklenen bir ektir: çalışıyor ile çalışmıyor kelime düzeyinde iki ayrı özniteliktir. Alt kelime tokenizer’ı olumsuzluk ekini ayrı bir parça olarak ortaya çıkarabilir; ama kelime torbası temsili bu parçanın hangi fiile, hangi kapsamla bağlandığını yine de temsil etmez.
Eş anlamlılığı ve çok anlamlılığı doğrudan kodlamaz. pompa arızalandı ile pompa bozuldu, pompa dışında hiçbir şey paylaşmaz. Küçük harfe dönüştürülmüş bir kelime torbası temsilinde şirket olarak Apple ile meyve olarak apple aynı sütuna düşer — büyük/küçük harf korunursa iki ayrı sütun olurlar; bu da normalleştirme merdiveninde (Bölüm 2.2.1) verdiğiniz kararın doğrudan sonucudur. Bu temsilde farklı terimler ayrı sütunlardadır; aralarındaki anlamsal ilişki, 2. adımda one-hot vektörlerde gördüğümüz gibi, doğrudan kodlanmaz. Embedding bölümündeki (Bölüm 3) yoğun temsiller bu ilişkileri öğrenmeye yardımcı olur. Bir kelimenin kullanıma göre değişen anlamını ayırmak için ayrıca bağlamı hesaba katmak gerekir.
2.6 Bütün bunların hâlâ bir önemi var mı?
Var — ve bunu görüşle değil sayılarla söyleyebilmelisiniz. İşte gerçek bir deney: dört haber grubu, 2.327 eğitim belgesi, 1.550 test belgesi, lojistik regresyon; hiperparametre araması yapılmadı. Yakınsama için max_iter=2000 kullanıldı.
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.pipeline import make_pipeline
cats = ["sci.med", "sci.space", "rec.autos", "talk.politics.guns"]
drop = ("headers", "footers", "quotes")
train = fetch_20newsgroups(subset="train", categories=cats, remove=drop)
test = fetch_20newsgroups(subset="test", categories=cats, remove=drop)
setups = {
"raw counts": CountVectorizer(),
"counts + stop words": CountVectorizer(stop_words="english"),
"tf-idf": TfidfVectorizer(),
"tf-idf + stop words": TfidfVectorizer(stop_words="english"),
"tf-idf + bigrams": TfidfVectorizer(ngram_range=(1, 2)),
"tf-idf + sublinear tf": TfidfVectorizer(sublinear_tf=True),
}
for name, vec in setups.items():
clf = make_pipeline(vec, LogisticRegression(max_iter=2000, random_state=0))
clf.fit(train.data, train.target)
acc = accuracy_score(test.target, clf.predict(test.data))
print(f"{name:<24} accuracy {acc:.3f} "
f"features {len(clf[0].get_feature_names_out()):>7}")raw counts accuracy 0.767 features 32318
counts + stop words accuracy 0.795 features 32014
tf-idf accuracy 0.831 features 32318
tf-idf + stop words accuracy 0.853 features 32014
tf-idf + bigrams accuracy 0.819 features 249522
tf-idf + sublinear tf accuracy 0.843 features 32318
Bu tablo bir mekanizma gösterimi ve bir baseline karşılaştırmasıdır. Küçük farkları evrensel bir üstünlük olarak okumayın; aşağıdaki çekince bunun neden böyle olduğunu sayılarla anlatıyor.
Bu altı satırı dikkatle okuyun, çünkü içlerinde dört çıkarım var:
- TF-IDF, ham sayımlara göre 6,4 doğruluk puanı kazandırıyor — ek eğitim belgesi toplamadan ve hiperparametre araması yapmadan. IDF hesabı ve normalleştirme yine de ek işlem gerektirir.
- Stop word atmak burada faydalı (+2,2 puan): bu veri kümesinde ve bu konu sınıflandırma görevinde, çıkarılan işlev kelimeleri sınıfları ayıran güçlü bir sinyal taşımıyor gibi görünüyor. Duygu analizinde veya olumsuzluk tespitinde, liste görev için önemli kelimeleri çıkarıyorsa aynı adım zarar verebilir. Buradaki artış, çıkarılan bütün kelimelerin yararsız olduğunu tek başına göstermez. O listeyle ilgili bir not:
scikit-learn’ün kendi belgelendirmesistop_words='english'’i sorunlu olarak niteliyor — genel amaçlı bir liste değil, seçim ölçütleri belgelenmemiş ve birkaç girdisi zaten varsayılantoken_patternile hiç üretilemiyor. Stop word’ler sonucunuz için önemliyse listeyi kendiniz yazın ve ekrana basın. - İkili n-gram’lar eklenince doğruluk düştü; öznitelik sayısı ise 7,7 katına çıktı. Daha çok öznitelik, daha yüksek doğruluk garantisi vermez. Eklenen seyrek n-gram’lar aşırı öğrenme riskini artırabilir; bu deneydeki farkın nedenini belirlemek için düzenlileştirme ve öznitelik seçimini ayrıca karşılaştırmak gerekir.
sublinear_tfaynı yönü işaret ediyor (0,843’e karşı 0,831). Bu seçenek ham sayımı \(1 + \log(\text{tf})\) ile değiştirir — IDF için kurduğumuz aralık sıkıştırma argümanının, bu kez terim frekansı tarafına uygulanmış hâli.
Bu altı sayıdan herhangi birini aktarmadan önce bir çekince. Bunlar, yanında aralık verilmemiş nokta kestirimleri. Test kümesinde 1.550 belge var; belgeleri bağımsız sayan binom yaklaşımıyla 0,83 civarındaki tek bir doğruluğun %95 aralığı kabaca ±1,9 puandır. İki doğruluk bağımsız kestirimler gibi ele alınırsa aralarındaki farkın aralığı kabaca ±2,6 puan olur. Bu aralıklar belirsizliğin ölçeğini kabaca gösterir; ancak altı model aynı test belgelerinde ölçüldüğü için farklara bağımsız kestirim varsayımıyla karar vermek uygun değildir. Farkların belirsizliğini eşleştirilmiş tahminlerden hesaplamak gerekir; aşağıdaki not bunu yapıyor. Altı yapılandırma karşılaştırıldığı için sonuç seçimi ve çoklu karşılaştırma etkisini de hesaba katmak gerekir.
Notİleri not: aynı test örneklerini gören iki modeli karşılaştırmak
Altı model birebir aynı test belgelerini görüyor. Bu durumda uygun araç, McNemar gibi eşleştirilmiş bir testtir (McNemar 1947). McNemar testi, bir modelin doğru, diğerinin yanlış sınıflandırdığı belgelerin sayılarını karşılaştırır; iki modelin de doğru ya da ikisinin de yanlış sınıflandırdığı belgeler sonucu etkilemez. Böylece aynı test belgelerinden gelen eşleşme hesaba katılır.
Dört çıkarımın dayandığı karşılaştırmalar için bu deneydeki sayılar aşağıda; p-değerleri binom dağılımına dayanan kesin McNemar testinden geliyor:
| Karşılaştırma | Yalnızca ilki doğru | Yalnızca ikincisi doğru | p |
|---|---|---|---|
| TF-IDF ile ham sayım | 119 | 20 | 2,4 × 10⁻¹⁸ |
| TF-IDF + stop word ile TF-IDF | 86 | 52 | 0,0048 |
| TF-IDF + bigram ile TF-IDF | 17 | 35 | 0,018 |
| TF-IDF + sublinear tf ile TF-IDF | 29 | 11 | 0,0064 |
Dört karşılaştırma yapıldığı için Bonferroni düzeltmesiyle eşik 0,05/4 = 0,0125 olur. TF-IDF, stop word ve sublinear_tf farkları bu eşiğin altında kalıyor; bigram farkı kalmıyor. Bu p-değerleri yalnızca test belgelerinin örneklenmesine ilişkin belirsizliği ölçer. Tek bir eğitim–test bölmesine ve tek bir eğitim koşusuna dayanırlar; başka veri kümelerine ya da görevlere genellenemezler.
Altı satırdan genelleme yapmaya dair bir uyarı daha. Buradaki her sayı uzun belgeler üzerinde İngilizce konu sınıflandırmasından geliyor ve dört dersin ikisi görev değişince tersine dönebiliyor. Türkçe ürün yorumlarında — kısa belgeler, konu değil duygu — Bölüm 4’ün ilk tablosu ikisinde de ters yönü gösteriyor: sabit etiket bütçesinde en büyük kazancı ikili n-gram’lar veriyor, düz sayım da TF-IDF’i geçiyor. Ama ikinci sonuç IDF’ten gelmiyor. O bölümün ablasyonu, akla ilk gelen “IDF sık duygu kelimelerini bastırıyor” açıklamasını çürütüyor (asıl kaybı L2 normalleştirmesi getiriyor, IDF puanı geri kazandırıyor) ve sonucun ayarlanmamış bir C’ye bağlı olduğunu, eşit ayar bütçesinde tersine döndüğünü gösteriyor. Aktarılabilir olan ders sıralama değil, yöntem.
Önemsemek için ikinci sebep, modelin okunabilir olması. TF-IDF artı lojistik regresyon modeline ne öğrendiğini sorun; size kelimelerle söylesin:
rec.autos car, cars, engine, ford, oil, dealer, drive, price
sci.med msg, doctor, disease, medical, pain, patients, treatment
sci.space space, nasa, orbit, moon, launch, earth, spacecraft
talk.politics.guns gun, guns, weapons, fbi, law, government, firearms
Doğrusal kelime torbası modellerinde katsayılar, sözlükteki terimlere veya n-gram’lara bağlanabilir. Çok sınıflı modelde bu katsayıları sınıflara göre inceleriz; yukarıdaki tablo her sınıf için en yüksek katsayılı terimleri gösteriyor. Belirli bir belgedeki puan katkısı katsayı ile öznitelik değerinin çarpımına bağlıdır; yüksek katsayıyı tek başına nedensel açıklama saymayız. Yoğun temsillerde aynı açıklıkta bir terim–katsayı eşlemesi genellikle bulunmaz. Yoğun modeller için de öznitelik atfetme (attribution), SHAP benzeri açıklamalar ve yoklama (probing) yöntemleri var; ama hiçbiri yukarıdaki tablo kadar doğrudan okunmuyor. Bir modeli güvenlik kuruluna, düzenleyiciye ya da şüpheci bir tesis müdürüne anlatmanız gerektiğinde bu okunabilirlik, hataları incelemeyi ve model kararlarını tartışmayı kolaylaştırır. Doğrulukla yapılacak ödünleşimi ise uygulamanın gereksinimleri belirler.
Hesaplama maliyeti de düşük:
shape: (2327, 32318)
stored non-zeros: 234281
density: 0.312%
dense float64 would need : 601.6 MB
sparse csr actually needs: 2.8 MB
ratio: 213x
Matrisin %99,7’si sıfır; dolayısıyla seyrek temsil onu 601,6 MB yerine 2,8 MB’ta saklıyor (bunu sağlayan CSR biçimi ve hangi işlemlerin seyrekliği bozduğu mülakat sorularında). Bu örnek GPU gerektirmez. Sınıflandırma puanlarının hesabı seyrek iç çarpımlara dayanır; uçtan uca çıkarım ayrıca ön işlemeyi ve vektörleştirmeyi içerir. 2,8 MB yalnızca seyrek matrisin saklama maliyetidir; sözlük, IDF, model katsayıları ve çalışma belleği buna eklenir. Eğitim süresi donanıma ve çözücü ayarlarına bağlıdır: bu bölümün ölçümlerinin yapıldığı dizüstü bilgisayarın işlemcisinde yukarıdaki altı yapılandırmanın her biri, vektörleştirme, eğitim ve test tahmini dâhil 2 saniyenin altında tamamlandı.
Bütün bölümün kanıtını tek yere koyalım:
| Kelime torbası neden ayakta kaldı | Neden yeterli değil |
|---|---|
| Küçük bir seyrek matris. Bu örnekte 2.327 belgenin matrisi 2,8 MB; toplam model belleği daha büyüktür. GPU gerekmez. | Kelime sırası yok. pompa arızalandı, vana sağlam ile vana arızalandı, pompa sağlam aynı vektör. |
Okunabilir. Model kendi özniteliklerini adlandırır — gun, orbit, doctor. Terimlerin puana katkısını incelemeyi kolaylaştırır. |
Eş anlamlılık yok. araba ile otomobil, araba ile muz kadar ilgisizdir. Ya birebir eşleşme ya hiç. |
| Güçlü baseline. Burada 0,853; Bölüm 3’te ortalaması alınmış embedding’leri (gömme) yendi — sonra Bölüm 4’te Türkçe veride tekrar yendi. | Çok anlamlılık yok. yüz için tek sütun var; surat mı, sayı mı, fiil mi, fark etmez. |
| Kendi verinizin ötesinde eğitim verisi gerekmez. Ön eğitimli hiçbir şey, indirilen hiçbir şey, tedarikçi yok. | Sözlük dışı terimler olabilir. Yeni belgelerde, eğitimde oluşturulan sabit sözlükte bulunmayan terimler görülebilir. |
| Sabit hatla tekrarlanabilir. Ön işleme, sözlük, IDF ve model parametreleri sabitken aynı girdi aynı çıktıyı verir; terim katkıları incelenebilir. | Olumsuzlamanın kapsamı yok. değil de diğerleri gibi bir özniteliktir ve sinyal taşıyabilir; torbanın geri getiremediği şey neyin olumsuzlandığıdır — stop word’leri atmak ise onu tümüyle siler. |
Doğru okuma “eski teknoloji, hâlâ şaşırtıcı derecede iyi” değildir. Tablonun iki sütunu aynı temsilin güçlü yanlarını ve sınırlılıklarını gösteriyor. Hangi tarafın ağır bastığı, görevin gereksinimlerine bağlıdır.
Birebir terim eşleşmesi, hız ve izlenebilirlik önemliyse kelime torbası güçlü bir başlangıç seçeneğidir. Son kararı doğruluk, gecikme, bellek ve hata türlerini birlikte değerlendirerek verin.
Görev sıra veya bağlam bilgisine ihtiyaç duyuyorsa, tek kelimelik sayımlarla sınırlı temsili genişletmek gerekir. Yerel ilişkiler için n-gram’lar, yeni biçimler için karakter öznitelikleri yararlı olabilir. Daha kapsamlı anlamsal ilişkiler için embedding bölümünden (Bölüm 3) başlayarak yoğun ve bağlamsal temsilleri inceleyeceğiz.
2.6.1 Bunlar gerçekte nerede çalışıyor
Aşağıdaki örnekler, seyrek metin temsillerinin uygulamadaki kullanımını gösteriyor. Bazıları şirketlerin yayımladığı tarihli sistem raporlarına, bazıları yöntem makalelerine dayanıyor; anlatılan mimarileri kaynakların anlattığı dönemle sınırlı okuyun. Üç yeni terim geçecek; üçü de bu bölümdeki bir fikrin uygulamadaki hâli:
- BM25: TF-IDF ailesinin arama için geliştirilmiş ağırlıklandırması.
- Feature hashing (öznitelik hash’leme): sözlüğü bellekte tutmadan sabit boyutlu seyrek temsil üretme yöntemi.
- Shingling: kelime sırasının bir kısmını koruyan, n-gram tabanlı temsil.
Kullanım yerlerinin kendisi:
- Uber’de talep yönlendirme. Uber’in 2018’de anlattığı müşteri destek sistemi COTA, bu bölümdeki hattın büyük bölümünü kullanıyor: metni temizle, token’lara ayır, stop word’leri at, kelimeleri sözlük biçimine indir (lemmatizasyon), TF-IDF hesapla, LSA ile boyut indir (latent semantic analysis, yani TF-IDF matrisinin kesik SVD’si; aşağıdaki mülakat sorularına bakın). Ardından LSA vektörlerinden hesaplanan kosinüs benzerliğini, talep ve yolculuk öznitelikleriyle birlikte random forest tabanlı bir eşleşme modeline ver ve aday çözümleri bu modelin puanına göre sırala (Uber Engineering 2018). Yazıya göre sistem, destek talebi (ticket) çözüm süresini %10’dan fazla kısaltabiliyor. Aynı yazı bu sıralayıcıyı, LSA konu vektörlerini doğrudan öznitelik olarak kullanan çok sınıflı bir sınıflandırıcıyla aynı veri kümesinde, aynı tür algoritma ve aynı hiperparametrelerle karşılaştırıyor; sıralayıcı bağıl doğrulukta %25 daha iyi sonuç veriyor. Bu sonuç, LSA vektörlerini doğrudan kullanmak yerine onlardan eşleşme öznitelikleri üretmenin bu deneyde yararlı olduğunu gösteriyor. Yazının sözünü ettiği sorun, anlamlı bir temsil için tutulması gereken yüzlerce, binlerce boyutun çoğunun sıfıra yakın olmasıdır; bu, başlangıçtaki sayım matrisinin çok sayıda tam sıfır içermesiyle aynı seyreklik türü değildir.
- Arama sıralaması. Lucene — dolayısıyla Elasticsearch ve OpenSearch — belgeleri varsayılan olarak BM25 ile puanlar. BM25, TF-IDF ailesinden gelir; ona belge uzunluğu normalleştirmesi ve doyuma giden bir terim frekansı ekler (Robertson ve Zaragoza 2009). Elastic’in Practical BM25 yazı dizisi formülü ve iki ayar sabitini ayrıntılı örneklerle anlatır. BM25, Lucene tabanlı birçok metin arama sisteminde kullanılan yaygın bir puanlama yöntemidir; her arama kutusunun aynı yöntemi kullandığı varsayılamaz.
NotBM25 tek formülde ve iki düğmesi neden zaten tanıdık
\[ \mathrm{BM25}(q, d) = \sum_{t \in q} \mathrm{idf}(t) \cdot \frac{c(t,d)\,(k_1 + 1)}{c(t,d) + k_1\!\left(1 - b + b\,\frac{|d|}{\mathrm{avgdl}}\right)} \]
Bu basitleştirilmiş formülde toplam, sorgudaki farklı terimler üzerinden alınır; sorgu terimlerinin tekrar ağırlıkları ayrıca modellenmez. \(c(t,d)\), \(t\) teriminin belgedeki sayımı; \(|d|\), analiz edilmiş belgenin token sayısı; \(\mathrm{avgdl}\) ise aynı derlemdeki ortalama belge uzunluğudur.
Formülü az önce kurduğunuz şeyle karşılaştırarak okuyun. \(\mathrm{idf}\) çarpanı bizimkiyle aynı fikir (BM25 olasılıksal bir varyant kullanıyor). Kesir, ham terim frekansının yerine doyuma giden bir frekans koyuyor: diğer değişkenler, özellikle belge uzunluğu sabitken, \(c(t,d)\) büyüdükçe kesir \(k_1 + 1\) sınırına yaklaşır ve ek tekrarların katkısı giderek azalır. sublinear_tf de tekrarların etkisini yavaşlatır; ancak \(1 + \log(\mathrm{tf})\) sınırsız büyür ve BM25 ile aynı fonksiyon değildir. \(b \in [0,1]\) ise belge uzunluğunu yok saymak (\(b = 0\)) ile terim frekansını belge uzunluğunun derlem ortalamasına oranına bölmek (\(b = 1\)) arasında kademeli bir ayar sağlıyor. Bu, L2 normalleştirmesinin ele aldığı aynı derdi — uzun belgeler sırf uzunluklarıyla kazanmasın — başka bir formülle ve açma-kapama anahtarı yerine ayar düğmesiyle çözüyor; ikisi aynı işlem değil. Lucene 9.12.1’in BM25Similarity sınıfında varsayılanlar \(k_1 = 1{,}2\) ve \(b = 0{,}75\) değerleridir.
- Google’ın metin sınıflandırma rehberi bu temsili öneriyor (Google Developers 2022). Karşılaştırdığı kodlamalar arasında en doğru sonucu TF-IDF n-gram’larının verdiğini bildiriyor. Rehberin İngilizce metin deneylerinde doğruluk birçok veri kümesinde yaklaşık 20.000 seçilmiş öznitelik civarında yataylaşıyor; daha fazla öznitelik çok az katkı veriyor ve bazen başarıyı düşürüyor. Bu, bütün diller için sabit bir eşik değildir. Bizim ikili n-gram sonucumuz da (249.522 öznitelik, daha düşük doğruluk) daha fazla özniteliğin kendiliğinden kazanç sağlamadığını gösteriyor; iki deneydeki düşüşün aynı nedenden kaynaklanıp kaynaklanmadığı ayrıca sınanmalıdır.
- Melez erişim (hybrid retrieval). Modern erişim yığınları, yoğun embedding aramasıyla seyrek BM25 aramasını birleştirir; çünkü seyrek yarı, embedding’lerin kaçırabildiği birebir eşleşmeleri — parça numaraları, hata kodları, soyadları — yakalar. Bunun bir örneğini Bölüm 19’da kuracaksınız; Bölüm 20 onu bir dil modeline bağlar.
- Büyük ölçekte istenmeyen içerik ve kötüye kullanım filtreleme. Seyrek metin öznitelikleri ilk hat filtresi olarak kullanılmayı sürdürüyor, çünkü her mesajda çalıştırılacak kadar ucuzlar ve saldırganlar taktik değiştirdiğinde veri büyüklüğüne, modele ve donanıma bağlı olarak kısa sürede yeniden eğitilebilirler. Bunun için kullanılabilecek yöntemlerden biri feature hashing’dir: terimleri bir hash fonksiyonuyla sabit sayıda sütuna eşler (Weinberger vd. 2009). Açık bir sözlük saklamaz; buna karşılık farklı terimler aynı sütuna düşebilir ve bir katsayıyı tek bir terime geri bağlamak zorlaşır.
- Yakın kopya tespiti. Web ölçeğinde yakın kopya tespitinde hash tabanlı yöntemler önemli bir yer tutar; kısa imzalar çok büyük belge kümelerinde karşılaştırmayı kolaylaştırır. Google’ın 2007’de yayımlanan web tarama tekilleştirme çalışması (Manku vd. 2007) bunun bir örneğidir: belgelerin ağırlıklı özniteliklerinden simhash parmak izleri çıkarır. Yaygın öteki yöntem ise shingle kümeleri üzerinde MinHash’tir (Broder 1997) (ayrıntısı aşağıdaki mülakat sorularında ve Bölüm 11.3’te). Bir shingle, belge boyunca kaydırılan sabit uzunlukta bir kelime (ya da karakter) penceresidir; pencereler çatı kiremitleri gibi üst üste bindiği için bu yapıya “kiremit” anlamına gelen shingle adı verilmiş. Pompa dün gece arızalandı cümlesinin üçlü kelime shingle’ları
pompa dün gecevedün gece arızalandıolur. Yani shingle’lar, yukarıda sıra bilgisini kurtarmak için kullandığımız n-gram’larla aynı yapıdadır.
Pratik kural: etiketiniz azsa, belgeleriniz uzunsa, sözlüğünüz teknik ve birebir eşleşme önemliyse ya da kısa sürede bir karşılaştırma modeli (baseline) gerekiyorsa TF-IDF’e başvurun. Metin sınıflandırma ve sözcüksel erişim gibi uygun görevlerde, TF-IDF tabanlı bir karşılaştırma modelini değerlendirmeye dâhil edin. Transformer’ınız onu geçemiyorsa, veriniz hakkında önemli bir şey öğrendiniz demektir — üstelik ucuza öğrendiniz.
2.7 Sezgi kontrolleri
Açıklamayı okumadan önce soruları kendiniz cevaplayın. Altısını da cevaplayabiliyorsanız, konuyu yalnızca kodu çalıştırmanın ötesinde anlamışsınız demektir.
Not1. TF-IDF matrisinizin 200.000 sütunu ve 5.000 satırı var. Endişelenmeli misiniz?
Aklınıza gelen sebepten değil. Yalnızca 200.000 sütun gördüğünüz için bellek paniği yapmayın: seyrek bir matriste gerçek bellek maliyeti büyük ölçüde sıfırdan farklı eleman sayısına bağlıdır, satır × sütun çarpımına değil.
Asıl endişe istatistiksel: 5.000 gözleme karşı 200.000 öznitelik. Bu bir p ≫ n problemidir; düzenlileştirme ve doğrulama tasarımının önem kazandığı bir durumdur. Alışıldık çözümler güçlü düzenlileştirme (L2 ya da L1) ve min_df ile öznitelik sayısını azaltmaktır. İkinci çözümün neden bu kadar çok sütun elediğini Zipf kısmında gördük: sözlüğün büyük bölümü az sayıda belgede görüldüğü için bu terimlerin etkisini güvenilir biçimde öğrenmek zorlaşabilir. Bununla birlikte, bazı nadir terimler belirli sınıfları ayırt etmekte yararlıdır. Yukarıdaki sınıflandırma deneyinin dört kategorili derleminde min_df=5, sözlüğü 32.318’den 6.281’e indiriyor — sütunların %81’i gidiyor.
Ama bunun bedelsiz olduğunu sanmayın. Silinen sütunlar token kullanımlarının %11,6’sını taşıyor. min_df=5, yalnızca toplamda bir kez görülen terimleri (hapax legomena) değil, beşten az farklı belgede geçen bütün terimleri eler. Bunlar tek bir belgede çok kez tekrar etmiş olabilir; bu nedenle silinen sütun oranıyla silinen token kullanım oranını ayrı raporlarız. Bu ödünleşimin değip değmediğini bir kural belirlemez; ölçerek cevaplanacak bir sorudur ve 3. alıştırma bunu ölçmenizi istiyor.
Not2. IDF eğitim kümesinde mi yoksa bütün veride mi hesaplanmalı?
Denetimli sınıflandırma değerlendirmesinde sözlük ve IDF yalnızca eğitim bölümünden öğrenilir; doğrulama ve test verisine değiştirilmeden uygulanır. Çapraz doğrulamada bu öğrenme her eğitim katında yeniden yapılır. IDF veriden kestirilen bir parametredir, dolayısıyla onu eğitim + test üzerinde hesaplamak test dağılımına dair bilgiyi özniteliklerinize sızdırır. Arama sistemlerinde IDF’nin dizinlenen belge koleksiyonundan hesaplanması ise ayrı bir değerlendirme düzenidir.
scikit-learn’de eğitimde fit_transform, testte transform çağırmak tam olarak bunu yapar — fit_transform’u iki kez çağırmak da bu yüzden gerçek ve yaygın bir hatadır. Üretimde aynı mantık şunu söyler: IDF vektörü, her yeni batch (veri partisi) için yeniden hesaplanan bir şey değil, modelle birlikte sürümleyip yayına aldığınız bir bileşendir; aksi hâlde trafik değiştikçe öznitelikleriniz fark edilmeden kayar.
Not3. Bir kelime her belgede geçiyor. TF-IDF ağırlığı nedir?
scikit-learn’ün düzleştirilmiş formülünde \(\mathrm{df} = n\) olunca \(\ln\frac{1+n}{1+n} + 1 = 1\) çıkar; yani IDF çarpanı 1 olur. Normalleştirmeden önceki TF-IDF değeri terimin TF değeridir. L2 bütün satırı ortak bir norma böler; nadir terimlere göre ağırlık farkını oluşturan adım IDF çarpımıdır.
Ders kitabının düzleştirilmemiş \(\log(n/\mathrm{df})\) formülünde ise tam olarak 0 olurdu ve terim vektörden düşerdi. smooth_idf=True varsayılandır. Ancak smooth_idf=False seçildiğinde de scikit-learn dışarıdaki \(+1\) terimini korur: \(\mathrm{idf} = \ln(n/\mathrm{df}) + 1\). Bu yüzden her belgede geçen terimin IDF değeri bu ayarda da 1 olur; sonuç ders kitabı formülüyle aynı değildir.
Not4. Belge vektörlerini neden normalleştiriyoruz da öznitelik sütunlarını normalleştirmiyoruz?
Çünkü iki normalleştirme farklı sorulara cevap verir. Satır bazında L2 normalleştirmesi, sıfır olmayan belge vektörlerini birim norma getirir. Böylece karşılaştırma mutlak vektör büyüklüğüne değil, ağırlıklı terimlerin oranlarına dayanır.
Sütunlardan sıfır olmayan ortalamalarını çıkarıp sonucu açıkça saklamak, mevcut sıfırları sıfırdan farklı değerlere dönüştürerek seyrekliği bozar. Örnekte matrisin bütün hücrelerinin yaklaşık %99,7’si sıfırdır; bu oran her sütunda aynı olmak zorunda değildir. Sütun ortalamalarını çıkarıp sonucu yoğun bir float64 dizide saklarsak 2,8 MB’lık bu matris için yaklaşık 601,6 MB gerekir. Ancak bütün sütun ölçeklemeleri böyle değildir: merkezleme yapmadan ölçekleme, örneğin StandardScaler(with_mean=False), seyrek yapıyı koruyabilir. Seyreklik, hangi işlemlerin uygulanabilir olduğunu belirleyen bir kısıttır.
Not5. Arama motorunuz “P-101” parça numarası için hiçbir şey döndürmüyor. Neden?
Önce sorgu ve belge çözümleyicilerinin ürettiği token’ları karşılaştırın. scikit-learn’ün varsayılan token_pattern=r"(?u)\b\w\w+\b" kalıbı en az iki kelime karakteri ister ve tireyi aşmaz; bu yüzden P-101 içindeki ön eki kaybettirir:
text = ["P-101 / P-102 pompası devre dışı"]
for name, pattern in [("default", r"(?u)\b\w\w+\b"), ("custom", r"(?u)\b\w[\w-]*\w\b")]:
cv = CountVectorizer(token_pattern=pattern).fit(text)
print(f"{name:<10} : {list(cv.get_feature_names_out())}")default : ['101', '102', 'devre', 'dışı', 'pompası']
custom : ['devre', 'dışı', 'p-101', 'p-102', 'pompası']
Ekipman etiketiniz P-101 sessizce 101’e döndü; P-102 de çıplak bir sayıya dönüştü. İkisi hâlâ ayrı token’lar, ama ekipman ön eki kayboldu: 101 artık bir sayfa numarasındaki, bir miktardaki ya da bir basınç değerindeki 101 ile aynı token. Sorgu ve belge tarafında aynı dönüşüm uygulanıyorsa 101 üzerinden eşleşme yine olabilir; hiç sonuç gelmemesi, sorgu ile dizin arasındaki çözümleyici uyumsuzluğundan veya başka filtrelerden de kaynaklanabilir. Teknik alanlarda bu tür tokenleştirme hataları, önemli tanımlayıcıları kaybettirerek model başarısını ciddi biçimde etkileyebilir. Üstelik kolay fark edilmez: hiçbir yerde hata alınmaz, yalnızca doğruluk olması gerekenden düşük kalır.
Bunu önleyen alışkanlık: herhangi bir vectorizer’ı (vektörleştirici — metni sayı matrisine çeviren CountVectorizer, TfidfVectorizer gibi nesneler) eğittikten sonra get_feature_names_out() çıktısını yazdırın ve okuyun. Gerçekten okuyun.
Not6. 2026’da kelime torbası ne zaman doğru seçimdir?
Pratik bir başlangıç sezgisi olarak, aşağıdaki koşullardan birkaçının bir arada bulunması kelime torbası baseline’ını özellikle çekici kılar: sınıf başına çok az etiketli örneğiniz var; sözlüğünüz alana özgü ve birebir eşleşme önemli (parça numaraları, gen adları, hukuki atıflar); her tahmini, üzerinizde yetkisi olan birine açıklamak zorundasınız; GPU’nuz yok ya da katı bir gecikme bütçeniz var; belgeleriniz kısa ve incelikli değil, uzun ve konusal.
Uygun sınıflandırma ve erişim görevlerinde, daha karmaşık yöntemlerin kazancını ölçmek için güçlü bir klasik karşılaştırma modeli (baseline) olarak da kullanın. “Transformer’ımız 0,91 alıyor” diyen ama TF-IDF’in 0,89 aldığını söylemeyen bir ekip için şunu söyleyebiliriz: güçlü bir klasik baseline olmadan 0,91’in ne kadar anlamlı bir kazanım olduğunu bilmiyoruz (Bölüm 22).
2.8 Mülakat soruları
Metin temsili, uygulamalı NLP mülakatlarına hazırlanırken çalışılabilecek temel konulardan biridir; adayın konuyu kavrayıp kavramadığı, yoksa yalnızca kütüphane çağrılarını mı bildiği bu konuda görülebilir. Aşağıdaki sorular, neyi sınadıklarına göre gruplandı.
Kaynak gösterimi üzerine — lütfen bir kez okuyun. İnternette “Google ve Amazon mülakatlarında sorulan NLP soruları” başlıklı çok sayıda sayfa bulacaksınız. Bu tür sayfalardaki şirket atıflarının her biri doğrulanabilir bir kaynağa dayanmayabilir. Bir sorunun belirli bir şirkette sorulduğunu ancak bunu destekleyen açık bir kaynak varsa aktaracağız; diğerlerini konu alıştırması olarak sunacağız.
Aşağıdaki soru grupları, iki ücretsiz kaynakta ele alınan konulardan yararlanılarak hazırlandı; örnekler ve cevaplar bu bölümün kapsamına uyarlandı: Chip Huyen’in Introduction to Machine Learning Interviews kitabı (Huyen 2021) — sorularının çoğunun gerçek makine öğrenmesi mülakatlarında sorulduğunu belirtir ama hiçbirini belirli bir şirkete bağlamaz — ve bir mühendisin birkaç büyük teknoloji şirketindeki kendi mülakat turlarından derlediği AIMLInterviews deposu; bu da konuları temellendirir, tek tek soruları etiketlemez. Bir soru, bir şirketin gerçekten belgelediği bir sisteme karşılık geliyorsa, o sistemin kaynağı gösterildi; böylece iddiayı kendiniz de doğrulayabilirsiniz.
2.8.1 Ağırlıklandırmayı anlayıp anlamadığınızı sınayanlar
S: TF-IDF’i hiç görmemiş birine anlatın. Sonra IDF teriminin neden logaritma kullandığını açıklayın.
Önce iki çarpanlı ayrıştırmayı verin: TF-IDF, diğer koşullar aynıyken, bu belgede sık geçen ve az sayıda belgede bulunan terime daha yüksek ağırlık verir. Bu ağırlık, görev açısından önemle aynı şey değildir. Sonra yukarıdaki sezgi kontrolündeki bilgi kuramsal yorumu verin: belgelerin eşit olasılıkla seçildiği modelde \(\log(n/\mathrm{df})\), terimi içeren bir belge seçme olayının öz bilgisidir. Aralık sıkıştırmadan da söz edin: \(1/\mathrm{df}\) nadir terimlere binlerce kat ağırlık verirken logaritma, pozitif df için ağırlığı 0 ile \(\log n\) arasında tutar. “1’e karşı 2 belge, 100’e karşı 200 belgeden daha önemli” gerekçesine kapılmayın; logaritma iki ikiye katlamayı eşit sayar. Spärck Jones’un bunu 1972’de görgül olarak önerdiğini (Spärck Jones 1972) ve kuramın sonradan geldiğini (Robertson 2004) eklerseniz, birincil kaynak okuduğunuzu göstermiş olursunuz.
S: Ham terim sayımları TF-IDF’ten ne zaman daha iyi olur?
Belge frekansı terimleri ayırt etmeye katkı vermediğinde ham sayımlar yeterli olabilir. Örneğin terimlerin df değerleri birbirine yakınsa IDF, ham sayımlara çok az ek ayrım katar; bütün IDF’ler eşitse son L2 normalleştirmesinde bu ortak çarpan sadeleşir. Ham sayımın daha iyi olup olmadığını yine ölçmek gerekir. İkinci durum üretici modellerdir: çok terimli (multinomial) naive Bayes’in üretici yorumu tam sayı sayımlarına dayanır. Bununla birlikte TF-IDF gibi kesirli girdiler uygulamada iyi sonuç verebilir; olasılıksal yorum ile deneysel sınıflandırma başarısını ayrı değerlendirin. Bir de sayıları bir modele değil, doğrudan bir istatistiksel teste vereceğiniz durum: ki-kare ya da log-olabilirlik oranı gibi testler girdinin sayım olmasını bekler, çünkü sayımlar toplanabilir — “bu terim iki kategoride toplam 47 kez geçiyor” cümlesi anlamlıdır. TF-IDF ağırlıklarının toplamı ise ham olay sayısı değildir. Bu nedenle ham frekanslardan kurulan bir kontenjans tablosunda sayımların yerine TF-IDF değerlerini koymak aynı testi uygulamak anlamına gelmez. Son olarak bir uyarı: Bölüm 4’te kısa Türkçe yorumlarda ham sayımın TF-IDF’i geçmesi IDF’ten değil, ayarlanmamış bir C ile L2 normalleştirmesinin etkileşiminden çıktı ve eşit ayar bütçesinde tersine döndü. “Ham sayım daha iyi” gözlemini temsile yazmadan önce iki tarafa da aynı ayar bütçesini verin.
S: İkili n-gram eklediniz ve doğruluk düştü. Ne oldu?
Olası nedenlerden biri, eklenen seyrek n-gram’ların aşırı öğrenmeyi artırmasıdır: öznitelik uzayı kabaca bir büyüklük mertebesi büyürken veri sabit kaldı. Ancak bunu yalnızca doğruluk farkından çıkaramayız; n-gram frekanslarını, düzenlileştirme gücünü, yakınsama durumunu ve eşleştirilmiş test sonuçlarını incelemek gerekir. Bu bölümdeki deneyde öznitelik sayısı 32.318’den 249.522’ye çıktı, doğruluk 0,831’den 0,819’a indi; eşleştirilmiş testte bu fark, dört karşılaştırma için düzeltilmiş eşiği aşmadı. Denenebilecek çareler: min_df değerini yükseltmek (min_df, bir terimin kendine bir sütun kazanabilmesi için en az kaç farklı belgede geçmesi gerektiğini söyleyen eşiktir; yükseltmek, yalnızca bir iki belgede görünen ikilileri daha sözlüğe girmeden eler), düzenlileştirmeyi güçlendirmek ya da yeterli sıklık koşulunu sağlayan ikililer arasında noktasal karşılıklı bilgiye (PMI) dayalı seçim yapmak. Her seçimin etkisini doğrulama verisinde ayrıca ölçün.
S: Stop word’leri atmak bir duygu analizi modeline neden bazen zarar verir?
Çünkü bazı stop word listeleri, belirli bir görevde önemli olan olumsuzluk ve vurgu kelimelerini de içerir. Örneğin kullanılan liste not, no, never, very, only veya but kelimelerini çıkarıyorsa, duygu için yararlı ayrımlar kaybolabilir; scikit-learn’ün İngilizce listesi bu altı kelimenin hepsini içerir. O durumda This is not good ile this is good aynı vektöre dönüşür. Bu bölümün kendi sayıları her iki yönü de gösteriyor: stop word atmak konu sınıflandırmasında 2,2 puanlık bir kazanç gösterdi, olumsuzlama örneğinde ise iki cümleyi ayırt edilemez hâle getirirdi. Açıkça dile getirilmesi gereken kural: ön işleme göreve göre seçilmelidir; varsayılan diye uygulanmamalıdır.
S: Boyut laneti metinde nasıl görünür?
Yüksek boyut ve seyreklik bir arada görünür. Bir TF-IDF matrisi 10⁵ boyutlu ve %0,3 yoğunlukta olabilir. Bu yüksek boyutluluk öğrenmeyi zorlaştırabilir; fakat belge çiftlerinin ne kadar ortak terim taşıdığını yalnızca yoğunluk oranından çıkaramayız. Benzerlik dağılımını ve komşuların ayırt edilebilirliğini veride ayrıca incelemek gerekir. Metni sıra dışı kılan noktaya dikkat çekin: boyut çok büyüktür ama seyreklik de öyledir. Güçlü düzenlileştirmeli doğrusal modeller metinde, ham boyut sayısının düşündürdüğünden daha iyi çalışabilir (Joachims 1998). Kaçış yolları: öznitelik seçimi (min_df), izdüşüm (truncated SVD, yani kesik SVD: yalnızca en büyük tekil değerleri tutar) ve seyrek temsilin yerine yoğun bir temsil öğrenmek (Bölüm 3).
2.8.2 Geometriyi anlayıp anlamadığınızı sınayanlar
S: Belgeler için neden Öklid uzaklığı yerine kosinüs benzerliği?
Uzunluktan bağımsızlık — yukarıdaki sezgi kontrolüne bakın. Güçlü bir cevap şöyle devam eder: L2 normalleştirilmiş vektörlerde ikisi \(\lVert a-b\rVert^2 = 2 - 2\cos(a,b)\) üzerinden monoton biçimde denktir; yani soru aslında normalleştirip normalleştirmediğinizle ilgilidir.
S: İki belgenin kosinüs benzerliği 0. Bu ne demek ve pratikte ne kadar olası?
İki vektör de sıfırdan farklıysa ve negatif bileşen taşımıyorsa — bu bölümün kelime torbası ve TF-IDF vektörlerinde olduğu gibi — sıfır kosinüs, ağırlığı pozitif olan hiçbir ortak özniteliğin bulunmadığını gösterir. Sıfır vektörlerde matematiksel kosinüs tanımsızdır; kütüphanenin döndürdüğü 0 bu durumdan ayrı tutulmalıdır. Genel olarak kosinüsün 0 olması vektörlerin dik olması demektir ve bu, negatif bileşen taşıyabilen temsillerde “ortak kelime yok” anlamına gelmez; Bölüm 3’teki embedding’lere geçince bu ayrım önem kazanıyor. Stop word’lerin korunduğu uzun İngilizce belgelerde bu nadirdir, çünkü ortak işlev kelimelerini paylaşma eğilimindedirler. Kısa olanlarda — bu kitabın 19 kelimelik Türkçe yorumları, bir log satırı, bir arama sorgusu — olağandır; iki belgenin gerçekten ortak söz varlığının olmadığı alan metinlerinde de öyle. Çok sayıda sıfır puan görürseniz önce boş vektörleri, sözlük dışı terimleri, filtreleri ve karşılaştırılan çiftlerin gerçekten ilişkili olup olmadığını inceleyin. İlgili metinler farklı sözcüklerle anlatılıyorsa yoğun temsillerin yararını bir değerlendirme kümesinde ölçün (Bölüm 3).
S: Belge-terim matrisiniz 10⁶ × 10⁵. Bu matrisi nasıl saklar, onunla nasıl çalışırsınız?
Önce sayının ne demek olduğunu söyleyin: bir milyon belge, yüz bin kelimelik bir sözlük, yani \(10^{11}\) hücre. Her hücreyi 8 baytlık float64 olarak saklarsanız yalnızca matrisin veri alanı 800 GB eder. Bu, sıradan bir bilgisayar için çok yüksek bir maliyettir; ara hesaplar ve model için gereken bellek de buna eklenir.
Sonra işi neyin kurtardığını söyleyin: bu hücrelerin neredeyse hepsi sıfır. Tek bir belgede yüz bin kelimenin belki birkaç yüzü geçer; metin matrislerinde sıfırdan farklı hücre oranı tipik olarak %0,1 ile %1 arasındadır. Sıfırları saklamanın hiçbir anlamı yok.
Sıkıştırılmış seyrek satır (compressed sparse row, CSR) biçimi bunu yapar: matrisi bir tablo olarak değil üç listeyle tutar — sıfırdan farklı değerler, her değerin hangi sütuna ait olduğu ve her satırın bu listede nereden başladığı. Böylece maliyet hücre sayısıyla değil, sıfırdan farklı hücre sayısıyla ölçeklenir. %0,1 yoğunlukta bu \(10^8\) değer demek. float64 değer ve 32 bit sütun indeksiyle, kayıt başına 12 bayttan yaklaşık 1,2 GB gerekir; satır işaretçileri ve nesne ek yükü ayrıca eklenir. Yani 800 GB yerine yaklaşık 1,2 GB, kabaca 670× kazanç. Aynı varsayımla %1 yoğunlukta değerler ve sütun indeksleri yaklaşık 12 GB tutar. 64 bit indeks gereken uygulamalarda maliyet daha yüksektir.
İyi bir cevabı asıl ayıran kısım şurası: seyreklik bir depolama numarası değil, hangi işlemleri yapabileceğinizi belirleyen bir kısıttır.
- Seyrekliği bozan işlemler. Sıfır olmayan sütun ortalamalarını çıkarıp merkezlenmiş matrisi açıkça saklamak, o sütunlardaki sıfırları sıfırdan farklı yapar ve yoğun matris maliyetini geri getirir. Ders kitabındaki PCA da önce ortalama merkezleme uygular. Bazı çözücüler merkezlemeyi örtük uygular (yeni
scikit-learnsürümlerinde belirli çözücüler seyrek girdiyi bu yolla destekler); bu nedenle kullandığınız PCA çözücüsünün seyrek girdi desteğini ve bellek davranışını ayrıca inceleyin. - Seyrekliği koruyan işlemler. Satır bazında L2 normalleştirme seyrek yapıyı korur. Seyrek matris–vektör çarpımı da girdi matrisini yoğunlaştırmadan hesaplanabilir; ancak sonuç vektörü yoğun olabilir.
- Girdisi seyrek kalan, çıktısı kalmayabilen işlemler. Asıl dikkat edilmesi gereken, tüm çiftler arasındaki benzerlik matrisidir. \(X\) matrisinin sıfır olmayan satırları önceden L2 ile normalleştirilmişse \(XX^{\top}\) kosinüsleri verir. Negatif olmayan ağırlıklarda ortak pozitif bir terim, ilgili çiftin iç çarpımını pozitif yapar; yani her belgede ortak bir kelimenin geçtiği bir derlemde bu matris tamamen yoğundur — \(n^2\) float, ki \(n = 50.000\)’de 20 GB eder.
cosine_similarityvarsayılan olarak yoğun bir dizi döndürür ve seyrek bir çıktı istemek saklama biçimini değiştirir, sıfırdan farklı hücre sayısını değil. Bloklar hâlinde hesaplayın ya da her satırın yalnızca en yakın komşularını tutun. - Boyut indirmek istiyorsanız kesik (truncated) SVD. PCA’nın aksine merkezleme adımını atlar, dolayısıyla doğrudan seyrek matris üzerinde çalışır. Terim sayımı veya TF-IDF matrisine bu biçimde uygulanan kesik SVD, örtük anlamsal analiz (latent semantic analysis, LSA) olarak adlandırılır. Seyrek girdiyi işleyebilir; fakat düşük boyutlu belge vektörleri ve bileşen matrisi genellikle yoğundur. Bellek hesabına bu çıktıları da katın. LSA, embedding’lere giden tarihsel köprüdür (Bölüm 3).
2.8.3 Bir şeyi yayına alıp almadığınızı sınayanlar
S: Çevrimdışı iyi, üretimde kötü çalışan bir metin sınıflandırıcı eğittiniz. Hata ayıklama sürecinizi anlatın.
Beklenen yapı, maliyet sırasına dizilmiş bir kontrol listesidir.
- Training/serving skew (eğitim/servis kayması: aynı verinin eğitimde ve kullanımda farklı işlenmesi). Buradaki “serving”, modelin eğitimi bittikten sonra gerçek isteklere tahmin sunduğu kullanım aşamasıdır. Sorulacak soru: birebir aynı ön işleme hattı her iki yerde de mi çalışıyor? Buna
TfidfVectorizernesnesinin kendisi de dâhil.fitile eğitildikten sonra o nesne artık yalnızca kod değildir; içinde eğitim verisinden öğrenilmiş iki şey taşır — hangi kelimenin hangi sütuna düştüğünü tutan sözlük ve terim başına hesaplanmış IDF vektörü. Üretimde eğitimdekiyle aynı ön işleme kuralları, sözlük ve IDF değerleri kullanılmalıdır. Vektörleştiriciyi farklı veride yeniden eğitmek sütun eşlemesini veya ağırlıkları değiştirebilir; bu durumda sorun sınıflandırıcıdan önce öznitelik hattında oluşur. - Sözlük kayması. Yeni ürün adları, yeni hata kodları — sözlükte olmadıkları için hiçbir sütuna düşmezler; yani model onları hiç görmez.
- Etiket dağılımında kayma.
- Çevrimdışı değerlendirmede sızıntı; örneğin
vectorizer’ı eğitim/test ayrımından önce eğitmek. - Ve ancak bundan sonra model.
Eğitilmiş vectorizer, modelle birlikte saklanması gereken bir bileşendir: tıpkı model ağırlıkları gibi kaydedilmesi, sürümlenmesi ve onlarla birlikte yayına alınması gerekir. İyi bir yanıtta bu nüansın da belirtilmesi beklenir.
S: 100 milyon belgelik bir derlemde yakın kopyaları tespit eden bir sistem tasarlayın.
Sınadıkları şey tek bir refleks: her belge çiftine tek tek bakmaya kalkmamak. 100 milyon belgenin \(\binom{10^8}{2} \approx 5 \times 10^{15}\) çifti vardır; saniyede bir milyar karşılaştırma yapabilseniz bile bu 58 gün sürer. Yani cevap “hızlandırırız” değil, “çiftleri hiç saymayız” olmalı.
Beklenen hattı adlandırmak yeterli: belgeyi kaydırılan kelime pencerelerinden (shingle) oluşan bir kümeye çevirin, benzerliği Jaccard ile tanımlayın, bu kümeleri MinHash ile sabit uzunlukta imzalara sıkıştırın, imzaları locality-sensitive hashing (LSH) ile kovalara dağıtıp yalnızca aynı kovaya düşen çiftleri aday sayın ve gerçek Jaccard’ı yalnızca adaylar üzerinde hesaplayın. Bu hat, uygun veri dağılımı ve parametrelerle incelenecek çift sayısını büyük ölçüde azaltabilir. Ancak aday sayısı büyük kovalarda karesel büyüyebilir; LSH bazı benzer çiftleri de kaçırabilir. Hem aday maliyetini hem yakalama oranını ölçmek gerekir; Bölüm 11.3 bunu ölçülmüş sayılarla kuruyor ve bant sayısının eşiği nasıl belirlediğini gösteriyor.
Bu bölümün malzemesi hattın ilk adımıdır ve önemi göründüğünden büyüktür: shingle’lar yukarıda tartışılan tokenization kararlarından çıkar. Harfleri küçük harfe çevirdiniz mi, noktalamayı attınız mı, kelime yerine karakter penceresi mi kullandınız — bunların her biri neyin “kopya” sayılacağını doğrudan değiştirir.
S: Kurum içi teknik dokümantasyon için bir arama motoru kurun. Nereden başlarsınız?
Elasticsearch ya da benzeri bir motor üzerinden BM25 ile (Robertson ve Zaragoza 2009); çünkü kısa sürede yayına alınabilecek güçlü bir karşılaştırma modelidir (baseline) ve uygun çözümleyici ile alan eşlemesi kullanıldığında birebir parça numarası eşleşmelerini güçlü biçimde kullanabilir. Parça numaralarının bozulmadığını token çıktılarında kontrol edin; yoğun erişimin eşleşme davranışını da aynı test sorgularıyla ölçün. Sonra başka sözcüklerle anlatım için yoğun erişimi ekleyin ve ikisini birleştirin. Bu, Bölüm 19’da kuracağınız melez erişim mimarisidir. İyi bir cevap, başlangıç yöntemini gereksinimlerle gerekçelendirir ve daha karmaşık yaklaşımın kazancını karşılaştırmalı ölçmeyi önerir.
2.8.4 Ön işleme kararlarını sınayanlar
S: Gövdeleme mi lemmatizasyon mu — ve bugün ikisinden birini kullanır mıydınız?
İkisini de tanımlayın, sonra bugünkü uygulamayı anlatın. Önceden eğitilmiş bir transformer kullanıyorsanız modelin beklediği tokenleştirme ve normalleştirme hattını korursunuz; ayrıca gövdeleme veya lemmatizasyon uygulamazsınız. Alt kelimeler farklı biçimler arasında parça paylaşımını artırabilir, fakat morfolojik çözümlemenin yerini bire bir tutmaz. Seyrek erişim veya sınıflandırmada bu dönüşümlerin yararını göreve özgü ölçün; iyi bir çözümleyicinizin olduğu morfolojik olarak zengin dillerde yararlı olabilirler (Oflazer 1994). Mülakatçılar teknikleri alışkanlıkla mı yoksa gerekçeyle mi uyguladığınıza bakıyor.
2.9 Daha derine inmek için
Aşağıdaki liste uzun; hepsini okumanız gerekmiyor. Önce ne istediğinize karar verin:
| Amacınız | Başlangıç kaynakları |
|---|---|
| Otuz dakikam var | 3Blue1Brown videosu, scikit-learn metin öznitelikleri sayfası |
| TF-IDF ve bilgi erişimi tarafını derinleştirmek istiyorum | Manning ve arkadaşlarının IR kitabı (idf ve BM25 sayfaları), scikit-learn belgeleri |
| Akademik referans istiyorum | Jurafsky & Martin, özgün makaleler (Zipf, Heaps) |
BPE, WordPiece ve tokenizer araçlarıyla ilgili kaynaklar, alt kelime tokenleştirmesinin işlendiği bölümün sonunda (Bölüm 8).
İzleyin.
- Large Language Models explained briefly, 3Blue1Brown. Yedi dakika; metnin sayısal temsile dönüştürülmesini kısa ve görsel bir anlatımla tanıtan video.
Ders kitapları.
- Speech and Language Processing, Jurafsky & Martin (Jurafsky ve Martin 2026). Alanın standart başvuru kaynağı; ücretsiz ve sürekli güncelleniyor. Bu bölüm için: 2. bölüm, Words and Tokens (tokenization, normalleştirme, Zipf, BPE), 5. bölüm, Embeddings (sayım temelli vektörler, kosinüs) ve TF-IDF için 11. bölüm, Information Retrieval and RAG. PPMI, ana içerik sayfasındaki J ekindedir. Bu numaralar kitabın 19 Ağustos 2026 tarihli çevrimiçi taslağına aittir; bölüm numaraları sürümler arasında değişir. Eski kaynaklardaki numaraları kullanırken ilgili taslağın içindekiler tablosunu kontrol edin.
- Introduction to Information Retrieval, Manning, Raghavan & Schütze (Manning vd. 2008). Vektör uzayı modelinin başvuru niteliğindeki anlatımı; tamamı ücretsiz. Bu bölüm için en faydalı tek sayfa ters belge frekansı sayfasıdır — belge frekansı ile terim ağırlığı arasındaki ilişkiyi ve IDF’nin temel formülünü açıklayan kısa bir başvuru metni. Ayrıca gövdeleme ve lemmatizasyon ve BM25 sayfalarına bakın.
- Text Mining with R: A Tidy Approach, Silge & Robinson (Silge ve Robinson 2017). İstatistikten geliyorsanız ve matrislerle değil veri çerçeveleriyle düşünüyorsanız, TF-IDF’i anlatan bu bölüm kafanızda yukarıdakilerin hepsinden daha hızlı yerine oturabilir. Çevrimiçi ücretsiz.
Başvuru dokümantasyonu ve dersler.
- scikit-learn: metinden öznitelik çıkarma (Pedregosa vd. 2011). Burada kullanılan düzleştirilmiş TF-IDF varyantını ve bu varyantın ders kitabı formülünden nerede ayrıldığını belgeler. Bir kez düzgün okuyun; “sayılarım neden kitapla tutmuyor” sorularının çoğu ortadan kalkar. 20 Newsgroups sınıflandırma örneği aynı veri kümesi ailesi üzerinde farklı sınıflandırıcıları ve özniteliklerin etkisini inceleyen, bu deneyi genişletmek için yararlanılabilecek bir örnektir.
- Stanford CS276, Information Retrieval and Web Search. Açık ders sayfaları arasında bu bölümün kapsamına en yakın olanı budur. CS224N ise Bölüm 3’ün başladığı yerden devam eder.
- NLP Course For You, Elena Voita (Voita 2020). Seyrek sayımlardan yoğun kelime vektörlerine geçişi görsel örneklerle anlatan bir ders.
- Introduction to Machine Learning Interviews, Chip Huyen (Huyen 2021). Makine öğrenmesi mülakatlarındaki konu ve soru türlerini örnekleyen ücretsiz bir rehber.
2.10 Alıştırmalar
Her alıştırmanın başındaki etiket zorluk düzeyini gösteriyor: Temel kavramı pekiştirir, Uygulama kendi verinizde ölçüm yaptırır, İleri ise bölümün ötesine geçer.
vectorizer’ı kırın. (Uygulama) Kendi alanınızdan on belge üzerinde birCountVectorizereğitin veget_feature_names_out()çıktısını yazdırın. En az iki yanlış token bulun — bozulmuş bir tanımlayıcı, birleşmiş bir çift, kaybolmuş bir birim. Sorun token sınırlarından kaynaklanıyorsatoken_patternile düzeltin; gerekiyorsa küçük harf dönüşümünü, ön işlemeyi veya özel tokenizer’ı da düzenleyin. Her değişikliğin hangi örneği düzelttiğini ve hangi yeni hataları oluşturabileceğini raporlayın.- Elle türetin. (Temel) Bu bölümdeki dört belgelik derlem için d3’ün TF-IDF vektörünü kâğıt kalemle hesaplayın, sonra
TfidfVectorizerile doğrulayın. Uyuşmazlık varsa önce TF tanımını, IDF formülünü, L2 normalleştirmesini ve token çıktısını karşılaştırın. Sütun sırasını, kullanılan derlemi ve yuvarlama hassasiyetini de denetleyin; farkın kaynağını belirtin. - Tekrarlayın ve genişletin. (Uygulama) Yukarıdaki sınıflandırma tablosunu yeniden üretin, sonra bir satır ekleyin:
min_df=5ile TF-IDF. Doğruluk düşüyor mu? Öznitelik sayısı ne kadar düştü? İki yapılandırmayı, eğitim verisinden ayırdığınız bir doğrulama bölümünde doğruluk, öznitelik sayısı, bellek ve işlem süresiyle karşılaştırın. Seçiminizi bu ölçütlerle bir meslektaşınıza gerekçelendirin; son test kümesini yalnızca seçimi tamamladıktan sonra kullanın. - Olumsuzlama hatasını bulun. (Uygulama) Kendi alanınızdan, operasyonel anlamı taban tabana zıt ve TF-IDF ile ölçülen kosinüs benzerliği 0,9’un üzerinde olan iki belge kurgulayın. Sonra, yalnızca yüksek TF-IDF kosinüsüne dayanarak bu iki belgenin aynı operasyonel kararı ifade ettiğine hükmetmenin neden uygun olmadığını açıklayan bir tasarım incelemesi cümlesi yazın.
değilgibi ayırt edici terimler korunuyorsa bir sınıflandırıcının bunları kullanabileceğini de belirtin. - Kendi verinizde Zipf. (Temel) Elinizdeki bir derlemde sıraya karşı frekansı log-log ölçekte çizin. Çizgi düz görünüyor mu? İlk beş token ne ve içlerinde
'axgibi bir artefakt var mı? - Mülakat alıştırması. (İleri) Chip Huyen’in kitabının §8.2.1 bölümündeki beş belgelik TF-IDF sıralama problemini kâğıt kalemle çözün; devam sorusuyla birlikte: bir belge sorgu terimini bir yerine üç kez geçiriyorsa sırası ne olur ve bu, TF-IDF’in arzu edilir bir özelliği midir? Kütüphaneyle kontrol etmeden önce sorudaki terim kümesini, biçim eşlemelerini ve TF-IDF varyantını açıkça tanımlayın; örneğin
birds → bird,gets → get,loves → loveeşlemelerini nasıl yaptığınızı belirtin. Sonra aynı sözlük, ön işleme, IDF ve normalleştirmeyi kullanacak biçimdeTfidfVectorizernesnesini yapılandırın. Devam sorusunda hem terim tekrarının paya katkısını hem normalleştirme paydasındaki değişimi inceleyin. BM25, diğer değişkenler sabitken terim frekansı katkısını sonlu bir sınıra yaklaştırır;sublinear_tfise katkının büyümesini logaritmik olarak yavaşlatır, fakat sonlu bir tavan koymaz. ::: {.callout-note collapse=“true”} ## Beklenen gözlemler
Alıştırmaların çözümü burada değil; ama neyi görmeniz beklendiğini bilmek kendinizi kontrol etmenizi kolaylaştırır.
- Varsayılan
token_patterntek harfli token’ları ve noktalamayı atar; tanımlayıcılar (P-101, sürüm numaraları, kod parçaları) bu yüzden bölünür. - Elle hesap ile kütüphane sonucu, aynı tanımlar ve yeterli hassasiyet kullanıldığında sayısal tolerans içinde uyuşmalıdır. Fark varsa TF ve IDF tanımlarını, normalleştirmeyi, token’ları, sütun sırasını ve yuvarlamayı kontrol edin.
min_df=5, beşten az belgede geçen terimleri eler. Öznitelik sayısının ve doğruluğun ne kadar değiştiğini ölçün; silinen terimlerin token kullanımlarındaki payını da raporlayın. Boyut azalmasıyla başarı kaybının küçük olacağını önceden varsaymayın.- Ayrı yazılan bir olumsuzluk kelimesi eklenirse ham sayımda tek bir sütun değişebilir; L2 normalleştirmesi diğer etkin değerleri de değiştirir. Fiile eklenen olumsuzluk ise farklı terim sütunları oluşturabilir. Yüksek kosinüsün karşıt anlamı gizleyebildiğini kendi örneğinizde gösterin.
- Log-log grafiğin bir bölümünde yaklaşık doğrusal bir eğilim görülebilir; baş ve kuyruk bölgelerinde sapmalar olabilir. İlk beş token’ı inceleyin ve varsa derlem kaynaklı artefaktları açıklayın. Görsel benzerliği tek başına kuvvet yasasının kanıtı saymayın.
- Terimin tekrar sayısını artırdıktan sonra puanı ve sıralamayı yeniden hesaplayın. Kosinüste hem iç çarpım hem belge normu değiştiği için tekrar artışı her durumda daha yüksek puan veya sıra sağlamaz. Sonucu seçtiğiniz TF-IDF varyantı ve sorgu vektörüyle birlikte açıklayın. :::
2.11 Akılda kalması gerekenler
Bölüm bittiğinde aşağıdaki sekiz maddeyi kendi kelimelerinizle söyleyebiliyorsanız, çekirdek öğrenme hedefine ulaşmışsınız demektir.
- Tokenization, metnin modele hangi birimlerle sunulacağına dair bir tasarım kararıdır.
- Normalleştirme kuralları bazı yazım veya kodlama ayrımlarını birleştirebilir. Hangi ayrımların korunacağı göreve ve kullanılacak modelin beklediği girdiye bağlıdır.
- Kelime düzeyinde sabit bir sözlük, yeni metinlerdeki bütün kelime biçimlerini kapsayamaz; sözlükte olmayan biçim sayımın dışında kalır. Bugünün dil modelleri bu sınırı kelimeden küçük, veriden öğrenilen birimlerle aşar (Bölüm 8.3).
- Zipf, kelime frekansları ile sıraları arasındaki; Heaps ise işlenen metin miktarı ile gözlenen farklı terim sayısı arasındaki yaklaşık ampirik ilişkiyi betimler. Bu ilişkilerin uyumu derleme, tokenleştirmeye ve ölçülen aralığa bağlıdır.
- Kelime torbası bir belgeyi hangi terimlerin kaç kez geçtiğiyle temsil eder ve kelime sırasını kaybeder.
- TF-IDF, diğer koşullar aynıyken, belgede sık geçen ve derlemin az sayıda belgesinde bulunan terime daha yüksek ağırlık verir.
- Kosinüs benzerliği belge uzunluğunu değil vektörlerin yönünü karşılaştırır; L2 normalleştirmesi bu geometriyi kullanışlı kılar.
- Kelime torbası hızlı, seyrek ve okunabilir bir karşılaştırma modelidir (baseline); ama sıra, olumsuzlama kapsamı ve anlamsal benzerlik gibi bilgileri doğrudan temsil edemez.