lepeap / DeepMorphy

Морфологический анализатор для русского языка на C# для .NET
MIT License
48 stars 8 forks source link
csharp deep-learning morphology net net-standard nlp russian

DeepMorphy

Nuget

DeepMorphy is a neural network based morphological analyzer for Russian language.


DeepMorphy - морфологический анализатор для русского языка. Доступен как .Net Standart 2.0 библиотека. Умеет:

  1. проводить морфологический разбор слова (определяет часть речи, род, число, падеж, время, лицо, наклонение, залог);
  2. приводить слова к нормальной форме;
  3. менять форму слова в рамках лексемы.

Терминология

Терминология в DeepMorphy частично заимствована из морфологического анализатора pymorphy2.

Граммема (англ. grammeme) - значение одной из грамматических категорий слова (например прошедшее время, единственное число, мужской род).

Грамматическая категория (англ. grammatical category) - множество из взаимоисключающих друг друга граммем, характеризующих какой-то общий признак (например род, время, падеж и тп). Список всех поддерживаемых в DeepMorphy категорий и граммем тут.

Тег (англ. tag) - набор граммем, характеризующих данное слово (например, тег для слова еж - существительное, единственное число, именительный падеж, мужской род).

Лемма (англ. lemma) - нормальная форма слова.

Лемматизация (англ. lemmatization) - приведение слова к нормальной форме.

Лексема - набор всех форм одного слова.

Принцип работы

Основным элементом DeepMorphy является нейронная сеть. Для большинства слов морфологический анализ и лемматизация выполняется сетью. Некоторые виды слов обрабатываются препроцессорами.

Препроцессоры

Имеется 3 препроцессора:

Нейронная сеть

Сеть построена и обучена на фреймворке tensorflow. В качестве датасета выступает словарь Opencorpora. В .Net интегрирована через TensorFlowSharp.

Граф вычислений для разбора слов в DeepMorphy состоит из 11 "подсетей":

Задача изменения формы слов решается 1 seq2seq сетью.

Примерная схема сети для разбора слов

Обучение производится последовательно, сначала обучаются сети по категориям (порядок не имеет значения). Далее обучается главная классификация по тегам, лемматизация и сеть для изменения формы слов. Обучение проводилось на 3-х GPU Titan X. Метрики работы сети на тестовой датасете для последнего релиза можно посмотреть тут.

Руководство пользователя

DeepMorphy для .NET представляет собой библиотеку .Net Standart 2.0. В зависимостях только библиотека TensorflowSharp (через нее запускается нейронная сеть).

Установка

Библиотека опубликована в Nuget, поэтому проще всего устанавливать через него.

Если есть менеджер пакетов:

 Install-Package DeepMorphy

Если проект поддерживает PackageReference:

 <PackageReference Include="DeepMorphy"/> 

Если кто-то хочет собрать из исходников, то C# исходники лежат тут. Для разработки используется Rider (без проблем все должно собраться и в студии).

Начало использования

Все действия осуществляются через объект класса MorphAnalyzer:

var morph = new MorphAnalyzer();

В идеале, лучше использовать его как синглтон, при создании объекта какое-то время уходит на загрузку словарей и сети. Потокобезопасен. При создании в конструктор можно передать следующие параметры:

Морфологический разбор

Для разбора используется метод Parse (на вход принимает IEnumerable со словами для анализа, возвращает IEnumerable с результатом анализа).

var results = morph.Parse(new string[]
{
    "королёвские",
    "тысячу",
    "миллионных",
    "красотка",
    "1-ый"
}).ToArray();
var morphInfo = results[0];

Список поддерживаемых грамматических категорий, граммем и их ключей тут. Если необходимо узнать самую вероятную комбинацию граммем (тег), то нужно использовать свойство BestTag объекта MorphInfo.

// выводим лучшую комбинацию граммем для слова
Console.WriteLine(morphInfo.BestTag);

По самому слову не всегда возможно однозначно установить значения его грамматических категорий (см. омонимы), поэтому DeepMorphy позволяет посмотреть топ тегов для данного слова (свойство Tags).

// выводим все теги для слова + их вероятность
foreach (var tag in morphInfo.Tags)
    Console.WriteLine($"{tag} : {tag.Power}");

Есть ли комбинация граммем в каком-нибудь из тегов:

// есть ли в каком-нибудь из тегов прилагательные единственного числа
morphInfo.HasCombination("прил", "ед");

Есть ли комбинация граммем в самом вероятном теге:

// ясляется ли лучший тег прилагательным единственного числа
morphInfo.BestTag.Has("прил", "ед");

Получение определенных из лучшего тега грамматических категорий:

// выводит часть речи лучшего тега и число
Console.WriteLine(morphInfo.BestTag["чр"]);
Console.WriteLine(morphInfo.BestTag["число"]);

Теги применяются для случаев, если нужна информация сразу о нескольких грамматических категориях (например часть речи и число). Если вас интересует только одна категория, то можно использовать интерфейс к вероятностям значений грамматических категорий объектов MorphInfo.

// выводит самую вероятную часть речи
Console.WriteLine(morphInfo["чр"].BestGramKey);

Так же можно получить распределение вероятностей по грамматической категории:

// выводит распределение вероятностей для падежа
foreach (var gram in morphInfo["падеж"].Grams)
{
    Console.WriteLine($"{gram.Key}:{gram.Power}");
}

Лемматизация

Если вместе с морфологическим анализом нужно получать леммы слов, то анализатор надо создавать следующим образом:

var morph = new MorphAnalyzer(withLemmatization: true);

Леммы можно получить из тегов слова:

Console.WriteLine(morphInfo.BestTag.Lemma);

Проверка, есть ли у данного слова лемма:

morphInfo.HasLemma("королевский");

Метод CanBeSameLexeme может быть использован для нахождения слов одной лексемы:

// выводим все слова, которые могут быть формой слова королевский
var words = new string[]
{
    "королевский",
    "королевские",
    "корабли",
    "пересказывают",
    "королевского"
};

var results = morph.Parse(words).ToArray();
var mainWord = results[0];
foreach (var morphInfo in results)
{
    if (mainWord.CanBeSameLexeme(morphInfo))    
        Console.WriteLine(morphInfo.Text);
}

Если необходима только лемматизация без морфологического разбора, то нужно использовать метод Lemmatize:

var tasks = new []
{
    new LemTask("синяя", morph.TagHelper.CreateTag("прил", gndr: "жен", nmbr: "ед", @case: "им")),
    new LemTask("гуляя", morph.TagHelper.CreateTag("деепр", tens: "наст"))
};

var lemmas = morph.Lemmatize(tasks).ToArray();
foreach (var lemma in lemmas)
    Console.WriteLine(lemma);

Изменение формы слова

DeepMorphy умеет изменять форму слова в рамках лексемы, список поддерживаемых словоизменений тут. Словарные слова возможно изменять только в рамках тех форм, которые доступны в словаре. Для изменения формы слов используется метод Inflect, на вход принимает перечисление объектов InflectTask (содержит исходное слово, тег исходного слова и тег, в который слово нужно поставить). На выходе перечисление с требуемыми формами (если форму не удалось обработать, то null).

var tasks = new[]
{
    new InflectTask("синяя", 
        morph.TagHelper.CreateTag("прил", gndr: "жен", nmbr: "ед", @case: "им"),
        morph.TagHelper.CreateTag("прил", gndr: "муж", nmbr: "ед", @case: "им")),
    new InflectTask("гулять", 
        morph.TagHelper.CreateTag("инф_гл"),  
        morph.TagHelper.CreateTag("деепр", tens: "наст"))
};

var results = morph.Inflect(tasks);
foreach (var result in results)
    Console.WriteLine(result);

Так же для слова имеется возможность получить все его формы с помощью метода Lexeme (для словарных слов возвращает все из словаря, для остальных все формы из поддерживаемых словоизменений).

var word = "лемматизировать";
var tag = m.TagHelper.CreateTag("инф_гл");
var results = m.Lexeme(word, tag).ToArray();

Одной из особенностей алгоритма является то, что при изменении формы или генерации лексемы, сеть может "выдумать" несуществующую (гипотетическую) форму слова, форму которая не употребляется в языке. Например, ниже получится слово "побежу", хотя в данный момент в языке оно не особо используется.

var tasks = new[]
{
    new InflectTask("победить", 
        m.TagHelper.CreateTag("инф_гл"),  
        m.TagHelper.CreateTag("гл", nmbr: "ед", tens: "буд", pers: "1л", mood: "изъяв"))
};
Console.WriteLine(m.Inflect(tasks).First());

Cтруктура репозитория

Планы по возмодным доработкам