Рубрики
Без рубрики

Наука о данных с помощью Python & R: Классификация настроений с использованием линейных методов

В этом уроке вы узнаете, как создать классификацию настроений с помощью линейных методов с помощью Python и R

Автор оригинала: Jose A Dianes.

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

Проще говоря, анализ настроений направлен на определение отношения говорящего или автора к какой-либо теме или общей контекстуальной полярности документа. В нашем случае мы будем использовать его, чтобы определить, имеет ли текст положительное, отрицательное или нейтральное настроение или враждебность. Представьте, например, что мы применяем его к записям в Twitter о хэштеге #Windows10. Мы сможем определить, как люди относятся к новой версии операционной системы Microsoft. Конечно, это не имеет большого значения применительно к отдельному фрагменту текста. Я верю, что средний человек всегда будет судить лучше, чем то, что мы построим здесь. Однако наша модель покажет свои преимущества при автоматической обработке больших объемов текста очень быстро или при обработке большого количества записей.

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

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

Наконец, нам понадобятся некоторые данные для обучения нашей модели. Для этого мы будем использовать данные конкурса Kaggle UMICH SI650 . Как описано там, это задача классификации настроений. Каждый документ (строка в файле данных) – это предложение, извлеченное из социальных сетей. Файлы содержат:

  • Обучающие данные: 7086 строк. Формат: 1/0 (вкладка) предложение
  • Тестовые данные: 33052 строки, каждая из которых содержит одно предложение.

Данные были первоначально собраны из opinmind.com (который больше не активен). Наша цель состоит в том, чтобы классифицировать настроение каждого предложения на “положительное” или “отрицательное”. Итак, давайте посмотрим, как загружать и подготавливать наши данные, используя как Python, так и R.

Весь исходный код для различных частей этой серии учебных пособий и приложений можно проверить на GitHub . Не стесняйтесь участвовать и делиться с нами своими успехами!

Загрузка и подготовка данных

Как обычно, мы сначала загрузим наши наборы данных локально, а затем загрузим их в фреймы данных как на R, так и на Python.

R

В R мы используем read.csv для чтения CSV-файлов в переменные data.frame . Хотя функция R read.csv может работать с URL-адресами, https во многих случаях является проблемой для R, поэтому вам нужно использовать пакет, такой как RCurl, чтобы обойти его. Более того, из описания страницы Kaggle мы знаем, что файл разделен вкладками, нет заголовка, и нам нужно отключить цитирование, так как некоторые предложения содержат кавычки, и это в какой-то момент остановит анализ файла.

library(RCurl)
## Loading required package: bitops
test_data_url <- "https://dl.dropboxusercontent.com/u/8082731/datasets/UMICH-SI650/testdata.txt"
train_data_url <- "https://dl.dropboxusercontent.com/u/8082731/datasets/UMICH-SI650/training.txt"

test_data_file <- getURL(test_data_url)
train_data_file <- getURL(train_data_url)

train_data_df <- read.csv(
    text = train_data_file, 
    sep='\t', 
    header=FALSE, 
    quote = "",
    stringsAsFactor=F,
    col.names=c("Sentiment", "Text"))
test_data_df <- read.csv(
    text = test_data_file, 
    sep='\t', 
    header=FALSE, 
    quote = "",
    stringsAsFactor=F,
    col.names=c("Text"))
# we need to convert Sentiment to factor
train_data_df$Sentiment <- as.factor(train_data_df$Sentiment)

Теперь у нас есть наши данные в кадрах данных. У нас есть 7086 предложений для обучающих данных и 33052 предложения для тестовых данных. Предложения находятся в столбце с именем Text , а тег Sentiment (только для обучающих данных) – в столбце с именем Sentiment . Давайте взглянем на первые несколько строк данных обучения.

head(train_data_df)
##   Sentiment
## 1         1
## 2         1
## 3         1
## 4         1
## 5         1
## 6         1
##                                                                                                                           Text
## 1                                                                                      The Da Vinci Code book is just awesome.
## 2 this was the first clive cussler i've ever read, but even books like Relic, and Da Vinci code were more plausible than this.
## 3                                                                                             i liked the Da Vinci Code a lot.
## 4                                                                                             i liked the Da Vinci Code a lot.
## 5                                                     I liked the Da Vinci Code but it ultimatly didn't seem to hold it's own.
## 6  that's not even an exaggeration ) and at midnight we went to Wal-Mart to buy the Da Vinci Code, which is amazing of course.

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

table(train_data_df$Sentiment)
## 
##    0    1 
## 3091 3995

То есть у нас есть данные, более или менее равномерно распределенные, с 3091 отрицательно помеченными предложениями и 3995 положительно помеченными предложениями. Как долго в среднем наши предложения состоят из слов?

mean(sapply(sapply(train_data_df$Text, strsplit, " "), length))
## [1] 10.88682

Длина около 10,8 слов.

Питон

Хотя в конечном итоге мы будем использовать sklearn.feature_extraction.text.CountVectorizer чтобы создать набор функций “мешок слов”, и эта библиотека напрямую принимает имя файла , нам нужно передать вместо этого последовательность документов, поскольку наш обучающий файл содержит не только текст, но и теги настроений (которые нам нужно удалить).

import urllib
    
# define URLs
test_data_url = "https://dl.dropboxusercontent.com/u/8082731/datasets/UMICH-SI650/testdata.txt"
train_data_url = "https://dl.dropboxusercontent.com/u/8082731/datasets/UMICH-SI650/training.txt"
    
# define local file names
test_data_file_name = 'test_data.csv'
train_data_file_name = 'train_data.csv'
    
# download files using urlib
test_data_f = urllib.urlretrieve(test_data_url, test_data_file_name)
train_data_f = urllib.urlretrieve(train_data_url, train_data_file_name)

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

import pandas as pd
    
test_data_df = pd.read_csv(test_data_file_name, header=None, delimiter="\t", quoting=3)
test_data_df.columns = ["Text"]
train_data_df = pd.read_csv(train_data_file_name, header=None, delimiter="\t", quoting=3)
train_data_df.columns = ["Sentiment","Text"]
train_data_df.shape
    (7086, 2)
test_data_df.shape
    (33052, 1)

Здесь header=0 указывает, что первая строка файла содержит имена столбцов, delimiter=\t указывает, что поля разделены вкладками, и quoting=3 говорит Python игнорировать двойные кавычки, в противном случае вы можете столкнуться с ошибками при попытке прочитать файл.

Давайте проверим первые несколько строк данных о поездах.

 train_data_df.head()
0 Книга “Код да Винчи” просто потрясающая. 1
1 это был первый Клайв Касслер, которого я когда-либо читал… 1
2 мне очень понравился “Код да Винчи”. 1
3 мне очень понравился “Код да Винчи”. 1
4 Мне понравился “Код да Винчи”, но в конечном итоге он мне понравился… 1

И данные испытаний.

test_data_df.head()
0 “Мне все равно, что кто-то говорит, мне нравится Хиллари…
1 отлично проведите время в Пердью!..
2 Да, я все еще в Лондоне, и это просто потрясающе…
3 Должен сказать, я ненавижу поведение Пэрис Хилтон…
4 я буду любить “Лейкерс”.

Давайте посчитаем, сколько меток у нас есть для каждого класса настроений.

train_data_df.Sentiment.value_counts()
    1    3995
    0    3091
    dtype: int64

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

import numpy as np 
np.mean([len(s.split(" ")) for s in train_data_df.Text])
    10.886819079875812

Подготовка корпуса

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

Работа с текстовыми корпусами предполагает использование методов обработки естественного языка. Bot, R и Python способны выполнять действительно мощные преобразования с текстовыми данными. Однако мы будем использовать только некоторые основные из них. Требования к классификатору “мешок слов” в этом смысле минимальны. Нам просто нужно посчитать слова, поэтому процесс сводится к некоторому упрощению и унификации терминов, а затем к их подсчету. Процесс упрощения в основном включает в себя удаление знаков препинания, строчных букв, удаление стоп-слов и сокращение слов до их лексических корней (т. Е. stemming ).

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

R

В R мы будем использовать пакет tm для интеллектуального анализа текста, поэтому сначала импортируем его, а затем создадим корпус.

library(tm)
## Loading required package: NLP
corpus <- Corpus(VectorSource(c(train_data_df$Text, test_data_df$Text)))

Давайте объясним, что мы только что сделали. Сначала мы использовали как тестовые, так и обучающие данные. Нам нужно рассмотреть все возможные миры в нашем корпусе. Затем мы создаем Векторный источник , который является типом ввода для функции Corpus , определенной в пакете tm . Это дает нам объект Corpus , который в основном представляет собой набор объектов контента+метаданных, где контент содержит наши предложения. Например, содержимое первого документа выглядит следующим образом.

corpus[1]$content
## [[1]]
## The Da Vinci Code book is just awesome.

Чтобы использовать этот корпус, нам нужно преобразовать его содержание следующим образом.

corpus <- tm_map(corpus, content_transformer(tolower))
# the following line may or may not be needed, depending on
# your tm  package version
# corpus <- tm_map(corpus, PlainTextDocument)
corpus <- tm_map(corpus, removePunctuation)
corpus <- tm_map(corpus, removeWords, stopwords("english"))
corpus <- tm_map(corpus, stripWhitespace)
corpus <- tm_map(corpus, stemDocument)

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

corpus[1]$content
## [[1]]
##  da vinci code book just awesom

Чтобы найти функции ввода документов для нашего классификатора, мы хотим поместить этот корпус в форму матрицы документов. Матрица документа-это числовая матрица, содержащая столбец для каждого отдельного слова во всем нашем корпусе и строку для каждого документа. Данная ячейка равна частоте в документе для данного термина.

Вот как мы это делаем в R.

dtm <- DocumentTermMatrix(corpus)
dtm
## DocumentTermMatrix (documents: 40138, terms: 8383)
## Non-/sparse entries: 244159/336232695
## Sparsity           : 100%
## Maximal term length: 614
## Weighting          : term frequency (tf)

Если мы рассмотрим каждый столбец как термин для нашей модели, мы получим очень сложную модель с 83 различными функциями. Это сделает модель медленной и, вероятно, не очень эффективной. Некоторые термины или слова более важны, чем другие, и мы хотим удалить те, которые не так важны. Мы можем использовать функцию removeSparseTerms из пакета tm , где мы передаем матрицу и число, которое дает максимально допустимую разреженность для термина в нашем корпусе. Например, если нам нужны термины, которые появляются по крайней мере в 1% документов, мы можем сделать следующее.

sparse <- removeSparseTerms(dtm, 0.99)
sparse
## DocumentTermMatrix (documents: 40138, terms: 85)
## Non-/sparse entries: 119686/3292044
## Sparsity           : 96%
## Maximal term length: 9
## Weighting          : term frequency (tf)

В итоге мы получаем всего 85 терминов. Чем ближе это значение к 1, тем больше терминов мы будем иметь в нашем разреженном объекте, так как количество документов, в которых нам нужен термин, меньше.

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

important_words_df <- as.data.frame(as.matrix(sparse))
colnames(important_words_df) <- make.names(colnames(important_words_df))
# split into train and test
important_words_train_df <- head(important_words_df, nrow(train_data_df))
important_words_test_df <- tail(important_words_df, nrow(test_data_df))

# Add to original dataframes
train_data_words_df <- cbind(train_data_df, important_words_train_df)
test_data_words_df <- cbind(test_data_df, important_words_test_df)

# Get rid of the original Text field
train_data_words_df$Text <- NULL
test_data_words_df$Text <- NULL

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

Питон

Теперь в Python. Класс sklearn.feature_extraction.text.CountVectorizer в замечательной библиотеке scikit learn Python преобразует коллекцию текстовых документов в матрицу подсчетов токенов. Это как раз то, что нам нужно реализовать позже в нашем линейном классификаторе “мешок слов”.

Сначала нам нужно инициализировать векторизатор . Нам нужно удалить знаки препинания, строчные буквы, удалить стоп-слова и стержневые слова. Все эти шаги могут быть непосредственно выполнены CountVectorizer , если мы передадим правильные значения параметров . Мы можем сделать это следующим образом. Обратите внимание, что для этапа стемминга нам самим нужно предоставить стеммер. Мы будем использовать базовую реализацию Porter Stemmer , широко используемого стеммера, названного в честь его создателя.

import re, nltk
from sklearn.feature_extraction.text import CountVectorizer        
from nltk.stem.porter import PorterStemmer

#######
# based on http://www.cs.duke.edu/courses/spring14/compsci290/assignments/lab02.html
stemmer = PorterStemmer()
def stem_tokens(tokens, stemmer):
    stemmed = []
    for item in tokens:
        stemmed.append(stemmer.stem(item))
    return stemmed

def tokenize(text):
    # remove non letters
    text = re.sub("[^a-zA-Z]", " ", text)
    # tokenize
    tokens = nltk.word_tokenize(text)
    # stem
    stems = stem_tokens(tokens, stemmer)
    return stems
######## 

vectorizer = CountVectorizer(
    analyzer = 'word',
    tokenizer = tokenize,
    lowercase = True,
    stop_words = 'english',
    max_features = 85
)

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

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

corpus_data_features = vectorizer.fit_transform(
    train_data_df.Text.tolist() + test_data_df.Text.tolist())

С массивами Numpy легко работать, поэтому преобразуйте результат в массив.

corpus_data_features_nd = corpus_data_features.toarray()
corpus_data_features_nd.shape
    (40138, 85)

Давайте взглянем на слова в словаре.

vocab = vectorizer.get_feature_names()
print vocab
    [u'aaa', u'amaz', u'angelina', u'awesom', u'beauti', u'becaus', u'boston', u'brokeback', u'citi', u'code', u'cool', u'cruis', u'd', u'da', u'drive', u'francisco', u'friend', u'fuck', u'geico', u'good', u'got', u'great', u'ha', u'harri', u'harvard', u'hate', u'hi', u'hilton', u'honda', u'imposs', u'joli', u'just', u'know', u'laker', u'left', u'like', u'littl', u'london', u'look', u'lot', u'love', u'm', u'macbook', u'make', u'miss', u'mission', u'mit', u'mountain', u'movi', u'need', u'new', u'oh', u'onli', u'pari', u'peopl', u'person', u'potter', u'purdu', u'realli', u'right', u'rock', u's', u'said', u'san', u'say', u'seattl', u'shanghai', u'stori', u'stupid', u'suck', u't', u'thi', u'thing', u'think', u'time', u'tom', u'toyota', u'ucla', u've', u'vinci', u'wa', u'want', u'way', u'whi', u'work']

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

# Sum up the counts of each vocabulary word
dist = np.sum(corpus_data_features_nd, axis=0)
    
# For each, print the vocabulary word and the number of times it 
# appears in the data set
for tag, count in zip(vocab, dist):
    print count, tag
    1179 aaa
    485 amaz
    1765 angelina
    3170 awesom
    2146 beauti
    1694 becaus
    2190 boston
    2000 brokeback
    423 citi
    2003 code
    481 cool
    2031 cruis
    439 d
    2087 da
    433 drive
    1926 francisco
    477 friend
    452 fuck
    1085 geico
    773 good
    571 got
    1178 great
    776 ha
    2094 harri
    2103 harvard
    4492 hate
    794 hi
    2086 hilton
    2192 honda
    1098 imposs
    1764 joli
    1054 just
    896 know
    2019 laker
    425 left
    4080 like
    507 littl
    2233 london
    811 look
    421 lot
    10334 love
    1568 m
    1059 macbook
    631 make
    1098 miss
    1101 mission
    1340 mit
    2081 mountain
    1207 movi
    1220 need
    459 new
    551 oh
    674 onli
    2094 pari
    1018 peopl
    454 person
    2093 potter
    1167 purdu
    2126 realli
    661 right
    475 rock
    3914 s
    495 said
    2038 san
    627 say
    2019 seattl
    1189 shanghai
    467 stori
    2886 stupid
    4614 suck
    1455 t
    1705 thi
    662 thing
    1524 think
    781 time
    2117 tom
    2028 toyota
    2008 ucla
    774 ve
    2001 vinci
    3703 wa
    1656 want
    932 way
    547 whi
    512 work

Линейный классификатор “Мешок слов”

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

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

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

R

Поэтому, чтобы получить наш оценочный набор, мы разделим его с помощью sample.split из пакета caTools .

library(caTools)
set.seed(1234)
# first we create an index with 80% True values based on Sentiment
spl <- sample.split(train_data_words_df$Sentiment, .85)
# now we use it to split our data into train and test
eval_train_data_df <- train_data_words_df[spl==T,]
eval_test_data_df <- train_data_words_df[spl==F,]

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

log_model <- glm(Sentiment~., data=eval_train_data_df, family=binomial)
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
summary(log_model)
## 
## Call:
## glm(formula = Sentiment ~ ., family = binomial, data = eval_train_data_df)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.9361   0.0000   0.0000   0.0035   3.1025  
## 
## Coefficients: (21 not defined because of singularities)
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  6.791e-01  1.674e+00   0.406 0.685023    
## aaa                 NA         NA      NA       NA    
## also         8.611e-01  9.579e-01   0.899 0.368690    
## amaz         2.877e+00  1.708e+00   1.684 0.092100 .  
## angelina            NA         NA      NA       NA    
## anyway      -1.987e+00  1.934e+00  -1.028 0.304163    
## awesom       2.963e+01  3.231e+03   0.009 0.992682    
## back        -1.227e+00  2.494e+00  -0.492 0.622807    
## beauti       2.946e+01  1.095e+04   0.003 0.997853    
## big          2.096e+01  2.057e+03   0.010 0.991871    
## boston              NA         NA      NA       NA    
## brokeback   -2.051e+00  2.559e+00  -0.801 0.423005    
## can         -2.175e+00  1.383e+00  -1.573 0.115695    
## citi                NA         NA      NA       NA    
## code         7.755e+00  3.029e+00   2.561 0.010451 *  
## cool         1.768e+01  2.879e+03   0.006 0.995101    
## crappi      -2.750e+01  4.197e+04  -0.001 0.999477    
## cruis       -3.008e+01  1.405e+04  -0.002 0.998292    
## dont        -5.064e+00  1.720e+00  -2.943 0.003247 ** 
## even        -1.982e+00  2.076e+00  -0.955 0.339653    
## francisco           NA         NA      NA       NA    
## friend       1.251e+01  1.693e+03   0.007 0.994102    
## fuck        -2.613e+00  2.633e+00  -0.992 0.321041    
## geico               NA         NA      NA       NA    
## get          4.405e+00  1.821e+00   2.419 0.015548 *  
## good         4.704e+00  1.260e+00   3.734 0.000188 ***
## got          1.725e+01  2.038e+03   0.008 0.993247    
## great        2.096e+01  1.249e+04   0.002 0.998660    
## harri       -3.054e-01  2.795e+00  -0.109 0.912999    
## harvard             NA         NA      NA       NA    
## hate        -1.364e+01  1.596e+00  -8.544  < 2e-16 ***
## hilton              NA         NA      NA       NA    
## honda               NA         NA      NA       NA    
## imposs      -6.952e+00  1.388e+01  -0.501 0.616601    
## ive          2.420e+00  2.148e+00   1.127 0.259888    
## joli                NA         NA      NA       NA    
## just        -4.374e+00  2.380e+00  -1.838 0.066077 .  
## know        -2.700e+00  1.088e+00  -2.482 0.013068 *  
## laker               NA         NA      NA       NA    
## like         5.296e+00  7.276e-01   7.279 3.36e-13 ***
## littl       -8.384e-01  1.770e+00  -0.474 0.635760    
## london              NA         NA      NA       NA    
## look        -2.235e+00  1.599e+00  -1.398 0.162120    
## lot          8.641e-01  1.665e+00   0.519 0.603801    
## love         1.220e+01  1.396e+00   8.735  < 2e-16 ***
## macbook             NA         NA      NA       NA    
## make        -1.053e-01  1.466e+00  -0.072 0.942710    
## miss         2.693e+01  4.574e+04   0.001 0.999530    
## mission      9.138e+00  1.378e+01   0.663 0.507236    
## mit                 NA         NA      NA       NA    
## mountain    -2.847e+00  2.042e+00  -1.394 0.163256    
## movi        -2.433e+00  6.101e-01  -3.988 6.67e-05 ***
## much         1.874e+00  1.516e+00   1.237 0.216270    
## need        -6.816e-01  2.168e+00  -0.314 0.753196    
## new         -2.709e+00  1.718e+00  -1.577 0.114797    
## now          1.416e+00  3.029e+00   0.468 0.640041    
## one         -4.691e+00  1.641e+00  -2.858 0.004262 ** 
## pari                NA         NA      NA       NA    
## peopl       -3.243e+00  1.808e+00  -1.794 0.072869 .  
## person       3.824e+00  1.643e+00   2.328 0.019907 *  
## potter      -5.702e-01  2.943e+00  -0.194 0.846372    
## purdu               NA         NA      NA       NA    
## realli       5.897e-02  7.789e-01   0.076 0.939651    
## right        6.194e+00  3.075e+00   2.014 0.043994 *  
## said        -2.173e+00  1.927e+00  -1.128 0.259326    
## san                 NA         NA      NA       NA    
## say         -2.584e+00  1.948e+00  -1.326 0.184700    
## seattl              NA         NA      NA       NA    
## see          2.743e+00  1.821e+00   1.506 0.132079    
## shanghai            NA         NA      NA       NA    
## still        1.531e+00  1.478e+00   1.036 0.300146    
## stori       -7.405e-01  1.806e+00  -0.410 0.681783    
## stupid      -4.408e+01  4.838e+03  -0.009 0.992731    
## suck        -5.333e+01  2.885e+03  -0.018 0.985252    
## thing       -2.562e+00  1.534e+00  -1.671 0.094798 .  
## think       -8.548e-01  7.275e-01  -1.175 0.239957    
## though       1.432e+00  2.521e+00   0.568 0.570025    
## time         2.997e+00  1.739e+00   1.723 0.084811 .  
## tom          2.673e+01  1.405e+04   0.002 0.998482    
## toyota              NA         NA      NA       NA    
## ucla                NA         NA      NA       NA    
## vinci       -7.760e+00  3.501e+00  -2.216 0.026671 *  
## want         3.642e+00  9.589e-01   3.798 0.000146 ***
## way          1.966e+01  7.745e+03   0.003 0.997975    
## well         2.338e+00  1.895e+01   0.123 0.901773    
## work         1.715e+01  3.967e+04   0.000 0.999655    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 8251.20  on 6022  degrees of freedom
## Residual deviance:  277.97  on 5958  degrees of freedom
## AIC: 407.97
## 
## Number of Fisher Scoring iterations: 23

Первый параметр-это формула в виде Output~Input , где . на входной стороне означает использовать каждую отдельную переменную, кроме выходной. Затем мы передаем фрейм данных и family=binomial , что означает, что мы хотим использовать логистическую регрессию.

Функция сводки дает нам действительно хорошее представление о модели, которую мы только что построили. В разделе коэффициенты перечислены все входные переменные, используемые в модели. Ряд звездочек в самом конце их дает нам важность каждого из них, причем *** является самым большим уровнем значимости, и ** или * также важен. Эти стандарты относятся к значениям в Pr . например, мы получаем, что стебель удивительный имеет большое значение, с высокой положительной Оценкой значением. Это означает, что документ с таким стержнем, скорее всего, будет помечен сентиментом 1 (положительный). Мы видим противоположный случай со стеблем ненависть . Мы также видим, что есть много терминов, которые, по-видимому, не имеют большого значения.

Итак, давайте использовать нашу модель с тестовыми данными.

log_pred <- predict(log_model, newdata=eval_test_data_df, type="response")
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type =
## ifelse(type == : prediction from a rank-deficient fit may be misleading

Предыдущий predict , вызванный с type="response" , вернет вероятности (см. Логистическая регрессия ). Допустим, мы хотим, чтобы порог .5 для документа был классифицирован как положительный (тег настроения равен 1). Тогда мы можем рассчитать точность следующим образом.

# Calculate accuracy based on prob
table(eval_test_data_df$Sentiment, log_pred>.5)
##    
##     FALSE TRUE
##   0   453   11
##   1     9  590

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

(453 + 590) / nrow(eval_test_data_df)
## [1] 0.9811853

Это очень хорошая точность. Похоже, что наш подход к сумке слов хорошо работает с этой конкретной проблемой.

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

log_pred_test <- predict(log_model, newdata=test_data_words_df, type="response")
## Warning in predict.lm(object, newdata, se.fit, scale = 1, type =
## ifelse(type == : prediction from a rank-deficient fit may be misleading
test_data_df$Sentiment <- log_pred_test>.5
    
set.seed(1234)
spl_test <- sample.split(test_data_df$Sentiment, .0005)
test_data_sample_df <- test_data_df[spl_test==T,]

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

test_data_sample_df[test_data_sample_df$Sentiment==T, c('Text')]
##  [1] "Love Story At Harvard [ awesome drama!"                                                                                                                                                                                              
##  [2] "boston's been cool and wet..."                                                                                                                                                                                                       
##  [3] "On a lighter note, I love my macbook."                                                                                                                                                                                               
##  [4] "Angelina Jolie was a great actress in Girl, Interrupted."                                                                                                                                                                            
##  [5] "I love Angelina Jolie and i love you even more, also your music on your site is my fav."                                                                                                                                             
##  [6] "And Tom Cruise is beautiful...."                                                                                                                                                                                                     
##  [7] "i love Kappo Honda, which is across the street.."                                                                                                                                                                                    
##  [8] "It's about the MacBook Pro, which is awesome and I want one, but I have my beloved iBook, and believe you me, I love it.."                                                                                                           
##  [9] "I mean, we knew Harvard was dumb anyway -- right, B-girls? -- but this is further proof)..."                                                                                                                                         
## [10] "anyway, shanghai is really beautiful ï¼\u008c 滨æ±\u009f大é\u0081\u0093æ\u0098¯å¾\u0088ç\u0081µç\u009a\u0084å\u0095¦ ï¼\u008c é\u0082£ä¸ªstarbucksæ\u0098¯ä¸\u008aæµ·é£\u008eæ\u0099¯æ\u009c\u0080好ç\u009a\u0084starbucks ~ ~!!!"
## [11] "i love shanghai too =)."

И отрицательные.

test_data_sample_df[test_data_sample_df$Sentiment==F, c('Text')]
## [1] "the stupid honda lol or a BUG!.."                                                                                                               
## [2] "Angelina Jolie says that being self-destructive is selfish and you ought to think of the poor, starving, mutilated people all around the world."
## [3] "thats all i want from u capital one!"                                                                                                           
## [4] "DAY NINE-SAN FRANCISCO, CA. 8am sucks."                                                                                                         
## [5] "I hate myself \" \" omg MIT sucks!"                                                                                                             
## [6] "Hate London, Hate London....."

Питон

Для выполнения логистической регрессии в Python мы используем Логистическую регрессию . Но сначала давайте разделим наши данные обучения, чтобы получить набор оценок. Независимо от того, используем ли мы R или Python, проблема с отсутствием меток в нашем исходном наборе тестов все еще сохраняется, и нам нужно создать отдельный набор оценок из наших исходных обучающих данных, если мы хотим оценить наш классификатор. Мы будем использовать train test split .

from sklearn.cross_validation import train_test_split
    
# remember that corpus_data_features_nd contains all of our 
# original train and test data, so we need to exclude
# the unlabeled test entries
X_train, X_test, y_train, y_test  = train_test_split(
        corpus_data_features_nd[0:len(train_data_df)], 
        train_data_df.Sentiment,
        train_size=0.85, 
        random_state=1234)

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

from sklearn.linear_model import LogisticRegression
    
log_model = LogisticRegression()
log_model = log_model.fit(X=X_train, y=y_train)

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

y_pred = log_model.predict(X_test)

Существует функция классификации под названием sklearn.metrics.classification_report , которая вычисляет несколько типов (прогнозных) оценок в модели классификации. Проверьте также sklearn.metrics . В этом случае мы хотим знать точность наших классификаторов.

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
                 precision    recall  f1-score   support
    
              0       0.98      0.99      0.98       467
              1       0.99      0.98      0.99       596
    
    avg / total       0.98      0.98      0.98      1063

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

# train classifier
log_model = LogisticRegression()
log_model = log_model.fit(X=corpus_data_features_nd[0:len(train_data_df)], y=train_data_df.Sentiment)
    
# get predictions
test_pred = log_model.predict(corpus_data_features_nd[len(train_data_df):])
    
# sample some of them
import random
spl = random.sample(xrange(len(test_pred)), 15)
    
# print text and labels
for text, sentiment in zip(test_data_df.Text[spl], test_pred[spl]):
    print sentiment, text
    1 I love paris hilton...
    1 i love seattle..
    1 I love those GEICO commercials especially the one with Mini-Me doing that little rap dance.; )..
    1 However you look at it, I've been here almost two weeks now and so far I absolutely love UCLA...
    0 By the time I left the Hospital, I had no time to drive to Boston-which sucked because 1)
    1 Before I left Missouri, I thought London was going to be so good and cool and fun and a really great experience and I was really excited.
    0 I know you're way too smart and way too cool to let stupid UCLA get to you...
    0 PARIS HILTON SUCKS!
    1 Geico was really great.
    0 hy I Hate San Francisco, # 29112...
    0 I need to pay Geico and a host of other bills but that is neither here nor there.
    1 As much as I love the Lakers and Kobe, I still have to state the facts...
    1 I'm biased, I love Angelina Jolie..
    0 I despise Hillary Clinton, but I don't think she's cold.
    0 i hate geico and old navy.

Выводы

Так что судите сами. Хорошо ли работает наш классификатор? Учитывая, насколько малы наши обучающие данные, во-первых, мы получаем приличную точность в разделении оценок, а во-вторых, при получении выборки прогнозов для набора тестов большинство тегов имеют смысл. Было бы здорово найти больший обучающий набор данных с метками. Таким образом, мы сможем обучить лучшую модель, а также получить больше данных для разделения на наборы train/eval и проверки точности нашей модели.

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

Наконец, хотя цель этих учебных пособий состоит не в том, чтобы найти компромисс между R и Python, а в том, чтобы показать, что есть только проблемы для решения и методы, которые можно использовать с обеими платформами, мы находим здесь некоторые различия. Например, в случае R у нас есть хорошая функция summary , которую мы можем использовать с результатом обучения линейного классификатора. Это резюме показывает нам очень важную информацию о том, насколько значима каждая особенность (т. Е. Каждое слово в нашем случае). Кроме того, процесс анализа текста кажется более простым в R. Но Python, как обычно, более структурирован и гранулирован, и его легче адаптировать к нашим конкретным потребностям, подключив и конвейеризовав различные части процесса.

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

И помните, что весь исходный код для различных частей этой серии учебных пособий и приложений можно проверить на GitHub . Не стесняйтесь участвовать и делиться с нами своими успехами!