Анализ тональности текста или сентимент анализ - это метод классификации текста. Самый популярный пример из курсов по машинному обучению - прогноз оценки, которую поставит посетитель ресторана заведению на основе его отзыва. Или, по другому, можем ли мы спрогнозировать на основе отзыва посетителя, будет ли он рекомендовать этот ресторан или нет.
В 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.
__________________________________________________________
На этом все, читайте нас в телеграмме и вконтакте




Комментариев нет:
Отправить комментарий