Эта статья подготовлена по мотивам (первой части) учебного задания Bag of Words Kaggle, но это не перевод. Оригинальное задание сделано на Python. Я же хотел оценить возможности языка R для обработки текстов на естественном языке и заодно попробовать реализацию Random Forest в обертке R-пакета caret.
Смысл задания – построить «машину», которая будет определенным образом обрабатывать обзоры фильмов на английском языке и определять тональность обзора, относя его к одному из двух классов: негативные/позитивные. В качестве обучающей выборки в задании используется набор данных с двадцатью пятью тысячами ревю из IMDB, размеченных неизвестными добровольцами.
Код для статьи и подготовки задания можно найти в моем репозитарии на GitHub. Для его выполнения потребуется R версии 3.1.3 (если не обращать внимания на предупреждения от пакетов, то в 3.1.2 тоже всё будет работать). Дополнительно необходимо установить следующие пакеты:
- tm — набор инструментов для работы с текстами
- caret – обертка, поддерживающая более 100 методов машинного обучения
- randomForest — пакет, реализующий одноименный алгоритм
- SnowballC — вспомогательный пакет для лемматизации в tm
- e1071 — пакет дополнительных функций для randomForest
Задачу в сокращенном виде (тысяча обзоров и около ста наиболее часто встречающихся терминов) можно сделать на машине с 32-битной ОС, однако для полной версии потребуется 64 бита и 16 Гб RAM. Во время обучения модели используется около 12 Гб физической памяти.
Обучающая выборка представляет собой набор данных разделенных табуляцией. Контрольная выборка также содержит 25000 ревю, но естественно без разметки. Файлы можно загрузить с этой страницы. Для этого потребуется регистрация на Kaggle.
Предварительная обработка данных
После загрузки и распаковки рекомендую проверить размерность, должно быть 25000 строк на три столбца. У меня это получилось не сразу из-за кавычек в тексте. Я экспериментировал на первой записи. Соответствующий обзор довольно длинный, 2304 знака, поэтому привожу только его фрагмент:
"" Moonwalker is part biography, part feature film which i remember going to see at the cinema when it was originally released. Some of it has subtle messages about MJ's feeling towards the press and also the obvious message of drugs are bad m'kay.<br /><br />Visually impressive but of course this is all about Michael Jackson so unless you remotely lik ..."
В тексте есть знаки препинания, HTML-теги и специальные символы от которых необходимо было избавиться. Для начала я очистил ревю от HTML. Потом убрал знаки препинания, спецсимволы, цифры. Вообще говоря, при подобном анализе некоторые конструкции из знаков препинания, вроде смайликов, лучше не удалять, а преобразовывать в слова, но я этим заниматься не стал, просто преобразовал все символы в нижний регистр и разобрал на отдельные слова. Осталось убрать слова общей лексики, которые не добавляли смысла в обзор. Список таких слов для английского языка я взял из пакета tm. Ради интереса посмотрел на первые 20:
«i» «me» «my» «myself» «we» «our» «ours» «ourselves» «you» «your» «yours» «yourself» «yourselves» «he» «him» «his» «himself» «she» «her» «hers»
Далее я повторил перечисленные выше операции для всех 25000 записей в обучающей выборке. Заодно убрал однобуквенные слова, оставшиеся от конструкций типа MJ's.
Мешок слов
Мешок слов (или Bag of Words) это модель текстов на натуральном языке, в которой каждый документ или текст выглядит как неупорядоченный набор слов без сведений о связях между ними. Его можно представить в виде матрицы, каждая строка в которой соответствует отдельному документу или тексту, а каждый столбец — определенному слову. Ячейка на пересечении строки и столбца содержит количество вхождений слова в соответствующий документ.
Для подготовки Мешка слов я воспользовался возможностями пакета tm. Этот пакет в качестве объекта работает с так называемым лингвистическим корпусом первого порядка — коллекцией текстов, объединенных общим признаком. В нашем случае — все тексты это обзоры фильмов. Для того чтобы составить корпус сначала нужно преобразовать тексты в вектор, каждый элемент которого представляет отдельный документ. А потом построить на базе корпуса матрицу «документ-термин». Она и станет мешком слов.
Сначала у меня получилась матрица в 25000 документов и 73759 терминов. Очевидно, что терминов было слишком много. После лемматизации, иными словами, приведения словоформы к лемме — нормальной, словарной форме, осталось 49549 терминов. Это было всё равно очень много. Поэтому я убрал слова, которые особенно редко встречаются в обзорах. В оригинальном задании использовалось 5000 слов, к сожалению, параметр разреженности нелинейно влияет на количество терминов, поэтому, варьируя его, я остановился на цифре 5072.
Вот так выглядел фрагмент Мешка с наиболее часто употребляемыми словами:
Docs act actor actual also anoth back bad best better can 1 0 0 1 2 1 0 3 0 0 1 2 0 0 0 0 0 0 0 0 0 1 3 0 1 0 0 0 0 1 0 1 0 4 0 1 0 1 2 0 0 0 0 1
Обучение модели
Для построения модели я выбрал пакет randomForest в обертке caret. Caret позволяет гибко управлять процессом обучения, имеет встроенные возможности для предобработки данных и контроля качества обучения. К тому с caret же можно использовать сразу несколько ядер процессора.
На реальной выборке (25000 x 5072) с заданным количеством деревьев, nree=100, обучение заняло около 110 часов на компьютере с OS X 10.8, 2.3 ГГц Intel Core i7, 16 Гб 1600 MГц DDR3.
Замечу, что при использовании caret последовательно обучается сразу три модели с разными значениями mtry, а затем по показателю Точность (Accuracy) выбирается оптимальная. mtry задает количество терминов, которые случайным образом выбираются при каждом разветвлении дерева.
По умолчанию при обучении модели предполагается использовать бутстреппинг с 25 циклами. В целях экономии машинного времени я заменил его на скользящий контроль с пятью группами, т.е. в каждом цикле 80% используется для обучения, 20% для контроля.
Лучшая модель для сокращенной выборки показала точность в 0.71. Это означало, что примерно 29% всех ревю в результате будут классифицированы неверно. Но это на контрольной выборке, пусть даже со скользящим контролем. На тестовой выборке точность будет несколько хуже. Но для нашего сокращенного набора данных это не так уж и плохо.
Функция varImp из пакета caret позволяет получить перечень показателей, использованных для обучения модели, в порядке убывания важности. Первые 10 выглядели вполне ожидаемо:
term rate
- great 100.00000
- bad 81.79039
- movi 59.40987
- film 53.80526
- even 42.43621
- love 39.98373
- time 38.14139
- best 36.50986
- one 36.36246
- like 35.85307
Прогнозирование и проверка в Kaggle
Тестовую выборку я обрабатывал точно также как и обучающую за одним исключением – словарь, полученный из обучающей выборки, использовался для отбора терминов в тестовой.
После того как у меня получилась матрица документы/термины, я преобразовал её в датафрейм, который отправил в predict() для прогнозирования по обученной модели. Прогноз сохранил в csv и через форму на сайте отправил в Kaggle.
Результат достаточно скромный, 231 из 277, но всё же моя «машина» ошиблась примерно в одном случае из шести, что внушает некоторый оптимизм.
Как оказалось, возможностей R вполне достаточно для обработки текстов на естественном языке. К сожалению, у меня нет данных о том, насколько быстрее задача выполняется на Python. Пакет tm не единственный доступный инструмент, в R есть интерфейсы к OpenNLP, Weka и целый ряд пакетов, позволяющих решать отдельные задачи. Более-менее актуальный перечень инструментом можно посмотреть здесь. Существует даже реализация Google word2vec и, несмотря на то, что она еще в разработке, можно попробовать сделать вторую часть задания Kaggle, где word2vec является центральным технологическим компонентом.
Автор: khmelkoff