Анализ тональности текста или сентимент анализ - это метод классификации текста. Самый популярный пример из курсов по машинному обучению - прогноз оценки, которую поставит посетитель ресторана заведению на основе его отзыва. Или, по другому, можем ли мы спрогнозировать на основе отзыва посетителя, будет ли он рекомендовать этот ресторан или нет.
В HR сама собой напрашивается аналогичная задача: можем ли мы спрогнозировать на основе отзыва увольняющегося работника в exit интервью спрогнозировать, будет ли он рекомендовать нашу компанию коллегам / будет ли он отзываться о компании позитивно или негативно. Хотя, безусловно, класс решаемых задач значительно шире. Я бы отослал здесь к статье Raja Sengupta Как NLP может в корне изменить HR. NLP - это Natural language processing или проще - анализ текстов. Моя задача проще - я хочу показать код для решения одной задачи в Python.
Этот подход интуитивно понятен, но он имеет ряд недостатков (что делать с "не"? и т.п...), но главное: в этом подходе не отражается смысл слов, фраз.
word2vec
Преодолением такого подхода является метод word2vec (буквально 'word' to 'vector'), который превращает весь текст в N-мерное пространство, и каждое слово это вектор со своими координатами, т.е. буквально можно записать так:
'опрос': array([ 0.05069825, -0.01941545, 0.00567565, -0.0276236 , 0.01180002,
....... 0.00385726])
Не показываю весь вектор, потому что он имеет 100 значений. И эти 100 значений - это переменные в нашем уравнении.
"Плюс" этого метода в том, что близкие по значению слова имеют близкие координаты векторов. Например, когда я делал модель для функционала HR, то для слова "компенсации" самые близкие координаты вектора имело слово "c&b". И это замечательно, потому что в подходе "bag of words" слова "c&b" и "компенсации" это разные слова, а в подходе word2vec эти слова хоть и не идентичны, но очень близки.
Я свои данные взял из нашего исследования факторов текучести персонала (участвуем в исследовании) . Помимо всего прочего, в нашем опросе есть две переменные:
"Отзыв о компании";
"Готовы ли Вы рекомендовать эту компанию в качестве работодателя своим знакомым, коллегам?"
Итак, начинаем с загрузки данных
Да, у нас очень небольшой датасет, лучше иметь несколько тысяч, даже несколько десятков тысяч строк данных. Но моя задача скромнее - показать алгоритм. Выборка у нас достаточно сбалансированная - соотношение тех, кто готов рекомендовать компанию, и тех, кто не готов - почти 50/50.
Первая задача, которую нам надо решить - создать словарь слов. Т.е. присвоить каждому слову координаты вектора. Для этого нам необходимо взять весь текст и обучить его. Но прежде нам необходимо преобразовать наши отзывы из формата pandas в формат, годный для преобразований word2vec.
Преобразуем таким образом
В HR сама собой напрашивается аналогичная задача: можем ли мы спрогнозировать на основе отзыва увольняющегося работника в exit интервью спрогнозировать, будет ли он рекомендовать нашу компанию коллегам / будет ли он отзываться о компании позитивно или негативно. Хотя, безусловно, класс решаемых задач значительно шире. Я бы отослал здесь к статье Raja Sengupta Как NLP может в корне изменить HR. NLP - это Natural language processing или проще - анализ текстов. Моя задача проще - я хочу показать код для решения одной задачи в Python.
word2vec vs "bag of words"
Одним из самых популярных методов анализа текстов (а точнее, этот метод просто хронологически более ранний - и, может быть, более интуитивно понятный) является метод "мешок слов "bag of words". Мы просто получаем столько переменных, сколько у нас слов в тексте (исключая "мусорные" или редкие слова). Т.е. если в отзывах у нас используется 1 485 слов, то у нас будет 1485 новых переменных / колонок. И если в отзыве содержится слово - оно же название переменной - то переменная принимает значение "1", в противном случае "0". Т.е. если респондент написал отзыв "хорошая компания", то из 1485 ячеек напротив данного респондента будет только две "1" - в колонках "хорошая" и "компания".Этот подход интуитивно понятен, но он имеет ряд недостатков (что делать с "не"? и т.п...), но главное: в этом подходе не отражается смысл слов, фраз.
word2vec
Преодолением такого подхода является метод word2vec (буквально 'word' to 'vector'), который превращает весь текст в N-мерное пространство, и каждое слово это вектор со своими координатами, т.е. буквально можно записать так:
'опрос': array([ 0.05069825, -0.01941545, 0.00567565, -0.0276236 , 0.01180002,
....... 0.00385726])
Не показываю весь вектор, потому что он имеет 100 значений. И эти 100 значений - это переменные в нашем уравнении.
"Плюс" этого метода в том, что близкие по значению слова имеют близкие координаты векторов. Например, когда я делал модель для функционала HR, то для слова "компенсации" самые близкие координаты вектора имело слово "c&b". И это замечательно, потому что в подходе "bag of words" слова "c&b" и "компенсации" это разные слова, а в подходе word2vec эти слова хоть и не идентичны, но очень близки.
Данные
У меня свой датасет, которым я с вами не поделюсь, но вы можете опробовать этот код на своих данных. Структура данных достаточно проста:- переменная "текст";
- бинарная переменная 1/ 0, +1 / -1 и т.п..
Я свои данные взял из нашего исследования факторов текучести персонала (участвуем в исследовании) . Помимо всего прочего, в нашем опросе есть две переменные:
"Отзыв о компании";
"Готовы ли Вы рекомендовать эту компанию в качестве работодателя своим знакомым, коллегам?"
Реализация в Python
Итак, начинаем с загрузки данных
import pandas as pd import numpy as np df = pd.read_csv('data.csv', sep=',', encoding = 'cp1251') df.info() Data columns (total 2 columns): y 792 non-null int64 Отзыв о компании 792 non-null object dtypes: int64(1), object(1)
df['y'].value_counts() 1 404 0 388 Name: y, dtype: int64
Да, у нас очень небольшой датасет, лучше иметь несколько тысяч, даже несколько десятков тысяч строк данных. Но моя задача скромнее - показать алгоритм. Выборка у нас достаточно сбалансированная - соотношение тех, кто готов рекомендовать компанию, и тех, кто не готов - почти 50/50.
Первая задача, которую нам надо решить - создать словарь слов. Т.е. присвоить каждому слову координаты вектора. Для этого нам необходимо взять весь текст и обучить его. Но прежде нам необходимо преобразовать наши отзывы из формата pandas в формат, годный для преобразований word2vec.
Преобразуем таким образом
import nltk import nltk.data tokenizer = nltk.data.load('tokenizers/punkt/english.pickle') from nltk.tokenize import sent_tokenize, word_tokenize import re
def preproc(sentence): sent_text = re.sub(r'[^\w\s]','', sentence) words = sent_text.lower().split() return(words)
def senttxt(sent, tokenizer, remove_stopwords=False ): raw_sentences = tokenizer.tokenize(oped.strip()) sentences = [] for raw_sentence in raw_sentences: sentences.append(preproc(raw_sentence)) len(sentences) return sentences
txt_snt = df['Отзыв о компании'].tolist() sentences = [] for i in range(0,len(nyt_opeds)): sent = txt_snt[i].replace("/.", '') sentences += senttxt(sent, tokenizer)В итоге мы получаем объект вот такого формата
sentences[0] ['классическая', 'российская', 'компания', 'с', 'назначениями', 'не', 'по', 'знаниям', 'а', 'по', 'личной', 'приверженности', 'не', 'соблюдающая', 'свои', 'же', 'правила', 'делающая', 'глупость', 'за', 'глупостью', 'и', 'оправдывающая', 'их', 'еще', 'большими', 'глупостями']Этот формат уже можно использовать для создания словаря. Что мы и делаем.
from gensim.models.word2vec import Word2Vec model = Word2Vec(size=100, min_count=1) model.build_vocab(sentences)Обращаю ваше внимание, что я здесь задаю минимальные параметры модели (size и min_count). Более подробно рекомендую ознакомиться models.word2vec – Word2vec embeddings. Далее мы тренируем модель и получаем объект типа dictionary (объясню позже, зачем).
model.train(sentences, total_examples=model.corpus_count, epochs=model.iter) w2v = dict(zip(model.wv.index2word, model.wv.syn0))w2v представляет собой вот такой объект. Это словарь dict слов, где каждое слово имеет вектор со 100 значениями (см. выше, в модели мы указали size = 100). И этот словарь нам необходим для тренировки уже модели классификации.
{'поощрялась': array([-3.5042786e-03, 5.2169146e-04, 2.1134880e-03, 3.4255565e-03, 2.9535536e-03, -2.5336174e-04, 3.5182014e-03, 4.7578118e-03, ......................................................... -2.3358944e-03, 3.9440244e-03, 7.6886819e-05, -3.4618229e-04], dtype=float32),Далее я ввожу объект типа class. По сути дела это та же предобработка текста, что и выше, но поскольку мы будем модель тренировать в pipeline, то эту предобработку нам необходимо обернуть в объект типа class
class normword2vec(): def transform(self, X, y=None, **fit_params): X['Отзыв о компании'] = X['Отзыв о компании'].str.strip() X['Отзыв о компании'] = X['Отзыв о компании'].str.lower() X['Отзыв о компании'] = X['Отзыв о компании'].astype(str) X['Отзыв о компании'] = [re.sub(r'[^\w\s]', 'hr директор', e) for e in X['Отзыв о компании']] return X['Отзыв о компании'] def fit_transform(self, X, y=None, **fit_params): self.fit(X, y, **fit_params) return self.transform(X) def fit(self, X, y=None, **fit_params): return selfТеперь формула обработки слов. У нас каждое слово имеет размерность 100, но мы помним, что в данных у нас не одно слово соответствует "1" или "0", а целое предложение. И нам надо это предложение как то преобразовать. Для этого служит следующая формула.
class MeanVect(object): def __init__(self, word2vec): self.word2vec = word2vec # if a text is empty we should return a vector of zeros # with the same dimensionality as all the other vectors self.dim = len(word2vec.values()) def fit(self, X, y): return self def transform(self, X): return np.array([ np.mean([self.word2vec[w] for w in words if w in self.word2vec] or [np.zeros(100)], axis=0) for words in X ])Объект w2v нужен нам в этой формуле для извлечения векторов слов. В этой формуле self.word2vec = w2v. Вот это выражение
np.mean([self.word2vec[w] for w in words if w in self.word2vec] or [np.zeros(100)], axis=0)дает нам среднее значение вектора по всем словам предложения. Т.е. если у нас в предложение из пяти слов, то эта формула возвращает вектор размером 100, но каждое его значение является средним значением векторов всех слов в предложении. А если респондент не оставил отзыв о компании, то машина возвращает вектор из нулей np.zeros(100). Понятно, что в этом месте вы можете играться с параметром: медиана, сумма и т.п..
Теперь создаем pipeline
import sklearn import gensim.sklearn_api from sklearn.pipeline import Pipeline from xgboost import XGBClassifier from gensim.sklearn_api import W2VTransformer xgb = XGBClassifier() pipeline = Pipeline([ ('selectword2vec', normword2vec()), ("word2vec", MeanVect(w2v)), ('model_fitting', xgb)])
from sklearn import model_selection from sklearn.model_selection import train_test_split y = df['y'] X = df X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size = 0.3,random_state = 2) pipeline.fit(X_train, y_train)
Результаты
pred = pipeline.predict(X_test) pd.crosstab(y_test, pred) 0 1 0 65 48 1 43 82Confusion matrix
import seaborn as sns import matplotlib.pylab as plt %matplotlib inline from matplotlib.pylab import rcParams rcParams['figure.figsize'] = 3, 3 plt.figure(figsize=(2,2)) sns.set(font_scale=1.5) ax = sns.heatmap((pd.crosstab(y_test, pred).apply(lambda r: r/r.sum()*100, axis=0)), cbar=None, annot=True, cmap="Blues") ax.set_ylabel("") ax.set_xlabel("") plt.yticks(rotation=0, size = 15) plt.xticks(rotation=0, size = 15)Метрика Precision = 0, 63. Т.е. из всех, про кого мы говорим, что он будет рекомендовать компанию, прогноз оправдывается в 63 % случаев. Базовая наша точность 404/792 = 51 %, поэтому мы можем говорить, что точность модели выше случайной.
Показатели ROC кривой
from sklearn.metrics import roc_auc_score pred_proba = pipeline.predict_proba(X_test) print ('ROC AUC score = %0.4f' % roc_auc_score(y_test, pred_proba[:, 1])) ROC AUC score = 0.6658
from sklearn import metrics fpr, tpr, threshold = metrics.roc_curve(y_test, pred_proba[:, 1]) roc_auc = metrics.auc(fpr, tpr) plt.title('Receiver Operating Characteristic') plt.plot(fpr, tpr, color='red', lw = 2, label = 'AUC = %0.2f' % roc_auc) plt.legend(loc = 'lower right') plt.plot([0, 1], [0, 1]) plt.xlim([0, 1]) plt.ylim([0, 1]) plt.ylabel('True Positive Rate') plt.xlabel('False Positive Rate') plt.show()
Выше плинтуса, но не так, чтобы очень.
Bag of words
Давайте сравним на тех же данных технику bag of words для сравнения. Я дам код, но уже без комментариев, вы можете а) использовать его в работе и б) найти ошибки у меня. Под bag of words я понимаю и использую TfIdf векторизатор.import pymorphy2 morph = pymorphy2.MorphAnalyzer() def wrk_words(sent): words=word_tokenize(sent.lower()) arr=[] for i in range(len(words)): if re.search(u'[а-яА-Яa-zA-Z&]',words[i]): arr.append(morph.parse(words[i])[0].normal_form) words1=[w for w in arr ] return " ".join(words1) class norm(): def transform(self, X, y=None, **fit_params): X['Отзыв о компании'] = X['Отзыв о компании'].apply(lambda row: wrk_words(row)) X['Отзыв о компании'] = X['Отзыв о компании'].replace('', 'пусто', regex = True) return X['Отзыв о компании'] def fit_transform(self, X, y=None, **fit_params): self.fit(X, y, **fit_params) return self.transform(X) def fit(self, X, y=None, **fit_params): return self from sklearn.feature_extraction.text import TfidfVectorizer pipeline = Pipeline([ ('selector', norm()), ("word2vec", TfidfVectorizer()), ('model_fitting', xgb)])Результаты получились вот такими
pd.crosstab(y_test, pred) 0 1 0 86 27 1 45 80Confusion matrix
Prrecision точность модели = 0, 75, что выше 0, 63 в случае word2vec.
ROC кривая
Площадь под кривой 0, 76 против 0, 67 в случае word2vec.
Итого, на наших данных мы не смогли показать преимущество метода word2vec над методом Bag of words.
__________________________________________________________
На этом все, читайте нас в телеграмме и вконтакте
Комментариев нет:
Отправить комментарий