piątek, 27 lutego 2009

Wikistatystyki

Pisałem ostatnio o bardzo oczywistym sposobie wykorzystania Wikipedii do generowania danych leksykalnych, czyli tworzeniu słownika za pomocą linków międzywikipediowych. Innym prostym pomysłem jest statystyczna analiza występowania znaków kanji. W tym celu napisałem poniższy program w Pythonie.


import codecs
import re

print "Collecting..."
lineCounter = 0
kanjiCounter = 0
kanjiStatistics = []
inFile = codecs.open("data/jawiki-20090124-pages-articles.xml", "r", "utf-8")
outFile = file("data/wikipedia.kanji", "w")
chinesePattern1 = re.compile("\[\[zh:\W+\]\]")
chinesePattern2 = re.compile("\[\[zh-classical:\W+\]\]")
for line in inFile:
if re.search(chinesePattern1, line) != None: continue
if re.search(chinesePattern2, line) !== None: continue
kanjis = u""
for newKanji in line:
if ((ord(newKanji) >= 0x4e00 and ord(newKanji) <= 0x9fa5)
or (ord(newKanji) >= 0x3400 and ord(newKanji) <= 0x4db5)
or (ord(newKanji) >= 0xf900 and ord(newKanji) <= 0xfa2d)
or (ord(newKanji) >= 0x20000 and ord(newKanji) <= 0x2a6d6)
or (ord(newKanji) >= 0x2f800 and ord(newKanji) <= 0x2fa1d)):
kanjis = kanjis + newKanji
if len(kanjiStatistics) == 0:
kanjiStatistics.append({'kanji':newKanji, 'count':1})
print u"Znalazłem nowy znak:", newKanji, u"( znaków:", len(kanjiStatistics), ")"
else:
seenBefore = False
for oldKanji in kanjiStatistics:
if oldKanji['kanji'] == newKanji:
oldKanji['count'] = oldKanji['count'] + 1
seenBefore = True
break
if not seenBefore:
kanjiStatistics.append({'kanji':newKanji, 'count':1})
print u"Znalazłem nowy znak:", newKanji, u"( znaków:", len(kanjiStatistics), ")"
lineCounter = lineCounter + 1
kanjiCounter = kanjiCounter + len(kanjis)
if (lineCounter % 1000) == 0:
print "Linia", lineCounter, "ilość znaków:", kanjiCounter, "różnych znaków:", len(kanjiStatistics)
outFile.write(kanjis.encode("utf8"))
inFile.close()
outFile.close()

print u"Zapisuję wyniki..."
kanjiStatistics.sort()
outFile = file("data/statistics_wiki.txt", "w")
for kanji in kanjiStatistics:
print kanji['kanji'].encode("utf8"), ":", kanji['count'], "(", float(kanji['count'])/float(kanjiCounter*100), "percent )"
outFile.write(kanji['kanji'].encode("utf8") + " " + str(kanji['count']) + " " + str(float(kanji['count'])/float(kanjiCounter*100)) + "\n")
outFile.close()
print u"Zapisalem wyniki do data/statistics_wiki.txt"


Jest on niestety nieco dłuższy od poprzedniego i na pewno znajdzie się ktoś, kto powie mi, że jest on niezwykle nieefektywny pod względem prędkości działania. Rzeczywiście, przeanalizowanie pliku XML japońskiej wikipedii zajęło mojemu komputerowi ponad 25 godzin. Trudno. Program daje mi to, co chcę i to się liczy. A tym, co chcę jest oczywiście liczba wystąpień danego znaku we wspomnianym pliku XML. Aby uniknąć dołączania do statystyki znaków z języka chińskiego, program ignoruje wszystkie linki do stron wikipedii chińskiej (w wersji zarówno tradycyjnej, jak i uproszczonej). Nie jesteśmy w stanie oczywiście pominąć występujących na wikipedii nazw chińskich, ale przecież można argumentować, że one również są częścią języka.

Analiza częstotliwości występowania najczęstszych 1945 znaków, które powinny odpowiadać liście jouyou, chociaż nic tego nie gwarantuje (użycie znaków na pewno się z resztą zmieniło od czasu opracowania listy w 1981; z tego też powodu podpis osi poziomej na wykresie kłamie), przedstawiona została na rysunku poniżej.



Wniosek: po pokonaniu pierwszego, stromego zbocza eksponencjalnej krzywej nauki znaków kanji i opanowaniu pierwszego 1000 znaków (czyli dojścia do poziomu egzaminu JLPT na poziomie 2), powinniśmy być w stanie przeczytać zdecydowaną większość japońskiego tekstu i dalej powinno już być „z górki”.

Wykres otrzymałem ładując otrzymane dane do środowiska R za pomocą poniższego kodu.


png("odsetek.png", width=800, height=600, units="px")
dane <- read.table("statistics_wiki_seded.txt")
procentowe = sort(dane[,2]/sum(dane[,2]), decreasing=T)
plot(cumsum(procentowe[1:1945])*100, type="l", xlab="Numer znaku z listy jouyou", ylab="Odsetek znanych znaków na japońskiej wikipedii [%]", main="Znajomość znaków kanji")
jlpt = round((cumsum(procentowe)*100)[c(103, 284, 1023)])
abline(v=103, lty=3)
text(x=103, y=jlpt[1], paste("JLPT4 ", jlpt[1], "%", sep=""), pos=4)
abline(v=284, lty=3)
text(x=284, y=jlpt[2], paste("JLPT3 ", jlpt[2], "%", sep=""), pos=4)
abline(v=1023, lty=3)
text(x=1023, y=jlpt[3]-2, paste("JLPT2 ", jlpt[3], "%", sep=""), pos=4)
dev.off()

niedziela, 22 lutego 2009

Wikipedia

Jedną z bardzo przydatnych cech Wikipedii jest jej dostępność w wersji skonsolidowanej do pojedynczego pliku XML, umożliwiająca przetwarzanie zawartych w niej danych offline. Uzyskać możemy w ten sposób całkiem sporo danych, które można wykorzystać w automatycznej leksykografii. Najoczywistsze jest wykorzystanie linków międzywikipediowych. W ten sposób możemy właściwie stworzyć całkiem użyteczny prosty słownik.

Plik XML z japonską wersją Wikipedii (24 stycznia 2009) ma 58708851 linii i 67470 linków do polskich odpowiedników haseł, które wydobyć można na przykład za pomocą poniższego kodu w Pythonie.

  
# -*- coding: utf-8 -*-

import re
import codecs

from xml.sax import saxutils
from xml.sax import make_parser
from xml.sax.handler import feature_namespaces

class InterWiki(saxutils.DefaultHandler):
def __init__(self):
self.searchPattern = re.compile("(\[\[pl:)([ąćęłńóśżźĄĆĘŁŃÓŚŻŹ\(\) a-zA-Z]+)(\]\])")
def startElement(self, name, attrs):
self.inTitleContent = 0
self.inTextContent = 0
if name == 'title':
self.inTitleContent = 1
if name == 'text':
self.inTextContent = 1
def characters(self, ch):
if self.inTitleContent:
self.title = ch
self.inTitleContent = 0
if self.inTextContent:
translation = re.search(self.searchPattern, ch)
if translation != None:
print (self.title + u" = " + self.searchPattern.sub(r'\2', ch)).encode("utf8")

if __name__ == '__main__':
parser = make_parser()
parser.setFeature(feature_namespaces, 0)
dh = InterWiki()
parser.setContentHandler(dh)
parser.parse("data/jawiki-20090124-pages-articles.xml")


W wyniku otrzymamy listę odpowiadających sobie terminów po japońsku i po polsku:


...
アダド・ニラリ1世 = Adadnirari I
弥富市 = Yatomi
ザ・ウォール = The Wall
ヘルマン・フリートマン = Hermann Friedmann
タリスマン (バンド) = Talizman
ハールレム = Haarlem
クロード・ニコラ・ルドゥー = Claude Nicolas Ledoux
D.C.トーク = Dc Talk
フランクリン郡 (ネブラスカ州) = Hrabstwo Franklin (Nebraska)
インタビュー = Wywiad (rozmowa)
カルカルの戦い = Bitwa pod Karkar
グッド・シャーロット = Good Charlotte
セレウキア = Seleucja nad Tygrysem
チェイス郡 (ネブラスカ州) = Hrabstwo Chase (Nebraska)
チェイス郡 = Hrabstwo Chase
アマチュアレスリング = Zapasy
マリインスキー劇場 = Teatr Maryjski
フラニツェ = Hranice
キャラック船 = Karaka
シャルマネセル1世 = Salmanasar I
ニサ = Nisa
サム = Sam
タマーラ・カルサヴィナ = Tamara Karsawina
WWE・クルーザー級王座 = WWE Cruiserweight Championship
...


Oczywiście wiele pozycji będzie nazwami własnymi i trudno odróżnić je od nazw pospolitych w sposób automatyczny. Niemniej jednak wygenerowana w ten sposób lista znakomicie nadaje się do wspomagania tworzenia mojego słownika.

Niecierpliwi znajdą wyniki działania programu tutaj.

Aktualny stan

W chwili obecnej mój słownik, tworzony w języku serializacji danych YAML, zajmuje 1,4 MiB i składa się z 81639 linii tekstu. Zawiera już ok. jedną trzecią słownictwa i znaków wymaganych do zdania egzaminu JLPT na poziomie 2. Pierwsze jego upublicznienie planuję na czas, gdy przepiszę mniej więcej całość wymaganej wiedzy.

Start

Ten blog poświęcam przygotowywanemu przeze mnie elektronicznemu słownikowi japońsko-polskiemu oraz wszelkim informacjom związanym z automatyczną leksykografią japońską.