Java
June 2018 15

Извлечение сущностей из текста с помощью Stanford NLP с нуля

From Sandbox
Данная статья предназначена для тех, кто никогда не работал со Stanford nlp и столкнулся с необходимостью в кратчайшие сроки изучить его и применить.

Данный софт достаточно распространен, и, в частности, наша фирма — БалтИнфоКом — использует эту программу.

Для начала надо понять простую вещь: Stanford NLP работает по принципу аннотирования слов, то есть на каждое слово «навешиваются» одна или более аннотаций, например POS (Part of Speech — часть речи), NER (Named-Entity Recognizing – именованная сущность) и т.д.

Первое, что видит новичок, зайдя на сайт Stanford NLP в раздел "быстрый старт", это следующую конструкцию:

Properties props = new Properties();
props.setProperty("annotators", "tokenize,ssplit,pos,lemma,ner,regexner,parse,depparse,coref");
StanfordCoreNLP pipeline = new StanfordCoreNLP(props);
// create a document object
CoreDocument document = new CoreDocument(text);
// annnotate the document
pipeline.annotate(document);

Здесь StanfordCoreNLP – это конвейер, на вход которому подается наш текст, предварительно упакованный в обьект CoreDocument. StanfordCoreNLP, это самый главный и часто используемый объект во всей структуре, с помощью которого происходит вся основная работа.

Сначала задаем параметры в StanfordCoreNLP и указываем, осуществление каких действий нам нужно. При этом все возможные комбинации этих параметров можно найти на официальном сайте по этой ссылке.

  • tokenize – соответственно разбиение на токены
  • ssplit – разбиение на предложение
  • pos — определение части речи
  • lemma – добавление к каждому слову его начальной формы
  • ner — определение именованных сущностей, таких как «Организация», «Лицо» и т.д.
  • regexner — определение именованных сущностей посредством регулярных выражений
  • parse — разбор каждого слова по семантике (род, число и так далее)
  • depparse — разбор синтаксических зависимостей между словами в предложении
  • coref- поиск упоминания одной и той же именованной сущности в тексте, например «Мария» и «она»

Вот пример того, как аннотаторы (parse и depparse) работают вместе:

image

Если Вам непонятны аннотации над токенами, то на этих сайтах вы найдете их значения: значения связей в предложениях, значения частей речи.

По каждому из этих параметров можно найти дополнительные флаги для более тонкой настройки здесь в разделе «Аnnоtаtоrs».

Данные конструкции задаются, если Вы хотите использовать встроенные модели Stanford NLP, но Вы также можете их задать вручную с помощью метода addAnnotator(Annotator…) или через пополнение параметров перед созданием объекта StanfordCoreNLP.

Теперь о том, какими способами можно извлечь именованные сущности из текста. Для этого в Stanford NLP есть три встроенных класса основанных на регулярных выражениях и один класс, предназначенный для разметки токенов через модель.

Классы, основанные на регулярных выражениях:

  • TokensRegexAnnotator – аннотатор, работающий по правилам – SequenceMatchRules.
    Рассмотрим пример маппинга для него, построенного на данных правилах.

    ner = { type: "CLASS", value:
    "edu.stanford.nlp.ling.CoreAnnotations$NamedEntityTagAnnotation" }
    
    $EMAIL = "/.*([A-z0-9А-я]+?)(@)([A-z0-9А-я]+?).*/"
    
    {
    ruleType: "tokens",
    pattern: (([]) ($EMAIL)),
    action: (Annotate($0, ner, "MAIL")),
    priority:0
    } 

    В первой строке указываем, какой тип тегов мы будем заполнять в данном шаблоне.
    Во второй – создаем переменную, которая по правилам должна начинаться с символа «$» и быть вначале строки.

    После этого создаем блок, в котором задаем тип правил. Затем шаблон для сравнения (в нашем случае мы говорим, что нам нужен «[]» — любой токен, после которого идет наша переменная «$EMAIL». После этого задаем действие, в нашем случае мы хотим проаннотировать токен.

    Обратите внимание, в примере специально «[]» и «$EMAIL» заключены в круглые скобки, потому что $0 указывает, какую группу захвата мы хотим выделить из найденного шаблона, при этом под группой захвата подразумевается группа, заключенная в круглые скобки. Если задать 0, то во фразе «почта sobaka@mail.ru» все токены будут проаннотированы как «MAIL». Если задать 1(то есть первая группа захвата), то только слово «почта» будет проаннотировано; если 2, то только «sobaka@mail.ru».

    Для ситуаций, когда по двум правилам один и тот же токен может быть определен по-разному, можно выставить приоритет правила относительно другого. Например, в случае следующей фразы — «House 25 $», могут быть два противоречащих правила, по одному из которых число 25 будет определено как номер дома, а по второму – как его стоимость.
  • RegexNERAnnotator – этот аннотатор работает, используя классификатор RegexNERSequenceClassifier.

    Маппинг для него выглядит следующим образом

    regex1	TYPE	overwritableType1,Type2...	priority

    Здесь regex1 – регулярное выражение в формате TokenSequencePattern.

    TYPE – имя именованной сущности.
    overwritableType1,Type2… – типы, которые мы можем заменить, в случаях спорной ситуации.
    Priority – приоритет для спорных ситуаций, описанных выше.
    Обратите внимание, что в данном маппинге все колонки должны быть разделены табом.
  • TokensRegexNERAnnotator
    Данный аннотатор отличается от предыдущего тем, что использует библиотеку TokensRegex для регулярных выражений, ту же, что и первый аннотатор, что позволяет использовать более гибкие правила для сопоставления; а также тем, что может записывать значения тегов, отличных от тега NER.
    Маппинг для него составляется по правилам RegexNERAnnotator

Разметка текста через модель с помощью NERClassifierCombiner
Для того, чтобы воспользоваться этим классом, надо сначала иметь или обучить свою модель.

О том, как это сделать можно найти здесь;
После того, как Вы обучили модель, остается только создать NERClassifierCombiner, указав в нем путь к модели, и вызвать метод classify.

NERClassifierCombiner classifier = new NERClassifierCombiner(false, false, serialized_model);
String text = "Some lucky people working in BaltInfoCom Org.";
List<List<CoreLabel>> out = classifier.classify(text);

Полный список аннотаторов можно посмотреть здесь.

В дополнение к вышесказанному, если Вам требуется использовать Stanford NLP для русского языка, то могу посоветовать зайти сюда. Здесь есть модели для определения частей речи (pos-tagger) и для выявления связей в предложении (dependency parser).

Виды тэггеров, представленных там:
russian-ud-pos.tagger — просто тэггер,
russian-ud-mfmini.tagger — с основным списком morphological features,
russian-ud-mf.tagger — с полным списком morphological features, пример маппинга для которого можно посмотреть здесь.
+8
4.4k 37
Comments 6
Top of the day