Sunday 22 September 2013

Türkçe metinleri okuyup anlayabilen bir bilgisayar programı: son gelişmeler


Türkçe Kelime Ayrıştırıcısı (Turkish Word Parser) V00.00 bitti. En son gelişmeler:

Verilen bir metni anlayıp buna ilişkin soruları cevaplayan programımın ilk aşaması kabaca bitmişti.
Arada geçen zaman içinde dökümantasyon ve çok ince yan etkileri temizledim.
Bu aşamada hız sorununu halletmek artık kaçınılmaz hale geldi.  Bu yüzden sözlük dosyalarını
bir MySQL veritabanına taşıdım.  Steinbeck'in yaklaşık 100 sahifelik 'Farelere ve İnsanlara Dair'
kitabinı kelime ayrıştırıcısı programımın işlemesi  yaklaşık 4-5 saat alıyor.  Bir insanla
karşılaştırılabilir mertebede.

Dikkat edilirse aynı harflerden oluşan fakat anlamı farklı kelimeler vardır. 
Programım bu kelime alternatiflerini aynı kelime yapısı içinde listelemektedir.
"Sıcak bir günün akşamıydı." cümlesi Kelime Parçalayıcı sonrası şuna dönüşüyor::

SENTENCE(

0. WORD(
  Root(Fiil(Sıc,45474,0))
  Ext(gerundFromVerbExt(;;;.ak))

(0,0). root+extInfo=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak)))
  Root(not-identified(Sıcak,45444,0))

(0,1). rootInfo=Root(not-identified(Sıcak,45444,0))
  Root(Sıfat(Sıcak,45444,0))

(0,2). rootInfo=Root(Sıfat(Sıcak,45444,0))

)=Sıcak

1. WORD(
  Root(İsim(bir,6880,0))
(1,0). rootInfo=Root(İsim(bir,6880,0))
)=bir

2. WORD(
  Root(İsim(gün,20937,0))
  Ext(nounExt(%%=ün%))
(2,0). root+extInfo=Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%))
  Root(İsim(günü,21051,0))
  Ext(nounExt(%%=n%))
(2,1). root+extInfo=Root(İsim(günü,21051,0)) Ext(nounExt(%%n%))
)=günün

3. WORD(
  Root(İsim(akşam,1403,0))
  Ext(verbFromNounExt(<ı(3,0). root+extInfo=Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı)=akşamıydı
.

Daha sonra bunlar bir matrix içine alınıyor:
#words=4
(0,0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak)))
(0,1)=Root(not-identified(Sıcak,45444,0))
(0,2)=Root(Sıfat(Sıcak,45444,0))
(1,0)=Root(İsim(bir,6880,0))
(2,0)=Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%))
(2,1)=Root(İsim(günü,21051,0)) Ext(nounExt(%%n%))
(3,0)=Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
Bu matrixte deneme olarak kelime alternatifleri için kelime başına 20 değişik imkan tanıdım.
Yukarıda görüldüğü gibi 1. 'sıcak' kelimesinin 3 alternatifi var.
Daha sonra bu matrixin boyutunu 2'den 1'e indirdim:
(0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak)))
(1)=Root(not-identified(Sıcak,45444,0))
(2)=Root(Sıfat(Sıcak,45444,0))
(20)=Root(İsim(bir,6880,0))
(40)=Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%))
(41)=Root(İsim(günü,21051,0)) Ext(nounExt(%%n%))
(60)=Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
Daha sonra bir matris kombinasyonu algoritmasını kullanarak
mümkün bütün alternatifleri çıkarttım.  Bu alternatiflerden boş
hane taşıyan satırları çıkartınca:

run:
(0,0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak))) Root(İsim(bir,6880,0)) Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(4,0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak))) Root(İsim(bir,6880,0)) Root(İsim(günü,21051,0)) Ext(nounExt(%%n%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(1600,0)=Root(not-identified(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(1604,0)=Root(not-identified(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(günü,21051,0)) Ext(nounExt(%%n%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(3200,0)=Root(Sıfat(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(3204,0)=Root(Sıfat(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(günü,21051,0)) Ext(nounExt(%%n%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
BUILD SUCCESSFUL (total time: 15 seconds)

Burada en alttaki 'günü' kelimesi seyrek kullanılan bir isim. 'not-identified' kelime tipi kullandığım sözlüğün bu iş için oluşturulmamış olması nedeni ile birşeyler kaçırırım kaygımdan dolayı aldığım bir tip...  Temizlenebilir.

Görüldüğü gibi ilk kelimede bir 'fiil - sıfat' ikilemi var.  'gün - günü' ikilemi ile çarpılınca aşağıdaki
4 alternatif kalıyor:

(0,0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak))) Root(İsim(bir,6880,0)) Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(4,0)=Root(Fiil(Sıc,45474,0)) Ext(gerundFromVerbExt(;;;ak))) Root(İsim(bir,6880,0)) Root(İsim(günü,21051,0)) Ext(nounExt(%%n%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(3200,0)=Root(Sıfat(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(gün,20937,0)) Ext(nounExt(%%ün%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
(3204,0)=Root(Sıfat(Sıcak,45444,0)) Root(İsim(bir,6880,0)) Root(İsim(günü,21051,0)) Ext(nounExt(%%n%)) Root(İsim(akşam,1403,0)) Ext(verbFromNounExt(<ı
Buraya kadar sorun yok.  Ama matrix kombinasyonu alıp sonucu elde etmek için kullanacağımız tek boyutlu matrix'in eleman sayısı
  int maxRows = 6;
        int maxCols = 7;
        String[] matrix1 = new String[maxRows * maxCols];
        double nmax = Math.pow(maxCols, maxRows) * maxRows;
  resultMatrix = new String[(int) nmax];
  nmax=705894;

Eğer 
  int maxRows = 12;
        int maxCols = 3;
        String[] matrix1 = new String[maxRows * maxCols];
        double nmax = Math.pow(maxCols, maxRows) * maxRows
  resultMatrix = new String[(int) nmax];
  nmax=6377292; (kelime sayısı) 
 
Bundan sonra yapılması gerekenler şu şekildeydi: 

1 Bir filtre parserı kullanarak 'not-identified' vb gereksiz alternatifleri çıkarmak.

2- Bir dilbilgisi parserı ile alternatiflerini oluşturduğum cümlenin kelime sayısı bir gerund isim isim-ün eki isim fiil yapısını elemek, ya da böyle bir yapı varsa cümle alternatifi olarak saklamak. 

3- Cevap makinası parserı ile oluşturmuş olduğum bu filedan cevapları çekmek.

Fakat:

1- Elimdeki makina güçlü bir makina olduğu halde matrix kombinasyonu alabildiğim maksimum kelime sayısı/kelime alternatifi 12/3.  Yani en çok 12 kelime uzunluklu bir cümlenin en çok 3'er alternatifli kelimelerinin kombinasyonunu alarak alternatif cümleleri oluşturabiliyorum.  Oysa 'Fareler ve İnsanlar'da ilk sahifede bile 24 kelime uzunluklu cümleler var.

"Dökülen yapraklar, ağaçların altındaki kumluk kıyıda kalınca bir tabaka meydana getirmiş ve o kadar kurumuşlardır ki, bunların arasından bir kertenkele koşsa büyük bir hışırtı çıkarır." 25 kelime

"Dökülen yapraklar, 2 kelime sim.
ağaçların altındaki kumluk kıyıda kalınca bir tabaka meydana getirmiş 9
ve o kadar kurumuşlardır ki, 5
bunların arasından bir kertenkele koşsa büyük bir hışırtı çıkarır." 9
=25

Aslında bu sorun benzer şekilde insan okumasında da var.  Bunun için, virgül vb. ile bağlaçlar vb kullanılarak cümle subsentence'lara bölünüyor.  Bundan sonra yapmam gereken iş bu subsentence yapısını implemente etmek.

Tabii insan muhakemesi söz konusu olunca tamlamalar da (örn isim tamlamaları) bellek ve hız sınırlamasını alt etmeye hizmet ediyor.

Not:
1- Yukarıda bahsettiğim filtre amaçlı parserı yaptım.  Şimdi onu customize etmem gerek.

2- 'not-identified' benzeri materyali ya baştan hiç üretmeyeceğim, ya da bunları bir senginlik olarak saklayıp bir üst katmanda filtre edeceğim.  Düşünmek lazım.

3- En önemli sorun virgül vb ile bağlaç kullanarak tutarlı subsentence yapılarını oluşturmak.

4- Subsentence yapıları  oluşturulmalı:
SENTENCE(
 SUB1-alternate1(
  WORD1((word-alternate1)(word-alternate2)(word-alternate3))
  )
 SUB1-alternate2(
  WORD1((word-alternate1)(word-alternate2)(word-alternate3))
  )
)

5-Yukarıdaki yapıyı parse edip verilen soru ile ilgili yapıyı çıkartacak bir cevap motoru parserı yazmak gerek.
 
 Ali Rıza SARAL