Анализ настроений извлекает восприятие людьми определенной проблемы, бренда, схемы и т. Д. (Настроения) из текстовых данных. Он имеет широкий спектр приложений, от мониторинга бренда и анализа продуктов до разработки политики. В этой статье мы проводим анализ тональности твитов с хэштегом Swachh Bharat. Полный код этого проекта доступен здесь.

Эта статья разделена на следующие части.

  1. Различные подходы к анализу настроений
  2. Сбор данных
  3. Преобразование полезных данных в CSV
  4. Удаление повторяющихся строк и ненужных столбцов
  5. Очистка твитов
  6. Удаление стоп-слов
  7. Лемматизация и стемминг
  8. Визуализация данных
  9. Векторизация текста твитов
  10. Реализация классификаторов из sklearn
  11. Реализация моделей Gensim
  12. Резюме

Различные подходы к анализу настроений

Существует два основных подхода к выполнению анализа тональности на основе лексикона или с использованием моделей машинного обучения. Любой подход, основанный на лексиконе, в основном использует словарь слов с присвоенными им семантическими баллами для расчета окончательной полярности твита. Например, рассмотрим предложение «Мне нравится этот фильм, хотя вторая часть немного медленная». Теперь каждому из таких слов будет присвоена (общая) оценка тональности.

“I:0, love:0.750, this:0.0, movie:0.0, eventhough:0.0 the:0.0 second :0.0,part:0.0,is :0.0,boring:-0.250”

Положительный результат означает, что слово положительный, а отрицательный - отрицательный. Теперь, если мы сложим все оценки для этого предложения, получится -0,500, что является положительным мнением. Таким образом, твит будет отмечен как положительный. Доступны несколько важных ресурсов лексики: SentiWordNet, TextBlob, Afinn, MPQA (многоперспективный ответ на вопрос), лексика субъективности и шаблон. Я использовал этот словарный подход, используя SentiWordNet для маркировки моих твитов (объяснено позже).

Второй подход - машинное обучение - решает проблему как задачу классификации текста с использованием классификаторов после предварительной обработки данных. Мы следуем второму подходу, который будет подробно объяснен.

Относительно инструментов, необходимых для этой задачи

Существует множество библиотек для обработки текста и классификации - NLTK (комплексный и сложный для изучения), TextBlob (простой в использовании, подходит для новичков), Spacy (идеально подходит для работы в промышленности) и многие другие. Я предпочитаю NLTK, потому что он предоставляет множество вспомогательных библиотек для реализации многих методов обработки текста.

Давайте начнем!

Сбор данных

Теперь мне нужны текстовые данные, которые выражают мнение людей о Swachh Bharat. Некоторые доступные варианты используют Reddit API, Facebook API, Twitter API и сбор данных с веб-страниц. Я решил использовать twitter API, чтобы собирать твиты о Swachh Bharat, так как многие считают Twitter большой психологической базой данных. Твиты для этого проекта собираются из базы данных Twitter с использованием библиотеки Tweepy на Python. Чтобы получить доступ к твитам, нужно создать учетную запись разработчика в твиттере. Существуют разные типы учетных записей разработчиков: Standard Search (бесплатно), Premium (платно) и Enterprise (платно). Возможности API или доступность увеличиваются со стандартной учетной записи на корпоративную. Например, через стандартную учетную запись вы можете получить доступ к твитам за последние семь дней, в то время как это 30 в случае Premium. Для создания учетной записи вы должны предоставить подробную информацию об основном сценарии использования, намерении, бизнес-цели вашего использования, сведения об анализах, которые вы планируете провести, а также о методах или методах. И я советую предоставить как можно больше деталей (можно также добавить ссылки на веб-страницы проекта, похожие на ваш или основную тему проекта, когда вы хотите прояснить ситуацию) с первой попытки, иначе вам придется писать длинные электронные письма с повторным объяснением одного и того же содержания снова и снова на случай, если им понадобится дополнительная информация. Команда Twitter тщательно рассмотрит вашу заявку. Это хорошая статья о Twitter Data Mining.

Обычно получение письма с подтверждением занимает меньше дня при условии, что ваша информация является полной.

После этого вам нужно создать новое приложение в их интерфейсе и предоставить его описание (как показано ниже), которое генерирует API KEY, API SECRET KEY, ACCESS TOKEN и ACCESS TOKEN SECRET, которые мы будем использовать для входа в систему через терминал.

Теперь давайте подтвердим наши данные с помощью функции tweepy.AppAuthHandler (). Замените "*" своими данными аутентификации.

import tweepy
API_KEY="*********************"
API_SECRET="*************************"
ACCESS_TOKEN="*******************************"
ACCESS_TOKEN_SECRET="******************************"
auth = tweepy.AppAuthHandler(API_KEY, API_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
api = tweepy.API(auth)
print(api.me().name)

Если аутентификация прошла успешно, вы должны увидеть напечатанное имя вашей учетной записи.

IAmSai

После аутентификации мы готовы загружать твиты. Мы используем метод API.search () для получения твитов. Существует два основных способа поиска твитов с помощью Tweepy-поиск по хэштегу (или ключевому слову) и поиск по идентификатору твита (идентификатор каждого твита уникален). Второй метод полезен, когда у вас есть все идентификаторы твитов, которые нужно собрать (скажем, вы хотите собирать твиты о демонетизации с учетом идентификаторов. Эти идентификаторы будут предоставлены теми, кто ранее собирал данные по этой теме). Поскольку у меня нет идентификаторов, я использую первый метод.

search_query="#SwachhBharat"
new_tweets = api.search(q=search_query,count=tweetsPerQuery,lang="en",tweet_mode='extended')
auth = tweepy.AppAuthHandler(API_KEY,API_SECRET)
api = tweepy.API(auth,wait_on_rate_limit=True,wait_on_rate_limit_notify=True)
fName = 'sb_04_08_19.txt' # where i save the tweets

В приведенном выше фрагменте кода дано несколько важных строк кода. Пожалуйста, смотрите это для детального кода.

Мы ищем по ключевому слову #SwachhBharat. Позже, очищая данные, я обнаружил, что твиты с хэштегами - '# MyCleanIndia' и '#SwachhBharatUrban' также могут использоваться для поиска твитов, касающихся Swachh Bharat. Мы используем AppAuthHandler (3-я строка), а не OAuthHandler, который обычно используется для авторизации API. OAuthHandler допускает максимум 180 запросов за 15-минутное окно (и максимум 100 твитов за запрос). Таким образом, мы можем получить только 18 000 твитов за 15 минут. Но у AppAuthHandler есть ограничение на максимальную частоту запросов: 450 и 100 твитов на запрос. Таким образом, это помогает получить 45 000 твитов за пятнадцать минут. Кроме того, я использовал режим расширенного твита (вторая строка), чтобы убедиться, что мы получаем полные твиты, а не усеченные. После загрузки мы сохраним твиты в текстовом файле (в качестве альтернативы мы также можем сохранить их в файле .json). Я повторил описанную выше процедуру еще несколько раз, чтобы собрать большее количество твитов позже (с перерывом более семи дней между двумя сборами данных, иначе мы получим много повторяющихся твитов).

Преобразование полезных данных в CSV

Сначала мне было сложно понять это, поскольку я никогда не работал с API и JSON. Текстовый файл огромен и содержит около 1100 страниц информации. В sb.txt (текстовом файле) твиты представлены в формате JSON (JavaScriptObjectNotation). Взгляните на него.

С первого взгляда вы можете увидеть, что есть много функций (объектов), которые не важны для нашей задачи, таких как метаданные, индексы, screen_name. Итак, я просмотрел эту страницу, чтобы выяснить, какие объекты могут мне понадобиться для моей задачи. Я решил выбрать следующие объекты: full_text (полный текст твита), retweet_count (твита), избранное_count (твита), geo (географическое положение), координаты (координаты широты и долготы места), место ( из которого он твитнул), follower_count (пользователя, который твитнул), created_at (время и дата твита), id_str (id твита в строковом формате). Я сохраняю указанные выше объекты в своем CSV-файле, а остальные отбрасываю.

inputFile='sb_04_08_19.txt'
tweets = []
for line in open(inputFile, 'r'):
    tweets.append(json.loads(line))
for tweet in tweets:
    try:
        csvWriter.writerow([tweet['full_text'],tweet['retweet_count'],tweet['user']['followers_count'],tweet['favorite_count'],tweet['place'],tweet['coordinates'],tweet['geo'],tweet['created_at'],str(tweet['id_str'])])
        count_lines+=1
    except Exception as e:
        print(e)

Выше приведены несколько важных строк кода для преобразования текстового файла в CSV. Пожалуйста, смотрите это для большей ясности. Мы создаем список твитов, читающих каждую строку из файла (1-й цикл for). Затем мы используем csvWriter.writerow () для записи необходимой информации в файл CSV для каждого твита. И мы собираем только упомянутые выше предметы.

Попробуем загрузить CSV-файл в Jupyter Notebook. Очень важно установить параметр кодировки как «unicode_escape» в функции read_csv (), поскольку некоторые твиты также состоят из эмодзи и могут затруднить использование кодировки по умолчанию.

import pandas as pd
df = pd.read_csv('somall.csv', encoding = 'unicode_escape')
df.head(5)

Посмотрите на результат.

Удаление повторяющихся строк

Давайте посмотрим и удалим повторяющиеся строки, если таковые имеются. Есть два типа дубликатов-дубликатов с одинаковыми значениями для всех столбцов (это дублирование происходит, когда одни и те же твиты снова собираются сборщиком твитов) и дубликаты с одинаковым текстом для твитов (это происходит, когда два или более пользователей публикуют один и тот же твит. )

print(len(df.index))#14195
serlis=df.duplicated().tolist()
print(serlis.count(True))#112
serlis=df.duplicated(['full_text']).tolist()
print(serlis.count(True))#8585

Мы видим, что есть 112 повторяющихся строк и 8585 строк, которые дублируются в column-full_text, а row-duplicates являются подмножеством full_text-duplicates. Итак, мы отбрасываем все повторяющиеся строки.

df=df.drop_duplicates(['full_text'])

Изначально дубликатов не заметил. Таким образом, когда используются текстовые классификаторы, они дают мне высокую точность до 99,54%, что не очень хорошо.

Удаление ненужных столбцов

Похоже, что некоторые функции, такие как «место», «гео», имеют в качестве значений больше не числа (NaN). Давайте проверим, сколько пустых строк существует в некоторых выбранных столбцах.

print(df['geo'].isna().sum())
print(df['place'].isna().sum())
print(df['coordinates'].isna().sum())

Всего в нашем наборе данных 14 195 строк. Мы видим, что 14191 строка гео-столбца пуста. Пустые строки в «месте» и «координатах» также высоки (13951 и 14191). Поэтому мы отбрасываем эти столбцы вместе с «id_str» (который является идентификатором твита), поскольку идентификатор твита для нас не очень полезен. Хотя мы не используем retweet_count, user_followers_count, favourite_count в нашем контексте, мы не отбрасываем их, потому что они могут использоваться для других задач (например, для оценки популярности твита, которая может использоваться для присвоения веса твитам).

df=df.drop([‘place’,’coordinates’,’geo’,’id_str’],axis=1)
df.head(5)

Очистка твитов

В тексте твита могут быть ненужные символы, которые не важны для нашего анализа. Итак, давайте продолжим и очистим наши твиты.

Давайте рассмотрим полный текст любого конкретного твита.

Как мы видим, некоторые из ненужных текстов и символов, которые необходимо удалить, - это теги username_tags (например, @_WeMeanToClean), символ ретвита (RT), хэштеги (например, #MyCleanIndia), URL-адреса (например, https: //t.co/XK3ZReâ \ x80), числа и знаки препинания. Некоторые из значимых хэштегов передают смысл и могут иметь некоторую тональность после того, как слово разделено на полезные части (например, #ILoveSwachhBharat). Таким образом, вместо удаления всех слов, начинающихся с символов хэштега, удаляются только символы «#». Эту очистку текста мы выполняем с помощью модуля re в Python. Функция re.sub () ищет шаблон и заменяет его указанным нами текстом. Мы заменяем все эти символы пробелом.

import re
for i in range(len(df)):
    txt = df.loc[i]["full_text"]
    txt=re.sub(r'@[A-Z0-9a-z_:]+','',txt)#replace username-tags
    txt=re.sub(r'^[RT]+','',txt)#replace RT-tags
    txt = re.sub('https?://[A-Za-z0-9./]+','',txt)#replace URLs
    txt=re.sub("[^a-zA-Z]", " ",txt)#replace hashtags
    df.at[i,"full_text"]=txt

Теперь мы видим, что наши твиты выглядят чистыми.

POS-теги и тональность

Мы закончили основную очистку текстовых данных. В алгоритмах машинного обучения, которые мы собираемся реализовать, 'full_text' твита действует как переменная-предиктор (другие переменные, которые можно использовать, - это количество ретвитов, количество избранных, если мы хотим предсказать влияние твита, но это не так. часть нашей задачи в настоящее время). Очевидно, что нам нужно создать целевые переменные (оценки настроения) для наших данных. Для этой цели мы используем SentiWordNet. SentiWordNet - это расширенный лексический ресурс, специально разработанный для поддержки приложений классификации настроений и анализа мнений. Он имеет большой корпус английских слов с тегами POS, а также их тональность.

SWN дает pos_score и neg_score для каждого слова, как в приведенном выше случае. Чем выше pos_score, тем более позитивным является слово. Мы используем простое линейное суммирование этих оценок (мы добавляем pos_score всех слов в твите, чтобы сформировать pos_total, и аналогичным образом получаем neg_total. Затем мы складываем эти два, чтобы получить sent_total) и помечаем предложение как положительное ( 1), если он (sent_total) больше 0, отрицательный (-1), если он меньше 0, и нейтральный (0), в противном случае. Как вы, наверное, догадались, нам нужно найти POS-теги (части речевых тегов) наших твитов, чтобы использовать SWN. Функция pos_tag в nltk помогает нам пометить текст, но она не упрощена (помимо существительного, местоимения, прилагательного, глагола она также помечает текст многими другими тегами, такими как присоединение, союз, определитель и т. Д.). Но SWN принимает только упрощенные теги. Поэтому мы пытаемся упростить теги с помощью функции map_tag из библиотеки nltk.

for i in range(len(df_copy.index)):
        text = df_copy.loc[i]['full_text']
        tokens = nltk.word_tokenize(text)
        tagged_sent = pos_tag(tokens)
        store_it = [(word, map_tag('en-ptb', 'universal', tag)) for word, tag in tagged_sent]

Вторая строка преобразует текст твита в токены, которые позже будут помечены соответствующими частями речи. Затем мы сохраняем слова и теги в списке store_it. Этот список впоследствии будет передан для маркировки настроений с помощью SWN.

Теги твита («Движение по осведомленности о разделении отходов от двери до двери было проведено членами nehru yuva kendra в kapurthala punjab»), помеченные с помощью pos_tag () и упрощенные POS-теги, представлены ниже.

Tagged Parts of Speech: [('a', 'DT'), ('door', 'NN'), ('to', 'TO'), ('door', 'VB'), ('waste', 'NN'), ('segregation', 'NN'), ('awareness', 'NN'), ('drive', 'NN'), ('was', 'VBD'), ('carried', 'VBN'), ('out', 'RP'), ('by', 'IN'), ('members', 'NNS'), ('of', 'IN'), ('nehru', 'JJ'), ('yuva', 'NN'), ('kendra', 'NN'), ('in', 'IN'), ('kapurthala', 'NN'), ('punjab', 'NN')]
Tagged Parts of Speech- Simplified: [('a', 'DET'), ('door', 'NOUN'), ('to', 'PRT'), ('door', 'VERB'), ('waste', 'NOUN'), ('segregation', 'NOUN'), ('awareness', 'NOUN'), ('drive', 'NOUN'), ('was', 'VERB'), ('carried', 'VERB'), ('out', 'PRT'), ('by', 'ADP'), ('members', 'NOUN'), ('of', 'ADP'), ('nehru', 'ADJ'), ('yuva', 'NOUN'), ('kendra', 'NOUN'), ('in', 'ADP'), ('kapurthala', 'NOUN'), ('punjab', 'NOUN')]

Затем мы находим тональность твита с помощью метода, упомянутого выше во введении. Пожалуйста, просмотрите полный код для большей ясности в отношении маркировки настроений. Получив отзывы от SWN, мы добавляем еще три столбца в наш CSV - 'pos_score', 'neg_score', 'sent_score'. Pos_score твита - это чистая положительная оценка всех слов в твите, и то же самое происходит с neg_score.

Значение 1 присваивается для положительного твита (pos_score ›neg_score), 0 для нейтрального твита (pos_score = neg_score) и -1 для отрицательного твита (pos_score‹ neg_score). Есть 3233 положительных твита, 814 нейтральных твитов и 1563 отрицательных твита. Я также использовал TextBlob и Afinn для определения тональности твитов. Результаты приведены ниже:

TEXTBLOB:
Total tweets with sentiment: 5610
positive tweets: 2171
negative tweets: 591
neutral tweets: 2848
AFINN:
Total tweets: 5610
positive tweets: 2551
negative tweets: 572
neutral tweets: 2487

Соотношение количества твитов от каждого настроения в каждом классификаторе является постоянным, что дает нам больше уверенности в оценке настроений. Кроме того, мы можем сделать вывод, что большинство твитов положительные.

Теперь мы закончили с маркировкой настроений, мы подготовим наши текстовые данные для дальнейшей загрузки в классификаторы машинного обучения.

Удаление стоп-слов

Стоп-слова - это часто используемые слова, такие как «of», «the», for »и т. Д. В нашем контексте они не придают большого значения настроению. Кроме того, они увеличивают размерность данных. Поэтому мы удаляем стоп-слова с помощью библиотеки nltk (в следующей части).

Прежде чем передать эти текстовые данные в алгоритмы машинного обучения, мы преобразуем их в векторы (в следующих частях статьи). И мы не хотим иметь разные (векторные) значения для похожих слов. Мы ожидаем одинаковых значений для всех этих слов - «распространять», «распространять», «распределять», «распространять», поскольку они, по сути, передают одно и то же значение. Это (присвоение одинаковых значений похожим словам) достигается лучше, если мы выполняем лемматизацию и построение с использованием модуля nltk.

Лемматизация и стемминг

Стечение сокращает слова до корней. Алгоритмы стемминга в основном основаны на правилах. Например, они сокращают все вышеупомянутые слова «распространять» до «распространять». Лемматизатор делает то же самое, что и Штеммер, но сохраняет лингвистику слова в контексте. Например, все приведенные выше слова будут сокращены до «распространять».

from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from nltk.stem import PorterStemmer
pstem = PorterStemmer()
lem = WordNetLemmatizer()
stop_words = stopwords.words('english')
for i in range(len(df.index)):
     text = df.loc[i]['full_text']
     tokens = nltk.word_tokenize(text)
     tokens = [word for word in tokens if word not in stop_words]
for j in range(len(tokens)):
         tokens[j] = lem.lemmatize(tokens[j])
         tokens[j] = pstem.stem(tokens[j])
tokens_sent=' '.join(tokens)
     df.at[i,"full_text"] = tokens_sent

Третья строка удаляет стоп-слова из списка токенов. После лемматизации и стемминга мы, наконец, заменяем старый текст новым.

Стоит отметить, что три вышеупомянутых процесса очистки (удаление стоп-слов, формирование основы, лемматизация) не выполняются раньше (маркировка настроения), потому что это вызовет нарушения в обнаружении тональности. Такие слова, как «распространять», «очищать» нельзя найти в корпусе английских слов SWN, поэтому SWN не может определить положительную и отрицательную оценку этих слов. Это создает твиты только из части набора данных, помеченной оценкой тональности. Мы также конвертируем все твиты в нижний регистр, используя библиотеку nltk.

Визуализация данных

Мы используем WordCloud для представления большинства слов в каждой тональности. Для этого мы добавляем весь текст твитов с положительной меткой, чтобы сформировать строку, и обозначим эту строку как pos_string. Мы применяем тот же метод для негативного и нейтрального текста.

for i in range(len(df_copy.index)):
    if(df_copy.loc[i]["sent_score"]==1):
        pos_text+=df_copy.loc[i]["full_text"]
    elif(df_copy.loc[i]["sent_score"]==-1):
        neg_text+=df_copy.loc[i]["full_text"]
    else:
        neut_text+=df_copy.loc[i]["full_text"]

Текстовые представления создаются с помощью модуля WordCloud в Python и строятся с помощью matplotlib.

list_text = [pos_text,neg_text,neut_text]
for txt in list_text:
    word_cloud = WordCloud(width = 600,height = 600,max_font_size = 200).generate(txt)
    plt.figure(figsize=(12,10))# create a new figure
    plt.imshow(word_cloud,interpolation="bilinear")
    plt.axis("off")
    plt.show()

Мы должны помнить, что слова были выделены и лемматизированы раньше, поэтому мы не сможем увидеть полные слова некоторых из них.

Как видите, хэштеги состоят из важных слов в Wordcloud с позитивным текстом. Это довольно интуитивно понятно, поскольку мы вообще не удаляли хэштеги из текста. Хотя это слово на хинди, «swachh» (положительное слово) встречается в нем довольно часто. Есть и другие полезные слова, такие как сортировка мусора, сбор мусора, чистка и т. Д.

В нейтральном WordCloud, за исключением нескольких хэштегов, мы можем увидеть много нейтральных слов, таких как часть, улица, дорога, окружение, стажировка и т. Д.

Прекратить засорять, сливать, сливать - это несколько негативных слов из облака, и мы также можем видеть, что оно содержит много слов из других чувств.

Разделение данных

Мы разбиваем наши данные на наборы для обучения, проверки и тестирования с помощью функции train_test_split модуля model_selection из библиотеки nltk. Разделение выглядит следующим образом - 90% данных обучения, 5% данных проверки и 5% данных тестирования. Большая часть данных предназначена для обучения, учитывая меньшее количество твитов.

import sklearn
from sklearn.model_selection import train_test_split
SEED=4
x = df.full_text
y = df.sent_score
x_train,x_val_test,y_train,y_val_test = train_test_split(x,y,test_size=0.1,random_state=SEED)
x_val,x_test,y_val,y_test = train_test_split(x_val_test,y_val_test,test_size=0.5,random_state=SEED)

Векторизация текста твитов

Прежде чем мы реализуем различные текстовые классификаторы ML, нам нужно преобразовать текстовые данные в векторы. Это очень важно, поскольку алгоритмы ожидают данные в некоторой математической, а не текстовой форме. Этот ответ Quora объясняет, почему важно преобразование в векторные пространства. Здесь мы реализуем два векторизатора из sklearn - Count Vectorizer и Tfidf Vectorizer. Оба они относятся к моделям Bag-of-Words.

Count Vectorizer подсчитывает, сколько раз слово появляется в документе (в каждом твите), и использует это значение в качестве веса. Для визуализации столбцы в векторе (после векторизации) состоят из всех отдельных слов во всех твитах, и каждая строка содержит твиты. Counter Vectorizer дает нам разреженную матрицу (заполненную множеством нулей) в качестве вектора.

from sklearn.feature_extraction.text import CountVectorizer
cv=CountVectorizer(decode_error='ignore',lowercase=False,max_features=11)
x_traincv=cv.fit_transform(x_train.values.astype('U'))

Давайте посмотрим на верхние слова (слова, которые имеют максимальную частоту во всех твитах вместе взятых) в CountVectorizer. См. This для кода для визуализации основных слов.

Давайте также реализуем Tfidf Vectorizer. TF-IDF расшифровывается как «термин« частота документа с обратной частотой ». Вес, присвоенный токену, является произведением частоты термина (слова) и обратной частоты слова в документе. Таким образом, если слово чаще встречается в твитах, в этом векторизаторе ему присваивается меньшая важность (IDF-вес).

from sklearn.feature_extraction.text import TfidfVectorizer
tf=TfidfVectorizer(decode_error='ignore',lowercase=False,max_features=11)
x_traintf=tf.fit_transform(x_train_copy.values.astype('U'))

И главные слова для Tfidf:

Сравнивая главные слова в CV и Tfidf, мы видим, что, хотя слова одинаковы, они различаются по порядку и частоте. Кроме того, Tfidf имеет дробные значения частоты, тогда как CV имеет только целые значения.

N-граммы

Проще говоря, n-граммы - это все комбинации соседних слов, которые мы можем найти в нашем тексте (твитах). Для предложения «я люблю игрушки» возможны все комбинации: униграммы - («я», «нравится», «игрушки»), биграммы - («мне нравится», «нравятся игрушки»), триграммы - («я люблю игрушки» ). За словом «Swachh» чаще следует «Bharat». Таким образом, биграммы предоставляют нам «Swachh Bharat» в качестве дополнительной характеристики наряду с «Swachh» и «Bharat». И CV, и Tfidf дают параметр ngram_range. Ngram_range как (1,1) обозначает униграмму, (1,2) биграмму, (1,3) триграмму и так далее. Мы реализуем uni, bi и триграммы как с CV, так и с Tfidf и наблюдаем, какой из них дает более высокую оценку точности.

Реализация классификаторов из sklearn

После векторизации наших твитов мы готовы реализовать алгоритмы классификации. Классификаторы, которые мы собираемся реализовать, - это наивные байесовские модели (MultinomialNB, BernoulliNB,), линейные модели (LogisticRegression, RidgeClassifier, PassiveAggressiveClassifier, Perceptron), ансамблевые модели (классификатор RandomForest, AdaBoostClassifier) ​​и модель SVM (LinearSVC). Мы используем показатель точности для измерения производительности модели (также рассчитываются показатель точности, отзывчивость и матрица путаницы).

K-кратная перекрестная проверка выполняется с каждым классификатором для проверки согласованности производительности модели с использованием модуля cross_validate в sklearn. Количество складок установлено равным 10, и отмечаются среднее и стандартное отклонение оценок перекрестной проверки. Время, затрачиваемое на обучение каждого классификатора, также измеряется с помощью функции time.time (). После этого результаты сохраняются в файле CSV. Ниже приведены несколько важных (не всех) строк кода. Пожалуйста, смотрите это для полного кода.

classifiers = [MultinomialNB(),BernoulliNB(),LogisticRegression(),LinearSVC(),AdaBoostClassifier(),RidgeClassifier(),PassiveAggressiveClassifier(),Perceptron(),RandomForestClassifier()]
for clf in classifiers:     
 model = make_pipeline(vec,clf)#vec is CV or Tfidf
           model.fit(x_train.values.astype('U'),y_train.values.astype('U'))
 labels = model.predict(x_val_copy.values.astype('U'))
 ac = accuracy_score(y_val_copy.values.astype('U'),labels)
 kfold = KFold(n_splits=10,shuffle=False,random_state=None)
 results = cross_validate( model,x_train_copy.values.astype('U'),   y_train_copy.values.astype('U'),cv=kfold)
      
            data.append([vec_gram,clf_names[i],ac,crossval_train_score_mean,crossval_test_score_mean,crossval_train_score_std,crossval_test_score_std, end-start])
            i+=1
d = pd.DataFrame(data,columns=['Vec_Gram','Classifier','Ac','crossval_train_score_mean','crossval_test_score_mean','crossval_train_score_std','crossval_test_score_std','Time.2'])
d.to_csv('all_clfs.csv')

Результаты приведены ниже.

На изображении выше «Ac» представляет оценку точности, а «cv_1» означает CountVectorizer с униграммами. И PassiveAggressiveClassifier (Tfidf-биграммы), и LinearSVC (Tfidf-триграммы) дают наивысшую точность 83,5714%. Но первое обучение занимает всего 4,5 секунды, а второе - 18 секунд. Что касается среднего балла перекрестной проверки и стандартного отклонения, LinearSVC кажется лучше. Стоит отметить, что наш набор данных несбалансирован - положительных и отрицательных твитов больше, чем нейтральных. Таким образом, в этом случае оценки точности могут привести к неправильным выводам. Чтобы этого избежать, мы также использовали классификатор случайного леса со сбалансированными весами классов. И этот классификатор также достаточно хорошо работает со средним показателем точности 74,94% и максимальной точностью 78,21%.

Реализация моделей Gensim

Мы использовали модели набора слов (CV и Tfidf) для числового представления текстовых документов. С математической точки зрения это довольно простой метод. В этом методе семантическая связь между словами не сохраняется. Скажем, в предложении четыре слова: король, королева, женщина, мужчина. В моделях BoW каждому из этих слов присваиваются уникальные идентификаторы. Таким образом, слово король может быть ближе (с точки зрения векторных значений) к женщине, чем королева. Поэтому нам нужна сложная модель, которая сохраняет отношения между словами, тем самым давая лучшее математическое представление текста. Модели Doc2Vec (расширение Word2Vec) делают это за нас.

Алгоритм Doc2Vec используется из библиотеки Gensim на Python. Gensim - это бесплатная библиотека Python, предназначенная для максимально эффективного автоматического извлечения семантических тем из документов. Мы реализуем три алгоритма: doc2vec-DBOW (пакет слов с распределенной памятью), DMC (объединенная распределенная память), DMM (среднее значение распределенной памяти) и несколько их комбинаций. Позже мы используем все эти модели для обучения тех же классификаторов, что и выше, с помощью sklearn. Шаги, которые необходимо выполнить для каждого из этих алгоритмов, следующие: объединить все наборы данных (обучение, проверка и тест), присвоить индексы (метки) всем твитам (могут быть случайные метки), обучить модель (DBOW, DMC или DMM), получите векторы от каждой модели.

x_all = pd.concat([x_train,x_val,x_test])

Этот первый шаг конкатенации не является обязательным (т. Е. Мы можем использовать только обучающий набор данных), но для достижения лучших результатов рекомендуется конкатенация. Затем мы помечаем текст «d2v_index», где «index» - это индекс твита.

def label_tweets(tweets,label):
    result = []
    prefix = label
    i=0
    for i, t in zip(tweets.index, tweets):
     t=str(t)
     result.append(LabeledSentence(t.split(), [prefix + '_%s' % i]))
  return result
x_all_w2v = label_tweets(x_all,'d2v')#x_all is obtained above by conatenating.

Затем мы обучаем трех моделей.

DBOW

cpu_count = multiprocessing.cpu_count()
model_dbow = Doc2Vec(dm=0,size=100,negative=5,min_count=2,alpha=0.065,workers=cpu_count,min_alpha=0.065)
model_dbow.build_vocab([each for each in tqdm(x_all_w2v)])
for epoch in range(30):
    model_dbow.train(utils.shuffle([each for each in tqdm(x_all_w2v)]),total_examples=len(x_all_w2v),epochs=1)
    model_dbow.alpha-=0.002
    model_dbow.min_alpha = model_dbow.alpha
train_vecs_dbow = get_d2v_vectors(model_dbow,x_train,100)
val_vecs_dbow = get_d2v_vectors(model_dbow,x_val,100)

В первой строке мы получаем количество процессоров для параллельной обработки. После определения модели (во второй строке) мы создаем наш словарь, используя помеченные твиты (x_all_w2v). Позже мы обучаем модель, регулируя скорость обучения на каждой итерации. Пожалуйста, прочтите эту документацию, чтобы узнать больше о параметрах функции Doc2Vec.

Две другие модели обучаются таким же образом после определения параметров, специфичных для каждой из них.

DMC

cpu_count = multiprocessing.cpu_count()
model_dmc = Doc2Vec(dm=1,dm_concat=1,size=100,negative=5,min_count=2,alpha=0.065,workers=cpu_count,min_alpha=0.065)
model_dmc.build_vocab([each for each in tqdm(x_all_w2v)])
for epoch in range(30):
    model_dmc.train(utils.shuffle([each for each in tqdm(x_all_w2v)]),total_examples=len(x_all_w2v),epochs=1)
    model_dmc.alpha-=0.002
    model_dmc.min_alpha = model_dmc.alpha
train_vecs_dmc = get_d2v_vectors(model_dmc,x_train,100)
val_vecs_dmc = get_d2v_vectors(model_dmc,x_val,100)

DMM

cpu_count = multiprocessing.cpu_count()
model_dmm = Doc2Vec(dm=1,dm_mean=1,size=100,negative=5,min_count=2,alpha=0.065,workers=cpu_count,min_alpha=0.065)
model_dmm.build_vocab([each for each in tqdm(x_all_w2v)])
for epoch in range(30):
    model_dmm.train(utils.shuffle([each for each in tqdm(x_all_w2v)]),total_examples=len(x_all_w2v),epochs=1)
    model_dmm.alpha-=0.002
    model_dmm.min_alpha = model_dmm.alpha
train_vecs_dmm = get_d2v_vectors(model_dmm,x_train,100)
val_vecs_dmm = get_d2v_vectors(model_dmm,x_val,100)

Мы сохраняем все три модели, чтобы исключить повторное обучение для использования в будущем.

model_dbow.save('d2v_model_dbow.doc2vec')
model_dmm.save('d2v_model_dmm.doc2vec')
model_dmc.save('d2v_model_dmc.doc2vec')
#AFTER THE FIRST TIME DON'T RETRAIN ALL THE MODELS,JUST LOAD THESE #like mod = model_dmm.load()

После обучения каждой из этих моделей мы также извлекаем векторы с помощью функции get_d2v_vectors (), как показано ниже (помните? Это наша основная цель использования моделей Gensim - получить векторизованное представление текстовых данных). Следующая функция вызывается после обучения моделей (в приведенных выше фрагментах).

def get_d2v_vectors(model,corpus,size):
    vecs = np.zeros((len(corpus),size))
    n=0
    for i in corpus.index:
        prefix='d2v_'+str(i)
        vecs[n] = model.docvecs[prefix]
        n+=1
    return vecs

Мы готовы с тремя векторными представлениями с использованием моделей doc2vec. (train_vectors_dbow, train_vectors_dmc, train_vectors_dmm). Перед использованием их с классификаторами необходимо выполнить еще один полезный шаг - объединить эти векторы (например, DBOW + DMM). Авторы Doc2vec рекомендуют этот шаг для достижения лучших результатов. Мы используем функцию numpy.append () для объединения этих векторов.

#FUNCTION TO CONCAT 2-MODELS VECTORS..NO SPECIAL TRAINING IS REQUIRED
def get_concat_vectors(model1,model2,corpus,size):
    vecs = np.zeros((len(corpus),size))
    n=0
    for i in corpus.index:
        prefix='d2v_'+str(i)
        vecs[n] = np.append(model1.docvecs[prefix],model2.docvecs[prefix])
        n+=1
    return vecs

Мы объединяем DBOW с DMM и DBOW с DMC.

train_vecs_dbow_dmm = get_concat_vectors(model_dbow,model_dmm,x_train,200)
val_vecs_dbow_dmm = get_concat_vectors(model_dbow,model_dmm,x_val,200)
train_vecs_dbow_dmc = get_concat_vectors(model_dbow,model_dmc,x_train,200)
val_vecs_dbow_dmc = get_concat_vectors(model_dbow,model_dmc,x_val,200)

Теперь мы используем те же алгоритмы sklearn для целей классификации, используя эти пять векторов аналогичным образом (как мы это делали с CV и Tfidf).

vecs = [(train_vecs_dbow,val_vecs_dbow),(train_vecs_dmc,val_vecs_dmc),(train_vecs_dmm,val_vecs_dmm),(train_vecs_dbow_dmm,val_vecs_dbow_dmm),(train_vecs_dbow_dmc,val_vecs_dbow_dmc)]
classifiers = [BernoulliNB(),LogisticRegression(),LinearSVC(),AdaBoostClassifier(),RidgeClassifier(),PassiveAggressiveClassifier(),Perceptron()]
gensim_names = ['DBOW','DMC','DMM','DBOW+DMM','DBOW+DMC']
data=[]
for train_vecs,val_vecs in vecs:
    for clf in classifiers:
        clf.fit(train_vecs,y_train)
        ac = clf.score(val_vecs,y_val)
        data.append([gensim_names[j],clf_names[i],ac,end-start])
d = pd.DataFrame(data,columns=['Model','Classifier','Ac','Time'])
d.to_csv('gensim_all_clfs.csv')

Выше приведены лишь несколько важных строк кода. Видеть это".

Посмотрим на результаты.

Логистическая регрессия (DBOW), LinearSVC (DBOW), логистическая регрессия (DBOW + DMC) и линейный SVC (DBOW + DMC) дает наивысшую точность 65,35%. Принимая во внимание время обучения, логистическая регрессия (DBOW) является лучшей из них (0,52 секунды). RidgeClassifier (DBOW) также показывает хорошую производительность с точностью 64,2% и всего 0,007 секунды на обучение. Модель DBOW превосходит две другие модели со средней точностью (по всем классификаторам) 60,4%. Модели Gensim показывают несколько худшую производительность, чем модели CV и Tfidf.

В целом, пассивно-агрессивный классификатор (Tfidf-bigrams) показывает лучшие результаты с точностью 83,57%. Итак, давайте оценим это с помощью тестовых данных.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import PassiveAggressiveClassifier
from sklearn.pipeline import make_pipeline
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score,precision_score,recall_score
import time
b=2
st = time.time()
tfidf = TfidfVectorizer(ngram_range=(1,b))
model = make_pipeline(tfidf,PassiveAggressiveClassifier())
model.fit(x_train.values.astype('U'),y_train.values.astype('U'))##
labels = model.predict(x_test.values.astype('U'))###
cm = confusion_matrix(y_test.values.astype('U'),labels)
ac = accuracy_score(y_test.values.astype('U'),labels)
pr = precision_score(y_test.values.astype('U'),labels,average=None)
rc = recall_score(y_test.values.astype('U'),labels,average=None)
en = time.time()
print(b,"-gram",ac,pr.mean(),rc.mean(),en-st)

Когда эта модель оценивается на данных испытаний, она дает точность 80,42%.

Резюме

Твиты собираются с использованием библиотеки Tweepy с последующим выбором полезных функций для моей задачи и преобразованием в CSV. Затем выполняется очистка данных, такая как удаление URL-адресов, символов ретвита, тегов имени пользователя и хэштегов. Лексика SentiWordNet используется для обозначения настроения твитов. Такие шаги, как удаление стоп-слов, лемматизация, выделение основы, выполняются с текстовыми данными. Позже WordCloud используется для визуализации данных. После разделения данных Count Vectorizer и Tfidf Vectorizer используются для математического представления текста. Затем на полученных ранее векторах реализуются девять алгоритмов классификации. Позже модели doc2vec (DBOW, DMC, DMM) обучаются и используют векторы (полученные с помощью этих моделей) для целей классификации, поскольку эти модели сохраняют семантические отношения между словами. Наконец, лучшая модель оценивается с использованием тестовых данных.

Любые предложения по улучшению этого проекта приветствуются. Спасибо за чтение!

Ссылки

1 https://towardsdatascience.com/another-twitter-sentiment-analysis-with-python-part-6-doc2vec-603f11832504

2 https://bhaskarvk.github.io/2015/01/how-to-use-twitters-search-rest-api-most-effectively./

3 https://medium.com/analytics-vidhya/twitter-sentiment-analysis-for-the-2019-election-8f7d52af1887

4https: //chrisalbon.com/machine_learning/trees_and_forests/handle_imbalanced_classes_in_random_forests/