Хочу поделиться опытом своего первого участия в kaggle конкурсе (учебный Bag of Words). И хотя мне не удалось достичь поражающих воображение результатов, я расскажу о том, как искала и находила способы улучшить примеры “учебника” (для этого сами примеры тоже кратко опишу), а также остановлю внимание на разборе своих просчетов. Должна предупредить, что статья будет интересна прежде всего новичкам в области text mining. Тем не менее, большинство методов я описываю кратко и упрощенно, давая при этом ссылки на более точные определения, поскольку цель моя — обзор практики, а не теории. К сожалению, конкурс уже завершился, но прочитать материалы к нему все равно может быть полезно. Ссылка на код к статье тут.
Само задание состоит в анализе эмоциональной окраски текста. Для этого взяты отзывы и оценки к фильмам с сайта IMDb. Отзывы с оценкой >=7 считаются позитивно окрашенными, с оценкой меньше — негативно. Задача: обучив модель на тренировочных данных, где каждому тексту проставлена оценка (негативный/позитивный), затем предсказать этот параметр для текстов из тестового сета. Качество предсказания оценивается с помощью параметра под названием ROC-кривая. Подробно можно прочитать по ссылке, но чем ближе этот параметр к 1 — тем точнее предсказание.
Все примеры написаны на языке Python и используют библиотеку scikit-learn, которая позволяет использовать готовые реализации всех нужных нам классификаторов и векторизаторов.
В нашем распоряжении чистый текст, а все data mining классификаторы требуют на вход числовые векторы. Поэтому первейшая задача — определиться со способом преобразования текстового блока в вектор (векторизация).
Самый простой метод — Bag of Words, с которого начинается первый пример учебника. Метод заключается в создании общего пула используемых слов, каждому из которых присваивается свой индекс. Предположим, что у нас есть два простых текста:
Каждому уникальному слову поставим в соответствие индекс:
«John»: 1,
«likes»: 2,
«to»: 3,
«watch»: 4,
«movies»: 5,
«also»: 6,
«football»: 7,
«games»: 8,
«Mary»: 9,
«too»: 10
Теперь каждое из этих предложений можно представить в виде вектора размерности 10, в котором число x в i-ой позиции означает, что слово под номером i встречается в тексте x раз:
Подробнее в википедии или в этой обзорной статье, посвященной тому же конкурсу.
В общих чертах все просто, но дьявол в деталях. Во-первых, в примере удаляются слова (a, the, am, i, is…), которые не несут никакой смысловой нагрузки. Во-вторых, операции с этой матрицей производятся в оперативной памяти, таким образом, объем памяти ограничивает допустимую размерность матрицы. Мне чтобы избежать “MemoryError” пришлось уменьшать пул слов до 7000 наиболее частых. В качестве классификатора во всех примерах учебника используется Random Forest classifier.
Далее нас призывают поэкспериментировать с различными параметрами, что мы и сделаем. Первое очевидная мысль — добавить лемматизацию, т.е. привести все слова к их словарным формам. Для этого используем функцию из библиотеки nltk:
Другая хорошая идея — немного поменять метод векторизации текста. Вместо простой характеристики “сколько раз слово встретилось в тексте” можно использовать чуть более сложную, но тоже хорошо известную — tf-idf (она присваивает словам ценность в зависимости от их редкости в коллекции документов).
Отправив на проверку результаты исходной и модифицированной программы, получаем улучшение с 0.843 до 0.844. Это не очень много. Используя этот пример как основу можно хорошенько поэкспериментировать и получить гораздо лучшие результаты. Но у меня было не так много времени, а, следовательно, и попыток (они ограничены 5-ю в день). Поэтому я приступила к следующим частям.
Следующие части учебника строятся на библиотеке под названием Word2vec, которая дает нам представление слов в виде числового вектора. Причем эти вектора обладают интересными свойствами. Например, минимальное расстояние между векторами будет у наиболее похожих по смыслу слов.
Итак, преобразовав все слова, получаем список векторов для каждого отзыва. Как преобразовать его в единый вектор? Первый вариант — просто вычислить среднее арифметическое (average vector). Результат, даже хуже, чем Bag of Words (0.829).
Как можно было бы улучшить этот метод? Ясно, что нет смысла усреднять все слова, слишком много среди них мусора, не влияющего на эмоциональную окраску текста. Интуитивно кажется, что сильнее всего будут влиять оценочные прилагательные и, возможно, некоторые другие слова. На наше счастье, существуют методы под общим названием feature selection, которые позволяют оценить, насколько сильно тот или иной параметр (в нашем случае слово) коррелирует со значением результирующей переменной (эмоциональной окраской). Применим один из таких методов и взглянем, на выбранные слова:
В результате получаем такой список слов, который подтверждает теорию:
Если теперь вычислять average vector, но учтя только слова из топ-листа (который мы после пары экспериментов расширим до 500 слов), то получим результат лучше (0.846), который обходит даже (хотя и ненамного) bag of centroids из следующего учебного примера этого конкурса. В этом решении (обозначим его как average of top words) в качестве классификатора тоже использовался Random Forest.
На этом количество моих попыток, и собственно конкурс, подошли к концу, и я отправилась на форум выяснять, как решали эту задачу люди более опытные. Я не буду касаться решений, которые получили действительно отличный результат (более 0.96) потому, что они как правило довольно сложные и многоходовые. Но укажу на некоторые варианты, которые позволили получить высокую точность простыми методами.
Например, указание на то, что удалось достичь хорошего результата с помощью простого tf-idf и логистической регрессии, побудило меня исследовать другие классификаторы. При прочих равных (TfidfVectorizer с ограничением в 7000 столбцов) LogisticRegression дает результат — 0.88, LinearRegression — 0.91, Ridge regression — 0.92.
Если бы я использовала линейную регрессию в своем решении (average of top words) вместо Random forest, то получила бы результат 0.93 вместо 0.84. Таким образом первая моя ошибка была в том, что я считала, что метод векторизации влияет сильнее, чем выбор классификатора. На ошибочные мысли меня натолкнул материал учебной статьи, но мне следовало все самостоятельно проверить.
Вторую идею я извлекла, внимательнее взглянув на код этого примера. Нюанс в том, как именно был использован TfidfVectorizer. Был взят сет из объединенных тестовых и тренировочных данных, не ставились ограничения на максимальное количество столбцов, более того, фичи формировались не только из отдельных слов, но и из пар слов (параметр ngram_range=(1, 2)). Если ваша программа не падает с MemoryError от такого объема, то это существенно повышает точность предсказания (автор заявил результат 0.95). Вывод номер два — точность можно повысить ценой большего объема вычислений, а не каких-то особо хитрых методов. Для этого можно, например, прибегнуть к какому-нибудь сервису для облачных вычислений, если собственный комп не слишком мощный.
В качестве заключения хочу сказать, что участвовать в конкурсе kaggle было крайне интересно, и ободрить тех, кто пока не решился по каким-либо причинам. Разумеется, на kaggle встречаются и гораздо более сложные конкурсы, для первого раза стоит выбирать задачу по силам. И последний совет — читайте форум. Даже в течение конкурса там публикуют полезные советы, идеи, а иногда даже целые решения.
Обзор конкурса
Само задание состоит в анализе эмоциональной окраски текста. Для этого взяты отзывы и оценки к фильмам с сайта IMDb. Отзывы с оценкой >=7 считаются позитивно окрашенными, с оценкой меньше — негативно. Задача: обучив модель на тренировочных данных, где каждому тексту проставлена оценка (негативный/позитивный), затем предсказать этот параметр для текстов из тестового сета. Качество предсказания оценивается с помощью параметра под названием ROC-кривая. Подробно можно прочитать по ссылке, но чем ближе этот параметр к 1 — тем точнее предсказание.
Все примеры написаны на языке Python и используют библиотеку scikit-learn, которая позволяет использовать готовые реализации всех нужных нам классификаторов и векторизаторов.
Методы решения задачи
В нашем распоряжении чистый текст, а все data mining классификаторы требуют на вход числовые векторы. Поэтому первейшая задача — определиться со способом преобразования текстового блока в вектор (векторизация).
Самый простой метод — Bag of Words, с которого начинается первый пример учебника. Метод заключается в создании общего пула используемых слов, каждому из которых присваивается свой индекс. Предположим, что у нас есть два простых текста:
John likes to watch movies. Mary likes movies too.
John also likes to watch football games.
Каждому уникальному слову поставим в соответствие индекс:
«John»: 1,
«likes»: 2,
«to»: 3,
«watch»: 4,
«movies»: 5,
«also»: 6,
«football»: 7,
«games»: 8,
«Mary»: 9,
«too»: 10
Теперь каждое из этих предложений можно представить в виде вектора размерности 10, в котором число x в i-ой позиции означает, что слово под номером i встречается в тексте x раз:
[1, 2, 1, 1, 2, 0, 0, 0, 1, 1]
[1, 1, 1, 1, 0, 1, 1, 1, 0, 0]
Подробнее в википедии или в этой обзорной статье, посвященной тому же конкурсу.
В общих чертах все просто, но дьявол в деталях. Во-первых, в примере удаляются слова (a, the, am, i, is…), которые не несут никакой смысловой нагрузки. Во-вторых, операции с этой матрицей производятся в оперативной памяти, таким образом, объем памяти ограничивает допустимую размерность матрицы. Мне чтобы избежать “MemoryError” пришлось уменьшать пул слов до 7000 наиболее частых. В качестве классификатора во всех примерах учебника используется Random Forest classifier.
Далее нас призывают поэкспериментировать с различными параметрами, что мы и сделаем. Первое очевидная мысль — добавить лемматизацию, т.е. привести все слова к их словарным формам. Для этого используем функцию из библиотеки nltk:
from nltk import WordNetLemmatizer
wnl = WordNetLemmatizer()
meaningful_words = [wnl.lemmatize(w) for w in meaningful_words]
Другая хорошая идея — немного поменять метод векторизации текста. Вместо простой характеристики “сколько раз слово встретилось в тексте” можно использовать чуть более сложную, но тоже хорошо известную — tf-idf (она присваивает словам ценность в зависимости от их редкости в коллекции документов).
Отправив на проверку результаты исходной и модифицированной программы, получаем улучшение с 0.843 до 0.844. Это не очень много. Используя этот пример как основу можно хорошенько поэкспериментировать и получить гораздо лучшие результаты. Но у меня было не так много времени, а, следовательно, и попыток (они ограничены 5-ю в день). Поэтому я приступила к следующим частям.
Следующие части учебника строятся на библиотеке под названием Word2vec, которая дает нам представление слов в виде числового вектора. Причем эти вектора обладают интересными свойствами. Например, минимальное расстояние между векторами будет у наиболее похожих по смыслу слов.
Итак, преобразовав все слова, получаем список векторов для каждого отзыва. Как преобразовать его в единый вектор? Первый вариант — просто вычислить среднее арифметическое (average vector). Результат, даже хуже, чем Bag of Words (0.829).
Как можно было бы улучшить этот метод? Ясно, что нет смысла усреднять все слова, слишком много среди них мусора, не влияющего на эмоциональную окраску текста. Интуитивно кажется, что сильнее всего будут влиять оценочные прилагательные и, возможно, некоторые другие слова. На наше счастье, существуют методы под общим названием feature selection, которые позволяют оценить, насколько сильно тот или иной параметр (в нашем случае слово) коррелирует со значением результирующей переменной (эмоциональной окраской). Применим один из таких методов и взглянем, на выбранные слова:
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
select = SelectKBest(chi2, k=50)
X_new = select.fit_transform(train_data_features, train["sentiment"])
names = count_vectorizer.get_feature_names()
selected_words = np.asarray(names)[select.get_support()]
print(', '.join(selected_words))
В результате получаем такой список слов, который подтверждает теорию:
acting, amazing, annoying, avoid, awful, bad, badly, beautiful, best, boring, brilliant, crap, dull, even, excellent, fantastic, favorite, great, highly, horrible, just, lame, laughable, love, loved, mess, minutes, money, no, nothing, oh, pathetic, perfect, plot, pointless, poor, poorly, ridiculous, save, script, stupid, superb, supposed, terrible, waste, wasted, why, wonderful, worse, worst
Если теперь вычислять average vector, но учтя только слова из топ-листа (который мы после пары экспериментов расширим до 500 слов), то получим результат лучше (0.846), который обходит даже (хотя и ненамного) bag of centroids из следующего учебного примера этого конкурса. В этом решении (обозначим его как average of top words) в качестве классификатора тоже использовался Random Forest.
Работа над ошибками
На этом количество моих попыток, и собственно конкурс, подошли к концу, и я отправилась на форум выяснять, как решали эту задачу люди более опытные. Я не буду касаться решений, которые получили действительно отличный результат (более 0.96) потому, что они как правило довольно сложные и многоходовые. Но укажу на некоторые варианты, которые позволили получить высокую точность простыми методами.
Например, указание на то, что удалось достичь хорошего результата с помощью простого tf-idf и логистической регрессии, побудило меня исследовать другие классификаторы. При прочих равных (TfidfVectorizer с ограничением в 7000 столбцов) LogisticRegression дает результат — 0.88, LinearRegression — 0.91, Ridge regression — 0.92.
Если бы я использовала линейную регрессию в своем решении (average of top words) вместо Random forest, то получила бы результат 0.93 вместо 0.84. Таким образом первая моя ошибка была в том, что я считала, что метод векторизации влияет сильнее, чем выбор классификатора. На ошибочные мысли меня натолкнул материал учебной статьи, но мне следовало все самостоятельно проверить.
Вторую идею я извлекла, внимательнее взглянув на код этого примера. Нюанс в том, как именно был использован TfidfVectorizer. Был взят сет из объединенных тестовых и тренировочных данных, не ставились ограничения на максимальное количество столбцов, более того, фичи формировались не только из отдельных слов, но и из пар слов (параметр ngram_range=(1, 2)). Если ваша программа не падает с MemoryError от такого объема, то это существенно повышает точность предсказания (автор заявил результат 0.95). Вывод номер два — точность можно повысить ценой большего объема вычислений, а не каких-то особо хитрых методов. Для этого можно, например, прибегнуть к какому-нибудь сервису для облачных вычислений, если собственный комп не слишком мощный.
В качестве заключения хочу сказать, что участвовать в конкурсе kaggle было крайне интересно, и ободрить тех, кто пока не решился по каким-либо причинам. Разумеется, на kaggle встречаются и гораздо более сложные конкурсы, для первого раза стоит выбирать задачу по силам. И последний совет — читайте форум. Даже в течение конкурса там публикуют полезные советы, идеи, а иногда даже целые решения.
Комментарии (5)
zyrik
24.07.2015 15:54+1А почему Вы не используете кросс-валидацию, а проверяете всё на сервере?
P.S. Удачи в следующих конкурсах!Jaylla Автор
24.07.2015 17:37Резонное замечание. Разумеется, следовало использовать кросс-валидацию. Единственная причина в том, что ее использование требует разобраться в некоторых дополнительных моментах (например, как выбирать оптимальное соотношение обучающей и контрольной выборки). Но к следующему разу я проясню для себя эти вещи.
ArtemE
Вы большая молодец! Мне когда пришлось с командой участвовать в конкурсе по иерархической классификации текста — это было очень увлекательно, но дело заглохло.
Кстати, многие действительно запускают обучение в облаке для получения нормальных результатов. Сейчас это очень даже дешево.
Jaylla Автор
Спасибо за похвалу!
Иерархическая классификация – задача более сложная. К тому же организовать несколько человек труднее, чем самого себя (особенно если у них хватает проблем и помимо конкурса). Но опыт командной работы – несомненно ценней, чем одиночной.
ArtemE
Конечно, задача посложнее будет. Но принципы те же. Та же векторизация, тоже нужно чистить вектор по умному, чтобы уменьшить размерность. С таких простых ходов строятся схемы получения ответов в сложных задачах.