Применение Yargy-парсера при переводе русскоязычных слов в числа

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Проект Natasha хорошо зарекомендовал себя в решении задач NER для работы с русским языком. Он предоставляет возможности для базовой обработки текстов: сегментация на токены и предложения, морфологический и синтаксический анализы, лемматизация и, наконец, распознавание именованных сущностей – аспект, который мы подробно рассмотрим сегодня. Также будет представлено составление своих правил с помощью Yargy-парсера, что необходимо в тех случаях, когда готовые решения показывают низкое качество.

Распространенный метод извлечения именованных сущностей из текста – готовые библиотеки. Они дают неплохой результат, но, в случае с переводом русскоязычных слов в числа возникают проблемы. Одна из них – склеивание цифр. Например, такое происходит, когда во фрагменте проговариваются только числа, без уточнения, что из них является номером документа, а что – его датой. Библиотека Word-to-Number (Russia), которая реализована на основе Yargy и Natasha в такой ситуации работает недостаточно корректно, выдавая в качестве результата неупорядоченный набор цифр.

Однако, данную задачу можно решить посредством доработки правил, используя Yargy-парсер . Он представляет собой библиотеку для извлечения структурированной информации из текстов на русском языке. Правила описываются контекстно-свободными грамматиками и словарями ключевых слов. Парсер реализует алгоритм Earley parser. Библиотека написана на чистом Python, поддерживает Python 3.5+ и Pypy 3, использует Pymorphy2 для работы с морфологией.

Перейдем к конкретному решению поставленной задачи по извлечению параметров сделки из аудиозаписи на русском языке.

Тестовый образец аудиозаписи сделки выглядит следующим образом:

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

Устанавливаем библиотеку Yargy:

pip install yargy

Импортируем только те модули, которые необходимы для составления правил:

from yargy.interpretation import fact from yargy import (     Parser,     rule ) from yargy.predicates import (     normalized,     dictionary ) from yargy.pipelines import morph_pipeline import re 

Составляем функции для предобработки и поиска необходимого фрагмента, чтобы исключить ненужные параметры и увеличить скорость работы Yargy-парсера, так как он достаточно медленный, а тексты в аудиозаписях отличаются большим объемом.

def preprocess(text):     text = text.replace(' ', ' ')     text = find_section(text)     return text  def find_section(text):     res = []     result = re.finditer('соглашени[еяю]s', text)     for el in result:         res.append(text[el.start()-10:el.end()+45])     return res   line = preprocess(text)  

В функции find_section строка res.append(text[el.start()-10:el.end()+45]) устанавливает диапазон знаков, который был определен методом подбора, чтобы необходимый фрагмент параметра точно попал в него с небольшим запасом.

Получившийся фрагмент:

[‘ согласно соглашению номер восемьсот пятьдесят семь пятого марта д’] Примечание: номер может быть только трехзначный. 

Прописываем правила для парсера. Метод fact является интерпретатором, он работает в связке с конструкцией intepretation(…), чтобы извлекать числа в нужную вершину дерева разбора. Конструкция, выполняющая интерпретацию извлекаемых параметров для дальнейшей обработки:

Number = fact(     'number',     ['num1', 'num2', 'num3', ‘flag’] ) 

Заводим словари, чтобы извлекать в поля fact значения num1, num2, num3, flag:

DIGIT_NUM1 = {     'один': 1,     'два': 2,     'три': 3,     'четыре': 4,     'пять': 5,     'шесть': 6,     'семь': 7,     'восемь': 8,     'девять': 9,     'сто': 100,     'двести': 200,     'триста': 300,     'четыреста': 400,     'пятьсот': 500,     'шестьсот': 600,     'семьсот': 700,     'восемьсот': 800,     'девятьсот': 900 }  DIGIT_NAME_NUM1 = dictionary(     DIGIT_NUM1 )  DIGIT_NUM2 = {     'один': 1,     'два': 2,     'три': 3,     'четыре': 4,     'пять': 5,     'шесть': 6,     'семь': 7,     'восемь': 8,     'девять': 9,     'десять': 10,     'одиннадцать': 11,     'двенадцать': 12,     'тринадцать': 13,     'четырнадцать': 14,     'пятнадцать': 15,     'шестнадцать': 16,     'семнадцать': 17,     'восемнадцать': 18,     'девятнадцать': 19,     'двадцать': 20,     'тридцать': 30,     'сорок': 40,     'пятьдесят': 50,     'шестьдесят': 60,     'семьдесят': 70,     'восемьдесят': 80,     'девяносто': 90 }  DIGIT_NAME_NUM2 = dictionary(     DIGIT_NUM2 )  DIGIT_NUM3 = {     'один': 1,     'два': 2,     'три': 3,     'четыре': 4,     'пять': 5,     'шесть': 6,     'семь': 7,     'восемь': 8,     'девять': 9 }  DIGIT_NAME_NUM3 = dictionary(     DIGIT_NUM3 )  MONTH_NAME = morph_pipeline([     'январь',     'февраль',     'март',     'апрель',     'май',     'июнь',     'июль',     'август',     'сентябрь',     'октябрь',     'ноябрь',     'декабрь' ]).interpretation(Number.flag) 

Дополняем правила:

RULE = rule(     normalized('соглашение'),     normalized('номер').optional(),     DIGIT_NAME_NUM1.interpretation(Number.num1.normalized().custom(DIGIT_NUM1.get)),     DIGIT_NAME_NUM2.interpretation(Number.num2.normalized().custom(DIGIT_NUM2.get)).optional(),     DIGIT_NAME_NUM3.interpretation(Number.num3.normalized().custom(DIGIT_NUM3.get)).optional(),     MONTH_NAME.optional() ).interpretation(     Number ) 

Пояснение:

Извлекаем элементы в нужной нам форме в определенное поле факта для дальнейшей обработки. Указываем имя переменной факта – Number и добавляем в нужное поле – num1. Уточнение: normalized()— это предикат, он позволит искать в словаре все формы слова, что избавляет нас от необходимости прописывания всех вариантов (в парсер встроено много готовых предикатов, есть возможность добавить свои). custom создает предикат из произвольной функции и может применяться вместе сnormalized(), тогда слово сначала ставится в нормальную форму, а затем к нему применяется функция DIGIT_NUM1.get для извлечение из словаря значения по ключу «один»: 1 ? извлечь «1».

DIGIT_NAME_NUM1.interpretation(Number.num1.normalized().custom(DIGIT_NUM1.get)

Добавление optional() во второй и последующих позициях делает этот параметр не обязательным. Это сделано для того чтобы номера из одного слова наподобие “восемьсот” тоже извлекались.

DIGIT_NAME_NUM2.interpretation(Number.num2.normalized().custom(DIGIT_NUM2.get)).optional()

MONTH_NAME.optional() добавлен в правило для извлечения названия месяца и представляет собой словарь, созданный газеттиром  morph_pipeline (перед работой приводит слова к нормальной форме). Для его интерпретации создано поле факта flag. Позже напишем функцию extractor, в которой реализуем возможность не учитывать числа, если они относятся к дате. Этот прием позволит убрать часть ошибок при извлечении номера соглашения.

MONTH_NAME.optional()

Создаем объект парсера:

parser = Parser(RULE)

У парсера есть метод findall, он ищет все строки, удовлетворяющие правилам. Собираем результат работы в список:

matches = list(parser.findall(text))

Результат разбора на токены:

matches [Match(      tokens=[MorphToken(           value='соглашению',           span=[102, 112),           type='RU',           forms=[Form('соглашение', Grams(NOUN,datv,inan,neut,sing))]       ),       MorphToken(           value='номер',           span=[113, 118),           type='RU',           forms=[Form('номер', Grams(NOUN,inan,masc,nomn,sing)),            Form('номер', Grams(NOUN,accs,inan,masc,sing))]       ),       MorphToken(           value='восемьсот',           span=[119, 128),           type='RU',           forms=[Form('восемьсот', Grams(NUMR,nomn)),            Form('восемьсот', Grams(NUMR,accs))]       ),       MorphToken(           value='пятьдесят',           span=[129, 138),           type='RU',           forms=[Form('пятьдесят', Grams(NUMR,nomn)),            Form('пятьдесят', Grams(NUMR,accs))]       ),       MorphToken(           value='семь',           span=[139, 143),           type='RU',           forms=[Form('семь', Grams(NUMR,nomn)),            Form('семь', Grams(NUMR,accs))]       )],      span=[102, 143)  )] 

Посмотрим на факты:

facts = [_.fact for _ in matches]  facts [number(      num1=800,      num2=50,      num3=7,      flag=None  )] 

Создаем функцию для извлечения параметров сделок:

def extractor(text):     fraction = preprocess(text)     for el in fraction:         matches = parser.findall(el)         if matches:             result = []             res = []             facts = [_.fact for _ in matches]             for f in facts:                 res += [f.num1, f.num2, f.num3]                 res = [int(i) for i in res if i]                 if len(res) == 1:                     result.append(f.num1)                 elif len(res) == 2:                     if f.flag:                         result.append(res[0])                     else:                         result.append(f"{res[0]+res[1]}")                 elif len(res)==3 and res[0]<10 and res[1]<10 and res[2]<10 and not f.flag:                     digit = f"{res[0]}{res[1]}{res[2]}"                     result.append(int(digit))                 elif len(res)==3:                     if f.flag:                         result.append(f"{res[0]+res[1]}")                     else:                         result.append(f"{res[0]+res[1]+res[2]}")             return result         else:             return ''                extractor(text) 

Результат:  [‘857’].

Yargy-парсер предоставляет достаточно широкий функционал, чтобы справиться с нестандартными задачами. Он позволяет создавать расширенные правила, чтобы более эффективно извлекать необходимую информацию, минимизировав неточности получения данных из русскоязычных аудиозаписей.


Источник: newtechaudit.ru

Комментарии: