Многосрочные именованные объекты в именном реестре

Я использую именный указатель Entity Recognizer 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());           
            }
        }
    }

Однако проблема, которую я нахожу, - это идентификация имен и фамилий. Если распознаватель встречает "Джо Смит", он возвращает "Джо" и "Смит" отдельно. Мне бы очень хотелось, чтобы он вернулся "Джо Смит" в качестве одного термина.

Может ли это быть достигнуто через распознаватель, возможно, через конфигурацию? До сих пор я ничего не нашел в джавадоке.

Спасибо!

Ответ 1

Это потому, что ваш внутренний цикл выполняет итерацию над отдельными токенами (словами) и добавляет их отдельно. Вам нужно изменить вещи, чтобы сразу добавить целые имена.

Один из способов - заменить внутренний цикл for регулярным циклом с циклом while внутри него, который принимает смежные функции non-O одного и того же класса и добавляет их как единый объект. *

Другим способом было бы использовать вызов метода CRFClassifier:

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

который даст вам целые объекты, которые вы можете извлечь форму String, используя substring на исходном входе.

* В моделях, которые мы распространяем, используется простая схема ярлыков IO, где вещи обозначаются как PERSON или LOCATION, а подходящая вещь - просто объединить соседние маркеры с одной и той же меткой. Многие системы NER используют более сложные метки, такие как метки IOB, где такие коды, как B-PERS, указывают, где начинается человеческое лицо. Классы классов и функций CRFClassifier поддерживают такие метки, но они не используются в моделях, которые мы в настоящее время распространяем (начиная с 2012 года).

Ответ 2

Друг метода 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;
}

Ответ 3

У меня была та же проблема, поэтому я тоже посмотрел. Метод, предложенный Кристофером Мэннингем, эффективен, но деликатный момент состоит в том, чтобы знать, как решить, какой тип разделителя подходит. Можно сказать, что должно быть разрешено только пространство, например. "Джон Зорн" → одна сущность. Однако я могу найти форму "Я. Зорн", поэтому я также должен допускать определенные знаки препинания. Но как насчет "Джека, Джеймса и Джо"? Я мог бы получить 2 сущности вместо 3 ( "Джек Джеймс" и "Джо" ).

Копаясь немного в классах Стэнфордского НЭР, я действительно нашел правильную реализацию этой идеи. Они используют его для экспорта объектов в виде отдельных объектов 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, которое представляет строку, отделяющую текущее слово от предыдущего. Эта последняя точка позволяет решить возникшую ранее проблему, касающуюся выбора соответствующего разделителя.

Ответ 4

List<List<CoreLabel>> out = classifier.classify(text);
for (List<CoreLabel> sentence : out) {
    String s = "";
    String prevLabel = null;
    for (CoreLabel word : sentence) {
      if(prevLabel == null  || prevLabel.equals(word.get(CoreAnnotations.AnswerAnnotation.class)) ) {
         s = s + " " + word;
         prevLabel = word.get(CoreAnnotations.AnswerAnnotation.class);
      }
      else {
        if(!prevLabel.equals("O"))
           System.out.println(s.trim() + '/' + prevLabel + ' ');
        s = " " + word;
        prevLabel = word.get(CoreAnnotations.AnswerAnnotation.class);
      }
    }
    if(!prevLabel.equals("O"))
        System.out.println(s + '/' + prevLabel + ' ');
}

Я написал небольшую логику, и она отлично работает. то, что я сделал, это групповые слова с одинаковой меткой, если они смежны.

Ответ 5

Код для вышесказанного:

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

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

Ответ 6

Используйте уже предоставленные вам классификаторы. Я считаю, что это то, что вы ищете:

    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);
}

Ответ 7

Вот мой полный код, я использую NLP для ядра Stanford и алгоритм записи для объединения имен Multi Term.

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;
}
}

Ответ 8

Другой подход к работе с многословными сущностями. Этот код объединяет несколько токенов вместе, если они имеют одну и ту же аннотацию и идут в строке.

Ограничение:
Если тот же токен имеет две разные аннотации, последний будет сохранен.

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;