我该如何使用斯坦福解析器将文本分成句子?

28

http://nlp.stanford.edu/software/ - Brian Roach
1
我已经下载了解析器包并在其上运行了一个简单的程序,我想了解一些有关使用解析器从文本中提取句子的想法,是否有任何方法可以用来从文本中提取句子.. - S Gaber
12个回答

31
你可以查看DocumentPreprocessor类。以下是一个简短的代码片段。我认��可能有其他方法可以实现你想要的功能。
String paragraph = "My 1st sentence. “Does it work for questions?” My third sentence.";
Reader reader = new StringReader(paragraph);
DocumentPreprocessor dp = new DocumentPreprocessor(reader);
List<String> sentenceList = new ArrayList<String>();

for (List<HasWord> sentence : dp) {
   // SentenceUtils not Sentence
   String sentenceString = SentenceUtils.listToString(sentence);
   sentenceList.add(sentenceString);
}

for (String sentence : sentenceList) {
   System.out.println(sentence);
}

1
将这段代码转换为文字。我想要的是将段落分成句子。 - S Gaber
1
我只是一个初学者,如果您不介意的话,能否提供一个关于这个问题的简单示例呢? - S Gaber
1
不用谢,但是请尝试一个带有括号和引号的句子。我认为这是分词过程的一部分,它会用一些符号代替它们。 - Kenston Choi
1
这会在标记之间放置空白。例如,在每个句点之前,这并不太好。 - user1893354
9
我使用增强型for循环简化了代码,并利用Sentence类中的便捷方法,将标记列表转换回字符串。 - Christopher Manning
显示剩余5条评论

24

我知道已经有一个被接受的答案了...但通常你只需要从一个带注释的文档中获取SentenceAnnotations。

// creates a StanfordCoreNLP object, with POS tagging, lemmatization, NER, parsing, and coreference resolution 
Properties props = new Properties();
props.put("annotators", "tokenize, ssplit, pos, lemma, ner, parse, dcoref");
StanfordCoreNLP pipeline = new StanfordCoreNLP(props);

// read some text in the text variable
String text = ... // Add your text here!

// create an empty Annotation just with the given text
Annotation document = new Annotation(text);

// run all Annotators on this text
pipeline.annotate(document);

// these are all the sentences in this document
// a CoreMap is essentially a Map that uses class objects as keys and has values with custom types
List<CoreMap> sentences = document.get(SentencesAnnotation.class);

for(CoreMap sentence: sentences) {
  // traversing the words in the current sentence
  // a CoreLabel is a CoreMap with additional token-specific methods
  for (CoreLabel token: sentence.get(TokensAnnotation.class)) {
    // this is the text of the token
    String word = token.get(TextAnnotation.class);
    // this is the POS tag of the token
    String pos = token.get(PartOfSpeechAnnotation.class);
    // this is the NER label of the token
    String ne = token.get(NamedEntityTagAnnotation.class);       
  }

}

来源-http://nlp.stanford.edu/software/corenlp.shtml(在页面中间位置)

如果您只需要查找句子,可以从管道初始化中删除后续步骤,例如“解析”和“dcoref”,这将为您节省一些负载和处理时间。摇滚并且快速执行。
~K


17

接受的答案存在几个问题。首先,分词器会转换一些字符,例如字符“”被转换为两个字符``。其次,使用空格将标记化的文本拼接在一起并不能返回与之前相同的结果。因此,来自接受答案的示例文本以非微不足道的方式转换了输入文本。

然而,分词器使用的CoreLabel类会跟踪它们映射到的源字符,如果您有原始字符,则重新构建正确的字符串很容易。

下面的方法1显示了接受的答案方法,方法2显示了我的方法,可以克服这些问题。

String paragraph = "My 1st sentence. “Does it work for questions?” My third sentence.";

List<String> sentenceList;

/* ** APPROACH 1 (BAD!) ** */
Reader reader = new StringReader(paragraph);
DocumentPreprocessor dp = new DocumentPreprocessor(reader);
sentenceList = new ArrayList<String>();
for (List<HasWord> sentence : dp) {
    sentenceList.add(Sentence.listToString(sentence));
}
System.out.println(StringUtils.join(sentenceList, " _ "));

/* ** APPROACH 2 ** */
//// Tokenize
List<CoreLabel> tokens = new ArrayList<CoreLabel>();
PTBTokenizer<CoreLabel> tokenizer = new PTBTokenizer<CoreLabel>(new StringReader(paragraph), new CoreLabelTokenFactory(), "");
while (tokenizer.hasNext()) {
    tokens.add(tokenizer.next());
}
//// Split sentences from tokens
List<List<CoreLabel>> sentences = new WordToSentenceProcessor<CoreLabel>().process(tokens);
//// Join back together
int end;
int start = 0;
sentenceList = new ArrayList<String>();
for (List<CoreLabel> sentence: sentences) {
    end = sentence.get(sentence.size()-1).endPosition();
    sentenceList.add(paragraph.substring(start, end).trim());
    start = end;
}
System.out.println(StringUtils.join(sentenceList, " _ "));

这将输出:

My 1st sentence . _ `` Does it work for questions ? '' _ My third sentence .
My 1st sentence. _ “Does it work for questions?” _ My third sentence.

谢谢,这正是我在寻找的。但愿有一种快速的方法可以从Stanford CoreNLP服务器获取它或通过命令行进行简单的类调用。我不想为一个用例创建整个Java项目。 你有什么想法如何修改它以接受文本文件作为输入(而不是将其加载为字符串)? - Sudhi
实际上,似乎Sentence类有一个名为Sentence.listToOriginalTextString的方法,该方法接受您代码中的List<CoreLabel> sentence变量。它还提到PTBT分词器需要使用"invertible=true"选项运行。 - Sudhi
@Sudhi,现在有一个listToOriginalTextString方法,但它的操作方式与Dan在被接受的答案中使用的listToString方法类似。如果您有原始文本(可能没有),我的方法不需要-invertible=true标志,并且效率更高(O(2) vs O(n))。 - dantiston
@dantiston 感谢您提供这两种方法;非常有帮助,可以看到文本如何进行句子拆分。然而,我认为根据下游操作的不同,方法#1实际上是最理想的。这是非常重要的句子级归一化,当人们想要将混乱的数据与原始报纸写作相同对待时,这非常有用。但是,如果您需要引用#1生成的句子,请准备好痛苦,而使用#2则非常简单 :) - dmn
1
@dmn 我同意。在大多数实际场景中,CoreNLP在进行标记化后进行句子拆分的决定并不是不切实际的,因为您通常希望获得标记,并且句子边界要么没有意义,要么提供了不同的信息层次。但是,当您只想获取句子边界时,处理起来确实很麻烦。 - dantiston
显示剩余3条评论

9
使用 .net C# 包: 这将分割句子,正确获取括号,并保留原始的空格和标点符号:
public class NlpDemo
{
    public static readonly TokenizerFactory TokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(),
                "normalizeParentheses=false,normalizeOtherBrackets=false,invertible=true");

    public void ParseFile(string fileName)
    {
        using (var stream = File.OpenRead(fileName))
        {
            SplitSentences(stream);
        }
    }

    public void SplitSentences(Stream stream)
    {            
        var preProcessor = new DocumentPreprocessor(new UTF8Reader(new InputStreamWrapper(stream)));
        preProcessor.setTokenizerFactory(TokenizerFactory);

        foreach (java.util.List sentence in preProcessor)
        {
            ProcessSentence(sentence);
        }            
    }

    // print the sentence with original spaces and punctuation.
    public void ProcessSentence(java.util.List sentence)
    {
        System.Console.WriteLine(edu.stanford.nlp.util.StringUtils.joinWithOriginalWhiteSpace(sentence));
    }
}

输入: - 这句话的字符具有一定的魅力,通常可以在标点和散文中找到。这是第二个句子吗?确实是。

输出: 3个句子('?'被视为句子结束符)

注意:对于像“哈维舍夫人的班级在所有方面都无可挑剔(就我所知!)”这样的句子,分词器将正确地辨别出Mrs.结尾处的句号不是EOS,但是它会错误地将括号内的感叹号!视为EOS,并将“在所有方面。”分成第二个句子。


2

使用由Stanford CoreNLP版本3.6.0或3.7.0提供的简单API

以下是3.6.0版本的示例。在3.7.0版本中完全相同。

Java代码片段

import java.util.List;

import edu.stanford.nlp.simple.Document;
import edu.stanford.nlp.simple.Sentence;
public class TestSplitSentences {
    public static void main(String[] args) {
        Document doc = new Document("The text paragraph. Another sentence. Yet another sentence.");
        List<Sentence> sentences = doc.sentences();
        sentences.stream().forEach(System.out::println);
    }
}

输出:

文本段落。

另外一个句子。

还有另一个句子。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>stanfordcorenlp</groupId>
    <artifactId>stanfordcorenlp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/edu.stanford.nlp/stanford-corenlp -->
        <dependency>
            <groupId>edu.stanford.nlp</groupId>
            <artifactId>stanford-corenlp</artifactId>
            <version>3.6.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>2.6.1</version>
        </dependency>
    </dependencies>
</project>

你在开玩笑吗?抱歉,但我必须复制这个来看看它是否真的。 - moldovean
是的,不幸的是,它不起作用 :) 我导入了Maven版本3.6.0。 - moldovean

1
您可以使用文档预处理器。它非常容易使用,只需提供一个文件名即可。
    for (List<HasWord> sentence : new DocumentPreprocessor(pathto/filename.txt)) {
         //sentence is a list of words in a sentence
    }

1
你可以很容易地使用Stanford标记器来完成这个任务。
String text = new String("Your text....");  //Your own text.
List<List<HasWord>> tokenizedSentences = MaxentTagger.tokenizeText(new StringReader(text));

for(List<CoreLabel> act : tokenizedSentences)       //Travel trough sentences
{
    System.out.println(edu.stanford.nlp.ling.Sentence.listToString(act)); //This is your sentence
}

0
一个解决问题的@Kevin答案的变化如下:
for(CoreMap sentence: sentences) {
      String sentenceText = sentence.get(TextAnnotation.class)
}

这将为您提供句子信息,而不会打扰其他注释器。


0
在下面的代码中添加输入和输出文件的路径:
import java.util.*;
import edu.stanford.nlp.pipeline.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class NLPExample
{
    public static void main(String[] args) throws IOException 
    {
        PrintWriter out;
        out = new PrintWriter("C:\\Users\\ACER\\Downloads\\stanford-corenlp-full-     
        2018-02-27\\output.txt");
        Properties props=new Properties();
        props.setProperty("annotators","tokenize, ssplit, pos,lemma");
        StanfordCoreNLP pipeline = new StanfordCoreNLP(props);
        Annotation annotation;  
        String readString = null;
        PrintWriter pw = null;
        BufferedReader br = null;
        br = new BufferedReader (new 
        FileReader("C:\\Users\\ACER\\Downloads\\stanford- 
        corenlp-full-2018-02-27\\input.txt" )  ) ;
        pw = new PrintWriter ( new BufferedWriter ( new FileWriter ( 
        "C:\\Users\\ACER\\Downloads\\stanford-corenlp-full-2018-02-   
        27\\output.txt",false 
        ))) ;      
        String x = null;
        while  (( readString = br.readLine ())  != null)
        {
            pw.println ( readString ) ; String 
            xx=readString;x=xx;//System.out.println("OKKKKK"); 
            annotation = new Annotation(x);
            pipeline.annotate(annotation);    //System.out.println("LamoohAKA");
            pipeline.prettyPrint(annotation, out);
        }
        br.close (  ) ;
        pw.close (  ) ;
        System.out.println("Done...");
    }    
}

0

另一个元素,除了一些被踩的答案之外,没有得到解决,那就是如何设置句子分隔符?最常见的方式,也是默认方式,是依赖于标点符号来表示句子的结尾。还有其他文档格式,可能会从收集的语料库中面临,其中之一是每行都是一个句子。

要像接受的答案一样为DocumentPreprocessor设置分隔符,您可以使用setSentenceDelimiter(String)。要使用@Kevin提出的管道方法,可以使用ssplit属性。例如,要使用上一段落提出的行末方案,可以将属性ssplit.eolonly设置为true


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接