Генерируем заголовки фейковых новостей в стиле Ленты.ру

в 16:59, , рубрики: python, искусственный интеллект, машинное обучение, нейронные сети, обработка естественного языка

Сравниваем 2 подхода к генерации текста c помощью нейронных сетей: Char-RNN vs Word Embeddings + забавные примеры в конце.

Когда становится совсем нечего читать, открывать книгу не хочется, все статьи на Хабре прочитаны, все нотификации на телефоне обработаны, и даже просмотрен спам в ящиках, я открываю Ленту.ру. У моей жены — профессионального журналиста — в такие моменты начинается аллергия, и понятно, почему. После того, как в 2014 году из Ленты ушла старая команда, уровень желтизны издания пошел вверх, а качество текста и редактуры — вниз. Со временем, периодически по инерции продолжая читать Ленту, я стал замечать, что модели заголовков новостей повторяются: «Обнаружено [вставить псевдо-сенсацию]», «Путин [что-то сделал]», «Безработный москвич [описание его приключений]» и так далее. Это была первая вводная.

Вторая вводная — недавно случайно нашел забавный отечественный аналог @DeepDrumph (это твиттер, в котором выкладываются фразы, сгенерированные нейросетью на основе официального твиттера Трампа) — @neuromzan. К сожалению, автор прекратил выкладывать новые твиты и затаился, но описание идеи сохранилось здесь: meduza.io/shapito/2016/08/05/neyroramzan-robot-kotoryy-uchilsya-pisat-na-instagrame-ramzana-kadyrova.

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

Замечание: здесь рассматривается именно задача генерации последующего текста на основе некоторого вводного. Это не задача Text Summarization, когда, например, на основе текста новости генерируется ее заголовок. Текст новости в моем случае вообще никак не используется.

Данные

Перспектива скачивать и парсить весь контент Ленты меня совсем не радовала, и я стал искать, не делал ли кто уже это до меня. И мне повезло: как раз за несколько дней до того, как у меня появилась эта идея, Ильдар Габдрахманов ildarchegg выложил пост, где он описывает, как граббил контент Ленты, и делится полным архивом. В конце он добавляет «Надеюсь, что кто-то посчитает эти данные интересными и сможет найти им применение.» Конечно! У меня уже есть применение! Спасибо тебе, Ильдар! Твои усилия сэкономили мне несколько дней!

Итак, берем этот архив и выдергиваем оттуда статьи за нужный нам период: с 01.04.2014 (когда ушла старая команда) по настоящее время. Подробно на предварительной обработке данных я останавливаться не буду. Кому интересно, в репозитории на Гитхабе есть отдельный ноутбук с пошаговым описанием процесса.

Первая попытка обучения — Char-RNN

Судя по указанной выше статье, автор neuromzan’а использовал архитектуру Char-RNN, описанную Андреем Карпаты (Andrej Karpathy) в его уже ставшей легендарной статье "The Unreasonable Effectiveness of Recurrent Neural Networks". Давайте попробуем решить нашу задачу с помощью этого подхода. Реализацию Char-RNN под разные языки и фреймворки можно легко найти в интернете. Я взял вот эту — на Keras она получается довольно компактной. Немного модифицировав ее под свои нужды (изменил гиперпараметры и добавил сохранение результатов и более информативный вывод), я запустил обучение.

И результаты меня не порадовали. Идея Char-RNN в том, что сеть учится предсказывать следующий символ на основе N предыдущих. То есть сети в качестве входных данных подается некоторый исходный текст, а дальнейший текст генерируется посимвольно. Поэтому, чтобы научиться писать относительно читабельные тексты, сеть должна:

  1. Научиться отделять последовательности символов (псевдо-слова) пробелами и иногда ставить точку;
  2. Научиться генерировать последовательности символов, похожие на настоящие слова;
  3. Научиться генерировать похожие на настоящие слова с учетом предыдущих почти настоящих слов.

Вот что получилось с двухслойной сетью с 64 LSTM-ячейками, обученной на ограниченном наборе заголовков (<800). В квадратных скобках — вводный текст для сети, генерируем следующие 150 символов:

1 эпоха: "[грязи в стокгольме . названа причина массового сам]ииоеал стаа  и ата еааир онсст рррв   аитислт  и  ио о и скд ииау р п  .аа итео  ли таи уеаа  сао     аоеи  сооааои   лртаип оооа лаае о соа ра тлс от";
2 эпоха: "[о прокомментировал слухи об отстранении россии от ]п ма и во  роги . по га мо по по к кал ы та поникита ги . о си кай вав пе петбого . па рака с меры пали ва са па . вока са и . ва 2а с ра по в слило и";
3 эпоха: "[лнении плана по ремонту дорог . в якутии девушка и] подра праттонго пробилерей в постедела . сралива . порграны . престрасий придали пелелия . редодной . сотурика . прусбиря . в россирей . в подирачили";
4 эпоха: "[ую шкуру . кэаз: продукция , которая эффективно за] ворое за полита . дона семе ацерика в советили полино . кий рассии в кодат нашрет с на ды пой подо пояли рассийский за в подовали ресскию уна помода ";
5 эпоха: "[экс - чемпиона мира . маккейн раскритиковал трампа]ни . покодила . придовов разнал российский из ради . воростия . в начил подровать . подовых борту . в примозного об ан дто . ав россия боль . маниц . ";
...
25 эпоха: "[а произошло землетрясение магнитудой 7,2. британка]м приходах полицию пообещала за поставшую собчак за глупеледователеся в москве. подмосковные самое ископью мир-стали. колян признался кратите шезвым м";
...
50 эпоха: "[итета олимпиады - 2018 прокомментировал возможное ]полицейские с горовулеской заглаку у наслучать бесажно с кубанского банки в сообщили о вклачуют признили встревахся в петербурге оказались новый сборн"

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

Альтернативный метод — Word Embeddings

Я вспомнил, что чуть менее года назад на курсе Udacity Deep Learning Foundation у меня было задание — написать сеть, которая бы генерировала сценарий к Симпсонам (конкретнее, одну сцену в таверне у Мо), взяв для обучения оригинальные сценарии аналогичных сцен из 25 сезонов мультфильма. В этом случае использовался другой подход — Word Embeddings, когда текст генерируется не посимвольно, а по словам, но тоже на основе распределения вероятностей появления определенных слов при заданных [1..N] предыдущих словах.

Этот подход имеет ряд существенных преимуществ перед Char-RNN:

  1. Сети не нужно учиться генерировать слова — они уже сами по себе являются атомарными элементами, соответственно, сгенерированные фразы изначально будут состоять только из слов из нашего корпуса, не будет ненастоящих слов;
  2. Слова во фразах лучше согласуются друг с другом, потому что для набора слов {«в», «на», «поехал», «машине», «машину»} большей суммарной вероятностью будет обладать последовательность [«поехал», «на», «машине»], чем [«поехал», «в», «машину»].
  3. Выше скорость обучения, т.к. для одного и того же корпуса для Char-RNN токенами будут являться символы, а для Word Embeddings — слова. Количество слов в тексте заведомо меньше, чем символов, а для машины что в первом, что во втором случае токен — просто индекс в словаре. Соответственно, за одну итерацию Word Embeddings нужно будет обработать гораздо меньше токенов, чем Char-RNN, и времени на это потребуется примерно пропорционально меньше.

Быстрая проверка гипотезы на ограниченном датасете (те же 2 слоя и 64 ячейки LSTM, генерируем 100 следующих токенов):

Эпоха 1: "[макгрегор]...................................................................................................."
...
Эпоха 4: "[макгрегор]... гигантский двойников.. попросили правительство правительство...... facebook........... попросили.......... попросили............... попросили.... попросили...... попросили. попросили......... пользователей. попросили........... боевой попросили попросили попросили..."
...
Эпоха 10: "[макгрегор]. алексей кадры янин пожиратель двойников техники. иркутск техники в в. в. в.. защитить собственный соглашения пользователей iphone8. в... полиция автоматического. гей-партнерстве. джона продажи через соцсеть. савченко. пост возможность в. продлить победа правительство продлить крыма.. от с. продление мира в. для ветераны правительство продлить о.. с facebook в иркутск для в в в продажи кредит. россии. выполнять соглашения по по петербурга. иркутск олимпиаде. покажут facebook в. android. в. мир. на. От"
...
Эпоха 20: "[макгрегор]. опубликованы кадры выброски боевой техники с воздуха в рязанской в рязанской в. россии собственный защитившего арнольд российской авиатехникой. защитившего техники в рио. моргана моргана. моргана мир в ральф лорен надел в в россии. индия российской 12. на арнольд мира в рио. рио.. назвал для. выполнять первый музей крыма. против проект. иркутск для в моргана против крыма. моргана. android от питтсбург авиатехникой. минэкономразвития крыма правительство крыма. гей-партнерстве против санкций. моргана. для в против крыма. россии. продлить мира на выполнять заходили"

Итак, что тут происходит. На первой эпохе сеть видит, что один из самых часто встречающихся токенов — точка. «А давай-ка я везде понаставлю точек, раз у нее такая высокая частота», — думает она, и получает гигантскую ошибку. Затем она еще пару итераций сомневается, и на 4-й решает, что между точками стоит вставлять какие-нибудь слова. «Ого! Ошибка стала меньше», — радуется сеть, — «Надо продолжить в этом духе, будем вставлять разные слова, но мне очень нравятся точки, поэтому пока я продолжу их ставить». И продолжает ставить точки, разбавляя их словами. Постепенно она понимает, что чаще стоит ставить слова, чем точки, причем некоторые слова должны идти недалеко друг от друга, например она запоминает некоторые отрывки из датасета: «правительство продлить», «выполнять соглашения», «пожиратель двойников» и т.п. К 20 эпохе она уже помнит довольно длинные фразы, типа «опубликованы кадры выброски боевой техники с воздуха». И это показывает, что в принципе подход работает, но на таком маленьком наборе данных сеть довольно быстро переобучается (даже несмотря на dropout), и вместо уникальных фраз выдает заученные.

Посмотрим, что будет, если обучить ее на полном наборе заголовков:

Эпоха 1: "[визажисты] в сша после ссоры с подругой.. девять национализированного украиной приватбанка попросился в отставку. генсек нато похвалил летчиков за перехваты над балтикой. дочка шоколадницы наблюдателей в мире в университете в таиланде после ссоры с подругой. сша заявили об отказе шотландская школьница подлила в подготовке госпереворота. стальная плоть. дк продуктов в париж. девять друзей тесака. в футляр для альта соперницы кислоту. в совфеде предложили дк зил. девять друзей тесака. в новая для ссоры с подругой. сша заявили об отказе объяснено отсутствие антивещества во вселенной. тесак получил 10 лет"
...
Эпоха 5: "[визажисты] в ньюкасле. несвобода вместо свободы. швеция пригласила на вокзалах при ниже 59 рублей. в германии ограничили возможность предвыборных выступлений иностранных политиков. литовский туроператор дать балашихе о часы в свою жизнь. подругу-гадалку слухи о попавшем в плен на воду forbes и путина. тесак получил 10 посетить человек. российских туристов в доминикане выросло в 300. захарова рассказала путина остановить земельный имиджа мире в дтп с такси. путин стали в почти 3 миллиона долларов на неподходящий камуфляж. в хабаровском опекуне-педофиле. власти кндр отказалась от формирования совместной с южной иванушек. резцова рассказала"
…
Эпоха 10: “[визажисты] погиб. велосипедист в буденновске на человека в погибли без перелета. стальная плоть. лесного продажи халифата ввести самые распространенные наркотики в российском tor. в подмосковье. найдена новая опасность солнцезащитных кремов. врачи отговорили горбачева от поездки на прощание с колем. минимальный набор продуктов в россии резко подорожал почти до 72 лет. вирус третьей компанией в турции. приедут архитекторы из россии. слуцкий назвал причину пообещали своем слова об обысках в москве череповца. в нато выразили обеспокоенность растущей военной возможность встречи трампа и съемки репортажа о кражах. забытого матерью в песочнице грудного”

Здесь хватило всего 10 эпох, чтобы loss перестал уменьшаться. Как видим, в этом случае сеть также заучила некоторые довольно длинные куски, но при этом, есть и много относительно оригинальных фраз. Часто можно видеть, как сеть создает длинные фразы из других длинных, например вот так: «тиньков назвал дурова быдлом и публично стер telegram» + «пьяный москвич угнал трактор и попал в дтп с такси» = «пьяный москвич угнал трактор и публично стер telegram».

Однако, все равно, в большинстве случаев фразы получаются не такими красивыми, как у DeepDrumph и neuromzan. Что же тут не так? Нужно обучать дольше, глубже и шире? И тут на меня снизошло озарение. Нет, эти ребята не нашли магическую архитектуру, выдающую красивые тексты. Они просто генерят длинные тексты, отбирают потенциально смешные куски и вручную их редактируют! Финальное слово за человеком — вот в чем секрет!

После некоторой ручной правки можно получить вполне приемлемые варианты:

  • «в отреагировал. песков продажи возможность заммэра» >>> «песков отреагировал на возможность продажи должности заммэра»
  • «вице-спикера сочли человек» >>> «вице-спикера сочли человеком»
  • «депутат лебедев признал неуместным предложение предложение убийстве» >>> «депутат лебедев признал неуместным предложение об убийстве»

… и так далее.

Здесь есть еще один важный момент, связанный с русским языком. Генерировать фразы для русского языка гораздо сложнее, потому что слова и фразы в предложении нужно согласовывать. В английском, конечно, тоже, но в гораздо меньшей степени. Вот пример:
«машина» >>> «car»
«на машине» >>> «by car»
«вижу машину» >>> «see the car»

То есть в английском слово в разных падежах — один и тот же токен с точки зрения сети, а в русском — разные за счет отличающихся окончаний и других частей слова. Поэтому результаты работы модели на английском языке выглядят более правдоподобными, чем на русском. Можно, конечно, лемматизировать слова в русском корпусе, но тогда и сгенерированный текст будет состоять из таких слов, и здесь уж точно без ручного допиливания не обойтись. К слову, я попробовал сделать это, используя модуль pymorphy2, но результат, на мой личный взгляд, был местами даже хуже, несмотря на то, что количество уникальных токенов (слов) после нормализации сократилось более чем в 2 раза. После 20 эпох результат был такой:

“[звездный]. минфин отказать экономика россия по волейбол. для должник по волейбол. греция пообещать побывать в сжр побывать в побывать в отскок. греция пообещать побывать. в красноярск греция пообещать побывать в русало. для должник по волейбол. в отскок. песок. в россия. индия оснастить собственный авианосец российский авиатехника. песок песок. в россия. греция пообещать побывать в рио. ральф лорен надеть на facebook с мобильный.. в рязанский область. российский пшеница подорожать. в россия. в россия появиться один музей солженицын.. украинец попросить правительство вывесить”

Также можно заметить, что часто теряется первоначальное значение слова. Например, в приведенном выше отрывке фамилию “Песков” pymorphy нормализовал в слово “песок” — серьезная потеря для всего корпуса.

Выводы

  • Word Embeddings гораздо лучше справляется с генерацией текста, чем Char-RNN;
  • При прочих равных качество генерации английского текста в общем случае выше, чем русского, из-за особенностей языков;
  • Когда вы слышите о том, что ИИ написал книгу (как это было на днях, когда объявили что алгоритм написал книгу о Гарри Поттере, делите это утверждение на 1000, потому что, скорее всего, алгоритм лишь мог 1) сгенерировать имена и описания персонажей, или 2) сгенерировать общую канву повествования, или 3) сгенерировать полуабсурдный текст, который потом долго и упорно правили и согласовывали люди-редакторы. Либо все это вместе взятое. Но уж точно ИИ не писал книгу от первого до последнего слова. Не на том уровне развития еще наши технологии.

Десерт

Ну и в конце некоторые перлы после ручных правок, как мне кажется, вполне в духе Ленты.ру:

  • Минфин отказался от вечеринки в Рязанской области
  • Australian отвергла возможность iPhone8 на Android
  • Иркутск оставили без воздуха
  • Полиция Петербурга против мира Петербурга
  • В Китае начались продажи должников
  • Минэкономразвития сообщило о гей-партнерстве против санкций
  • Греция выступила с мишкой
  • Путин помирился с музеем и с Солженицыным
  • Голый турист погиб в Буденновске
  • Врачи отговорили Горбачева от поездки на прощание с проблемами в области работорговли
  • В Британии задумались полуфинальные пары Кубка Конфедераций
  • Депутат Лебедев признал неуместным предложение оштрафовать такси
  • В Совфеде предложили выбирать почетных Леопольдов
  • Вице-спикер Думы обосновал предложение о подготовке госпереворота
  • Литовские онкологи в Москве приступили к лечению
  • ЦБ выделит деньги на починку каменного пениса тролля
  • Снимавшимся в кино сложнее прилично шутить о новой подмосковной ведущей
  • Нетрезвый задержанный спровоцировал Госдуму
  • Израиль ответил кислотой в лицо
  • Глава ФРС США пообещала отсутствие кремов
  • Проблему бараков в Ижевске решили через постель
  • На Ямале кречеты впервые вывели потомство от самолета Евровидения
  • Подмосковье за год потратит больше Бразилии
  • У берегов Ливии такси из Иванушек
  • Опубликовано число погибших при крушении туристического судна в Раде
  • Ким Кардашьян обвинили в подготовке еще одной химической атаки
  • Правительство пообещало отсутствие денег на внутренние дела России
  • Тысячу вожатых подготовили для работы в России
  • Брейвик пожаловался на обстрел своей территории авиаударом
  • США потребовали дать в морду Жиркову
  • Петербуржец-шизофреник предупредил о считанных днях до блокировки Telegram

Код с ноутбуками и комментариями выложен на Гитхабе. Там же есть предобученная сеть, скрипт для генерации текста (lenta_ai.py) и инструкции по использованию. В 99% случаев вы будете получать бессмысленные наборы слов, и лишь изредка будет попадаться что-то интересное. Ну а если вам хочется “просто посмотреть”, я запустил маленькое веб-приложение на heroku, где можно погенерить заголовки без запуска кода у себя на машине.

Автор: Rinat Maksutov

Источник

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


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