Построение собственного анализатора тональности

Описание анализатора тональности текста

Здравствуйте и вновь добро пожаловать на занятия по теме «Обработка данных: обработка естественных языков на языке Python».

В этой лекции мы ознакомимся с анализом тональности текста. Под тональностью мы будем понимать меру того, насколько положительный или отрицательный оттенок несёт некоторый текст. Такой анализ можно немедленно применить к отзывам на Amazon, твиттам, отзывам на Yelp, обзорам отелей и т. д., когда мы хотим узнать, является ли утверждение положительным или отрицательным, анализируя лишь слова.

На этих занятиях мы создадим простейший анализатор тональности. В этой лекции я познакомлю вас с некоторыми аспектами этого дела, прежде чем мы начнём писать код. Вы увидите, что существует много тонкостей, способных стать серьёзным препятствием.

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

Это отзывы с Amazon, поэтому имеют пятиразрядные рейтинги. Мы рассмотрим категорию «Электроника». Я скопировал файл данных в папку по курсу NLP. Обратите внимание, что это XMS-файл, поэтому вам понадобится XMS-парсер.

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

Как я уже отмечал, мы рассмотрим только категорию «Электроника», но вы можете попробовать написать такой же код для других категорий, чтобы проверить методику.

Поскольку наши данные находятся в формате XML, поэтому мы используем синтаксический XML-парсер BeautifulSoap. Мы пропустим избыточные данные, а будем использовать только ключ review_text. Чтобы создать вектор признаков, мы поступим так же, как и при создании первого набора данных, когда посчитали число упоминаний каждого слова и поделили его на общее число слов. Для этого нам понадобится дважды обработать данные. Первый раз – с целью посчитать общее количество отдельных слов, чтобы мы могли знать величину нашего вектора признаков и, возможно, удалить слова из стоп-листа вроде «это», «я» или «два» для уменьшения объёма словаря. Таким образом мы определим индекс каждого признака. При втором проходе мы сможем установить значения для каждого состояния вектора данных.

После этого надо будет просто создать классификатор, подобный тому, который мы создали для детектора спама. Следовательно, единственное, что необходимо сделать, – использовать модель логистической регрессии. Таким образом мы сможем посмотреть на весовые коэффициенты обученной модели, чтобы узнать оценку для каждого отдельного входящего слова. Так, например, если вы увидите слово вроде «ужасно» и оно имеет весовой коэффициент -1, то это значит, что слово связано с отрицательными отзывами.

Анализ тональности текста на языке Python

Теперь мы напишем код для нашего анализатора тональности текста.

Если вы не хотите писать код, а лишь просмотреть его или запустить, то перейдите по адресу и в папке nlp_class найдите файл sentiment.py.

В этом примере мы воспользуемся библиотекой NLTK. Подробнее о ней мы поговорим позже, а сейчас нам понадобится одна из её функций. Кроме того, нам понадобятся библиотеки NumPy и scikit-learn (чтобы использовать логистическую регрессию), а также XMS-парсер.

import nltk

import numpy as np

from nltk.stem import WordNetLemmatizer

from sklearn.linear_model import LogisticRegression

from bs4 import BeautifulSoap

Инициируем теперь наш лемматизатор. Кратко говоря, лемматизатор приводит слова в основную (словарную) форму. К примеру слова «собаки» и «собака» или «коты» и «кот» становятся одинаковыми словами, что позволяет уменьшить размер словаря.

wordnet_lemmatizer = WordNetLemmatizer()

Далее, у нас должен быть стоп-лист слов, которые не должны браться во внимание. Это простые слова вроде «про», «около», «свыше» и так далее, поскольку в действительности они не имеют никакого конкретного значения для нашей цели. Список этих слов уже есть у нас в папке в файле stopwords.txt.

stopwords = set(w.rstrip() for w in open(‘stopwords.txt’))

Теперь загрузим отзывы. Если вы не умеете пользоваться BeautifulSoup, я настоятельно рекомендую ознакомиться с соответствующей документацией, поскольку это весьма и весьма полезная библиотека. Как и ранее, ориентируемся мы только на один ключ «review_text».

positive_reviews = BeautifulSoap(open(‘electronics/positive.review’).read())

positive_reviews = positive_reviews.findAll(‘review_text’)

negative_reviews = BeautifulSoap(open(‘electronics/negative.review’).read())

negative_reviews = negative_reviews.findAll(‘review_text’)

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

np.random.shuffle(positive_reviews)

positive_reviews = positive_reviews[:len(negative_reviews)]

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

Поэтому создадим переменную word_index_map, которая будет увеличиваться на единицу в случае встречи нового слова, а затем присвоим словам индексы. Для этого нам понадобится функция my_tokenizer.

word_index_map = {}

current_index = 0

for review in positive_reviews:

tokens = my_tokenizer(review.text)

for token in tokens:

if token in tokens:

word_index_map[token] = current_index

current_index += 1

Итак, что же делает функция my_tokenizer?

Во-первых, она переводит все буквы в словах в нижний регистр, чтобы слова, написанные с заглавной буквы, не считались отличными от таких же слов, написанных малыми. Для этого мы воспользуемся специальной функцией из библиотеки NLTK. Далее, мы убираем все короткие слова, поскольку это явно предлоги, не имеющие никакой ценности. Мы оставим все слова, чья длина больше двух символов. Далее, как вы помните, мы инициировали лемматизатор, приводящий слова в основную форму – «прыгающий» в «прыгать», «собаки» в «собака» и так далее. Используем его. И последнее – мы убираем слова из стоп-листа.

def my_tokenizer(s):

s = s.lower()

tokens = nltk.tokenize.word_tokenize(s)

tokens = [t for t in tokens if len(t) > 2]

tokens = [wordnet_lemmatizer.lemmatize(t) for t in tokens]

tokens = [t for t in tokens if t not in stopwords]

return tokens

Запустим программу, чтобы посмотреть, как всё работает. Программа не работает. Это было сделано специально с целью показать, как установить библиотеку NLTK, поскольку недостаточно просто загрузить и установить библиотеку. Кроме этого, надо ввести

sudo pip install nltk

sudo pip install –upgrade pip

Попробуем теперь ещё раз. На экране вы можете видеть, что получается, когда библиотека NLTK пытается найти ресурсы, которые ещё не загружены. В таких случаях я обычно использую Iphyton.

import nltk

nltk download()

Это запускает загрузчик NLTK. Нам нужно найти и загрузить tokenizers/punkt/english.pickle. Некоторые полагают, что лучше просто установить всё скопом, но по-моему, это неудачная мысль.

Загрузив и установив, мы обнаружим, что нужен ещё и wordnet. Повторим операцию – запустим загрузчик NLTK, найдём wordnet и установим его.

Попробуем запустить ещё раз. Теперь всё работает.

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

positive_tokenized = []

negative_tokenized = []

for review in positive_reviews:

tokens = my_tokenizer(review.text)

pozitive_tokenized.append(tokens)

for token in tokens:

if token in tokens:

word_index_map[token] = current_index

current_index += 1

И делаем то же самое для отрицательных отзывов с добавлением новых слов в словарь.

for review in negative_reviews:

tokens = my_tokenizer(review.text)

negative_tokenized.append(tokens)

for token in tokens:

if token in tokens:

word_index_map[token] = current_index

current_index += 1

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

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

def tokens_to_vector(tokens, label):

x = np.zeros(len(word_index_map) + 1)

for t in tokens:

i = word_index_map[t]

x[i] += 1

x = x / x.sum()

x[-1] = label

return x

Размерность N равна общему количеству примеров, являющемуся суммой количества отрицательных и положительных слов.

N = len(positive_tokenized) + len(negative_tokenized)

Теперь создадим наш вектор данных. Сначала создадим массив из нулей, а потом заполним его. Обратите внимание, что при положительном отзыве значение метки равно единице, при отрицательном – нулю.

data = np.zeros((N, len(word_index_map) + 1))

i = 0

for tokens in positive_tokenized:

xy = tokens_to_vector(tokens, 1)

data[i; :] = xy

i += 1

for tokens in negative_tokenized:

xy = tokens_to_vector(tokens, 0)

data[i; :] = xy

i += 1

Как и ранее, перемешаем наши данные. Наше X составляют все данные, кроме последнего столбца, а Y, являющейся меткой, – последний столбец.

np.random.shuffle(data)

X = data[:, :-1]

Y = data[:, -1]

И точно так же, как и ранее, последние 100 примеров будут проверочным набором данных, а все остальные – учебным.

Xtrain = X[:-100,]

Ytrain = Y[:-100,]

Xtest = X[-100:,]

Ytest = Y[-100:,]

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

model = LogasticRegression()

model.fit(Xtrain, Ytrain)

print ‘’Classification rate:’’, model.score(Xtest, Ytest)

Теперь удостоверимся, что всё работает. Коэффициент классификации оказался равным 65% – отнюдь не самый замечательный результат, но куда лучше, чем простое угадывание. Повторный запуск даёт коэффициент классификации уже равным 74%, что очень неплохо.

Вот какую интересную вещь мы можем при этом сделать. Мы можем посмотреть на весовой коэффициент каждого слова, чтобы узнать, какое значение – положительное или отрицательное – имеет это слово. При этом нам нужны не все весовые коэффициенты, а лишь те, чьё значение сильно отлично от нуля.

Установим пороговый уровень равным 0,5, хотя вы можете самостоятельно поэкспериментировать с разными значениями. Мы проверим все слова из нашего списка, чтобы получить их весовые коэффициенты в модели, и те из них, чьё значение больше значения порогового уровня или меньше отрицательного значения порогового уровня, выведем на экран вместе с их весовыми коэффициентами.

threshold = 0.5

for word, index in word_index_map.iteritems():

weight = model.coef_[0][index]

if weight > threshold or weight < -threshold:

print word, weight

Как вы можете убедиться, некоторые строки имеют смысл. «Отлично» имеет положительный оттенок, «легко» также имеет положительный оттенок, «поддержка» имеет отрицательный оттенок, «любовь» имеет положительный оттенок, «возвращён» – отрицательный. «Кабель» несёт положительный оттенок, что странно – это и другие слова должны были оказаться в стоп-листе. «Замечательный» имеет положительный оттенок, «отходы» – отрицательный, «высокий» – вновь положительный. Ряд слов, правда, не несёт смысла. «Качественный» – положительный оттенок.

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

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Share via
Copy link