斯坦福命名实体识别器中的多项命名实体

时间:2012-12-07 14:45:43

标签: nlp stanford-nlp named-entity-recognition

我正在使用Stanford命名实体识别器http://nlp.stanford.edu/software/CRF-NER.shtml,它运行正常。这是

    List<List<CoreLabel>> out = classifier.classify(text);
    for (List<CoreLabel> sentence : out) {
        for (CoreLabel word : sentence) {
            if (!StringUtils.equals(word.get(AnswerAnnotation.class), "O")) {
                namedEntities.add(word.word().trim());           
            }
        }
    }

然而,我发现的问题是识别名字和姓氏。如果识别器遇到“Joe Smith”,它将分别返回“Joe”和“Smith”。我真的希望将“乔·史密斯”作为一个词来回归。

这可以通过识别器通过配置来实现吗?到目前为止,我在javadoc中找不到任何东西。

谢谢!

8 个答案:

答案 0 :(得分:20)

这是因为你的内部for循环遍历单个标记(单词)并单独添加它们。您需要更改内容以立即添加全名。

一种方法是用一个常规for循环替换内部for循环,其中包含一个while循环,它接受相同类的相邻非O事物并将它们作为单个实体添加。*

另一种方法是使用CRFClassifier方法调用:

List<Triple<String,Integer,Integer>> classifyToCharacterOffsets(String sentences)

将为您提供整个实体,您可以在原始输入上使用substring提取字符串形式。

*我们分发的模型使用简单的原始IO标签方案,其中标记为PERSON或LOCATION,并且适当的做法是简单地合并具有相同标签的相邻标记。许多NER系统使用更复杂的标签,例如IOB标签,其中像B-PERS这样的代码表示人员实体的起始位置。 CRFClassifier类和功能工厂支持此类标签,但它们并未在我们当前分发的模型中使用(截至2012年)。

答案 1 :(得分:5)

classifyToCharacterOffsets方法的对应部分是(AFAIK)您无法访问实体的标签。

正如克里斯托弗所提出的,这里是一个循环的例子,它汇集了“相邻的非O事物”。此示例还计算出现次数。

public HashMap<String, HashMap<String, Integer>> extractEntities(String text){

    HashMap<String, HashMap<String, Integer>> entities =
            new HashMap<String, HashMap<String, Integer>>();

    for (List<CoreLabel> lcl : classifier.classify(text)) {

        Iterator<CoreLabel> iterator = lcl.iterator();

        if (!iterator.hasNext())
            continue;

        CoreLabel cl = iterator.next();

        while (iterator.hasNext()) {
            String answer =
                    cl.getString(CoreAnnotations.AnswerAnnotation.class);

            if (answer.equals("O")) {
                cl = iterator.next();
                continue;
            }

            if (!entities.containsKey(answer))
                entities.put(answer, new HashMap<String, Integer>());

            String value = cl.getString(CoreAnnotations.ValueAnnotation.class);

            while (iterator.hasNext()) {
                cl = iterator.next();
                if (answer.equals(
                        cl.getString(CoreAnnotations.AnswerAnnotation.class)))
                    value = value + " " +
                           cl.getString(CoreAnnotations.ValueAnnotation.class);
                else {
                    if (!entities.get(answer).containsKey(value))
                        entities.get(answer).put(value, 0);

                    entities.get(answer).put(value,
                            entities.get(answer).get(value) + 1);

                    break;
                }
            }

            if (!iterator.hasNext())
                break;
        }
    }

    return entities;
}

答案 2 :(得分:3)

我遇到了同样的问题,所以我也查了一下。克里斯托弗曼宁提出的方法是有效的,但最重要的是要知道如何确定哪种分离器是合适的。可以说只允许一个空间,例如“John Zorn”&gt;&gt;一个实体。但是,我可能会找到“J.Zorn”的形式,所以我也应该允许某些标点符号。但是“杰克,詹姆斯和乔”呢?我可能得到2个实体而不是3个(“Jack James”和“Joe”)。

通过在斯坦福NER课程中挖掘一点,我实际上找到了这个想法的正确实现。他们使用它以单String个对象的形式导出实体。例如,在方法PlainTextDocumentReaderAndWriter.printAnswersTokenizedInlineXML中,我们有:

 
 private void printAnswersInlineXML(List<IN> doc, PrintWriter out) {
    final String background = flags.backgroundSymbol;
    String prevTag = background;
    for (Iterator<IN> wordIter = doc.iterator(); wordIter.hasNext();) {
      IN wi = wordIter.next();
      String tag = StringUtils.getNotNullString(wi.get(AnswerAnnotation.class));

      String before = StringUtils.getNotNullString(wi.get(BeforeAnnotation.class));

      String current = StringUtils.getNotNullString(wi.get(CoreAnnotations.OriginalTextAnnotation.class));
      if (!tag.equals(prevTag)) {
        if (!prevTag.equals(background) && !tag.equals(background)) {
          out.print("</");
          out.print(prevTag);
          out.print('>');
          out.print(before);
          out.print('<');
          out.print(tag);
          out.print('>');
        } else if (!prevTag.equals(background)) {
          out.print("</");
          out.print(prevTag);
          out.print('>');
          out.print(before);
        } else if (!tag.equals(background)) {
          out.print(before);
          out.print('<');
          out.print(tag);
          out.print('>');
        }
      } else {
        out.print(before);
      }
      out.print(current);
      String afterWS = StringUtils.getNotNullString(wi.get(AfterAnnotation.class));

      if (!tag.equals(background) && !wordIter.hasNext()) {
        out.print("</");
        out.print(tag);
        out.print('>');
        prevTag = background;
      } else {
        prevTag = tag;
      }
      out.print(afterWS);
    }
  }

他们迭代每个单词,检查它是否具有与前一个相同的类(答案),如前所述。为此,他们利用被认为不是实体的事实表达式使用所谓的backgroundSymbol(类“O”)标记。它们还使用属性BeforeAnnotation,它表示将当前单词与前一单词分隔开的字符串。关于选择合适的分隔符,最后一点可以解决我最初提出的问题。

答案 3 :(得分:2)

以上代码:

<List> result = classifier.classifyToCharacterOffsets(text);

for (Triple<String, Integer, Integer> triple : result)
{
    System.out.println(triple.first + " : " + text.substring(triple.second, triple.third));
}

答案 4 :(得分:2)

www.example.com/drupal

我刚写了一个小逻辑,它工作得很好。如果它们相邻,我所做的就是将具有相同标签的单词组合在一起。

答案 5 :(得分:1)

利用已经提供给您的分类器。我相信这就是你要找的东西:

    private static String combineNERSequence(String text) {

    String serializedClassifier = "edu/stanford/nlp/models/ner/english.all.3class.distsim.crf.ser.gz";      
    AbstractSequenceClassifier<CoreLabel> classifier = null;
    try {
        classifier = CRFClassifier
                .getClassifier(serializedClassifier);
    } catch (ClassCastException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    System.out.println(classifier.classifyWithInlineXML(text));

    //  FOR TSV FORMAT  //
    //System.out.print(classifier.classifyToString(text, "tsv", false));

    return classifier.classifyWithInlineXML(text);
}

答案 6 :(得分:0)

这是我的完整代码,我使用Stanford核心NLP和编写算法来连接多项名称。

import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.Annotation;
import edu.stanford.nlp.pipeline.StanfordCoreNLP;
import edu.stanford.nlp.util.CoreMap;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Created by Chanuka on 8/28/14 AD.
 */
public class FindNameEntityTypeExecutor {

private static Logger logger = Logger.getLogger(FindNameEntityTypeExecutor.class);

private StanfordCoreNLP pipeline;

public FindNameEntityTypeExecutor() {
    logger.info("Initializing Annotator pipeline ...");

    Properties props = new Properties();

    props.setProperty("annotators", "tokenize, ssplit, pos, lemma, ner");

    pipeline = new StanfordCoreNLP(props);

    logger.info("Annotator pipeline initialized");
}

List<String> findNameEntityType(String text, String entity) {
    logger.info("Finding entity type matches in the " + text + " for entity type, " + entity);

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

    // run all Annotators on this text
    pipeline.annotate(document);
    List<CoreMap> sentences = document.get(CoreAnnotations.SentencesAnnotation.class);
    List<String> matches = new ArrayList<String>();

    for (CoreMap sentence : sentences) {

        int previousCount = 0;
        int count = 0;
        // traversing the words in the current sentence
        // a CoreLabel is a CoreMap with additional token-specific methods

        for (CoreLabel token : sentence.get(CoreAnnotations.TokensAnnotation.class)) {
            String word = token.get(CoreAnnotations.TextAnnotation.class);

            int previousWordIndex;
            if (entity.equals(token.get(CoreAnnotations.NamedEntityTagAnnotation.class))) {
                count++;
                if (previousCount != 0 && (previousCount + 1) == count) {
                    previousWordIndex = matches.size() - 1;
                    String previousWord = matches.get(previousWordIndex);
                    matches.remove(previousWordIndex);
                    previousWord = previousWord.concat(" " + word);
                    matches.add(previousWordIndex, previousWord);

                } else {
                    matches.add(word);
                }
                previousCount = count;
            }
            else
            {
                count=0;
                previousCount=0;
            }


        }

    }
    return matches;
}
}

答案 7 :(得分:0)

处理多字实体的另一种方法。 如果这些代码具有相同的注释并且连续存在,则此代码将多个令牌组合在一起。

限制:
如果相同的令牌有两个不同的注释,则最后一个将被保存。

private Document getEntities(String fullText) {

    Document entitiesList = new Document();
    NERClassifierCombiner nerCombClassifier = loadNERClassifiers();

    if (nerCombClassifier != null) {

        List<List<CoreLabel>> results = nerCombClassifier.classify(fullText);

        for (List<CoreLabel> coreLabels : results) {

            String prevLabel = null;
            String prevToken = null;

            for (CoreLabel coreLabel : coreLabels) {

                String word = coreLabel.word();
                String annotation = coreLabel.get(CoreAnnotations.AnswerAnnotation.class);

                if (!"O".equals(annotation)) {

                    if (prevLabel == null) {
                        prevLabel = annotation;
                        prevToken = word;
                    } else {

                        if (prevLabel.equals(annotation)) {
                            prevToken += " " + word;
                        } else {
                            prevLabel = annotation;
                            prevToken = word;
                        }
                    }
                } else {

                    if (prevLabel != null) {
                        entitiesList.put(prevToken, prevLabel);
                        prevLabel = null;
                    }
                }
            }
        }
    }

    return entitiesList;
}

进口:

Document: org.bson.Document;
NERClassifierCombiner: edu.stanford.nlp.ie.NERClassifierCombiner;