working on morh
git-svn-id: https://russianmorphology.googlecode.com/svn/trunk@38 d817d54c-26ab-11de-abc9-2f7d1455ff7a
This commit is contained in:
parent
e4dd3a7a76
commit
70842ecfb7
@ -16,20 +16,15 @@
|
||||
|
||||
package org.apache.lucene.russian.morphology;
|
||||
|
||||
import org.apache.lucene.russian.morphology.dictonary.DictonaryReader;
|
||||
import org.apache.lucene.russian.morphology.dictonary.FrequentyReader;
|
||||
import org.apache.lucene.russian.morphology.dictonary.GrammaReader;
|
||||
import org.apache.lucene.russian.morphology.dictonary.IgnoredFormReader;
|
||||
import org.apache.lucene.russian.morphology.dictonary.*;
|
||||
import org.apache.lucene.russian.morphology.heuristic.HeuristicBySuffixLegth;
|
||||
import org.apache.lucene.russian.morphology.heuristic.StatiticsCollectors;
|
||||
import org.apache.lucene.russian.morphology.heuristic.SuffixCounter;
|
||||
import org.apache.lucene.russian.morphology.heuristic.SuffixHeuristic;
|
||||
import org.apache.lucene.russian.morphology.heuristic.SimpleSuffixHeuristic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
||||
public class HeuristicBuilder {
|
||||
@ -57,47 +52,28 @@ public class HeuristicBuilder {
|
||||
heuristic.addHeuristic(((SuffixCounter) objects[i]).getSuffixHeuristic());
|
||||
}
|
||||
|
||||
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
|
||||
final Map<Long,Set<SimpleSuffixHeuristic>> map = heuristic.getUnkowns();
|
||||
|
||||
int ct = 0;
|
||||
for (Set<SuffixHeuristic> s : heuristic.getHeuristics().values()) {
|
||||
Integer d = map.get(s.size());
|
||||
map.put(s.size(), 1 + (d == null ? 0 : d));
|
||||
if (s.size() == 1) {
|
||||
ct++;
|
||||
continue;
|
||||
}
|
||||
SuffixHeuristic heuristic1 = s.iterator().next();
|
||||
Integer sufixSize = heuristic1.getActualSuffixLength();
|
||||
String normalSuffix = heuristic1.getNormalFromSuffix();
|
||||
if (heuristic1.getFormSuffix().length() < 6) {
|
||||
ct++;
|
||||
continue;
|
||||
}
|
||||
Boolean flag = true;
|
||||
if (sufixSize > 3) continue;
|
||||
for (SuffixHeuristic sh : s) {
|
||||
flag = flag && (sufixSize.equals(sh.getActualSuffixLength()))
|
||||
&& (normalSuffix.equals(sh.getNormalFromSuffix()));
|
||||
}
|
||||
if (flag) {
|
||||
System.out.println(s);
|
||||
ct++;
|
||||
}
|
||||
//HashSet<String> integers = new HashSet<String>();
|
||||
// for(SuffixHeuristic sh:s){
|
||||
// integers.add(sh.getMorphInfoCode());
|
||||
// }
|
||||
// if(s.size() == integers.size()){
|
||||
// ct++;
|
||||
// }else{
|
||||
// if(s.size() == 2) System.out.println(s);
|
||||
// }
|
||||
}
|
||||
System.out.println(objects.length);
|
||||
System.out.println(heuristic.getHeuristics().size());
|
||||
System.out.println(ct);
|
||||
System.out.println(map);
|
||||
//heuristic.writeToFile("russianSuffixesHeuristic.txt");
|
||||
//final RussianSuffixDecoderEncoder decoderEncoder = new RussianSuffixDecoderEncoder(6);
|
||||
final AtomicLong c = new AtomicLong(0L);
|
||||
final AtomicLong all = new AtomicLong(0L);
|
||||
dictonaryReader.proccess(
|
||||
new WordProccessor(){
|
||||
public void proccess(WordCard wordCard) throws IOException {
|
||||
for (FlexiaModel fm : wordCard.getWordsFroms()) {
|
||||
String form = fm.create(wordCard.getBase());
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.suffixLength ? form.length() - RussianSuffixDecoderEncoder.suffixLength : 0;
|
||||
String formSuffix = form.substring(startSymbol);
|
||||
Long aLong = RussianSuffixDecoderEncoder.encode(formSuffix);
|
||||
all.incrementAndGet();
|
||||
if(map.containsKey(aLong)) c.incrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
System.out.println("Ankown words " + all.longValue());
|
||||
System.out.println("Ankown words " + c.longValue());
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,17 @@ package org.apache.lucene.russian.morphology;
|
||||
*/
|
||||
public class RussianSuffixDecoderEncoder {
|
||||
public static final int RUSSIAN_SMALL_LETTER_OFFSET = 1071;
|
||||
public static final int SUFFIX_LENGTH = 6;
|
||||
static public int suffixLength = 6;
|
||||
public static final int EE_CHAR = 34;
|
||||
public static final int E_CHAR = 6;
|
||||
public static final int DASH_CHAR = 45;
|
||||
public static final int DASH_CODE = 33;
|
||||
|
||||
|
||||
public RussianSuffixDecoderEncoder(int suffixLength) {
|
||||
RussianSuffixDecoderEncoder.suffixLength = suffixLength;
|
||||
}
|
||||
|
||||
static public Long encode(String string) {
|
||||
if (string.length() > 12) throw new SuffixToLongException("Suffix length should not be greater then " + 12);
|
||||
long result = 0L;
|
||||
|
@ -8,6 +8,6 @@ import java.io.IOException;
|
||||
public class Test {
|
||||
public static void main(String[] args) throws IOException {
|
||||
GrammaReader grammaReader = new GrammaReader("dictonary/Dicts/Morph/rgramtab.tab");
|
||||
System.out.println(grammaReader.getInversIndex().size());
|
||||
//System.out.println(grammaReader.getInversIndex().size());
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class SuffixHeuristic {
|
||||
}
|
||||
|
||||
public String getCanonicalForm(String form) {
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.SUFFIX_LENGTH ? form.length() - RussianSuffixDecoderEncoder.SUFFIX_LENGTH : 0;
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.suffixLength ? form.length() - RussianSuffixDecoderEncoder.suffixLength : 0;
|
||||
String suffixS = form.substring(startSymbol);
|
||||
|
||||
if (!chechSuffix(suffixS)) return form;
|
||||
|
@ -6,12 +6,14 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
//todo spleet this class on two.
|
||||
public class GrammaReader {
|
||||
private String fileName;
|
||||
private String fileEncoding = "windows-1251";
|
||||
private Map<Integer, String> grammaInfo = new HashMap<Integer, String>();
|
||||
private List<String> grammaInfo = new ArrayList<String>();
|
||||
private Map<String, Integer> inversIndex = new HashMap<String, Integer>();
|
||||
|
||||
public GrammaReader(String fileName) throws IOException {
|
||||
@ -34,21 +36,17 @@ public class GrammaReader {
|
||||
String[] strings = line.split(" ", 2);
|
||||
Integer i = grammaInfo.size();
|
||||
inversIndex.put(strings[0], i);
|
||||
grammaInfo.put(i, strings[1]);
|
||||
grammaInfo.add(i, strings[1]);
|
||||
}
|
||||
line = bufferedReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, String> getGrammaInfo() {
|
||||
public List<String> getGrammaInfo() {
|
||||
return grammaInfo;
|
||||
}
|
||||
|
||||
public void setGrammaInfo(Map<Integer, String> grammaInfo) {
|
||||
this.grammaInfo = grammaInfo;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getInversIndex() {
|
||||
public Map<String, Integer> getGrammInversIndex() {
|
||||
return inversIndex;
|
||||
}
|
||||
|
||||
|
@ -28,16 +28,16 @@ import java.util.TreeMap;
|
||||
public class Heuristic {
|
||||
private TreeMap<Long, Long> encodedSuffixesPairs = new TreeMap<Long, Long>();
|
||||
|
||||
public void addHeuristic(SuffixHeuristic suffixHeuristic) {
|
||||
// Long suffix = RussianSuffixDecoderEncoder.encode(suffixHeuristic.getFormSuffix());
|
||||
public void addHeuristic(SimpleSuffixHeuristic simpleSuffixHeuristic) {
|
||||
// Long suffix = RussianSuffixDecoderEncoder.encode(simpleSuffixHeuristic.getFormSuffix());
|
||||
// Long longs = encodedSuffixesPairs.get(suffix);
|
||||
// if (longs == null) {
|
||||
// encodedSuffixesPairs.put(suffix, RussianSuffixDecoderEncoder.encode(suffixHeuristic.getNormalSuffix()));
|
||||
// encodedSuffixesPairs.put(suffix, RussianSuffixDecoderEncoder.encode(simpleSuffixHeuristic.getNormalSuffix()));
|
||||
// }
|
||||
}
|
||||
|
||||
public String getNormalForm(String form) {
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.SUFFIX_LENGTH ? form.length() - RussianSuffixDecoderEncoder.SUFFIX_LENGTH : 0;
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.suffixLength ? form.length() - RussianSuffixDecoderEncoder.suffixLength : 0;
|
||||
Long suffix = RussianSuffixDecoderEncoder.encode(form.substring(startSymbol));
|
||||
|
||||
Long normalSuffix = encodedSuffixesPairs.get(suffix);
|
||||
|
@ -9,19 +9,77 @@ import java.util.Set;
|
||||
|
||||
|
||||
public class HeuristicBySuffixLegth {
|
||||
private Map<Long, Set<SuffixHeuristic>> heuristics = new HashMap<Long, Set<SuffixHeuristic>>();
|
||||
private Map<Long, Set<SimpleSuffixHeuristic>> heuristics = new HashMap<Long, Set<SimpleSuffixHeuristic>>();
|
||||
|
||||
public void addHeuristic(SuffixHeuristic suffixHeuristic) {
|
||||
Long suffix = RussianSuffixDecoderEncoder.encode(suffixHeuristic.getFormSuffix());
|
||||
Set<SuffixHeuristic> suffixHeuristics = heuristics.get(suffix);
|
||||
if (suffixHeuristics == null) {
|
||||
suffixHeuristics = new HashSet<SuffixHeuristic>();
|
||||
heuristics.put(suffix, suffixHeuristics);
|
||||
public void addHeuristic(SimpleSuffixHeuristic simpleSuffixHeuristic) {
|
||||
Long suffix = RussianSuffixDecoderEncoder.encode(simpleSuffixHeuristic.getFormSuffix());
|
||||
Set<SimpleSuffixHeuristic> simpleSuffixHeuristics = heuristics.get(suffix);
|
||||
if (simpleSuffixHeuristics == null) {
|
||||
simpleSuffixHeuristics = new HashSet<SimpleSuffixHeuristic>();
|
||||
heuristics.put(suffix, simpleSuffixHeuristics);
|
||||
}
|
||||
suffixHeuristics.add(suffixHeuristic);
|
||||
simpleSuffixHeuristics.add(simpleSuffixHeuristic);
|
||||
}
|
||||
|
||||
public Map<Long, Set<SuffixHeuristic>> getHeuristics() {
|
||||
public Map<Long, Set<SimpleSuffixHeuristic>> getHeuristics() {
|
||||
return heuristics;
|
||||
}
|
||||
|
||||
public Map<Long,SimpleSuffixHeuristic> getSingleSuffixes(){
|
||||
HashMap<Long, SimpleSuffixHeuristic> result = new HashMap<Long, SimpleSuffixHeuristic>();
|
||||
for(Long st:heuristics.keySet()){
|
||||
if(heuristics.get(st).size() == 1){
|
||||
result.put(st,heuristics.get(st).iterator().next());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public Map<Long,Set<SimpleSuffixHeuristic>> getWordWithMorphology(){
|
||||
HashMap<Long, Set<SimpleSuffixHeuristic>> result = new HashMap<Long, Set<SimpleSuffixHeuristic>>();
|
||||
for(Long st:heuristics.keySet()){
|
||||
if(heuristics.get(st).size() == 1) continue;
|
||||
if(checkSetOnSuffix(heuristics.get(st))) {
|
||||
result.put(st,heuristics.get(st));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<Long,Set<SimpleSuffixHeuristic>> getOnonyms(){
|
||||
HashMap<Long, Set<SimpleSuffixHeuristic>> result = new HashMap<Long, Set<SimpleSuffixHeuristic>>();
|
||||
for(Long st:heuristics.keySet()){
|
||||
if(heuristics.get(st).size() == 1) continue;
|
||||
if(checkSetOnSuffix(heuristics.get(st))) continue;
|
||||
if(heuristics.get(st).iterator().next().getFormSuffix().length() < 6){
|
||||
result.put(st,heuristics.get(st));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<Long,Set<SimpleSuffixHeuristic>> getUnkowns(){
|
||||
HashMap<Long, Set<SimpleSuffixHeuristic>> result = new HashMap<Long, Set<SimpleSuffixHeuristic>>();
|
||||
for(Long st:heuristics.keySet()){
|
||||
if(heuristics.get(st).size() == 1) continue;
|
||||
if(checkSetOnSuffix(heuristics.get(st))) continue;
|
||||
if(heuristics.get(st).iterator().next().getFormSuffix().length() >= 6){
|
||||
result.put(st,heuristics.get(st));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Boolean checkSetOnSuffix(Set<SimpleSuffixHeuristic> sshs) {
|
||||
SimpleSuffixHeuristic heuristic = sshs.iterator().next();
|
||||
String normalSuffix = heuristic.getFormSuffix();
|
||||
Integer suffixLenght = heuristic.getActualSuffixLength();
|
||||
Boolean result = true;
|
||||
for(SimpleSuffixHeuristic ssh:sshs){
|
||||
result = result && ssh.getActualSuffixLength().equals(suffixLenght) && ssh.getNormalSuffix().endsWith(normalSuffix);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright 2009 Alexander Kuznetsov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.russian.morphology.heuristic;
|
||||
|
||||
/**
|
||||
* Represent evristic that assume that
|
||||
* canonical from of word is defined by word suffix.
|
||||
* It contains to suffixes from given position of
|
||||
* canonical word form and for form.
|
||||
*/
|
||||
public class SimpleSuffixHeuristic {
|
||||
private String formSuffix;
|
||||
private Integer actualSuffixLength;
|
||||
private String normalSuffix;
|
||||
private String morphInfoCode;
|
||||
|
||||
public SimpleSuffixHeuristic(String formSuffix, Integer actualSuffixLength, String normalSuffix, String morphInfoCode) {
|
||||
this.formSuffix = formSuffix;
|
||||
this.actualSuffixLength = actualSuffixLength;
|
||||
this.normalSuffix = normalSuffix;
|
||||
this.morphInfoCode = morphInfoCode;
|
||||
}
|
||||
|
||||
public String getFormSuffix() {
|
||||
return formSuffix;
|
||||
}
|
||||
|
||||
public Integer getActualSuffixLength() {
|
||||
return actualSuffixLength;
|
||||
}
|
||||
|
||||
public String getNormalSuffix() {
|
||||
return normalSuffix;
|
||||
}
|
||||
|
||||
public String getMorphInfoCode() {
|
||||
return morphInfoCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SimpleSuffixHeuristic that = (SimpleSuffixHeuristic) o;
|
||||
|
||||
if (actualSuffixLength != null ? !actualSuffixLength.equals(that.actualSuffixLength) : that.actualSuffixLength != null)
|
||||
return false;
|
||||
if (formSuffix != null ? !formSuffix.equals(that.formSuffix) : that.formSuffix != null) return false;
|
||||
if (morphInfoCode != null ? !morphInfoCode.equals(that.morphInfoCode) : that.morphInfoCode != null)
|
||||
return false;
|
||||
if (normalSuffix != null ? !normalSuffix.equals(that.normalSuffix) : that.normalSuffix != null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = formSuffix != null ? formSuffix.hashCode() : 0;
|
||||
result = 31 * result + (actualSuffixLength != null ? actualSuffixLength.hashCode() : 0);
|
||||
result = 31 * result + (normalSuffix != null ? normalSuffix.hashCode() : 0);
|
||||
result = 31 * result + (morphInfoCode != null ? morphInfoCode.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formSuffix + " " + actualSuffixLength + " " + normalSuffix + " " + morphInfoCode;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import java.util.Map;
|
||||
|
||||
|
||||
public class StatiticsCollectors implements WordProccessor {
|
||||
Map<SuffixHeuristic, SuffixCounter> statititics = new HashMap<SuffixHeuristic, SuffixCounter>();
|
||||
Map<SimpleSuffixHeuristic, SuffixCounter> statititics = new HashMap<SimpleSuffixHeuristic, SuffixCounter>();
|
||||
private Map<String, Double> wordsFreq;
|
||||
private GrammaReader grammaInfo;
|
||||
|
||||
@ -40,12 +40,12 @@ public class StatiticsCollectors implements WordProccessor {
|
||||
|
||||
public void proccess(WordCard wordCard) {
|
||||
for (FlexiaModel fm : wordCard.getWordsFroms()) {
|
||||
SuffixHeuristic suffixHeuristic = createEvristic(wordCard.getCanonicalFrom(), wordCard.getCanonicalSuffix(), fm);
|
||||
if (suffixHeuristic == null) continue;
|
||||
SuffixCounter suffixCounter = statititics.get(suffixHeuristic);
|
||||
SimpleSuffixHeuristic simpleSuffixHeuristic = createEvristic(wordCard.getBase(), wordCard.getCanonicalSuffix(), fm);
|
||||
if (simpleSuffixHeuristic == null) continue;
|
||||
SuffixCounter suffixCounter = statititics.get(simpleSuffixHeuristic);
|
||||
if (suffixCounter == null) {
|
||||
suffixCounter = new SuffixCounter(suffixHeuristic);
|
||||
statititics.put(suffixHeuristic, suffixCounter);
|
||||
suffixCounter = new SuffixCounter(simpleSuffixHeuristic);
|
||||
statititics.put(simpleSuffixHeuristic, suffixCounter);
|
||||
}
|
||||
Double freq = wordsFreq.get(wordCard.getCanonicalFrom());
|
||||
if (freq != null) {
|
||||
@ -57,27 +57,17 @@ public class StatiticsCollectors implements WordProccessor {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<SuffixHeuristic, SuffixCounter> getStatititics() {
|
||||
public Map<SimpleSuffixHeuristic, SuffixCounter> getStatititics() {
|
||||
return statititics;
|
||||
}
|
||||
|
||||
private SuffixHeuristic createEvristic(String wordBase, String canonicalSuffix, FlexiaModel fm) {
|
||||
private SimpleSuffixHeuristic createEvristic(String wordBase, String canonicalSuffix, FlexiaModel fm) {
|
||||
String form = fm.create(wordBase);
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.SUFFIX_LENGTH ? form.length() - RussianSuffixDecoderEncoder.SUFFIX_LENGTH : 0;
|
||||
int startSymbol = form.length() > RussianSuffixDecoderEncoder.suffixLength ? form.length() - RussianSuffixDecoderEncoder.suffixLength : 0;
|
||||
String formSuffix = form.substring(startSymbol);
|
||||
String actualSuffix = fm.getSuffix();
|
||||
Integer actualSuffixLengh = actualSuffix.length();
|
||||
// if (word.length() < startSymbol) {
|
||||
// ignoredCount++;
|
||||
// return null;
|
||||
// }
|
||||
// String wordSuffix = word.length() > startSymbol ? word.substring(startSymbol) : "";
|
||||
// if (wordSuffix.length() > 12) {
|
||||
// System.out.println(word + " " + form);
|
||||
// return null;
|
||||
// }
|
||||
// return new SuffixHeuristic(formSuffix, wordSuffix);
|
||||
return new SuffixHeuristic(formSuffix, actualSuffixLengh, canonicalSuffix, fm.getCode());
|
||||
return new SimpleSuffixHeuristic(formSuffix, actualSuffixLengh, canonicalSuffix, fm.getCode());
|
||||
}
|
||||
|
||||
|
||||
|
@ -21,11 +21,11 @@ package org.apache.lucene.russian.morphology.heuristic;
|
||||
* in dictionary.
|
||||
*/
|
||||
public class SuffixCounter implements Comparable {
|
||||
private SuffixHeuristic suffixHeuristic;
|
||||
private SimpleSuffixHeuristic simpleSuffixHeuristic;
|
||||
private Double amnout = 0.0;
|
||||
|
||||
public SuffixCounter(SuffixHeuristic suffixHeuristic) {
|
||||
this.suffixHeuristic = suffixHeuristic;
|
||||
public SuffixCounter(SimpleSuffixHeuristic simpleSuffixHeuristic) {
|
||||
this.simpleSuffixHeuristic = simpleSuffixHeuristic;
|
||||
}
|
||||
|
||||
public void incrementAmount() {
|
||||
@ -36,12 +36,12 @@ public class SuffixCounter implements Comparable {
|
||||
amnout += wordFreq;
|
||||
}
|
||||
|
||||
public SuffixHeuristic getSuffixHeuristic() {
|
||||
return suffixHeuristic;
|
||||
public SimpleSuffixHeuristic getSuffixHeuristic() {
|
||||
return simpleSuffixHeuristic;
|
||||
}
|
||||
|
||||
public void setSuffixEvristic(SuffixHeuristic suffixHeuristic) {
|
||||
this.suffixHeuristic = suffixHeuristic;
|
||||
public void setSuffixEvristic(SimpleSuffixHeuristic simpleSuffixHeuristic) {
|
||||
this.simpleSuffixHeuristic = simpleSuffixHeuristic;
|
||||
}
|
||||
|
||||
public Double getAmnout() {
|
||||
@ -59,6 +59,6 @@ public class SuffixCounter implements Comparable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + amnout + " " + suffixHeuristic.toString();
|
||||
return "" + amnout + " " + simpleSuffixHeuristic.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +1,10 @@
|
||||
/**
|
||||
* Copyright 2009 Alexander Kuznetsov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.russian.morphology.heuristic;
|
||||
|
||||
/**
|
||||
* Represent evristic that assume that
|
||||
* canonical from of word is defined by word suffix.
|
||||
* It contains to suffixes from given position of
|
||||
* canonical word form and for form.
|
||||
*/
|
||||
|
||||
public class SuffixHeuristic {
|
||||
private String formSuffix;
|
||||
private Integer actualSuffixLength;
|
||||
private String normalFromSuffix;
|
||||
private String morphInfoCode;
|
||||
|
||||
public SuffixHeuristic(String formSuffix, Integer actualSuffixLength, String normalFromSuffix, String morphInfoCode) {
|
||||
this.formSuffix = formSuffix;
|
||||
this.actualSuffixLength = actualSuffixLength;
|
||||
this.normalFromSuffix = normalFromSuffix;
|
||||
this.morphInfoCode = morphInfoCode;
|
||||
private SuffixTypes suffixType;
|
||||
private Byte suffixLengh;
|
||||
private Short indexOfWordTransorm;
|
||||
private Short indexOfMothInfo;
|
||||
}
|
||||
|
||||
public String getFormSuffix() {
|
||||
return formSuffix;
|
||||
}
|
||||
|
||||
public Integer getActualSuffixLength() {
|
||||
return actualSuffixLength;
|
||||
}
|
||||
|
||||
public String getNormalFromSuffix() {
|
||||
return normalFromSuffix;
|
||||
}
|
||||
|
||||
public String getMorphInfoCode() {
|
||||
return morphInfoCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SuffixHeuristic that = (SuffixHeuristic) o;
|
||||
|
||||
if (actualSuffixLength != null ? !actualSuffixLength.equals(that.actualSuffixLength) : that.actualSuffixLength != null)
|
||||
return false;
|
||||
if (formSuffix != null ? !formSuffix.equals(that.formSuffix) : that.formSuffix != null) return false;
|
||||
if (morphInfoCode != null ? !morphInfoCode.equals(that.morphInfoCode) : that.morphInfoCode != null)
|
||||
return false;
|
||||
if (normalFromSuffix != null ? !normalFromSuffix.equals(that.normalFromSuffix) : that.normalFromSuffix != null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = formSuffix != null ? formSuffix.hashCode() : 0;
|
||||
result = 31 * result + (actualSuffixLength != null ? actualSuffixLength.hashCode() : 0);
|
||||
result = 31 * result + (normalFromSuffix != null ? normalFromSuffix.hashCode() : 0);
|
||||
result = 31 * result + (morphInfoCode != null ? morphInfoCode.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formSuffix + " " + actualSuffixLength + " " + normalFromSuffix + " " + morphInfoCode;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
package org.apache.lucene.russian.morphology.heuristic;
|
||||
|
||||
|
||||
public class SuffixHeuristicMerger {
|
||||
|
||||
public SuffixHeuristic merge(SuffixHeuristic one, SuffixHeuristic two) {
|
||||
if (!one.getMorphInfoCode().equals(two.getMorphInfoCode()))
|
||||
return null;
|
||||
SuffixHeuristic min = one.getActualSuffixLength() > two.getActualSuffixLength() ? two : one;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.apache.lucene.russian.morphology.heuristic;
|
||||
|
||||
|
||||
public enum SuffixTypes {
|
||||
SINGLE,
|
||||
DIFFIRENT_MORPH,
|
||||
ONONIMS
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.apache.lucene.russian.morphology.informations;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class GrammaInfo implements Serializable{
|
||||
private String[] grammaInfo;
|
||||
|
||||
public GrammaInfo(String[] grammaInfo) {
|
||||
this.grammaInfo = grammaInfo;
|
||||
}
|
||||
|
||||
public String getInfo(Integer index){
|
||||
return grammaInfo[index];
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package org.apache.lucene.russian.morphology.informations;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class NormalSuffixCollection implements Serializable{
|
||||
private String[] normalSuffixes;
|
||||
|
||||
public NormalSuffixCollection(String[] normalSuffixes) {
|
||||
this.normalSuffixes = normalSuffixes;
|
||||
}
|
||||
|
||||
public String getSuffix(Integer index){
|
||||
return normalSuffixes[index];
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.apache.lucene.russian.morphology;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.apache.lucene.russian.morphology.analayzer.RussianMorphlogyAnalayzer;
|
||||
import org.apache.lucene.analysis.Token;
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class SpeedTest {
|
||||
|
||||
@Test
|
||||
public void getTestOfSpeed() throws IOException {
|
||||
Long startTime = System.currentTimeMillis();
|
||||
RussianMorphlogyAnalayzer morphlogyAnalayzer = new RussianMorphlogyAnalayzer();
|
||||
System.out.println("To build analayzer take " + (System.currentTimeMillis() - startTime) + " ms.");
|
||||
InputStream stream = this.getClass().getResourceAsStream("/org/apache/lucene/russian/morphology/text.txt");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
|
||||
|
||||
|
||||
final Token reusableToken = new Token();
|
||||
|
||||
Token nextToken;
|
||||
|
||||
|
||||
startTime = System.currentTimeMillis();
|
||||
Integer count = 0;
|
||||
TokenStream in = morphlogyAnalayzer.tokenStream(null, reader);
|
||||
for (; ;) {
|
||||
nextToken = in.next(reusableToken);
|
||||
count++;
|
||||
if (nextToken == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
System.out.println("It takes " + (System.currentTimeMillis() - startTime) + " ms. To proccess " + count + " words." );
|
||||
}
|
||||
}
|
160
src/test/resources/org/apache/lucene/russian/morphology/text.txt
Normal file
160
src/test/resources/org/apache/lucene/russian/morphology/text.txt
Normal file
@ -0,0 +1,160 @@
|
||||
Морфологический анализатор для русского языка — это что-то заумное? Программа, которая приводит слово к начальной форме, определяет падеж, находит словоформы — непонятно, как и подступиться? А на самом деле все не так и сложно. В статье — как я писал аналог mystem, lemmatizer и phpmorphy на Python, и что из этого получилось.
|
||||
|
||||
Сразу скажу, получилась библиотека для морфологического анализа на Python, которую Вы можете использовать и дорабатывать согласно лицензии MIT.
|
||||
|
||||
Первый вопрос — зачем все это?
|
||||
|
||||
Потихоньку делаю один хобби-проект, назрело решение писать его на Python. А для проекта был жизненно необходим более-менее качественный морфологический анализатор. Вариант с mystem не подошел из-за лицензии и отсутствием возможности поковыряться, lemmatizer и phpmorphy меня слегка напрягали своими словарями не в юникоде, а приличного аналога на python я почему-то не нашел. Вообщем причин много, все, если честно, не очень серьезные, на самом деле просто захотелось. Решил я, в итоге, изобрести велосипед и написать свою реализацию.
|
||||
|
||||
За основу взял наработки с сайта aot.ru. Словари (LGPL) для русского и английского, а также идеи — оттуда. Там были описаны и конкретные алгоритмы реализации, но в терминах теории автоматов. Я знаком с теорией автоматов (даже диплом по формальным грамматикам защитил), но мне показалось, что тут можно прекрасно обойтись без нее. Поэтому реализация — независимая, что имеет как плюсы, так и минусы.
|
||||
|
||||
Для начала, библиотека должна, как минимум, уметь:
|
||||
1. Приводить слово к нормальной форме (например, в ед.ч., И.п. для существительных) — для слова «ЛЮДЕЙ» она должна вернуть «ЧЕЛОВЕК»
|
||||
2. Определять форму слова (или формы). Например, для слова «ЛЮДЕЙ» она должна как-то дать знать, что это существительное, во множественном числе, может оказаться в родительном или винительном падежах.
|
||||
|
||||
Разбираемся со словарями
|
||||
|
||||
Словари с сайта aot.ru содержат следующую информацию:
|
||||
1. парадигмы слов и конкретные правила образования;
|
||||
2. ударения;
|
||||
3. пользовательские сессии;
|
||||
4. набор префиксов (продуктивных приставок);
|
||||
5. леммы (незменяемые части слова, основы);
|
||||
и еще есть 6. грамматическая информация — в отдельном файле.
|
||||
|
||||
Из этого всего нам интересны правила образования слов, префиксы, леммы и грамматическая информация.
|
||||
|
||||
Все слова образуются по одному принципу:
|
||||
[префикс]+[приставка]+[основа]+[окончание]
|
||||
|
||||
Префиксы — это всякие «мега», «супер» и т.д. Набор префиксов хранится просто списком.
|
||||
|
||||
Приставки — имеются в виду приставки, присущие грамматической форме, но не присущие неизменяемой части слова («по»,«наи»). Например, «наи» в слове «наикрасивейший», т.к. без превосходной степени будет «красивый».
|
||||
|
||||
Правила образования слов — это то, что надо приписать спереди и сзади основы, чтобы получить какую-то форму. В словаре хранятся пары «приставка — окончание», + «номер» записи о грамматической информации (которая хранится отдельно).
|
||||
|
||||
Правила образования слов объединены в парадигмы. Например, для какого-нибудь класса существительных может быть описано, как слово выглядит во всех падежах и родах. Зная, что существительное принадлежит к этому классу, мы сможем правильно получить любую его форму. Такой класс — это и есть парадигма. Первой в парадигме всегда идет нормальная форма слова (если честно, вывод эмпирический))
|
||||
|
||||
Леммы — это неизменяемые части слов. В словаре хранится информация о том, какой лемме соответствуют какие парадигмы (какой набор правил для образования грамматических форм слова). Одной лемме может соответствовать несколько парадигм.
|
||||
|
||||
Грамматическая информация — просто пары («номер» записи, грам. информация). «Номер» в кавычках, т.к. это 2 буквы, просто от балды, но все разные.
|
||||
|
||||
Файл со словарем — обычный текстовый, для каждого раздела сначала указано число строк в нем, а потом идут строки, формат их описан, так что написать парсер труда не составило.
|
||||
|
||||
Поняв структуру словаря, уже несложно написать первую версию морфологического анализатора.
|
||||
|
||||
Пишем морфологический анализатор
|
||||
|
||||
По сути, нам дано слово, и его надо найти среди всех разумных комбинаций вида
|
||||
<префикс>+<приставка>+<лемма>+<окончание> и
|
||||
<приставка>+<лемма>+<окончание>
|
||||
|
||||
Дело упрощает то, что оказалось (как показала пара строчек на питоне), что «приставок» у нас в языке (да и в английском вроде тоже) всего 2. А префиксов в словаре — порядка 20 для русского языка. Поэтому искать можно среди комбинаций
|
||||
<префикс>+<лемма>+<окончание>, объединив в уме список приставок и префиксов, а затем выполнив небольшую проверочку.
|
||||
|
||||
С префиксом разберемся по-свойски: если слово начинается с одного из возможных префиксов, то мы его (префикс) отбрасываем и пытаемся морфологически анализировать остаток (рекурсивно), а потом просто припишем отброшенный префикс к полученным формам.
|
||||
|
||||
В итоге получается, что задача сводится к поиску среди комбинаций
|
||||
<лемма>+<окончание>, что еще лучше. Ищем подходящие леммы, потом смотрим, есть ли для них подходящие окончания*.
|
||||
|
||||
Тут я решил сильно упростить себе жизнь, и задействовать стандартный питоновский ассоциативный массив, в который поместил все леммы. Получился словарь вида lemmas: {base -> [rule_id]}, т.е. ключ — это лемма, а значение — список номеров допустимых парадигм. А дальше поехали — сначала считаем, что лемма — это первая буква слова, потом, что это 2 первых буквы и т.д. По лемме пытаемся получить список парадигм. Если получили, то в каждой допустимой парадигме пробегаем по всем правилам и смотрим, получится ли наше слово, если правило применить. Получается — добавляем его в список найденных форм.
|
||||
|
||||
*Еще был вариант — составить сразу словарь всех возможных слов вида <лемма>+<окончание>, получалось в итоге где-то миллионов 5 слов, не так и много, но вариант, вообщем, мне не очень понравился.
|
||||
|
||||
Дописываем морфологический анализатор
|
||||
|
||||
По сути — все готово, мы написали морфологический анализатор, за исключением некоторых деталей, которые заняли у меня 90% времени)
|
||||
|
||||
Деталь первая
|
||||
Если вспомнить пример, который был в начале, про «ЛЮДЕЙ» — «ЧЕЛОВЕК», то станет понятно, что есть слова, у которых неизменяемая часть отсутствует. И где тогда искать формы этих слов — непонятно. Пробовал искать среди всех окончаний (точно так же, как и среди лемм), ведь для таких слов и «ЛЮДЕЙ», и «ЧЕЛОВЕКУ» в словаре будут храниться как окончания. Для некоторых слов работало, для некоторых выдавало, кроме правильного результата, еще и кучу мусора. В итоге, после долгих экспериментов выяснилось, что есть в словаре такая хитрая магическая лемма "#", которая и соответствует всем пустым леммам. Ура, задача решена, ищем каждый раз еще и там.
|
||||
|
||||
Деталь вторая
|
||||
Хорошо бы иметь «предсказатель», который смог бы работать и со словами, которых нет в словаре. Это не только неизвестные науке редкие слова, но и просто описки, например.
|
||||
|
||||
Тут есть 2 несложных, но вполне работающих подхода:
|
||||
1. Если слова отличаются только тем, что к одному из них приписано что-то спереди, то, скорее всего, склоняться они будут однаково
|
||||
2. Если 2 слова оканчиваются одинаково, то и склоняться они, скорее всего, будут одинаково.
|
||||
|
||||
Первый подход — это угадывание префикса. Реализуется очень просто: пробуем считать сначала одну первую букву слова префиксом, потом 2 первых буквы и т.д. А то, что осталось, передаем морфологическому анализатору. Ну и делаем это только для не очень длинных префиксов и не очень коротких остатков.
|
||||
|
||||
Второй (предсказание по концу слова) подход чуть сложнее в реализации (так-то сильно сложнее, если нужна хорошая реализация)) и «поумнее» в плане предсказаний.
|
||||
Первая сложность связана с тем, что конец слова может состоять не только из окончания, но и из части леммы. Тут я тоже решил «срезать углы» и задействовал опять ассоциативный массив с предварительно подготовленными всеми возмоными окончаниями слов (до 5 букв). Не так и много их получилось, несколько сот тысяч. Ключ массива — конец слова, значение — список возможных правил. Дальше — все как при поиске подходящей леммы, только у слова берем не начало, а 1, 2, 3, 4, 5-буквенные концы, а вместо лемм у нас теперь новый монстромассив.
|
||||
Вторая сложность — получается много заведомого мусора. Мусор этот отсекается, если учесть, что полученные слова могут быть только существительными, прилагательными, наречиями или глаголами.
|
||||
Даже после этого у нас остается слишком много не-мусорных правил. Для определенности, для каждой части речи оставляем только самое распространенное правило.
|
||||
По идее, если слово не было предсказано как существительное, хорошо бы добавить вариант с неизменяемым существительным в ед.ч. и.п., но это в TODO.
|
||||
|
||||
Идеальный текст для проверки работы предсказателя — конечно же, «Сяпала Калуша по напушке», про то, как она там увазила бутявку и что из этого вышло:
|
||||
|
||||
Сяпала Калуша по напушке и увазила бутявку. И волит:
|
||||
— Калушата, калушаточки! Бутявка!
|
||||
Калушата присяпали и бутявку стрямкали. И подудонились.
|
||||
А Калуша волит:
|
||||
— Оее, оее! Бутявка-то некузявая!
|
||||
Калушата бутявку вычучили.
|
||||
Бутявка вздребезнулась, сопритюкнулась и усяпала с напушки.
|
||||
А Калуша волит:
|
||||
— Бутявок не трямкают. Бутявки дюбые и зюмо-зюмо некузявые. От бутявок дудонятся.
|
||||
А бутявка волит за напушкой:
|
||||
— Калушата подудонились! Калушата подудонились! Зюмо некузявые! Пуськи бятые!
|
||||
|
||||
|
||||
Из текста предсказатель не справился с именем собственным Калуша, с «Калушата»(они стали мужиками «Калуш» и «Калушат»), с междометием «Оее», загадочным «зюмо-зюмо», и вместо «Пуська» опять выдал мужика «Пусек», остальное все, на мой взгляд, определил правильно. Вообщем есть куда стремиться, но уже неплохо.
|
||||
|
||||
О ужас, «хабрахабр» предсказывается тоже никак. А вот «хабрахабра» — уже понимает, что «хабрахабр».
|
||||
|
||||
Тут можно было бы, в принципе, подвести итог, если бы компьютеры работали мгновенно. Но это не так, поэтому есть
|
||||
|
||||
Деталь №3: ресурсы процессора и оперативной памяти
|
||||
Скорость работы первого варианта меня вполне устроила. Получалось, по прикидкам, тыщ до 10 слов в секунду для простых русских слов, около тыщи для навороченных. Для английского — еще быстрее. Но было 2 очевидных (еще до начала разработки) «но», связанных с тем, что все данные грузились в оперативную память (через pickle/cPickle).
|
||||
1. первоначальная загрузка занимала 3-4 секунды
|
||||
2. кушалось порядка 150 мегабайт оперативной памяти с psyco и порядка 100 без ( +удалось немного сократить, когда привел всякие там питоновские set и list к tulpe, где возможно)
|
||||
|
||||
Не долго думая, провел небольшой рефакторинг и вынес все данные в отдельную сущность. А дальше мне на помощь пришла магия Python и duck typing. Вкратце — в алгоритмах использовались данные в виде ассоциативных и простых массивов. И все будет работать без переписывания алгоритмов, если вместо «настоящих» массивов подсунуть что-нибудь, что ведет себя как массив, а конкретнее, для нашей задачи, — поддерживает [] и in. Все) В стандартной поставке питона обнаружились такие «массивные» интерфейсы к нескольким нереляционным базам данных. Эти базы (bsddb, ndbm, gdbm), по сути, и есть ассоциативные массивы на диске. Как раз то, что нужно. Еще там обнаружилась пара высокоуровневых надстроек над этим хозяйством (anydbm и shelve). Остановился на том, что унаследовался от DbfilenameShelf и добавил поддержку ключей в юникоде и ключей-целых чисел. А, еще добавил кеширование результата, которое почему-то есть в Shelf, но убито в DbfilenameShelf.
|
||||
|
||||
Данные по скорости на тестовых текстах (995 слов, русский словарь, macbook):
|
||||
get_pickle_morph('ru', predict_by_prefix = False): 0.214 CPU seconds
|
||||
get_pickle_morph('ru'): 0.262 CPU seconds
|
||||
get_shelve_morph('ru', predict_by_prefix = False): 0.726 CPU seconds
|
||||
get_shelve_morph('ru'): 0.874 CPU seconds
|
||||
|
||||
Памяти вариант shelve, можно сказать, не кушал совсем.
|
||||
|
||||
Варианты shelve похоже, работали, когда словари уже сидели в дисковом кеше. Без этого время может быть и 20 секунд с включенным предсказателем. Еще замечал, что медленнее всего работает предсказание по префиксу: до того, как прикрутил кеширование к своим наследникам от DbfilenameShelf, тормозило это предсказание раз в 5 больше, чем все остальное вместе взятое. А в этих тестах вроде не сильно уже.
|
||||
|
||||
Кстати, пользуясь случаем, хочу спросить, вдруг кто-то знает, как в питоне можно узнать количество занятой текущим процессом памяти. По возможности кроссплатформенно как-нибудь. А то ставил в конец скрипта задержку и мерил по списку процессов.
|
||||
|
||||
Пример использования
|
||||
|
||||
import pymorphy
|
||||
morph = pymorphy.get_shelve_morph('ru')
|
||||
#слова должны быть в юникоде и ЗАГЛАВНЫМИ
|
||||
info = morph.get_graminfo(unicode('Вася').upper())
|
||||
|
||||
|
||||
Так все-таки, зачем?
|
||||
|
||||
В самом начале я уже объяснил, зачем стал писать морфологический анализатор.
|
||||
Теперь время объяснить, почему я стал писать эту статью. Я ее написал в надежде на то, что такая библиотека интересна и полезна, и вместе мы эту библиотеку улучшим. Дело в том, что функционал, который уже реализован, вполне достаточен для моих нужд. Я буду ее поддерживать и исправлять, но не очень активно. Поэтому эта статья — не подробная инструкция по применению, а попытка описать, как все работает.
|
||||
|
||||
В поставке
|
||||
|
||||
pymorphy.py — сама библиотека
|
||||
shelve_addons.py — наследники от shelf, может кому пригодится
|
||||
encode_dicts.py — утилита, которая преобразовывает словари из формата AOT в форматы pymorphy. Без параметров, работает долго, ест метров 200 памяти, запускается 1 раз. Сконвертированные словари не распространяю из-за возможной бинарной несовместимости и большого объема.
|
||||
test.py — юнит-тесты для pymorphy
|
||||
example.py — небольшой пример и тексты с теми 995 словами
|
||||
dicts/src — папка с исходными словарями, скачанными с aot.ru
|
||||
dicts/converted — папка, куда encode_dicts.py будет складывать сконвертированные словари
|
||||
|
||||
Напоследок
|
||||
|
||||
ссылки:
|
||||
www.assembla.com/wiki/show/pymorphy
|
||||
hg.assembla.com/pymorphy
|
||||
trac-hg.assembla.com/pymorphy
|
||||
|
||||
Лицензия — MIT.
|
||||
|
||||
проверял только под Python 2.5.
|
||||
|
||||
Замечания, предложения, вопросы по делу — приветствуются. Если интересно — подключайтесь к разработке.
|
Loading…
x
Reference in New Issue
Block a user