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

image


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


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


Дистрибутивная семантика 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=((, 'черт', 55, 8),)), Parse(word='чёрт', tag=OpencorporaTag('NOUN,anim,masc sing,nomn'), normal_form='чёрт', score=0.411764, methods_stack=((, 'чёрт', 3019, 0),))]

>>> morph.parse('чёрт')
[Parse(word='чёрт', tag=OpencorporaTag('NOUN,anim,masc sing,nomn'), normal_form='чёрт', score=1.0, methods_stack=((, 'чёрт', 3019, 0),))]

Поэтому:


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


вместо

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


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


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


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


Ссылки


  • RusVectōrēs, конкретно ссылка на использованную модель (420 Мб).
  • Gensim
  • Pymorphy2
  • Замена слов на похожие в «Гордости и предубеждении» (нет проблем с морфологией)

Комментарии (8)

  • 13 апреля 2017 в 04:58

    –5

    Вы никогда не задумывались, почему тексты классических русских писателей так ценятся, а сами писатели считаются мастерами слова? (…) почему, собственно, так прекрасно, что в этом месте появилось именно это слово, и чем это лучше какого-то другого?

    Задумывались, пришли к выводу, что ценность классических русских — да, собственно, и любых других высоко ценимых писателей есть, в значительной мере, самоподдерживающаяся иллюзия. Ценный, потому что великий, великий, потому что классический, классический, потому что ценный. Владение языком на высоком уровне, да, но нет там никакой гениальной выверенности каждого слова — да написал вот так, потому что написал, уже после потомки нашли там гениальность, классик же!
    • 13 апреля 2017 в 05:16

      +6

      Вы не правы.
      Да, в формировании канона действительно присутствует доля социальной конвенции (см., например, книгу А.И. Рейтблата «Как Пушкин вышел в гении»), но списывать всё только на иллюзию было бы наивно.
      На самом деле, художественный текст есть сложнейшая высокоорганизованная система, в которой действуют очень непростые отношения между элементами разных уровней.
      Ну, а что такое «написал вот так, потому что написал», я совсем не понимаю. Случайности не существует. Написал так потому что это чем-то детерминировано.
      • 13 апреля 2017 в 06:05

        –4

        Я прав.
        Я не списывал всё только на иллюзию, я сказал — «в значительной мере».
        В школе на уроках литературы все уши прожужжали этой глупостью. Почему у Пушкина кораблик «летит себе в волнах на раздутых парусах»? Пушкин — гений, у него это «себе» неспроста! Почему он употребил именно это слово? Думайте, дети, думайте. Да размер стихотворный ему надо было выровнять, вот почему! Это пример того, как «написал, потому что написал». Ещё бы то Тредиаковского с его «стозёвной-и лаей» докопались, какой там глубинный смысл этой »-и».

        Был (есть?), кстати, занятный проект «Из песни… не выкинешь», там юзерам предлагалось угадывать пропущенные слова в относительно малоизвестных стихах.

        • 13 апреля 2017 в 06:28

          +4

          Нет, не правы, увы.
          Действительно, в том, что такое впечатление создаётся, вина именно упомянутых Вами уроков литературы, которые часто представляют предмет в огрублённом виде. Правда и то, что люди чаще почитают, чем читают классиков (Вольтер подметил, что французы так делают с Данте).
          Но ведь чтобы делать столь далеко идущие выводы, стоит сначала глубже изучить дело, не только на уровне плохих школьных уроков? Начать, наверное, можно было бы с работ Ю.М. Лотмана по анализу текста и семиотике культуры. Там хорошо видно, что в текстах тех авторов, которых мы помним до сих пор, действительно есть гениальная выверенность каждого слова.
          Приведённые Вами аргументы не очень работают. Стихотворный размер? Во-первых, он допускает довольно много способов сказать по-другому. Во-вторых, его можно сменить. Выбор стихотворного размера тоже не случаен, у этого есть интересные закономерности, описанные М.Л. Гаспаровым в книге «Метр и смыл».
          В принципе, я хорошо Вас понимаю: если не быть знакомым с деталями (в которых дьявол), то всё кажется простым, а люди, думающие иначе, идиотами. Но поверьте, если немного расширить свои знания за счёт хороших научных работ, открывается невероятный и увлекательный мир.
          Какой «глубинный смысл» у чего бы то ни было в литературе, мне неведомо. По-моему, поиски «глубинного смысла» — пустая трата времени. Я говорю не о смысле, а о сложных отношениях элементов системы. Что до цитаты из Радищева (у Тредиаковского иначе: «тризевной и Лаей»), то «стозёвной» там быть не может: буква «ё» в печати появилась только через 5 лет после публикации «Путешествия из Петербурга в Москву».

          Отличный проект! Спасибо за ссылку, действительно, это по-своему близко к тому, что я тут представил.

          • 13 апреля 2017 в 07:12

            0

            Да прав, увы. Можно, разумеется, написать по-другому, допустим, «он колбасится в волнах на раздутых парусах» — так в чём проблема, гениальность найдут и в слове «колбасится». Размер менять, написав, может, уже к тому моменту три четверти поэмы?

            А приведённые вами аргументы ломаются, к примеру, на переводах. Сколько у нас переводов Шекспира на русский язык, разных, и все гениальные, и сам Шекспир гениален, хотя в оригинале и не читал никто, да и самого оригинала, вроде как, не существует.

            И нет, я нигде не писал, что «всё просто, все идиоты». Всё, наоборот, сложно, но сложность эта более высокого порядка, чем подбор якобы единственного верного слова в каждый слот.

            • 13 апреля 2017 в 07:38

              0

              К сожалению, нет. Дело в том, что если написать по-другому, эффект от этого тоже будет другой. Размер можно менять постоянно. Вы же помните «Двенадцать» Блока? Или у помянутого Вами Лермонтова: «Воля» (1831) или «Слышу ли голос твой…» (1838).

              Все переводы Шекспира гениальны? Не могу с этим согласиться.
              С текстологией Шекспира действительно всё не просто, но более-менее конвенциональный текст всё же есть (с разной степенью устойчивости для разных произведений).

              Да, Вы действительно не писали, что всё просто. Но Вы настаиваете на слишком большом участии случайности в том, как устроен текст. Если мы говорим о литературе, которая состоит из слов, любой уровень сложности будет в конечном счёте упираться в слова.
              А какой порядок сложности Вам видится в литературе?

  • 13 апреля 2017 в 07:43

    0

    Интересная по теме, но бестолковая какая-то статья получилась — суть «простой, но отлично работающей идеи» не описана, пример применения — бестолковый и популистский, …

    Интересно, почему, несмотря на обилие статей по данной тематике, все они не выходят за рамки hello word?

    • 13 апреля 2017 в 07:43

      0

      Спасибо за Ваш отзыв!
      Мне казалось, что как раз суть дистрибутивной семантики описана. Хотя бы вот тут:
      Слова с похожим значением будут появляться в сходных контекстах и наоборот. Сам контекст можно представить в виде вектора (отсюда и «векторные модели») и посчитать сходство и различие в значении разных слов

      Что бы Вам хотелось видеть в этом описании ещё?

      Я не могу согласиться с тем, что это «hello world» (тогда уж, скорее, «hello word»). Результат вполне конкретный словесный материал, с которым может работать литературовед (сеансы медленного чтения) или веб-мастер (квиз «угадай настоящую фразу из классического романа»). Если для Вас лично получившийся результат бесполезен, я сожалею.

© Habrahabr.ru