Векторные модели и русская литература

от автора

Вы никогда не задумывались, почему тексты классических русских писателей так ценятся, а сами писатели считаются мастерами слова? Дело явно не только в сюжетах произведений, не только в том, о чём написано, но и в том, как написано. Но при быстром чтении по диагонали осознать это трудно. Кроме того, текст какого-нибудь значимого романа нам просто не с чем сравнить: почему, собственно, так прекрасно, что в этом месте появилось именно это слово, и чем это лучше какого-то другого? В какой-то мере реальное словоупотребление могло бы контрастно оттенить потенциальное, которое можно найти в черновиках писателя. Писатель не сразу вдохновенно пишет свой текст от начала до конца, он мучается, выбирает между вариантами, те, что кажутся ему недостаточно выразительными, он вычеркивает и ищет новые. Но черновики есть не для всех текстов, они отрывочны и читать их сложно. Однако можно провести такой эксперимент: заменить все поддающиеся замене слова на похожие, и читать классический текст параллельно с тем, которого никогда не было, но который мог бы возникнуть в какой-то параллельной вселенной. Попутно мы можем попытаться ответить на вопрос, почему это слово в этом контексте лучше, чем другое, похожее на него, но всё-таки другое.

А сейчас всё это (кроме собственно чтения) можно сделать автоматически.

Дистрибутивная семантика aka векторные модели

На Хабре уже была статья о том, как использовать дистрибутивную семантику в поиске по т.н. «пирожкам». Дистрибутивная семантика — это довольно простая, но отлично работающая идея, что значение слова связано с его окружением в тексте. Слова с похожим значением будут появляться в сходных контекстах и наоборот. Сам контекст можно представить в виде вектора (отсюда и "векторные модели") и посчитать сходство и различие в значении разных слов. Для русского языка на основе этой идеи сделан превосходный сервис — RusVectōrēs, который не только позволяет найти семантически наиболее похожие слова, но и предоставляет в свободный доступ посчитанные создателями ресурса модели.

Процессинг

Берём 5 классических русских романов. Дмитрий Быков где-то говорил, что самыми знаковыми для русской литературы являются романы, в названиях которых есть союз «и». Значит: «Преступление и наказание», «Война и мир», «Отцы и дети», «Мастер и Маргарита». Ну и «Евгений Онегин», тоже важный русский роман, хотя и в стихах.

Кроме того, нам потребуется модель с сайта RusVectōrēs, которая построена на текстах Национального корпуса русского языка и русской википедии. Работать с ней нам позволит библиотека Gensim. Чтобы искать в этой модели т.н. квазисинонимы, то есть фактически ближайшие соседи семантического графа (а эти соседи не всегда «настоящие" синонимы, часто наиболее семантически близким словом оказывается антоним, хотя это и кажется контринтуитивным), нам потребуется нормализованная форма слова (лемма), получить которую должен помочь морфологический анализатор, программа умеющая эту самую форму находить. Сначала я думал использовать Mystem от Яндекса, но в итоге остановился на другом, Pymorphy2, дальше будет понятно, почему.

import gensim import pymorphy2  model = gensim.models.KeyedVectors.load_word2vec_format("ruwikiruscorpora_0_300_20.bin.gz", binary=True) model.init_sims(replace=True)  morph = pymorphy2.MorphAnalyzer()

Итак, идём по тексту, берём из него слова, нормализуем (то есть восстанавливаем инфинитив для глаголов или именительный падеж единственного числа для имён), подыскиваем в модели наиболее похожее слово, при этом нужно следить за тем, чтобы это слово было той же части речи, что и исходное, потому что по умолчанию это не обязательно будет так. См., например, наиболее близкие слова к синева: тут есть и такие же существительные голубизна, чернота, но есть и прилагательное голубоватый, и глагол синеть. Я уж не говорю о том, что каких-то слов в модели и не будет совсем. Дело в том, что для низкочастотных в исходном корпусе слов вектор по соображениям оптимизации не строится, и квазисиноним найти не получится. Значит, оставим в этом месте исходное слово. Кроме того, нет смысла искать замены для местоимений, предлогов и прочих неполнозначных частей речи.

Генерация форм

А вот дальше самое интересное. В исходном тексте слово стоит в какой-нибудь косвенной форме, а из модели мы получили лемму. Теперь её нужно поставить в ту же форму, чтобы текст оказался связным и читабельным, а не казался каким-нибудь плохим переводом с инопланетного типа Мама мыть рама. И в этом как раз нам может помочь тот же Pymorphy2, который умеет не только разбирать слова, но и ставить их в нужную форму по заданным признакам. Mystem этого не может, а если при разборе запоминать не только лемму, которую выдал анализатор, но и набор признаков формы слова (те же число и падеж), то их потом очень удобно будет снова отправить в ту же программу уже для генерации формы. Правда, тут выяснилось, что не все те теги, которые выдаёт Pymorphy2-анализатор, знает Pymorphy2-генератор, то есть его правая рука не всегда знает, что делает левая. Но это не страшно, немного танцев с бубном и мы получаем нужную форму:

Функция, в которой автор пляшет с бубном вокруг Pymorphy2

def flection(lex_neighb, tags):     tags = str(tags)     tags = re.sub(',[AGQSPMa-z-]+? ', ',', tags)     tags = tags.replace("impf,", "")     tags = re.sub('([A-Z]) (plur|masc|femn|neut|inan)', '\\1,\\2', tags)     tags = tags.replace("Impe neut", "")     tags = tags.split(',')     tags_clean = []     for t in tags:         if t:             if ' ' in t:                 t1, t2 = t.split(' ')                 t = t2             tags_clean.append(t)     tags = frozenset(tags_clean)     prep_for_gen = morph.parse(lex_neighb)

Допиливание

Теперь собираем всё вместе, не забываем обработать капитализацию и знаки препинания. Вуаля! Альтернативная русская литература, которой не было, у нас на экране:

Поговорить об Ювенале,
В середине записки оставить vale,
Да вспомнил, хоть не без прегрешения,
Из Энеиды два стихотворения.

Для тех, кто забыл, что там на самом деле

Потолковать об Ювенале,
В конце письма поставить vale,
Да помнил, хоть не без греха,
Из Энеиды два стиха.

Но всё-таки что-то не то. Местами текст всё-таки превышает допустимый градус бессвязности:

Дружки Людмилы и Руслана!
С героиней моего повести
Без послесловий, посей же полчаса
Позвольте познакомиться вас

Если хочется заглянуть в оригинал и прикоснуться к прекрасному

Друзья Людмилы и Руслана!
С героем моего романа
Без предисловий, сей же час
Позвольте познакомить вас

Дело в роде существительных! Из модели мы достаём ту же часть речи и ставим её в ту же форму, но мы не учли такой постоянный признак существительных, как род. Он у разных существительных разный, и правильная генерация формы не спасёт от несогласованности. Теперь вводим правило, по которому существительные, которые выдала нам модель, ещё дополнительно проверяем на родовую принадлежность:

Больше плясок с бубном

def flection(lex_neighb, tags):     tags = str(tags)     tags = re.sub(',[AGQSPMa-z-]+? ', ',', tags)     tags = tags.replace("impf,", "")     tags = re.sub('([A-Z]) (plur|masc|femn|neut|inan)', '\\1,\\2', tags)     tags = tags.replace("Impe neut", "")     tags = tags.split(',')     tags_clean = []     for t in tags:         if t:             if ' ' in t:                 t1, t2 = t.split(' ')                 t = t2             tags_clean.append(t)     tags = frozenset(tags_clean)     prep_for_gen = morph.parse(lex_neighb)     ana_array = []     for ana in prep_for_gen:         if ana.normal_form == lex_neighb:             ana_array.append(ana)     for ana in ana_array:         try:             flect = ana.inflect(tags)         except:             print(tags)             return None         if flect:             word_to_replace = flect.word             return word_to_replace     return None

Стало лучше:

Дружки Людмилы и Руслана!
С персонажем моего рассказа…

и т.д.

Что получилось

Теперь можно заняться медленным чтением:

Мастер и Маргарита из параллельного мира

Оригинал:

Глава 1

Никогда не разговаривайте с неизвестными

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

"Перевод":

Глава 1

Никогда не беседуйте с невыясненными

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

Преступление и наказание из параллельного мира

"Преступление и наказание":

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

"Преступление и наказание" из параллельного мира:

Он счастливо избегнул беседы с своею соседкой на ступеньке. Комнатка его вынуждала под самою крышей низкого девятиэтажного особняка и походила более на шкафчик, чем на квартирку. Жилищный же соседка его, у которой он нанимался эту комнатку с ужином и прислугой, размещалась одною ступенькой ниже, в обособленной квартирке, и ядущий раз, при релизе на набережную, ему разумеется надо было пройти навстречу мужниной столовой, почти всегда наглухо отворенной на ступеньку.

Ну, и, конечно, при всех серьёзных и даже почти научных целях, о которых я писал в начале статьи, кое-где это просто-напросто смешно, хотя и неполиткорректно:

– Вы – фашист? – осведомился Безнадзорный.
– Я-то?.. – Переспросил доцент и вдруг задумался. – Да, пожалуй, фашист… – сказал он.

На самом деле у Булгакова было так

– Вы – немец? – осведомился Бездомный.
– Я-то?.. – Переспросил профессор и вдруг задумался. – Да, пожалуй, немец… – сказал он.

Полный текст романов:

Оставшиеся проблемы

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

Кое-где лучше определить форму могла бы помочь буква ё, которую в текстах романов обычно не воспроизводят:

>>> morph.parse('черт') [Parse(word='черт', tag=OpencorporaTag('NOUN,inan,femn plur,gent'), normal_form='черта', score=0.588235, methods_stack=((<DictionaryAnalyzer>, 'черт', 55, 8),)), Parse(word='чёрт', tag=OpencorporaTag('NOUN,anim,masc sing,nomn'), normal_form='чёрт', score=0.411764, methods_stack=((<DictionaryAnalyzer>, 'чёрт', 3019, 0),))]  >>> morph.parse('чёрт') [Parse(word='чёрт', tag=OpencorporaTag('NOUN,anim,masc sing,nomn'), normal_form='чёрт', score=1.0, methods_stack=((<DictionaryAnalyzer>, 'чёрт', 3019, 0),))]

Поэтому:

Усмехаться и подумать про себя:
Когда же особенностей возьмет тебя!’

вместо

Вздыхать и думать про себя:
Когда же черт возьмет тебя!

Описание идеи «векторных романов» и дополнительные материалы лежат на специальной странице.

Весь код для замены выложен на Github.

Приятного чтения!

Ссылки

ссылка на оригинал статьи https://habrahabr.ru/post/326380/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *