我有一组文章描述,我必须将文本分成句子。第一个实现使用 opennlp 工具 sentdetect 效果很好,但对于我的目的来说太慢了。有没有类似的东西执行得更快并且质量相似或稍差?
注意:我正在处理(大量)简短的编辑德语文本。
我有一组文章描述,我必须将文本分成句子。第一个实现使用 opennlp 工具 sentdetect 效果很好,但对于我的目的来说太慢了。有没有类似的东西执行得更快并且质量相似或稍差?
注意:我正在处理(大量)简短的编辑德语文本。
是的,提及您正在使用德语会有所帮助:)
可以在GATE中找到带有缩写列表的基于正则表达式的句子检测器。它使用位于此处的三个文件。正则表达式非常简单:
//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 个文件的代码可以在这里找到。
我会用可以在网上找到的东西来增强正则表达式,比如这个。
然后我会想到 GATE 列表中所有单词的德语翻译。如果这还不够,我会浏览其中一些缩写列表:1、2,然后自己创建列表。
编辑:
如果性能如此重要,我不会将整个 GATE 用于句子拆分器 - 切换到他们的文档、创建注释、然后将它们解析回来等需要时间和内存。
我认为对您来说最好的方法是从 RegexSentenceSplitter 类(上面的链接)中获取代码并根据您的上下文进行调整。
我认为代码太长,无法粘贴到这里。您应该看到 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()});
}
还要检查否决方法,该方法“检查可能的匹配是否被非拆分匹配否决。如果可能的匹配与否决区域重叠,则可能的匹配被否决。”
希望这可以帮助。
Maybe String.split("\\. |\\? |! ");
does it?
总的来说,我认为 OpenNLP 会比基于规则的分割器(如斯坦福分割器或实现正则表达式来解决任务)更好(在性能方面)。基于规则的分段器必然会错过一些例外情况。例如,德语句子“Ich wurde am 17.Dezember geboren”(我出生于 12 月 17 日)会被许多基于规则的分段器错误地分成 2 个句子,尤其是如果它们是内置的关于英语规则而不是德语。即使您的文本质量非常好,也会出现这样的句子,因为它们构成了语法正确的德语。因此,检查您要使用的分段器是以哪种语言模型为模型的非常重要。
PS:在 OpenNLP、BreakIterator 分段器和斯坦福分段器中,OpenNLP 最适合我。
值得一提的是,Java 标准 API 库提供了依赖于语言环境的功能来检测测试边界。BreakIterator可用于确定句子边界。
还有一种解决方案。不知道与您的解决方案相比性能如何,但肯定是最全面的。您可以使用 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);
}
}
}