Анализ эмоциональной окраски отзывов с Кинопоиска |
||
МЕНЮ Искусственный интеллект Поиск Регистрация на сайте Помощь проекту ТЕМЫ Новости ИИ Искусственный интеллект Разработка ИИГолосовой помощник Городские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Техническое зрение Чат-боты Авторизация |
2019-09-12 08:55 Вступление Обработка естественного языка (NLP) является популярной и важной областью машинного обучения. В данном хабре я опишу свой первый проект, связанный с анализом эмоциональной окраски кино отзывов, написанный на Python. Задача сентиментного анализа является довольно распространенной среди тех, кто желает освоить базовые концепции NLP, и может стать аналогом 'Hello world' в этой области. Формирование корпуса данных Для решения данной задачи можно было бы использовать какой-нибудь уже готовый и аннотированный корпус данных с отзывами с IMDB, коих множество на GitHub. Но было решено создать свой с отзывами на русском языке, взятых с Кинопоиска. Чтобы вручную их не копировать, напишем веб парсер. Для отправки http запросов буду использовать библиотеку requests, а для обработки html файлов BeautifulSoup. Для начала определим функцию, которая будет принимать ссылку на отзывы фильма и извлекать их. Для того, чтобы Кинопоиск не распознал в нас бота, необходимо задать аргумент headers в функции requests.get, который позволит имитировать работу браузера. В него необходимо передать словарь с ключами User-Agent, Accept-language и Accept, значения которых можно найти в инструментах разработчика браузера. Далее создается парсер и извлекаются со страницы отзывы, которые хранятся в классе _reachbanner_ html разметки.
От html разметки мы избавились, однако наши отзывы все еще являются объектами BeautifulSoup, нам же необходимо преобразовать их в строки. Функция convert как раз делает это. Напишем также функцию, которая извлекает имя фильма, которое позже будет использоваться для сохранения отзывов.
Последняя функция парсера будет принимать ссылку на главную страницу фильма, класс отзыва и путь для сохранения отзывов. Также в функции определены задержки (delays) между запросами, которые необходимы для избегания бана. В функции содержится цикл, который извлекает и сохраняет отзывы начиная с первой страницы, пока не встретит несуществующую страницу, из которой функция load_data извлечет пустой список и цикл прервется.
Далее с помощью следующего цикла можно извлечь отзывы с фильмов, которые содержатся в списке urles. Список с фильмами необходимо будет создать вручную. Можно было бы, например, получить список ссылок на фильмы, написав функцию, которая бы извлекала их из топа 250 фильмов кинопоиска, чтобы не делать этого вручную, но для формирования небольшого датасета в тысячу отзывов на каждый класс хватит и 15-20 фильмов. Также если вы получите бан, то программа выведет на каком фильме и классе парсер остановился, чтобы продолжить с того же места после прохождения бана.
Предварительная обработка После написания парсера, вспоминания рандомных фильмов для него и нескольких банов от кинопоиска, я перемешал отзывы в папках и отобрал по 900 отзывов от каждого класса для обучения и оставшиеся для контрольной группы. Теперь необходимо предварительно обработать корпус, а именно его токенизировать и нормализовать. Токенизировать означает разбить текст на составляющие, в данном случае на слова, так как мы будем использовать представление мешка слов. А нормализация заключается в преобразовании в нижний регистр слов, удалении стоп-слов и лишнего шума, стэмминга и любых других приемов, помогающих сократить пространство признаков. Скрытый текст Начнем с определения нескольких небольших функций для предварительной обработки текста. Первая под названием lower_pos_tag будет принимать список со словами, преобразовывать их к нижнему регистру и сохранять каждую лексему в кортеж с её частью речи. Операция добавления части речи слова называется Part of speech (POS) tagging и часто используется в NLP для извлечения сущностей. В нашем случае мы будем использовать части речи в следующей функции для фильтрации слов.
В текстах содержится большое число слов, которые встречаются слишком часто, чтобы быть полезными для модели (так называемые стоп слова). В основном это предлоги, союзы, местоимения по которым нельзя определить к какому классу относится отзыв. Функция clean оставит только существительные, прилагательные, глаголы и наречия. Заметьте, что она удаляет части речи, так как для самой модели они не нужны. Также можно заметить, что в этой функции используется стэмминг, суть которого заключается в отбрасывании суффиксов и приставок у слов. Это позволяет сократить размерность признаков, так как слова с разными родами и падежами будут сокращены до одинаковых лексем. Существует более мощный аналог стэмминга – лемматизация, она позволяет восстановить начальную форму слова. Однако работает она медленнее стэмминга, и, помимо этого, в NLTK нет русского лемматизатора.
Далее напишем финальную функцию, которая будет принимать метку класса и извлекать все отзывы с этим классом. Для чтения корпуса будем использовать метод raw объекта PlaintextCorpusReader, который позволяет извлечь текст из указанного файла. Далее для токенизации используется RegexpTokenizer, работающий на основе регулярного выражения. Помимо отдельных слов я добавил в модель биграммы, представляющие собой комбинации всех соседних слов. Также в этой функции используется объект FreqDist, который возвращает частоту встречаемости слов. Он здесь используется для того, чтобы удалить слова, которые встречаются во всех отзывах определенного класса только один раз (их еще называют гапаксами). Таким образом, функция будет возвращать словарь, содержащий документы, представленные в виде мешка слов и список всех слов для определенного класса.
Этап предварительной обработки является самым долгим, поэтому имеет смысл распараллелить обработку нашего корпуса. Сделать это можно с помощью модуля multiprocessing. В следующем куске программного кода я запускаю три процесса, которые будут одновременно обрабатывать три папки с разными классами. Далее результаты будут собраны в один словарь. На этом предварительная обработка закончена.
Векторизация После того, как мы предварительно обработали корпус, у нас есть словарь, где для каждой метки класса содержится список с отзывами, которые мы токенизировали, нормализовали и обогатили биграммами, а также список слов со всех отзывов этого класса. Так как модель не может воспринимать естественный язык так, как мы, теперь стоит задача представить наши отзывы в числовом виде. Для этого мы создадим общий вокабуляр, состоящий из уникальных лексем, и с помощью него векторизируем каждый отзыв.
Существует несколько способов векторизовать текст. Наиболее популярные из них: TF-IDF, прямое и частотное кодирование. Я использовал частотное кодирование, суть которого представить каждый отзыв в виде вектора, элементы которого являются числом вхождения каждого слова из вокабуляра. В NLTK есть свои классификаторы, можно использовать и их, но работают они медленнее аналогов из scikit-learn и имеют меньше настроек. Ниже представлен программный код для кодирования для NLTK. Однако я буду использовать модель Наивного Байеса из scikit-learn и закодирую отзывы, сохранив признаки в разреженной матрице из SciPy, а метки классов в отдельном массиве NumPy.
Так как в датасете отзывы с определенными метками идут друг за другом, то есть сначала все нейтральные, потом все негативные и так далее, необходимо их перемешать. Для этого можно воспользоваться функцией shuffle из scikit-learn. Она как раз подходит для ситуаций, когда признаки и метки классов находятся в разных массивах, потому что позволяет перемешать два массива в унисон. Обучение модели Теперь осталось обучить модель и проверить ее точность на контрольной группе. В качестве модели будем использовать модель Наивного Байесовоского классификатора. В scikit-learn есть три модели Наивного Байеса в зависимости от распределения данных: бинарного, дискретного и непрерывного. Так как распределение наших признаков дискретное, выберем MultinomialNB.
В моем случае грид серч выдает оптимальное значение гиперпараметра равное 0 с точностью в 0.965. Однако такое значение явно не будет оптимальным для контрольного датасета, так как там будет большое число слов, не встречающихся ранее в обучающем наборе. Для контрольного набора данных эта модель имеет точность 0.598. Однако если увеличить alpha до 0.1, точность на обучающих данных упадет до 0.82, а на контрольных возрастет до 0.62. Вероятнее всего, на большем наборе данных разница будет существенней.
Вывод Предполагается, что модель должна использоваться для прогнозирования отзывов, чьи слова не использовались для формирования вокабуляра. Поэтому качество модели можно оценивать по ее точности на контрольной части данных, которая равна 0.62. Это почти в два раза лучше простого угадывания, но точность все ещё довольно низка. Источник: habr.com Комментарии: |
|