Awesome
Модели для решения задач AGRR-2019
Далее описываются мои подходы к решению задачи AGGR-2019. Я не стал включать реализацию с использованием BERT, так как из-за ограниченных вычислительных ресурсов мне не удалось повысить сложность BERT'а до необходимого уровня и обойти базовую нейросетевую модель (см. далее "Модель для бинарной классификации") по точности.
Используемые внешние библиотеки
Весь код написан на Python, должен работать на 2 и 3 версиях. Для нейросетевых моделей используется Keras, бэкэнд - tensorflow.
Также используются следующие библиотеки и пакеты:
rutokenizer - простой питоновский модуль для токенизации русскоязычных текстов, правильно учитывающий составные слова с дефисами ("какой-либо").
ruword2tags - русская морфологическая база с информацией о грамматических свойствах большого количества слов.
rusyllab - разбивка слов на слоги; это использовалось в ходе исследования задачи как альтернатива SentencePiece.
rupostagger - частеречная разметка предложений (на базе "классической" библиотеки CRFSuite).
gensim - обучение векторных моделей слов.
hyperopt - автоматизированный подбор архитектуры и гиперпараметров моделей
keras-contrib - используется слой CRF для sequence labeling и оптимизатор FTML.
Также использовались стандартные для таких задач pandas, numpy, sklearn и другие библиотеки.
Подготовка векторной модели word2vector
Выбранные в ходе оптимизации архитектуры модели используют векторные модели слов, полученные на русскоязычном корпусе с помощью gensim. Код w2v/w2v.py для тренировки с использованием gensim тривиален. Для решения AGRR выбран вариант с параметрами:
model = gensim.models.word2vec.Word2Vec(sentences,
size=64,
window=5,
cbow_mean=1,
min_count=2,
sorted_vocab=1,
iter=1)
Единственный нюанс, на которых хочется обратить внимание, связан с препроцессингом текста. В отличие от большинства аналогичных моделей, я НЕ убираю пунктуацию из текста, на котором учится w2v. Связано это с тем, что задача AGRR в значительной степени опирается на правильный учет некоторых символов пунктуации, в частности "-". Консистентное встраивание пунктуаторов в общее векторное пространство позволяет решать задачу точнее. Соответствующая подготовка корпуса с разбивкой сырых текстов на токены и сохранением пунктуации выполняется с помощью упомянутого выше пакета rutokenizer (https://github.com/Koziev/rutokenizer).
Векторная аппроксимация морфологии wordchar2vector
Код модели (см. каталог wc2v) с минимальными изменениями взят из проекта чатбота.
Основная идея использования следующая.
Пакет ruword2tags позволяет для большинства слов, включая отсутствующие в исходном словаре, определить грамматические признаки - падеж, число, род, время, вид и так далее, включая категории частей речи - существительное, глагол, прилагательное, наречие.
Но такое деление достаточно грубое, многие важные грамматические отличия слов при этом теряются, не говоря уже об оттенках смысла. Например, глаголы "запеть" и "допеть" отличаются способом глагольного действия и выражают разные идеи законченности действия.
Самый простой способ донести до нейросетевых моделей такую информацию, не занимаясь трудоемкой ручной разметкой слов заключается в разбивке слов на слоги. Действительно, многие приставки легко отделяются формальными правилами слогоделения: за-петь, до-петь. Именно для этого используется библиотечка rusyllab.
Но есть другой способ - посимвольное векторное встраивание слов. Соответствующая модель тренируется как автоэнкодер, который учится для произвольной цепочки символов (слова) давать вектор фиксированного размера, из которого можно восстановить слово. В получающийся вектор попадает информация о морфемном составе слов, так как морфемы хорошо коррелируют с частотными n-граммами и учитываются автоэнкодером для сжатия информации. Кроме того, wc2v векторы неявно содержат информацию о посимвольной близости слов, что позволяет нейросетевым моделям лучше обобщаться: обучившись на сэмпле со словом "быть", модели с большей вероятностью будут делать правильный инференс для сэмплов со словом "бывать". Для некоторых слов такое обобщение очень важно в силу большого дисбаланса в частотах употребления. Например, в моем 120Гб корпусе русских текстов слово "чтобы" встречается 18394181 раз, а вариант "чтоб" - 1109921, то есть более чем на порядок реже. Так как автоэнкодерная модель wc2v учится на словах без учета их частотности, то векторы для "чтобы" и "чтоб" содержат примерно равные по качеству представления.
Обучение модели wc2v идет достаточно долго, на GTX1080 Ti до нескольких часов. В результате на диске сохраняется модель (файл с описанием архитектуры и веса), которая затем загружается и используется для получения векторов слов в задачах AGRR.
Модель для бинарной классификации (первая задача AGRR)
Задача заключается в определении, содержит ли предложение пропущенное сказуемое:
Я превращу твое сердце в траву, а все, что ты любишь, в овец. - есть пропуск Все это сведения не из Библии, а из Протоевангелия Иакова - нет пропуска
Нейросетевая модель реализована в коде gridsearch_model1.py.
Для поиска оптимальной архитектуры сетки и подбора гиперпараметров нужно в строке 80 задать флаг do_hyperopt=True, а в строке 88 - флаг run_best=False.
В ходе поиска программа будет сохранять текущую лучшую архитектуру и прочие параметры в файле tmp\gridsearch_model1.best_params.json. Поиск архитектуры занимает очень много времени (сутки), поэтому по достижении некоторой приемлемой точности можно просто прервать работу, поменять значения флагов на do_hyperopt=False, run_best=True, и запустить модель в режиме обработки тестовых данных соревнования. В результате инференса будет сохранен файл с результатами бинарной классификации предложений в файле data/input_file.txt.
Для итоговой разметки выбрана рекуррентная нейросеть (bidirectional LSTM), работающая с цепочками слов. Слова представляются соединением векторов word2vector, wordchar2vector и грамматических тегов слов (ruword2tags).
Модели для разметки фрагментов предложений (вторая задача AGRR)
Более сложная часть задачи AGRR - посимвольная разметка частей предложения на шесть меток. Вот пример того, как это выглядит:
Я превращу твое сердце в траву, а все, что ты любишь, в овец.
cV=2:10
cR1=11:22
cR2=23:30
V=54:54
R1=34:52
R2=54:60
По символам (cV=1, cR1=2, cR2=3, R1=5, R2=6):
Я превращу твое сердце в траву, а все, что ты любишь, в овец.
--11111111-22222222222-3333333----555555555555555555--666666
Можно увидеть, что разметка для пяти меток (cV, cR1, cR2, R1 и R2) идет строго по границам слов. Для метки V помечается первый символ слова, перед которым нужно вставить пропущенный глагол.
Разметка слов в предложении хорошо решается методами sequence labeling. В частности, для нейросетевых моделей отлично работает слой CRF в keras-contrib. За счет учета широкого контекста, поставляемого рекуррентным слоем LSTM, и глобальной оптимизации цепочки меток, достигается неплохая точность.
Чтобы упростить реализацию моделей, я разделил обучение для меток (cV, cR1, cR2, R1 и R2) и V на два отдельных этапа. В файле gridsearch_model2_kerascrf.py в строках 74 и 75 задается группа меток, для которой выполняется обучение или инференс.
Сначала надо выполнить поиск архитектуры для выбранной группы метод. Для этого в строке 66 выставляется флаг do_hyperopt=True, в строке 71 run_best=False. Поиск будет идти очень долго, вплоть до нескольких суток. По мере нахождения лучшего набора гиперпараметров они сохраняются в json-файле. Поэтому можно прервать поиск по достижению разумного качества.
Для прогона тестовых данных соревнования надо задать флаги do_hyperopt=False и run_best=True, опять для каждой группы меток. Результаты разметки будут сохранены в текстовом файле.