ZEMBEREK — Doğal Dil İşleme

Uğur Özker
19 min readSep 23, 2019

--

Son günlerde fazlasıyla popüler olan NLP diye bir başlık var. Bu başlıkta da bizi ilgilendiren Türkçe dil destekli doğal dil işleme konusu var. Türkçe sondan eklemeli bir dil oldğu için maalesef dil işleme konusunda çok kaynak bulunmamaktadır ve bulunan kaynaklarında verim oranları beklenen seviyenin altında kalmaktadır. Yazdığım bu yazımda kısa kısa Türkçe dil destekli NLP uygulamaları ile ilgili örnekler vermeye çalıştım umarım konuyla ilgilenen arkadaşlara yardımı dokunur :)

Zemberek Kaynak: https://github.com/ahmetaa/zemberek-nlp

UYARI: BU DÖKÜMANTASYON ORİJİNAL DOKÜMANTASYONUN ÇEVİRİLMESİ VE BAZI EKSİK NOKTALARIN NETLEŞTİRİLMESİ ADINA HAZIRLANMIŞTIR. BAZI KISIMLAR ORİJİNAL DOKUMANTASYONDAN BİREBİR ALINMIŞTIR!!!

Zemberek-NLP Introduction

Zemberek-NLP, Türkçe olarak doğal dil işlemenizi sağlayan bir araçtır. Geliştiricisi Ahmet A. Akın’dır.

Modules

USAGE

pom.xml dosyasına repo ve dependency’ler ayrı ayrı kullanılmak istenen her modül için eklenir. Proje bir kez build edilerek, fonksiyonlar kullanılabilir.

java -jar zemberek-full.jar -> bu komut ile terminalde kullanılabilir.

MORPHOLOGY

Introduction

Türkçe, morfolojik açıdan zengin bir dildir. Zemberek, morfolojik analiz, eş anlamlılık çözme ve kelime üretme işlevlerini sunar. Bu modül sayesinde kendi sözlüğünüzü ekleyebilirsiniz. Kelime Analizi yapabilirsiniz ve gayrı resmi kelimeler için öneri alabilirsiniz. TDK tarafından tanınmayan kelimeler gayrı resmidir.

Creating TurkishMorphology Object

Bu modül temel olarak Türkçe morfolojik analiz yapmayı ve üretmeyi sağlar. Analizler cümle ve kelime seviyesinde yapılır. Kelime analizi ve üretimi için TurkishMorphology sınıfı kullanılır. Bu sınıfı temel ayarlarıyla kullanmak için aşağıdaki temel kod parçası kullanılır.

TurkishMorphology m = TurkishMorphology.createWithDefaults();

Bundan sonra, Türkçe sonek grafiği oluşturulur, dahili sözlükler yüklenir ve ilgili grafik düğümlerine bağlanır. Bu nesnenin yaratılması zaman aldığı ve hafızayı tükettiği için, uygulamanın ömrü boyunca tek bir örnek kullanılmalıdır.

TurkishMorphology sınıfının oluşturulması sırasında kendi sözlüklerinizi ekleyebilir veya mevcut öğeleri kaldırabilirsiniz. Örneğin, aşağıdaki listeyi içeren my-dictionary.txt adlı bir sözlük dosyanız var (korkunç kelimelere rağmen):

● show

● relaks [P:Adj]

● gugıllamak

● Hepsiburada

Bu kelimeleri sözlüğe eklemek için RootLexicon Builder mekanizması kullanılır. Örnek kullanım için aşağıdaki kod kullanılabilir.

RootLexicon.Builder builderLexicon = RootLexicon.builder();

builderLexicon.setLexicon(RootLexicon.getDefault());

builderLexicon.addTextDictionaries(Paths.get(“my-own-dictionary-file.txt”));

RootLexicon myLexicon = builderLexicon.build();

TurkishMorphology m = TurkishMorphology.create(myLexicon);

Bu sayede özelleştirilmiş sözlükle birlikte morphology hazırlanmış olur. Alternatif olarakta Morphology’i build etmek için aşağıdaki kodlar kullanılabilir.

TurkishMorphology.Builder lb = TurkishMorphology.builder();

lb.setLexicon(RootLexicon.getDefault());

lb.addTextDictionaries(Paths.get(“my-own-dictionary-file.txt”));

TurkishMorphology analyzer = lb.build();

Build etme sırasında başka metodlarda vardır. Onlardan biri de disableCache metodudur. Bu metod sayesinde cache mekanizmasını kapatabilirsiniz. Standart olarak build edilerken proje cache mekanizması açık bırakılır. Cache mekanizmasını kapatarak build işleminin yavaşlamasına neden olmuş olursunuz. Cache mekanizmasını kaldırmak için aşağıdaki komut kullanılabilir.

lb.disableCache();

Oluşturulan sözlüğe manuel olarakta kelimeler eklenebilir. Bunun için aşağıdaki komut kullanılabilir.

lb.addDictionaryLines(“foo”,”rar”);

Single Word Morphological Analysis

Bir kelimeyi analiz etmek için, analyze metodu kullanılabilir. Bu metod WordAnalysis tipinde bir obje dönderir. Bu obje verilen inputu ve SingleAnalysis tipinde objeler dönderir.

● Eğer kelime parçalanamadıysa, WordAnalysis 0 adet SingleAnalysis tipinde obje dönderir.

● Kelime hazırlanan Lexicon’da bulunamasa bile sistem kelimeyi analiz etmeyi dener. Bu şekilde, büyük harflerle başlayan ve tek bir alıntı içeren sayılar veya uygun isimler analiz edilebilir. Kullanıcılar TurkishMorphology nesnesini oluştururken disableUnidentifiedTokenAnalyzer () yöntemini çağırarak bu davranışı devre dışı bırakabilirler.

○ Örnek olarak “Matsumoto’ya” ve “153’ü” kelimelerini ele alalım. Sistem geçici olarak “Matsumoto” ve “153” için geçici DictionaryItem nesneleri üretecek ve kelimeleri ayrıştırmaya çalışacaktır.

○ Başarılı olursa, WordAnalysis tipinde bir nesne dönderecektir ve bu nesne geçici olarak üretilmiş DictionaryItem nesnelerini içeren SingleAnalysis nesnesini barındıracaktır.

○ Kullanıcı, DictionaryItem nesnesinin geçici olarak oluşturulup oluşturulmadığını SingleAnalysis nesnesini oluşturmada isRuntime() metodunu kullanılma durumana bakarak yada DictionaryItem nesnesinin root özellikleri içerisinde RootAttribute.Runtime bulunup bulunmadığına bakarak geçici oluşturma durumunu kontrol edebilir.

● Analiz için verilen kelime küçük harfli başlasa ve hatta kesme işareti içermese bile, Analyzer bunun özel ad olduğunu sınıflandırabilir. Analyzer noktalama işaretlerini önemsemiyor. Kullanıcı bunun doğru bir analiz olup olmadığını yapılan işlemden sonra karar verebilir. Gelecekteki versiyonlarda bu davranış değişebilir veya bazı ek özellikler eklenebilir. Kesme işareti, yalnızca bilinmeyen özel adlar için denetlenir.

○ Örnek

■ Input: ankaradan

■ Analysis = [Ankara:Noun,Prop] ankara:Noun+A3sg+dan:Abl

WordAnalysis’i tanımlamak için WordAnalysis result = morphology.analyze(“WORD”) kodu kullanılır. Analizi kullanmak için aşağıdaki kod parçasını kullanılabilir. :

TurkishMorphology m= TurkishMorphology.createWithDefaults();

WordAnalysis result = m.analyze(“kalemin”);

for (SingleAnalysis analysis : result) {

System.out.println(analysis.getDictionaryItem());

}

OUTPUT:

[kale:Noun] kale:Noun+A3sg+m:P1sg+in:Gen

[Kale:Noun,Prop] kale:Noun+A3sg+m:P1sg+in:Gen

[kalem:Noun] kalem:Noun+A3sg+in:Gen

[kalem:Noun] kalem:Noun+A3sg+in:P2sg

Stemming and Lemmatization

Stemming: Bir kelimede yer alan son ekleri ve ön ekleri keserek kelime kökünü bulmaya çalışır.

Lemmatization: Kelimelerin morfolojik analizlerini esas alır. Böylelikle, algoritmanın kelimenin kökünü bulması detaylandırılmış bir sözlüğe ihtiyacı vardır.

Aşağıdaki kod parçası kullanılarak Stemming ve Lemmatization yapılabilir. Stemming yapmak için analysis.getStems() fonksiyonu kullanılırken Lemmatization için analysis.getLemmas() fonksiyonu kullanılır.

TurkishMorphology m = TurkishMorphology.createWithDefaults();

WordAnalysis result = m.analyze(“yayla”);

for (SingleAnalysis analysis : result) {

System.out.println(analysis.getDictionaryItem());

System.out.println(analysis.formatLong());

System.out.println(“\tStems = “ + analysis.getStems());

System.out.println(“\tLemmas = “ + analysis.getLemmas());

}

OUTPUT

[kitap:Noun] kitab:Noun+A3sg+ımız:P1pl|Zero→Verb+sa:Cond+A3sg

Stems = [kitab, kitabımız]

Lemmas = [kitap, kitabımız]

Informal Turkish Words Analysis

Günlük kullanım sonucunda türkçede bazı kelimeler değişime uğramıştır ve bunlar TDK tarafından yayınlanan sözlükte bulunmamaktadır. Örnek olarak okuycam kelimesi verilebilir. Bu kelime okuyacağım kelimesinin günlük kullanımda zamanla değişmesiyle oluşmuştur.

Eğer okuycam kelimesini analiz ettirirseniz

[okumak:Verb] oku:Verb+yca:Fut_Informal+m:A1sg

sonucunun ortaya çıktığını göreceksiniz.

Resmi olmayan morfem adlarında (Fut_Informal gibi) _Informal sonek bulunur. Resmi olmayan kelime analizini kullanmak için aşağıdaki kod kullanılabilir:

TurkishMorphology m;

TurkishMorphology.Builder b = TurkishMorphology.builder();

b.setLexicon(RootLexicon.getDefault());

builder.useInformalAnalysis();

m = builder.build();

m.analyzeAndDisambiguate(“vurcam kırbacı”).bestAnalysis().forEach(x -> System.out.println(x));

OUTPUT

[vurmak:Verb] vur:Verb+uca:Fut_Informal+m:A1sg

[kırbaç:Noun] kırbac:Noun+A3sg+ı:P3sg

Cümle gayri resmi morfemler(Bir kelimedeki en anlamlı parça) içeriyorsa Ambiguity Resolution Mechanism yeterli derecede iyi çalışmayacaktır. InformalAnalysisConverter ismindeki bir fonksiyon basit şekilde resmi olmayan kelimeleri, resmi kelimelere dönüştürebiliyor.

InformalAnalysisConverter’ı kullanmak için aşağıdaki kod parçası kullanılabilir:

SentenceAnalysis sentenceAnalysis = morphology.analyzeAndDisambiguate(“okuycam diyo”);

List<SingleAnalysis> analyses = sentenceAnalysis.bestAnalysis();

for (SingleAnalysis a : analyses) {

System.out.println(a.surfaceForm() + “-” + a);

}

System.out.println(“Converting formal surface form:”);

InformalAnalysisConverter converter = new InformalAnalysisConverter(morphology.getWordGenerator());

for (SingleAnalysis a : analyses) {

System.out.println(converter.convert(a.surfaceForm(),a));

}

OUTPUT

okuycam-[okumak:Verb] oku:Verb+yca:Fut_Informal+m:A1sg

diyo-[demek:Verb] di:Verb+yo:Prog1_Informal+A3sg

Converting formal surface form:

okuyacağım-[okumak:Verb] oku:Verb+yacağ:Fut+ım:A1sg

diyor-[demek:Verb] di:Verb+yor:Prog1+A3sg

Diacritics Ignored Analysis

Morfolojik analiz, Türkçe fonetik işaretli harflerde [ç, ğ, i, ö, ü, ş] fonetik ekleri dışlamak için yapılandırılabilir. Bu amaçla ignoreDiacriticsInAnalysis () fonksiyonu kullanılır.

Bu fonksiyonu kullanmak için aşağıdaki kod gereklidir:

TurkishMorphology m;

TurkishMorphology.Builder b = TurkishMorphology.builder();

b.setLexicon(RootLexicon.getDefault());

b.useInformalAnalysis();

b.ignoreDiacriticsInAnalysis();

m = builder.build();

m.analyze(“kisi”).forEach(System.out::println);

OUTPUT

[kış:Noun,Time] kış:Noun+A3sg+ı:Acc

[kış:Noun,Time] kış:Noun+A3sg+ı:P3sg

[kişi:Noun] kişi:Noun+A3sg

Ambiguity Resolution

Türkçe çok belirsiz bir dildir. Aşağıdaki örnekte gösterildiği gibi “yarın” kelimesinin 9 farklı morfolojik analizi vardır. Belirsizliği çözmek için algılama tabanlı bir çözüm kullanılır.

Bu çözümü yapmak için aşağıdaki kod parçası kullanılır:

TurkishMorphology m = TurkishMorphology.createWithDefaults();

String sentence = “Yarın kar yağacak.”;

System.out.println(“Sentence = “ + sentence);

List<WordAnalysis> analysis = m.analyzeSentence(sentence);

System.out.println(“Before disambiguation.”);

for (WordAnalysis entry : analysis) {

System.out.println(“Word = “ + entry.getInput());

for (SingleAnalysis single : entry) {

System.out.println(single.formatLong());

}

}

System.out.println(“\nAfter disambiguation.”);

SentenceAnalysis after = m.disambiguate(sentence, analysis);

after.bestAnalysis().forEach(s-> System.out.println(s.formatLong()));

OUTPUT

Sentence = Yarın kar yağacak.

Before disambiguation.

Word = Yarın

[yarın:Adv] yarın:Adv

[yarmak:Verb] yar:Verb+Imp+ın:A2pl

[Yar:Noun,Prop] yar:Noun+A3sg+ın:Gen

[Yar:Noun,Prop] yar:Noun+A3sg+ın:P2sg

[yar:Noun] yar:Noun+A3sg+ın:Gen

[yar:Noun] yar:Noun+A3sg+ın:P2sg

[yarı:Noun] yarı:Noun+A3sg+n:P2sg

[yarın:Noun,Time] yarın:Noun+A3sg

[yarı:Adj] yarı:Adj|Zero→Noun+A3sg+n:P2sg

Word = kar

[karmak:Verb] kar:Verb+Imp+A2sg

[kar:Noun] kar:Noun+A3sg

[kâr:Noun] kar:Noun+A3sg

Word = yağacak

[yağmak:Verb] yağ:Verb+acak:Fut+A3sg

[yağmak:Verb] yağ:Verb|acak:FutPart→Adj

Word = [.:Punc] .:Punc

After disambiguation.

[yarın:Noun,Time] yarın:Noun+A3sg

[kar:Noun] kar:Noun+A3sg

[yağmak:Verb] yağ:Verb+acak:Fut+A3sg

[.:Punc] .:Punc

Word Generation

Zemberek basit bir kelime üretme fonksiyonu sunar. Üretmek için ya kelimenin köküne yada Sözlük elemanına ve morfemlere ihtiyaç vardır. Üretme mekanizması, analiz mekanizmasına çok benzemektedir. But it passes through empty Morphemes in the search graph even if they are not provided in the input.. For example, user does not need to provide A3sg morpheme as input as it’s surface is empty. Generation returns an inner static class Result instance. There may be multiple results. Form that object, generated word (surface form) and analysis results of the generated word can be accessed.

Örnek olarak TurkishMorphology örneği oluşturmak için tek bir “armut” kelimesi olan bir sözlük kullanılmıştır. Bundan sonra, bu sözcüğün çekimlerini oluşturmak için iyelik ve durum eki kombinasyonları kullanılır. Aşağıdaki örnekte hem kelime üretmek hemde fiil üretmek için örnek kod bulunmaktadır.

Bu örneği denemek için aşağıdaki kod parçası kullanılır:

System.out.println(“Generating Nouns.”);

String[] number = {“A3sg”, “A3pl”};

String[] possessives = {“P1sg”, “P2sg”, “P3sg”};

String[] cases = {“Dat”, “Loc”, “Abl”};

TurkishMorphology.Builder mb = TurkishMorphology.builder();

mb.setLexicon(“armut”);

mb.disableCache();

TurkishMorphology m = mb.build();

RootLexicon rLexicon = m.getLexicon();

DictionaryItem item = rLexicon.getMatchingItems(“armut”).get(0);

for (String numberM : number) {

for (String possessiveM : possessives) {

for (String caseM : cases) {

List<Result> results = m.getWordGenerator().generate(item, numberM, possessiveM, caseM);

results.forEach(s -> System.out.println(s.surface));

}

}

}

System.out.println(“Generating Verbs.”);

String[] positiveNegatives = {“”, “Neg”};

String[] times = {“Imp”, “Aor”, “Past”, “Prog1”, “Prog2”, “Narr”, “Fut”};

String[] persons = {“A1sg”, “A2sg”, “A3sg”, “A1pl”, “A2pl”, “A3pl”};

TurkishMorphology.Builder mb2 = TurkishMorphology.builder();

mb2.setLexicon(“armut”);

mb2.disableCache();

TurkishMorphology m2 = mb2.build();

for (String posNeg : positiveNegatives) {

for (String time : times) {

for (String person : persons) {

List<String> seq = Stream.of(posNeg, time, person)

.filter(s -> s.length() > 0)

.collect(Collectors.toList());

String stem = “oku”;

List<Result> results = m2.getWordGenerator().generate(stem, seq);

if (results.size() == 0) {

System.out.println(“Cennot generate Stem = [“ + stem + “] Morphemes = “ + seq);

continue;

}

System.out.println(results.stream().map(s -> s.surface)

.collect(Collectors.joining(“ “)) + “ “ + seq);

}

}

}

OUTPUT

Generating Nouns.

armuduma

armudumda

armudumdan

armuduna

armudunda

armudundan

armuduna

armudunda

armudundan

armutlarıma

armutlarımda

armutlarımdan

armutlarına

armutlarında

armutlarından

armutlarına

armutlarında

armutlarından

TOKENIZATION

Sentence Boundary Detection

USAGE

Zemberek metinden cümle çıkarmayı sağlayan araçlar sunar. Bu genellikle NLP için ilk adımdır. TurkishSentenceExtractor sınıfı bu amaç için kullanılır. Bu sınıf, cümle sınırlarını bulmak için basit kuralların ve İkili Ortalamalı Perceptron modelinin bir kombinasyonunu kullanır.

TurkishSentenceExtractor extractor = TurkishSentenceExtractor.DEFAULT;

kod parçası kullanılarak initialize edilir. extractor’u kullanmak için 3 method vardır. Bunlar:

List<String> sentences = extractor.fromParagraph(input);

List<String> sentences = extractor.fromParagraphs(input);

List<String> sentences = extractor.fromDocument(input);

fromParagraph() fonksiyonunu kullanarak sadece tek paragraf içeren String’ler üzerinde işlem yapılabilir. Eğer String “\n” içeriyorsa bu fonksiyon çalışmayacaktır. Bu işlem sonunda String tipinde List döndürür.

fromParagraphs() fonksiyonu kullanarak birden fazla paragraf içeren String’ler üzerinde işlem yapar. Bu işlem sonunda String tipinde List döndürür.

fromDocument() fonksiyonu kullanarak bir dosyadaki metinleri String’e çevirir ve ardından “\n” e göre parçalar ayırır ve her parça için fromParagraph() fonksiyonunu çağırır.

Extractor

TurkishSentenceExtractor extractor = TurkishSentenceExtractor.DEFAULT;

String input = “Merhaba! Bugün 2. köprü Fsm.’de trafik vardı.değil mi?”;

List<String> sentences = extractor.fromParagraph(input);

OUTPUT

sentences -> “Merhaba!”, “Bugün 2. köprü Fsm.’de trafik vardı.”, “değil mi?”

String input = “Merhaba\nNasılsınız?”;

List<String> sentences = extractor.fromDocument(input);

OUTPUT

sentences -> “Merhaba”, “Nasılsınız?”

Performance and speed

We compared our system with Maximum Entropy based OpenNlp SentenceDetector for performance and speed. TurkishSentenceExtractor was trained with 8300 sentences. OpenNLP model was trained with a very large but somewhat noisy data. We did try training it with our training data but results were worse. Test set contains 1916 sentences and many of them contains numbers with dot characters.

Precision and Recall values are calculated for correct/incorrect boundaries. NIST error rate is calculated by dividing total amount of boundary errors to sentence count.

Test platform: 2.3 Ghz AMD FX-8320, Ubuntu Linux 16.04 LTS

Both systems work quite well for this test set. Probably if OpenNlp system contained the rule based mechanism, it would give equivalent results.

Notes

● Extracting from paragraphs only splits from [.!?…] characters.

● fromDocument method also splits from line breaks.

● Class will not split if a sentence ends with an abbreviation or a number,

● Narration sentences such as [Ali “topu at.” dedi.] will be split.

Tokenization

Zemberek, TurkishTokenizer adında bir sınıfa sahiptir. Bu sınıf kural tabanlı bir tokenizerdır. Bu tokenizer özel bir Antlr grammar based Lexer kullanır.

TurkishTokenizer tokenizer = TurkishTokenizer.DEFAULT;

TurkishTokenizer tokenizer = TurkishTokenizer.ALL;

initialize etmek için 2 yapıdan biri kullanılır. DEFAULT tokenizer çoğu tokenizer type’nı görmezden gelir(Boşluk, TAB, line feed and carriage return). ALL tokenizer ise tüm tokenizer type’larını dahil eder. TurkishTokenizer instances are thread safe. Tokenizer için bir kaç yardımcı metod vardır. Bunların kullanımı için aşağıdaki kod parçası kullanılabilir

TurkishTokenizer tokenizer = TurkishTokenizer.DEFAULT;

List<Token> tokens = tokenizer.tokenize(“Saat 12:00.”);

for (Token token : tokens) {

System.out.println(“Content = “ + token.getText());

System.out.println(“Start = “ + token.getStart());

System.out.println(“Stop = “ + token.getEnd());

System.out.println(“Type = “ + token.getType());

System.out.println();

}

OUTPUT

Content = Saat

Type = Word

Start = 0

Stop = 3

Content = 12:00

Type = Time

Start = 5

Stop = 9

Content = .

Type = Punctuation

Start = 10

Stop = 10

token.getText() fonksiyonu gelen token’ın içeriğini gösterir.

token.getStartIndex() fonksiyonu gelen token’ın cümledeki başlangıç noktasını gösterir.

token.getStopIndex() fonksiyonu gelen token’ın cümledeki bitiş noktasını gösterir.

Tokenizer özelleştirilebilir. Bunun için aşağıdaki kod parçası kullanılır:

TurkishTokenizer.Builder tokenizerBuilder = TurkishTokenizer.builder();

tokenizerBuilder.ignoreTypes(Token.Type.Punctuation,

Token.Type.NewLine,

Token.Type.SpaceTab );

TurkishTokenizer tokenizer = tokenizerBuilder.build();

ignoreTypes() fonksiyonuna parametre olarak istenmeyen Token.Type.* verilirse o tiplerin tamamı dikkate alınmayacaktır.

Örnek olarak aşağıdaki kod çalıştırılırsa çıktısı DEFAULT olarak kullandığımız tokenizer’dan farklı olacaktır.

TurkishTokenizer.Builder tokenizerBuilder = TurkishTokenizer.builder();

tokenizerBuilder.ignoreTypes(Token.Type.Punctuation,

Token.Type.NewLine,

Token.Type.SpaceTab );

TurkishTokenizer tokenizer = tokenizerBuilder.build();

List<Token> tokens = tokenizer.tokenize(“Saat, 12:00.”);

for (Token token : tokens) {

System.out.println(token);

}

OUTPUT

[Saat Word 0–3]

[12:00 Time 6–10]

Ignore Types

● SpaceTab

● NewLine

● Word

● WordAlphanumerical

● WordWithSymbol

● Abbreviation

● AbbreviationWithDots

● Punctuation

● RomanNumeral

● Number

● PercentNumeral

● Time

● Date

● URL

● Email

● HashTag

● Mention

● MetaTag

● Emoji

● Emoticon

● UnknownWord

● Unknown

Speed

We tested the DEFAULT TurkishTokenizer with 100,000 lines of news sentences on an Intel Xeon E5–2680 @ 2.50GHz system. Tokenization speed is about 1,500,000 tokens per second.

Notes

● Tokenizer is not a very good classifier for some cases. There may be cases it splits meaningful words.

● It is suggested to use it for sentence tokenization after sentence extraction.

LANGUAGE MODELING

Language Model Compression: SmoothLm

This library provides a language model library compression algorithm implementation. SmoothLm is a compressed, optionally quantized, randomized back-off n-gram language model. It uses Minimal Perfect Hash functions for compression, This means actual n-gram values are not stored in the model. Implementation is similar with the systems described in Gutthrie and Hepple’s ‘Storing the Web in Memory: Space Efficient Language Models with Constant Time Retrieval (2010)’ paper.

This is a lossy model because for non existing n-grams it may return an existing n-gram probability value (false positive). Probability of this happening depends on the fingerprint hash length. This value is determined during the model creation. Regularly 8,16 or 24 bit fingerprints are used and false positive probability for an non existing n-gram is (probability of an n-gram does not exist in LM)*1/(2^fingerprint bit size). SmoothLm also provides quantization for even more compactness. So probability and back-off values can be quantized to 8, 16 or 24 bits.

There are many alternatives for language model compression such as KenLm, RandLm and BerkeleyLm. SmoothLm and BerkeleyLm are implemented in Java so they are probably good choices for Java applications. Otherwise KenLm may be a better fit.

Limitations

● SmoothLm can only compress language models where for an order, n-gram amount must be less than 2,147,483,648 (2³¹-1)

● SmoothLm requires Java 8.

● SmoothLm loads all model data to memory. It does not work from disk. So it may not be convenient when there is limited amount of memory and language model is huge.

Generating SmoothLm

SmoothLM dosyası standard ARPA formatted language models kullanılarak üretilir. Language Model sınıfının içerisinde ARPA dosyasından SmoothLM dosyasına dönüştürmeyi sağlayan bir uygulama vardır. Generally speaking for 1 billion N-Grams if 16–8–8 space parameter is used, generated file will be around 4.4GB.

How to create ARPA file?

ARPA dosyasını oluşturabilmek için uygun dilde corpus dosyasına ihtiyaç vardır.

ARPA dosyasını üretmek için kenLM yazılımını kullanılabilir. kenLM’nin çalışabilmesi için BOOST yazılımına ihtiyaç vardır. BOOST yazılımının macOS’da kurulumu için terminalde

brew install boost

komutunu çalıştırmak yeterli olacaktır. Bu komutla birlikte BOOST yazılımının kurulumu tamamlanmış olacaktır.

BOOST kurulduktan sonra kenLM’yi indirmemiz gerekmekte. Bunun için terminal ekranında yeni bir klasör açıp, bu klasöre geçiş yapılır.

git clone https://github.com/kpu/kenlm.git

komutunu yazarak kenLM’yi klasöre kopyalayabilirsiniz yada github üzerinden indirmek için bu bağlantıyı kullanabilirsiniz.

Terminal ekranında kenLM’nin bulunduğu klasöre geçiş yapıldıktan sonra

mkdir BUILD

cd BUILD

komutu kullanılarak BUILD klasörü açılır ve klasöre geçiş yapılır.

cmake ..

komutuyla proje derlenir ve

make -j 4

komutuyla build işlemi tamamlanmış olur. Eğer cmake yüklü değilse

brew install cmake

komutuyla yüklenebilir.

Bir sonraki aşama için python 3 gerekmektedir. Python 3 kurulumu yapıldıktan sonra terminal ekranında

pip install nltk

komutu kullanılarak NLTK kütüphanesini indirilir. Bu kütüphanenin DATA’larını indirmek için terminalde

python

yazılarak python arayüzüne geçilir ve alttaki kod parçası kullanılarak NLTK DATA indirilir:

import nltk

nltk.download()

komutları çalıştırılarak NLTK DATA indirilebilir. Eğer indirme sırasında problem alınıyorsa aşağıdaki kodlar çalıştırılarak indirilir.

import nltk

import ssl

try:

_create_unverified_https_context = ssl._create_unverified_context

except AttributeError:

pass

else:

ssl._create_default_https_context = _create_unverified_https_context

nltk.download()

Yukarıdaki işlemler tamamlandıktan sonra ARPA dosyası artık oluşturulabilir. ARPA dosyasını oluşturma işlemi sırasında process.py adlı bir dosyaya ihtiyaç duyulmaktadır. Bu dosyanın içeriği:

import sys, nltk

for line in sys.stdin:

for sentence in nltk.sent_tokenize(line):

print(‘ ‘.join(nltk.word_tokenize(sentence)).lower())

process.py dosyası da hazırlandıktan sonra aşağıdaki komut sayesinde ARPA dosyası üretilir:

bzcat corpus.tar.bz2 | python3 process.py | kenlm/build/bin/lmplz -o 3 > lm.arpa

> corpus.tar.bz2 -> corpus dosyasının adı

> process.py -> process.py dosyasının adı

> lm.arpa -> çıktı dosyasının adı

How to Create SMOOTH file?

Oluşturduğumuz ARPA dosyasını ZEMBEREK içerisinde kullanmak için bunu smooth dosyasına çevirmemiz gerekiyor. Bu çevrim için terminal ekranında

java -Xmx4G -cp zemberek-full.jar zemberek.apps.lm.CompressLm -in lm.arpa -out lm.smooth

> zemberek-full.jar -zemberek full jar dosyası

> lm.arpa -> kullanılan ARPA dosyasının ismi

> lm.smooth -> output dosyasının ismi

komutunu kullanıyoruz.

How to Build LM ?

Aşağıdaki komutlar kod kısmında kullanılarak lm dosyası build edilir.

SmoothLm.Builder buildertest = SmoothLm.builder(new File(“lm.smooth”));

buildertest.logBase(Math.E);

SmoothLm lm = buildertest.build();

Build edilme durumunu test etmek için aşağıdaki komutlar kullanılabilir.

System.out.println(lm.info());

// convert words to indexes.

int[] wordIds = lm.getVocabulary().toIndexes(“yedi”,”elma”);

// gets probability of an n-gram. applies back-off if necessary

float probability = lm.getProbability(wordIds);

// explains how probability is calculated.

System.out.println(lm.explain(wordIds));

LM Example

SmoothLm.Builder buildertest = SmoothLm.builder(new File(“lm.smooth”));

buildertest.logBase(Math.E);

SmoothLm lm = buildertest.build();

System.out.println(lm.info());

// convert words to indexes.

int[] wordIds = lm.getVocabulary().toIndexes(“yedi”,”elma”);

// gets probability of an n-gram. applies back-off if necessary

float probability = lm.getProbability(wordIds);

// explains how probability is calculated.

System.out.println(lm.explain(wordIds))

NORMALIZATION

Turkish Spell Checker

Yazım denetçisi yazılan kelimelerin doğru yazılıp yazılmadığını kontrol eder, eğer yazımda yanlış varsa bunu düzeltmek için öneriler sunar.

TurkishMorphology m = TurkishMorphology.createWithDefaults();

TurkishSpellChecker spellChecker = new TurkishSpellChecker(m);

Kodlarını yazarak spellChecker initialize edilir. Bundan sonra kelime yazımını kontrol etmek için birkaç metod vardır.

String[] words = {“okuyabileceğimden”,”okuyablirim”, “Ankara”, “Ankar’ada”, “3'de”, “3'te”};

for (String word : words) {

System.out.println(word + “ = “ + spellChecker.check(word));

}

OUTPUT

okuyabileceğimden = true

okuyablirim = false

Ankara = true

Ankar’ada = false

3'de = false

3'te = true

Spelling Suggestion

Zemberekte suggestForWord() fonksiyonu kullanılarak yazım yanlışları için öneriler alabilirsiniz. En basit halde kullanımı:

String[] words = {“okuyablirim”, “tartısıyor”, “Ankar’ada”, “knlıca”};

for (String word : words) {

System.out.println(word + “ = “ + spellChecker.suggestForWord(word));

}

OUTPUT

okuyablirim = [okuyabilirim]

tartısıyor = [tartışıyor, tartılıyor, tartınıyor]

Ankar’ada = [Ankara’da, Ankara’ya, Ankara’dan, Ankaray’da, Ankara’ma, Antara’da, Ankara’ca, Cankara’da, Anakarada, Ankara’na, Angara’da, Ankara’mda, Ankara’nda, Ankaray’a]

knlıca = [Kanlıca, kanlıca, In’lıca, kılıca, anlıca, Kanlı’ca, kınlıca, kalıca]

If user provides a higher order language model (bi-gram models are sufficient) and context words, ranking of the suggestions may improve. Method below is used for this.

suggestForWord(String word, String leftContext, String rightContext, NgramLanguageModel lm)

suggestForWord fonksiyonuna gerekli parametreler verildiği takdirde önerilerde iyileşmeler olacaktır. leftContext ve rightContext boş bırakılabilir.

Example

String fileName = “lm.smooth”;

SmoothLm.Builder buildertest = SmoothLm.builder(new File(fileName));

buildertest.logBase(Math.E);

SmoothLm lm = buildertest.build();

TurkishMorphology morphology = TurkishMorphology.createWithDefaults();

TurkishSpellChecker spellChecker = new TurkishSpellChecker(morphology);

String[] words = {“”okuyablirim”, “tartısıyor”, “Ankar’ada”, “knlıca”};

for (String word : words) {

if(spellChecker.check(word)){

Log.info(word + “ -> “ + spellChecker.check(word));

} else {

Log.info(“Wrong Word: “ + word + “ -> Suggestions:” + spellChecker.suggestForWord(word,lm));

}

Limitations

● It only may correct for 1 insertion, 1 deletion, 1 substitution and 1 transposition errors.

● It ranks the results with an internal unigram language model.

● There is no deasciifier.

● It does not correct numbers, dates and times.

● There may be junk results.

● For shorter words, there will be a lot of suggestions (sometimes >50 ).

● Suggestion function is not so fast (Around 500–1000 words/second).

Noisy Text Normalization

Zemberek kullanarak, yanlış yazılmış metinleri düzeltebilirsiniz. Bu modül ile resmi olmayan kelimeleri veya hatalı yazılmış kelimeleri kolaylıkla düzeltebilirsiniz.

Bazı NLP işlemlerinde, metine gerçek algoritmalar uygulanmadan önce ön işleme adımı olarak normalization gerekir. Normalization özellikle aşağıdaki alanlarda gelişmiş sonuçlara yardımcı olur:

● Sosyal medya ve forum metinleri

● Sohbet, mesajlaşma veya bot uygulamaları

● Cep telefonu klavyeleri, yazım düzeltmesi veya kötü düzeltmesi olmadan.

Bu modül, çok yüksek hassasiyetle düzeltme gerektiğinde istenilen sonucu veremeyebilir.

USAGE

Bu modülde kullanılacak metin ilk başta Tokenization modülü kullanılarak cümlelere bölünmelidir. Normalization’ı kullanabilmek için bazı lookup(arama) dosyalarına ve language model gereklidir. Lm klasörü compressed bi-gram language model içerirken normalization klasörü ise 2 lookup(arama) dosyası içermektedir. Bu model ve lookup dosyaları tüm senaryolara uymayabilir ama başlangıç için kullanılabilir. Spesifik alanlarda yapılacak normalization için özelleştirmeler yapmak zorunludur.

Path lookupRoot = Paths.get(“data/normalization”);

Path lmFile = Paths.get(“data/lm/lm.2gram.slm”);

TurkishMorphology morphology = TurkishMorphology.createWithDefaults();

TurkishSentenceNormalizer normalizer = new TurkishSentenceNormalizer(morphology, lookupRoot, lmFile);

Yukarıdaki kodları yazarak normalization initialize edilir. Initialize edildikten sonra

System.out.println(normalizer.normalize(“Yrn okua gidicem”));

kodu eklenerek normalize edilebilir. Aşağıdaki cümleler üzerinde yapılan denemelerin sonucu görülmektedir:

Yrn okua gidicem

yarın okula gideceğim

Tmm, yarin havuza giricem ve aksama kadar yaticam :)

tamam , yarın havuza gireceğim ve akşama kadar yatacağım :)

ah aynen ya annemde fark ettı siz evinizden cıkmayın diyo

ah aynen ya annemde fark etti siz evinizden çıkmayın diyor

gercek mı bu? Yuh! Artık unutulması bile beklenmiyo

gerçek mi bu ? yuh ! artık unutulması bile beklenmiyor

Hayır hayat telaşm olmasa alacam buraları gökdelen dikicem.

hayır hayat telaşı olmasa alacağım buraları gökdelen dikeceğim .

yok hocam kesınlıkle oyle birşey yok

yok hocam kesinlikle öyle bir şey yok

herseyi soyle hayatında olmaması gerek bence boyle ınsanların falan baskı yapıyosa

herşeyi söyle hayatında olmaması gerek bence böyle insanların falan baskı yapıyorsa

Görüldüğü gibi, bazı kelimeler düzeltilemedi. Ve çıktıların tamamı küçük harf. Bazı sorunların farkındayız. Normalleştirme umarım daha sonraki sürümlerde daha iyi çalışır.

EKLENECEKLER: LOOKUP FİLES,SLM DOSYALARININ NASIL DÜZENLENECEĞİ)

Method

Zemberek, metin normalleştirme için çeşitli sezgisel tarama, arama tabloları ve dil modelleri kullanmaktadır. İşin bir kısmı:

● From clean and noisy corpora, vocabularies are created using morphological analysis.

● With some heuristics and language models, words that should be split to two are found.

● From corpora, correct, incorrect and possibly-incorrect sets are created.

● For pre-processing, deasciifier, split and combine heuristics are applied.

● Using those sets and large corpora, a noisy to clean word lookup is generated using a modified version of Hassan and Menezes 2013 work [1].

● For a sentence, for every noisy word, candidates are collected from lookup tables, informal and ascii-matching morphological analysis and spell checker.

● Most likely correct sequence is found running Viterbi algorithm on candidate words with language model scoring.

Speed

According to our measurements speed is about 10 thousand tokens/second (with punctuations) using a single core. Later versions may work slower due to additional heuristics.

Test System: AMD FX-8320 3.5Ghz

Issues

This work is the result of our initial exploration on the subject, expect many errors. Therefore, normalization operation:

● may change correct words,

● may change formatting and casing or remove punctuations,

● may not work well for some cases,

● may generate profanity words

Example

Path lookupRoot = Paths.get(“data/normalization”);

Path lmFile = Paths.get(“data/lm/lm.2gram.slm”);

TurkishMorphology morphology = TurkishMorphology.createWithDefaults();

TurkishSentenceNormalizer normalizer = new TurkishSentenceNormalizer(morphology, lookupRoot, lmFile);

System.out.println(normalizer.normalize(“Yrn okua gidicem”));

TEXT CLASSIFICATION

Text Classification Introduction

Zemberek Classification, NLP işlerinden olan doküman sınıflama, spam tespiti ve duygu tespiti konularında kullanılabilir. Zemberek-NLP basit bir metin sınıfla modülü sunar. Bu modül fastText Projesi’nin javaya uyarlanmış halidir.

Generating a Classification Model

Data Preparation

Classification Model oluşturmak için, bir training set’e ihtiyaç vardır. Bu training seti document ve bunların labels içeren bir dosya olmalıdır. Training data fastText stilinde hazırlanmalıdır. Örnek olarak haber başlıkları ve kategorilerini içeren bir eğitim seti

__label__magazin Jackie Chan’a yapmadıklarını bırakmadılar!

__label__spor Fenerbahçe Akhisar’da çok rahat kazandı

__label__teknoloji Google Nexus telefonları Huawei de üretebilir!

Her satır mutlaka bir document ve bunların labels içermek zorundadır. Document cümle ya da paragraf olabilir. Algoritma bir sayfalık bir document ile çalışabilir ama bu performansının beklenenden daha düşük olmasına sebep olacaktır. label __label__ ön ekine sahip olmak zorundadır.

Ancak, genellikle traning set’in ön işleme tutulması gerekir. Aksi taktirde üretebilir! ile üretebilir farklı kelimeler olarak ele alınacaktır. Probleme göre ön işlemler değişmelidir. Ön işlemler için bir kaç örnek opsiyon:

● Tokenization

● Removal of some punctuations

● Removal or normalization of digits

● Using stems, lemmas or morphemes instead of words

● Lower casing

Bu işlemlerin çoğu, document’te bulunan metinlere zarar vermeden kelime çeşitliliğini azaltır ve muhtemelen performansta iyileşmelere sebep olacaktır. Ancak deneme yapılması kesinlikle önerilir.

Aşağıdaki metine yukarıdaki işlemler uygulandıktan sonra sonuç OUTPUT’ta olduğu gibi olacaktır.

__label__magazin Jackie Chan’a yapmadıklarını bırakmadılar!

__label__spor Fenerbahçe Akhisar’da çok rahat kazandı

__label__teknoloji Google Nexus telefonları Huawei de üretebilir!

OUTPUT

__label__magazin jackie chan’a yapmadıklarını bırakmadılar

__label__spor fenerbahçe akhisar’da çok rahat kazandı

__label__teknoloji google nexus telefonları huawei de üretebilir

OUTPUT dosyasına news-title-category-set ismini verelim. Buradan sonra kullandığım her news-title-category-set yukarıdaki OUTPUT olarak düşünülmesi gerekir.

Training

Training console application ya da API kullanılarak tamamlanabilir. Console Application kullanmak daha kolay bir yoldur. USE ZEMBEREK WITH DEPENDENCIES JAR:

java -jar zemberek-full.jar TrainClassifier -i news-title-category-set -o news-title-category-set.model — learningRate 0.1 — epochCount 50

> zemberek-full.jar -> Zemberek’i kullanmak için gereken jar dosyası.

> epochCount -> Training setin kaç kere eğitileceği (Detaylı bilgi için buraya bakılabilir.)

Eğer training başarılı olursa news-title-category-set.model adında bir dosya üretilecektir. Bu model dosyasının boyutu oldukça büyük olacaktır ama bu boyutu düşürmenin yolları bulunmaktadır.

Using the Classifier

Model bir kez üretildikten, direkt olarak kullanılabilir. En önemli şey ise tahmin etme işleminden önce, verilecek input eğitilen data ile aynı olmalıdır.

Örnek:

FastTextClassifier classifier = FastTextClassifier.load(modelPath);

String s = “Beşiktaş berabere kaldı.”

// process the input exactly the way trainin set is processed

String processed = String.join(“ “, TurkishTokenizer.DEFAULT.tokenizeToStrings(s));

processed = processed.toLowerCase(Turkish.LOCALE);

// results, only top three.

List<ScoredItem<String>> res = classifier.predict(processed, 3);

for (ScoredItem<String> re : res) {

System.out.println(re);

}

OUTPUT

__label__spor : 0.000010

__label__türkiye : -11.483298

__label__yaşam : -11.512561

Reducing the model size

fastText’e benzer olarak zemberekte modelin boyutunu düşürmek için quantization sunar. Genellikle model boyutu yüzlerce megabayt olabilir. Quantization ve l2-norm cut-off kullanarak model büyüklüğü küçük bir performans kaybıyla önemli ölçüde azaltılabilir.

For generating quantized models, — applyQuantization and — cutOff can be used. For example:

java -jar zemberek-full.jar TrainClassifier -i news-title-set -o news-title.model — learningRate 0.1 — epochCount 50 — applyQuantization — cutOff 15000

Yukarıdaki komutta çalıştırırsa elimizde iki adet model olacak. Bunlar: news-title-category-set.model ve news-title-category-set.model.q Her iki model de FastTextClassifier’ı başlatmak için kullanılabilir.

Yukarıda belirtilen set için, model boyutu 400 MB’den 1 MB’a düşürülür.

Performance and Speed

According to the [1] fastText classification algorithm gives comparable results to alternative more complex systems of 2016. However, more recent state of the art systems may give better results.

Despite not using GPUs, original fastText library is very fast. Our Java port’s speed is close to the C++ version. Training is multi-threaded. For example, using 4 threads, news title set with 68365 samples and 442.000 tokens, training takes about 20 seconds. Testing 1000 examples takes around 4.5 seconds with a single thread.

Test system: 2.3 Ghz AMD FX-8320, Ubuntu Linux 16.04 LTS.

[1] A. Joulin, E. Grave, P. Bojanowski, T. Mikolov, Bag of Tricks for Efficient Text Classification

Algorithm

As mentioned before, classification algorithm is based on a port of fastText project. Please refer to the project documentation and related scientific papers for more information.

Examples

There are two examples in examples module. NewsTitleCategoryFinder generates different classification models from Turkish news title category data set and evaluates.

SimpleClassification shown how to make category prediction in runtime.

There is also a Turkish wiki page on generating and evaluating classifier for Turkish news headline categories.

LANGUAGE IDENTIFICATION

Introduction

This library provides a text based language identification algorithm implementation. Implementation is based on simple character n-gram models. It can identify 62 languages.

Usage

For general usage, library can be initialized as:

LanguageIdentifier lid = LanguageIdentifier.fromInternalModels();

This will load all 62 models to memory. After initialization, several identification methods can be called.

lid.identify(“Merhaba dünya ve tüm gezegenler.”)

Will return the identified language code. In this case, “tr” should return. This method is the most accurate but for large documents slowest one. If document size is larger than 100 characters, using a method with sampling is preferable.

lid.identify(inputString, 50);

In this case only 50 samples from the document is collected and scored. There is even a faster method. But using method below only makes sense if there are more than 10 models.

lid.identifyFast(inputString, 50);

There is also a method for checking if only a part of the text contains a specified language.

String input = “merhaba dünya ve tüm gezegenler Hola mundo y todos los planetas”;

lid.containsLanguage(input, “tr”, 20); // returns true

lid.containsLanguage(input, “es”, 20); // returns true

But if only identifying a small amount of languages is required, and their models are available library can be instantiated as

LanguageIdentifier lid = LanguageIdentifier.fromInternalModelGroup(“tr_group”);

Here, tr_group contains about 8 languages and a special uknown language id.

Performance

Below are the presicion and recall numbers for Turkish and English languages from 60 different language documents with 20, 50 and 100 character lengths.

Speed

For a two model identification test speed numbers are:

--

--

Uğur Özker

Computer Engineer, MSc, MBA, PMP®, Senior Solution Architect IBM