我有一套文章描述,我必须将文本分成句子。第一个实现使用opennlp工具sentdetect,它工作得非常好,但对我来说太慢了。 是否有类似的东西表现得更快,并且质量相似或略差的结果?
注意:我正在处理(大量的)简短的德语文本。
答案 0 :(得分:7)
是的,这有助于提及你正在与德语合作:)
可以在GATE中找到带有缩写列表的基于正则表达式的句子检测器。它使用位于here的三个文件。正则表达式非常简单:
//more than 2 new lines
(?:[\u00A0\u2007\u202F\p{javaWhitespace}&&[^\n\r]])*(\n\r|\r\n|\n|\r)(?:(?:[\u00A0\u2007\u202F\p{javaWhitespace}&&[^\n\r]])*\1)+
//between 1 and 3 full stops
\.{1,3}"?
//up to 4 ! or ? in sequence
(!|\?){1,4}"?
可以找到使用这3个文件的代码here。
我会使用可在网络上找到的内容来增强正则表达式,例如this one。
然后我会想到GATE列表中所有单词的德语翻译。如果这还不够,我会浏览一些缩写列表:1,2,并自行创建列表。
编辑:
如果性能如此重要,我不会将整个GATE用于句子分割器 - 切换到文档,创建注释,然后再解析它们需要时间和内存等。
我认为最好的方法是从RegexSentenceSplitter类(link above)获取代码并根据您的上下文进行调整。
我认为代码太长而无法在此处粘贴。你应该看到execute()方法。通常,它会查找内部,外部和阻塞正则表达式的所有匹配项,然后迭代并仅使用那些不与任何阻塞重叠的内部和外部匹配。
以下是您应该查看/重用的一些片段:
如何解析文件
// for each line
if(patternString.length() > 0) patternString.append("|");
patternString.append("(?:" + line + ")");
//...
return Pattern.compile(patternString.toString());
在execute方法中,如何填充阻塞分割:
Matcher nonSplitMatcher = nonSplitsPattern.matcher(docText);
//store all non split locations in a list of pairs
List<int[]> nonSplits = new LinkedList<int[]>();
while(nonSplitMatcher.find()){
nonSplits.add(new int[]{nonSplitMatcher.start(), nonSplitMatcher.end()});
}
同时检查否决方法“检查可能的匹配是否被非分割匹配否决。如果与否决区域有任何重叠,则可能的匹配被否决。”
希望这有帮助。
答案 1 :(得分:2)
也许String.split("\\. |\\? |! ");
可以吗?
答案 2 :(得分:2)
总的来说,我认为OpenNLP比基于规则的分段器(如Stanford分段器)或实现正则表达式来解决任务更好(性能方面)。基于规则的分段器必然会遗漏一些例外情况。例如,德语句子“Ich wurde am 17. Dezember geboren”(我出生于12月17日)将被错误地分成17个句子后的许多基于规则的分段符号,特别是如果它们被构建英语规则,而不是德语。即使您的文本质量非常好,因为它们构成语法正确的德语,这样的句子也会出现。因此,非常重要的是要检查您想要使用的分段器的语言模型。
PS:在OpenNLP,BreakIterator分段器和Stanford分段器中,OpenNLP最适合我。答案 3 :(得分:1)
值得一提的是,Java标准API库为detecting test boundaries提供了与语言环境相关的功能。 BreakIterator可用于确定句子边界。
答案 4 :(得分:0)
还有一个解决方案。不知道与您的解决方案相比性能如何,但肯定是最全面的。您可以使用ICU4J库和srx文件。你可以在这里下载的图书馆http://site.icu-project.org/download/52#TOC-ICU4J-Download。就像它的多语言魅力一样。
package srx;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import net.sf.okapi.common.ISegmenter;
import net.sf.okapi.common.LocaleId;
import net.sf.okapi.common.Range;
import net.sf.okapi.lib.segmentation.LanguageMap;
import net.sf.okapi.lib.segmentation.Rule;
import net.sf.okapi.lib.segmentation.SRXDocument;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
if(args.length != 2) return;
SRXDocument doc = new SRXDocument();
String srxRulesFilePath = args[0];
String text = args[1];
doc.loadRules(srxRulesFilePath);
LinkedHashMap<String, ArrayList<Rule>> rules = doc.getAllLanguageRules();
ArrayList<LanguageMap> languages = doc.getAllLanguagesMaps();
ArrayList<Rule> plRules = doc.getLanguageRules(languages.get(0).getRuleName());
LocaleId locale = LocaleId.fromString("pl_PL");
ISegmenter segmenter = doc.compileLanguageRules(LocaleId.fromString("pl_PL"), null);
segmenter.computeSegments(text);
List<Range> ranges = segmenter.getRanges();
System.out.println(ranges.size());
for (Range range : ranges) {
System.out.println(range.start);
System.out.println(range.end);
}
}
}