Эволюция алгоритма Test The Text

в 10:05, , рубрики: django, pymorphy2, python, Блог компании Test The Text, Веб-разработка, метки: , ,

Test The Text выделяет стоп-слова в тексте. Стоп-слова делают текст тяжелее, слабее, длиннее.

Стоп-слова делятся на несколько категорий:
— модальные глаголы;
— усиляющие и обобщенные определения и наречия;
— клише и канцеляризмы;
— гиперонимы;
— паразиты времени;
— отглагольные существительные;
— пассивный залог;
— наречия;
— причастные обороты.

Прототип выделял модальные глаголы, используя список «мочь», «долженствовать» и «нуждаться» во всех формах:

    'modal': {
        'can': u"""могу, мог, могла, можешь, может, могло, можем, можете, могли, могут, 
                        смог, смогла, смогли, сможет, можно, нужен""",
        'need': u'нуждаться, нуждается, нужно, нужна, нужны',
        'should': u'должен, должна, должны, должно',
        'other': u'стоит, обязан, следует, необходимо, требуется'
    },

Текст разбивался на слова регулярным выражением (?:[s,.:]|A|Z), каждое слово сравнивалось со стоп-словами. Совпадения помечались в исходном тексте <span class=«класс стоп-слова»>, размеченный текст возвращался обратно и заменял текст на странице.

Прототип работал, список стоп-слов рос, но когда я добрался до клише, понял, что дальше перечислять стоп-слова во всех формах я не смогу. Каждое стоп-слово превращалось в 63. Три рода × три времени × семь падежей. Я подключил pystemmer.

Стеммер убирает окончание и суффикс слова, приводя его к нормальной форме.

нуждаться → нужда
инновационное → инновацион

высокий → высок
высокая → высок
высокую → высок

Pystemmer работает по алгоритму Snowball.

Теперь алгоритм пробегался по стоп словам, отбрасывая у них окончания и суффиксы, затем по словам текста. Начальные формы стоп-слов и слов в тексте сравнивались. Стоп-фразы, вроде, «сомнительное удовольствие», алгоритм разбирает на слова, стеммирует и собирает обратно. При поиске стоп-фразы в списке слов текста, берется проверяемое слово и несколько слов за ним.

К сожалению, через словари стоп-слов невозможно проверить наречия, пассивный залог и причастные обороты. Представляете себе список всех наречий русского языка? Пришло время подключать морфологический анализатор. Выбора для python, кроме pymorphy2 от kmike нет, поэтому останавливаюсь на нем.

Морфологический анализатор определяет для слова часть речи (существительное, глагол, прилагательное, ...), пол, единственное/множественное число, падеж, лицо, время, залог для глаголов. Полный список в исходниках. Захватывающая статья как устроен pymoprhy2.

[Parse(word=u'доставлен', tag=OpencorporaTag('PRTS,perf,past,pssv masc,sing'), normal_form=u'доставить', score=1.0, methods_stack=((<DictionaryAnalyzer>, u'доставлен', 745, 71),))]

[Parse(word=u'пищущие', tag=OpencorporaTag('ADJF plur,nomn'), normal_form=u'пищущий', score=0.212962962962963, methods_stack=((<DictionaryAnalyzer>, u'ищущие', 162, 20), (<UnknownPrefixAnalyzer>, u'п'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('ADJF inan,plur,accs'), normal_form=u'пищущий', score=0.212962962962963, methods_stack=((<DictionaryAnalyzer>, u'ищущие', 162, 24), (<UnknownPrefixAnalyzer>, u'п'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,tran,pres,actv plur,nomn'), normal_form=u'пискать', score=0.212962962962963, methods_stack=((<DictionaryAnalyzer>, u'ищущие', 1609, 33), (<UnknownPrefixAnalyzer>, u'п'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,tran,pres,actv inan,plur,accs'), normal_form=u'пискать', score=0.212962962962963, methods_stack=((<DictionaryAnalyzer>, u'ищущие', 1609, 37), (<UnknownPrefixAnalyzer>, u'п'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,intr,pres,actv plur,nomn'), normal_form=u'питать', score=0.03703703703703704, methods_stack=((<FakeDictionary>, u'пищущие', 1670, 33), (<KnownSuffixAnalyzer>, u'щущие'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,intr,pres,actv inan,plur,accs'), normal_form=u'питать', score=0.03703703703703704, methods_stack=((<FakeDictionary>, u'пищущие', 1670, 37), (<KnownSuffixAnalyzer>, u'щущие'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,tran,pres,actv plur,nomn'), normal_form=u'пистать', score=0.03703703703703704, methods_stack=((<FakeDictionary>, u'пищущие', 2631, 33), (<KnownSuffixAnalyzer>, u'щущие'))), 
Parse(word=u'пищущие', tag=OpencorporaTag('PRTF,impf,tran,pres,actv inan,plur,accs'), normal_form=u'пистать', score=0.03703703703703704, methods_stack=((<FakeDictionary>, u'пищущие', 2631, 37), (<KnownSuffixAnalyzer>, u'щущие')))]

После подключения морфологического анализатора Test The Text выделяет наречия, страдательный залог, причастный оборот и, заодно, междометия.

Осталось разобраться с проблемами на клиенте. Я и не подозревал, что с простым полем ввода текста может быть столько проблем. Пришлось написать java-script очистки текста при вставке и код вставки <br> по нажатию enter. В новой версии я заменил свой код на Wysihtml5. Wysihtml5 это легковесный html-редактор. Нашел его, изучая как сделали редактор в Basecamp.

Кроме того, пришлось перенести разметку текста на клиент. Проверка текста происходит не мгновенно, пользователь мог ввести еще пару предложений перед ответом. А так как текст размечался на сервере, то все изменения пользователя стирались.

Вместо этого сервер стал возвращать список стоп-слов с их классом, начальной и конечной позицией. А разметка уже происходила на клиенте. Если слово на позиции не совпадает с ответом от сервера, слово не помечается. На тот случай, если пользователь поменял текст и позиция слова сместилась.

Планы по развитию:
— Выделение предложений больше 17 слов, их трудно читать.
— Выделение абзацев длиннее 8 строчек. Скорее всего такие абзацы нужно разбить.
— Слежение за «ритмом» текста. В хорошем тексте длинные предложения чередуются со средними и короткими, текст становится не монотонным. Читатель не засыпает.
— Повторение слов в сосендних предложениях.
— Сослагательное наклонение.
— Публичное API.


Подписывайтесь на наш Хабра-блог и полезные письма. Пишем о информационном стиле, разбираем чужие посты.

Автор: TestTheText

Источник

  1. Алекс:

    Не понял из статьи, как решили вопрос с отглагольными существительными. Мне собственно для другой задачи нужно понимать от какого глагола происходит сущ.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js