В нашем блоге мы много пишем о создании email-рассылок и работе с электронной почтой. Мы уже обсудили сложности борьбы со спамом, будущее email, вопросы защиты почтовой переписки, а также техники работы с email, применяемые руководителями крупных ИТ-компаний.
В современном мире люди получают множество писем, и в полный рост встает проблема с их классификацией и упорядочиванием почтового ящика. Инженер из США Андрей Куренков в своем блоге рассказал о том, как решил эту задачу с помощью нейронной сети. Мы решили осветить ход этого проекта и представляем вам первую часть рассказа.
Код проекта доступен здесь.
Начало
Куренков пишет, что один из его любимых мини-проектов, EmailFiler, появился благодаря заданию на курсе «Введение в машинное обучение» в Технологическом Институте Джорджии. Задание состояло в том, чтобы взять какой-либо набор данных, применить к нему ряд контролируемых обучающихся алгоритмов и проанализировать результаты. Суть в том, что по желанию можно было использовать собственные данные. Разработчик так и поступил – экспортировал данные своего gmail для исследования возможностей машинного обучения в решении задачи сортировки электронной почты.
Куренков пишет, что давно понял, что электронные письма лучше всегда иметь под рукой на случай, если вдруг когда-нибудь понадобится к ним обратиться. С другой стороны, ему всегда хотелось свести количество непрочитанных входящих сообщений к нулю, какой бы безнадежной ни была эта идея. Поэтому много лет назад инженер начал сортировать электронные письма примерно по дюжине папок, и к моменту поступления на курсы машинного обучения, в этих папках накопились тысячи писем.
Так возникла идея проекта по созданию классификатора, который мог бы предложить единственную категорию для каждого входящего письма – так, чтобы отправлять письмо в соответствующую папку одним нажатием кнопки.
Категории (папки) и количество писем в каждой из них на момент старта проекта
В качестве входных данных выступали письма, в число выходных данных попадали категории писем. Сложность заключалась в том, что разработчику хотелось использовать тексты писем и метаданные, но понять, как превратить все это в пригодный для машинного обучения набор данных, было нелегко.
Любой, кто изучал обработку естественных языков, сразу предложит один простой подход – использование модели Bag of Words (Множество слов). Это один из простейших алгоритмов классификации текстов – найти N общих слов для всех текстов и составить таблицу бинарных признаков для каждого слова (признак равен 1, если заданный текст содержит слово, и 0 – если не содержит).
Куренков проделал это для группы слов, найденных во всех его письмах, а также для топ-20 отправителей писем (так как в некоторых случаях отправитель строго коррелирует с категорией письма; например, если отправитель –научный руководитель в университете, то категорией будет «Исследования») и для топ-5 доменов, с которых ему присылали письма (поскольку домены типа gatech.edu строго указывают на такие категории как, например, «Обучение»). Так, где-то после часа, потраченного на написание парсера электронной почты, он смог получать данные о своём ящике в формате csv (значения, разделённые запятыми).
Насколько хорошо все работало? Неплохо, но хотелось большего. Куренков говорит, что тогда увлекался фреймворком для машинного обучения Orange ML на Python, и, как и было положено по заданию, протестировал несколько алгоритмов на своем наборе данных. Выделялись два алгоритма – лучше всего показали себя деревья принятия решений, а хуже всего – нейронные сети:
Так с небольшим набором данных справились деревья принятия решений
А так – нейронные сети
Если внимательно взглянуть на эти графики из OpenOffice Calc, то можно увидеть, что лучший результат деревьев принятия решений на тесте – где-то 72%, а нейронных сетей – жалкие 65%. Ужас! Это, конечно, лучше, чем разложить всё наугад, учитывая, что там 11 категорий, но до отличного результата далеко.
Почему же результаты оказались такими удручающими? Дело в том, что признаки, полученные для набора данных, оказались весьма примитивны – простой поиск 500 наиболее часто встречающихся в письмах слов даст нам не так уж много полезных индикаторов, но много общеупотребительных конструкций, которые встречаются в английском, например «that» или «is». Инженер понял это и предпринял следующее: полностью убрал из списка слова из трёх и менее букв и написал немного кода, чтобы выбрать наиболее часто встречающиеся слова для каждой категории отдельно; но он все еще не представлял, как улучшить результат.
Попытка номер два
Спустя пару лет Куренков решил еще раз попытаться решить задачу — хоть сам проект в университете уже был давно сдан, хотелось улучшить результаты.
В этот раз инженер решил в качестве одного из основных инструментов применить Keras, потому как он написан на Python и также неплохо ладит с пакетами NumPy, pandas и scikit-learn и поддерживается библиотекой Theano.
К тому же получилось так, что у Keras есть в лёгком доступе несколько примеров, с которых можно начать работу, включая задачу классификации разнообразных текстов. И вот, что интересно – данный пример использует такие же функции, какие инженер использовал ранее. Он определяет 1000 наиболее часто встречающихся в документах слов, бинарные признаки и обучает нейронную сеть с одним скрытым слоем и dropout-регуляризацией предсказывать категорию заданного текста, основываясь исключительно на этих признаках.
Итак, первое, что приходит в голову: испробовать этот пример на собственных данных – посмотреть, улучшит ли работу Keras. К счастью, сохранился старый код для парсинга почтового ящика, а у Keras есть удобный класс Tokenizer для получения признаков текста. Поэтому можно легко создать набор данных в таком же формате, как и в примере, и получить новую информацию о текущем количестве электронных писем:
Using Theano backend. Label email count breakdown: Personal:440 Group work:150 Financial:118 Academic:1088 Professional:388 Group work/SolarJackets:1535 Personal/Programming:229 Professional/Research:1066 Professional/TA:1801 Sent:513 Unread:146 Professional/EPFL:234 Important:142 Professional/RISS:173 Total emails: 8023
Восемь тысяч писем никак не назовёшь большим набором данных, но всё же этого достаточно для того, чтобы немного позаниматься серьёзным машинным обучением. После перевода данных в нужный формат остаётся лишь посмотреть, работает ли обучение нейронной сети на этих данных. Пример с использованием Keras даёт легко сделать следующее:
7221 train sequences 802 test sequences Building model... Train on 6498 samples, validate on 723 samples Epoch 1/5 6498/6498 [==============================] - 2s - loss: 1.3182 - acc: 0.6320 - val_loss: 0.8166 - val_acc: 0.7718 Epoch 2/5 6498/6498 [==============================] - 2s - loss: 0.6201 - acc: 0.8316 - val_loss: 0.6598 - val_acc: 0.8285 Epoch 3/5 6498/6498 [==============================] - 2s - loss: 0.4102 - acc: 0.8883 - val_loss: 0.6214 - val_acc: 0.8216 Epoch 4/5 6498/6498 [==============================] - 2s - loss: 0.2960 - acc: 0.9214 - val_loss: 0.6178 - val_acc: 0.8202 Epoch 5/5 6498/6498 [==============================] - 2s - loss: 0.2294 - acc: 0.9372 - val_loss: 0.6031 - val_acc: 0.8326 802/802 [==============================] - 0s Test score: 0.585222780162
Точность: 0.847880299252
85% точности! Это намного выше жалких 65% старой нейронной сети. Отлично.
Но…почему?
Старый код делал, в общем-то, следующее – определял наиболее часто встречающиеся слова, создавал бинарную матрицу признаков и обучал нейронную сеть с одним скрытым слоем. Может быть, всё из-за нового нейрона «relu», dropout-регуляризации и использования методов оптимизации, отличных от стохастического градиентного спуска? Поскольку признаки, найденные по старым данным, бинарные и представлены в виде матрицы, их очень легко превратить в набор данных для обучения этой нейронной сети. Итак, вот результаты:
Epoch 1/5 6546/6546 [==============================] - 1s - loss: 1.8417 - acc: 0.4551 - val_loss: 1.4071 - val_acc: 0.5659 Epoch 2/5 6546/6546 [==============================] - 1s - loss: 1.2317 - acc: 0.6150 - val_loss: 1.1837 - val_acc: 0.6291 Epoch 3/5 6546/6546 [==============================] - 1s - loss: 1.0417 - acc: 0.6661 - val_loss: 1.1216 - val_acc: 0.6360 Epoch 4/5 6546/6546 [==============================] - 1s - loss: 0.9372 - acc: 0.6968 - val_loss: 1.0689 - val_acc: 0.6635 Epoch 5/5 6546/6546 [==============================] - 2s - loss: 0.8547 - acc: 0.7215 - val_loss: 1.0564 - val_acc: 0.6690 808/808 [==============================] - 0s Test score: 1.03195088158
Точность: 0.64603960396
Итак, старое решение по определению категорий электронных писем было неудачным. Возможно причиной стало сочетание сильных ограничений признаков (вручную определённые топовые отправители, домены и слова из каждой категории) и слишком маленького количества слов.
Пример, использующий Keras, просто отбирает 1000 наиболее часто встречающихся слов в большую матрицу без дополнительного фильтрования и передает её нейронной сети. Если сильно не ограничивать отбор признаков, из них могут быть выбраны более подходящие, что увеличивает общую точность работы алгоритма.
Причина или в этом или в наличии ошибок в коде: его изменение с целью сделать процесс отбора признаков менее ограничивающим приводит к повышению точности лишь до 70%. В любом случае, инженер выяснил, что можно было улучшить свой старый результат с использованием современной библиотеки машинного обучения.
Теперь возникал новый вопрос – можно ли добиться еще более высокой точности?
Продолжение следует…