Недавно газета Известия попросила нас провести опрос общественного мнения по фильму Звездные войны: Пробуждение силы, премьера которого состоялась 17 декабря. Для этого мы решили проанализировать настроения в русскоязычном сегменте Twitter с помощью несколько актуальных хэштегов. У нас было всего три дня, чтобы представить результаты (и это был конец года!), поэтому нам нужна была очень быстрая техника. Для этого мы нашли несколько онлайн-сервисов (таких как sentiment140 и tweet_viz), но оказалось, что они не поддерживают русский язык и почему-то анализируют лишь небольшой процент твитов. Сервис AlchemyAPI мог бы и пригодиться, но ограничение в 1000 запросов в день нас тоже не устраивало». Тогда мы решили сделать свой собственный анализатор настроений с блэкджеком и всем остальным, создав простую рекуррентную нейросеть с памятью. Результаты нашего опроса были использованы в статье Известий (только на русском языке), опубликованной 3 января.

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

Что такое RNN?

Ключевое отличие рекуррентных нейронных сетей (RNN) от традиционных заключается в том, как они работают. В RNN каждый нейрон взаимодействует сам с собой. Для сетей такого типа входной сигнал обычно представляет собой последовательность. Каждый элемент этой последовательности последовательно передается одним и тем же нейронам, которые отправляют свои прогнозы и следующий элемент последовательности обратно себе, пока последовательность не будет завершена. Как правило, такие сети используются для информационных последовательностей, в основном текстов и аудио/визуальных сигналов. Единицы RNN обычно обозначаются как обычные нейроны с дополнительной круговой стрелкой, которая показывает, что нейрон использует свое дополнительное скрытое состояние вместе с входящим сигналом. Если развернуть картинку, то получится цепочка одинаковых нейронов, каждый из которых получает элемент последовательности, выдает предсказание и передает его по цепочке как своего рода ячейку памяти. Следует иметь в виду, что это абстракция, так как один и тот же нейрон обрабатывает данные несколько раз.

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

Таким образом, моделирование памяти в нейронной сети добавляет к описанию процесса новое измерение — время. Представим, что мы вводим последовательность данных, например, текст, в нейронную сеть пословно или посимвольно. Затем каждый последующий элемент последовательности входит в нейрон в заданное время. К этому моменту нейрон уже сохранил результаты от начала ввода последовательности. В примере с солнцем x0 — это вектор, обозначающий предлог in, x1 — вектор, обозначающий слово небо и т. д. В конце концов, ht должен быть вектором, близким к слово солнце.

Основное различие между различными типами рекуррентных нейронов заключается в том, как они обрабатывают клетки памяти. Традиционный подход предлагает добавить два вектора (сигнал и память) и использовать, например, функцию tanh для вычисления активации. Получаем обычную сеть с одним скрытым слоем. Схема выглядит следующим образом:

Однако такая память очень кратковременна. Поскольку каждый раз, когда информация, хранящаяся в памяти, добавляется к информации в новом входном сигнале, она полностью перезаписывается за 5–7 итераций. Если вернуться к предсказанию последнего слова в предложении, то стоит отметить, что такая сеть будет достаточно хорошо функционировать в пределах одного предложения. Но в более длинном тексте зависимости в его начале не будут влиять на вычисления ближе к его концу. Кроме того, ошибка в одном из первых элементов последовательности больше не будет способствовать общей сетевой ошибке. Это очень абстрактное описание этого явления. По сути, это фундаментальная проблема нейронных сетей, проблема исчезающего градиента, вылившаяся в не менее чем третью зиму глубокого обучения в конце 20-го века, когда нейронные сети уступили место машинам опорных векторов и алгоритмы бустинга.

Чтобы устранить этот недостаток, была создана LSTM-RNN (рекуррентная нейронная сеть с кратковременной памятью) с внутренними преобразованиями, которые более бережно используют память. Вот схема:

Рассмотрим более подробно каждый слой.

Первый слой вычисляет степень, в которой он должен забыть предыдущую информацию. Фактически, он вычисляет множители для компонентов вектора памяти.

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

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

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

Таким образом, мы получаем ht и Ct, которые проходят дальше по сети. Без сомнения, существует множество вариаций функций активации, используемых каждым уровнем. Схемы можно немного видоизменить, но суть остается прежней: сначала нейроны забывают какую-то часть информации, затем запоминают часть нового сигнала и уже на основе этих данных вычисляют результат. Я использовал цифры отсюда. На этой странице также есть несколько примеров более сложных диаграмм LSTM.

Я не буду здесь подробно описывать обучение сети, а лишь упомяну, что оно использует алгоритм BPTT (обратное распространение во времени), обобщение стандартного алгоритма для случаев, когда сеть имеет измерение времени. Вы можете прочитать об этом алгоритме здесь или здесь.

Реализация LSTM-RNN

Рекуррентные нейронные сети, основанные на этих принципах, очень популярны. Вот несколько примеров таких проектов:

Также есть примеры случаев, когда сети LSTM успешно используются в качестве слоя в гибридных сетях. Ниже показан пример гибридной сети, отвечающей на визуальные вопросы, например «Сколько книг на картинке?».

В этом случае сеть LSTM работает вместе с модулем распознавания изображений (модель VIS+LSTM). Здесь можно найти сравнение различных гибридных архитектур для решения этой задачи.

Теано и Керас

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

Звучит здорово, за исключением того факта, что Theano сама генерирует и компилирует код C++. Это может быть моим предубеждением, но я очень подозрительно отношусь к таким системам, так как они обычно полны ошибок, которые очень трудно найти. Возможно, поэтому я так долго не обращал должного внимания на эту библиотеку. Тем не менее, Theano разрабатывался под руководством Йошуа Бенжио, одного из самых известных специалистов по глубокому обучению, в MILA (Монреальский институт алгоритмов обучения), и, конечно же, за свой недолгий опыт работы с ним я не обнаружил ошибок.< br />
Тем не менее, Theano — это всего лишь эффективная вычислительная библиотека, она требует обратного распространения, создания нейронов и т. д. Например, это код, написанный только на Theano, для той же LSTM-сети, которую я описал выше. Он содержит около 650 строк, что не соответствует заголовку этой статьи. И, возможно, я бы никогда не попробовал использовать Theano, если бы не потрясающая библиотека keras. Поскольку это всего лишь помощь в использовании интерфейса Theano, именно keras решает поставленную в заголовке задачу.

Любой код, написанный на keras, основан на модели, которая описывает типы и порядок слоев в вашей нейронной сети. Например, модель, которую мы использовали для оценки тональности твитов Звездных войн, вводила последовательность слов, поэтому ее тип был

модель = Последовательный()

При объявлении типа модели постепенно добавляются новые слои. Например, слой LSTM можно добавить командой this.

модель. добавить (LSTM (64))

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

model.compile(потеря=’binary_crossentropy’, оптимизатор=’adam’, class_mode=”binary”)

Компиляция занимает пару минут, после чего модель получает простые для понимания методы fit(), predict(), pred_proba() и Assessment(). Это оно. На мой взгляд, это идеальный способ начать погружение в глубокое обучение. Когда функционала keras недостаточно и хочется реализовать собственные функции потерь, например, можно спуститься на уровень ниже и написать часть кода в Theano. Кстати, если вы еще и боитесь программ, которые сами генерируют другие, в качестве бэкенда можно настроить свежий гугловский TensorFlow. Однако он работает гораздо медленнее.

Анализ тональности твитов

Вернемся к нашей основной задаче — определить, понравились ли Звездные войны российской аудитории. Я использовал простую библиотеку TwitterSearch как удобный инструмент для просмотра результатов поиска в Twitter. Как и все открытые API для больших систем, Twitter имеет некоторые ограничения. Библиотека позволяет каждый раз запрашивать обратный вызов, поэтому очень удобно делать паузы. Таким образом, мы загрузили около 50 000 твитов на русском языке со следующими хэштегами:

  • #Звездные войны
  • #Звездные войны
  • #Пробуждение Силы

Пока они скачивались, искал обучающую выборку. В свободном доступе можно найти несколько помеченных англоязычных корпусов твитов. Самой крупной из них является стэнфордская тренировочная выборка из вышеупомянутого настроения140. На сайте также есть список небольших наборов данных. Однако все они на английском языке, а задача была проанализировать русские твиты. В связи с этим хочу выразить особую благодарность Юлии Рубцовой, аспирантке (может быть, уже бывшей) Института систем информатики им. А.П. точность) в открытом доступе. Хотелось бы, чтобы в нашей стране было больше таких волонтеров, которые поддерживают общину. В общем, мы использовали этот набор данных. Прочитать и скачать его можно здесь.

Я отфильтровал твиты и оставил только сплошные кириллические и числовые последовательности, которые затем пропустил через PyStemmer. После этого я заменил те же слова на те же числовые коды. В результате у меня получился словарь примерно из 100 000 слов, а твиты стали числовыми последовательностями и были готовы к классификации. Я решил не фильтровать низкочастотный мусор, так как сеть умная и она поймет, что неактуально.

Это код нашей нейросети в керасе:

из последовательности импорта keras.preprocessing

из keras.utils импортировать np_utils

из keras.models импорт последовательный

из keras.layers.core импортировать Dense, Dropout, Activation

из keras.layers.embeddings импортировать

из keras.layers.recurrent импорт LSTM

max_features = 100000

макслен = 100

размер партии = 32

модель = Последовательный()

model.add (встраивание (max_features, 128, input_length = maxlen))

model.add (LSTM (64, return_sequences = True))

модель. добавить (LSTM (64))

model.add (Выпадение (0,5))

model.add (плотный (1))

model.add(Активация('сигмоид'))

model.compile (потеря = 'binary_crossentropy',

оптимизатор='адам',

class_mode="двоичный")

модель.фит(

X_поезд, у_поезд,

batch_size = размер_пакета,

nb_epoch=1,

show_accuracy = Истина

)

результат = модель.predict_proba(X)

За исключением импорта и объявления переменных, код занимает 10 строк, и его можно было написать в одну строку. Давайте посмотрим код. Сеть состоит из шести слоев:

1. Слой Embedding подготавливает объекты. По настройкам словарь содержит 100 000 признаков и сеть ожидает последовательности не более 100 слов.

2. Затем у нас есть два слоя LSTM. Первый выводит размер тензора batch_size/длина последовательности/единицы в LSTM, а второй выводит матрицу batch_size/единицы в LSTM. Флаг return_sequences=True устанавливается, чтобы второй слой понимал первый.

3. Слой Dropout используется для переобучения сети. Он обнуляет случайно выбранные признаки и предотвращает весовую коадаптацию (верим канадцам на слово).

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

5. Последний уровень активации подталкивает значение от 0 до 1, чтобы оно стало значением вероятности. Фактически, этот порядок Плотности и Активации является логистической регрессией.

Чтобы включить обучение на GPU во время работы кода, нужно установить соответствующий флаг. Например, это можно сделать так:

THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python myscript.py

Обучение этой модели на GPU было почти в 20 раз быстрее, чем на CPU. Обработка набора данных из 160 000 твитов (1/3 использовалась для валидации) заняла около 500 секунд.

Четких правил построения топологии сети для таких задач нет. Мы потратили полдня на эксперименты с различными конфигурациями, и эта дала наивысшую точность 75%. Мы сравнили сетевые прогнозы с логистической регрессией, которая дала точность 71% для того же набора данных с использованием векторизации текста tf-idf и почти такую ​​же точность 75% при использовании tf-idf для биграмм. Причина, по которой нейронная сеть почти не обогнала логистическую регрессию, скорее всего, кроется в зашумленной и слишком короткой обучающей выборке (откровенно говоря, такой сети нужна обучающая выборка твитов не менее 1 млн твитов). Обучение было одномоментным, так как мы также наблюдали сильную перетренированность.

Модель предсказала вероятность того, что твит будет положительным. Мы оценили отзывы с вероятностью более 0,65 как «положительные», с вероятностью менее 0,45 — как «отрицательные», а те, что находятся в промежутке между ними, — как «нейтральные». Суточная динамика выглядит следующим образом:

В целом, мы видим, что людям фильм понравился. Хотя я лично нет. :)

Примеры работы сети

Я выбрал пять образцов твитов из каждой группы (число — это вероятность положительного отзыва):

Положительное настроение

0.9945:

Теперь я могу выдохнуть, новые Звездные войны обладают этим олдскульным превосходством. Абрамс как всегда крут. Сценарий, музыка, актеры и съемки на высоте. — сноуденни (@maximlupashko) 17 декабря 2015 г.

0.9171:

Всем рекомендую посмотреть Звездные войны, фильм супер — николай (@shans9494) 22 декабря 2015

0.8428:

СИЛА ПРОБУДИЛАСЬ! ПУСТЬ СИЛА С ВАМИ СЕГОДНЯ, НА ПРЕМЬЕРЕ ЧУДА, КОТОРОГО МЫ ЖДАЛИ 10 ЛЕТ! #TheForceAwakens #StarWars — Владислав Иванов (@Mrrrrrr_J) 16 декабря 2015 г.

0.8013:

Хоть я и не фанат #ЗвездныхВойн, но этот спектакль великолепен! #StarWarsForceAwakens https://t.co/1hHKdy0WhB — Оксана Сторожук (@atn_Oksanasova) 16 декабря 2015 г.

0.7515:

Кто сегодня смотрел звездные войны? я меня меня )) — Анастасия Ананич (@NastyaAnanich) 19 декабря 2015

Смешанное мнение

0.6476:

Новые Звездные войны лучше первой серии, но хуже остальных — Игорь Ларионов (@Larionovll1013) 19 декабря 2015

спойлер

0.6473:

Хан Соло умирает. Приятного просмотра. #звездныевойны — Ник Силикон (@nicksilicone) 16 декабря 2015 г.

0.6420:

Звездные войны повсюду. Я один не в ногу? :/ — Ольга (@dlfkjskdhn) 19 декабря 2015 г.

0.6389:

Смотреть или не смотреть Звездные войны, вот в чем вопрос — annet_p (@anitamaksova) 17 декабря 2015

0.5947:

У меня смешанные чувства по поводу Звездных войн. И хорошо, и не очень. Иногда я не чувствовал, что это те же старые Звездные войны… Я чувствовал что-то инопланетное — Евгений Колот (@KOLOT1991) 21 декабря 2015

Отрицательное мнение

0.3408:

Повсюду столько разговоров о Звездных войнах, я один не фанат? #StarWars #StarWarsTheForceAwakens — современный разум (@modernmind3) 17 декабря 2015 г.

0.1187:

они вырвали мое бедное сердце из груди и разбили его на миллионы и миллионы кусочков #Звездные войны — Реми Эванс (@Remi_Evans) 22 декабря 2015 года

0.1056:

ненавижу своих одноклассников, они испортили мне звездные войны — пижама Найла (@harryteaxxx) 17 декабря 2015

0.0939:

Я проснулся и понял, что новые Звездные войны меня разочаровали. — Тим Фрост (@Tim_Fowl) 20 декабря 2015 г.

0.0410:

Я разочарован #пробуждениесилы — Eugenjkee; Звездные войны (@eugenjkeee) 20 декабря 2015 г.

P.S. После завершения опроса мы наткнулись на статью, восхваляющую сверточные сети для этой задачи. Мы попробуем их в следующий раз. keras также поддерживает ConvNets. Если вы решите попробовать их, комментарии приветствуются. Да прибудет с вами Сила больших данных!