Автор оригинала: 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 . Не стесняйтесь участвовать и делиться с нами своими успехами!