DeepMorphy is a neural network based morphological analyzer for Russian language.
DeepMorphy - морфологический анализатор для русского языка. Доступен как .Net Standart 2.0 библиотека. Умеет:
Терминология в 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());