3 product reviews
Введение¶
Цель¶
В этом ДЗ вы освоите работу с предобученными векторными представлениями.
Описание¶
В качестве данных возьмите либо датасет, собранный в первом занятии (предпочтительно), либо скачайте данные с отзывами на фильмы с сайта IMDB (https://www.kaggle.com/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews), в которых для каждого отзыва поставлена семантическая оценка - "позитивный" или "негативный".
- Разбейте собранные данные на train/test, отложив 20-30% наблюдений для тестирования. Примените tf-idf преобразование для текстового описания. Используйте как отдельные токены, так и биграммы, отсейте стоп-слова, а также слова, которые встречаются слишком редко или слишком часто (параметры min/max_df), не забудьте убрать l2 регуляризацию, которая по умолчанию включена.
- Обучите random forest или градиентный бустинг (LightGBM или catboost) на полученных векторах и подберите оптимальную комбинацию гиперпараметров с помощью GridSearch
- Теперь воспользуйтесь предобученными word2vec/fasttext эмбеддингами для векторизации текста. Векторизуйте тексты с помощью метода word2vec/fasttext c весами tf-idf
- Совет: для текстов на русском языке можно взять предобученные эмбеддинги с сайта rusvectores https://rusvectores.org/ru/models/ (вам подходят эмбеддинги с параметром тэгсет НЕТ). Для английского языка можете воспользоваться word2vec, обученными на Google News Повторите эксперимент из пункта 4 с использованием полученных в пункте 5 векторов
Критерии оценки¶
- Разбиение на train/test - 1 балл
- Предобработка текста при помощи tf-idf - 2 балла
- Обучение модели на tf-idf векторах - 2 балла
- Предобработка текста при помощи преобученных эмбеддингов word2vec/fasttext - 3 балла
- Обучение модели на предобученных эмбеддингах - 2 балла
Чтение данных¶
Отзывы Сбербанка¶
- Начнем с чтением наших данных которые мы получили с помощью парсера страницы отзывов
- В нашем датасете мы сохранили отзывы о сбербанке с признакамиВ:
user
- пользователь/клиентtime
- время написания отзываconclusion
- итоговый вывод отзываrating
- рейтингreview_cleaned
- отзыв пользователь/клиент
Корпус (corpus) который мы будем использовать review_cleaned
уже был очищен в предыдущем задании
Постановка Задачи¶
- Для упрощение задачи, возьмем целевую переменную conclusion
- Это один из критерии оценки клиентов (рекомендует либо не рекомендует), в данном датасете мы спарсили только общие отзывы о сбербанке, соответственно в него может входить разные услуги банка
- Будем создавать модель которая по отзыву клиента review_cleaned будет определять тональность отзыва; задача бинарной классификации в natural language processing (NLP)
import pandas as pd
reviews = pd.read_csv('/kaggle/input/product-review/review_cleaned.csv')
reviews.drop(['review'],axis=1,inplace=True)
reviews
user | time | conclusion | rating | review_cleaned | |
---|---|---|---|---|---|
0 | dncmail | 2023-06-21T08:34:25+02:00 | не рекомендует | 2 | Поделюсь с вами историей, которая произошла со... |
1 | fomicevaa851 | 2023-06-21T07:39:25+02:00 | рекомендует | 5 | Сама недавно узнала, что в Сбербанке можно пол... |
2 | AlexStulov | 2023-06-14T13:52:43+02:00 | не рекомендует | 1 | Сбер потерял мой миллион. В апреле брал ипотек... |
3 | Zakharkot | 2023-06-13T08:04:53+02:00 | рекомендует | 5 | Доброго времени суток всем, я открыл в Сбере в... |
4 | sanaan | 2023-06-11T23:40:00+02:00 | рекомендует | 4 | Живу с мамой, оплатой коммунальных платежей до... |
... | ... | ... | ... | ... | ... |
1117 | Mila_Krom | 2018-10-14T19:41:26+02:00 | рекомендует | 3 | После того, как нам в ВТБ понизили ставку все... |
1118 | Inrak | 2018-10-10T07:39:12+02:00 | рекомендует | 5 | В 2013 году при получении зарплатной карты, со... |
1119 | nastyamostya | 2018-10-03T12:19:22+02:00 | рекомендует | 5 | Давно выбрала для себя лучшее отделение Сбера.... |
1120 | Татьяна Окрайчик | 2018-10-02T15:59:41+02:00 | рекомендует | 5 | Всем доброго времени суток! Сегодня хочу расск... |
1121 | Галина_8_8_8 | 2018-09-26T13:35:02+02:00 | рекомендует | 5 | А мы вот брали ипотеку в Сбере. По нашим с муж... |
1122 rows × 5 columns
Распределение целевой переменной¶
В нашем датасете достаточно сбалансированная целевая переменная, accuracy
будет достаточно целесообразная метрика для оценки качества моделей
reviews['conclusion'].value_counts(dropna=False)
не рекомендует 574 рекомендует 548 Name: conclusion, dtype: int64
Разбиение на выборки¶
Из описания:
Разбейте собранные данные на train/test, отложив 20-30% наблюдений для тестирования
- Разобьем выборку на две части; тренировочную (train) и тестовую выборку (test)
- На test будем проверять обобщающая способность модели
from sklearn.model_selection import train_test_split as tts
X_train,X_test = tts(reviews,test_size=0.2,random_state=32)
# Тренировачная выборка
corpus_tr = list(X_train['review_cleaned']) # корпус
target_tr = list(X_train['conclusion']) # целевая функция
# Тестовая выборка
corpus_te = list(X_test['review_cleaned']) # корпус
target_te = list(X_test['conclusion']) # целевая функция
PART I - TF-IDF трансформация¶
Из требовании задания:
Примените
tf-idf
преобразование для текстового описания. Используйте какотдельные токены
(unigram), так ибиграммы
, отсейтестоп-слова
, а также слова, которые встречаются слишком редко или слишком часто (параметрыmin_df
/max_df
), не забудьте убрать l2 регуляризацию, которая по умолчанию включена
- Задачу разделяем на две части, в первой части мы будем использовать подход преобразование текста в числовое значение с помощью метода (TF-IDF)
- Вариантов комбинации параметров модели TF-IDF не мало, попробуем найти комбинацию параметров которая нам даст наиболее точную модель
Варианты TF-IDF¶
Все варианты преобразования документов в корпусе используя TF-IDF для создания векторного представления слов перечислены ниже
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords
russian_stopwords = stopwords.words("russian")
# минимальная частота слов
v1_vectoriser = TfidfVectorizer(min_df=3,norm=None)
v1_vectoriser.fit(corpus_tr)
X1 = v1_vectoriser.transform(corpus_tr)
v2_vectoriser = TfidfVectorizer(min_df=2,norm=None)
v2_vectoriser.fit(corpus_tr)
X2 = v2_vectoriser.transform(corpus_tr)
v3_vectoriser = TfidfVectorizer(min_df=1,norm=None)
v3_vectoriser.fit(corpus_tr)
X3 = v3_vectoriser.transform(corpus_tr)
# стоп слова варианты
v4_vectoriser = TfidfVectorizer(min_df=3,norm=None,stop_words=russian_stopwords)
v4_vectoriser.fit(corpus_tr)
X4 = v4_vectoriser.transform(corpus_tr)
v5_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords)
v5_vectoriser.fit(corpus_tr)
X5 = v5_vectoriser.transform(corpus_tr)
v6_vectoriser = TfidfVectorizer(min_df=1,norm=None,stop_words=russian_stopwords)
v6_vectoriser.fit(corpus_tr)
X6 = v6_vectoriser.transform(corpus_tr)
# н-грам варианты
v7_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(1,1))
v7_vectoriser.fit(corpus_tr)
X7 = v7_vectoriser.transform(corpus_tr)
v8_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(1,2))
v8_vectoriser.fit(corpus_tr)
X8 = v8_vectoriser.transform(corpus_tr)
v9_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(1,3))
v9_vectoriser.fit(corpus_tr)
X9 = v9_vectoriser.transform(corpus_tr)
v10_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(2,2))
v10_vectoriser.fit(corpus_tr)
X10 = v10_vectoriser.transform(corpus_tr)
v11_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(3,3))
v11_vectoriser.fit(corpus_tr)
X11 = v11_vectoriser.transform(corpus_tr)
v12_vectoriser = TfidfVectorizer(min_df=2,norm=None,stop_words=russian_stopwords,ngram_range=(4,4))
v12_vectoriser.fit(corpus_tr)
X12 = v12_vectoriser.transform(corpus_tr)
# максимальная частота слов
v13_vectoriser = TfidfVectorizer(min_df=2,max_df=0.9,norm=None,stop_words=russian_stopwords)
v13_vectoriser.fit(corpus_tr)
X13 = v13_vectoriser.transform(corpus_tr)
v14_vectoriser = TfidfVectorizer(min_df=2,max_df=0.8,norm=None,stop_words=russian_stopwords)
v14_vectoriser.fit(corpus_tr)
X14 = v14_vectoriser.transform(corpus_tr)
v15_vectoriser = TfidfVectorizer(min_df=2,max_df=0.7,norm=None,stop_words=russian_stopwords)
v15_vectoriser.fit(corpus_tr)
X15 = v15_vectoriser.transform(corpus_tr)
PART I - TF-IDF Модели¶
Варианты Моделей¶
- В плане моделей, попробуем два подхода ансамблья:
- случайный лес (который использует бэггинг и метод случайного пространства)
- градиентный бустинг СatBoost
Начнем с базового RandomForest (не очень глубокого) model_srf
, а потом попробуем улучшить результат с помощью model_drf
и model_ocb
from sklearn.ensemble import RandomForestClassifier
from catboost import CatBoostClassifier
model_srf = RandomForestClassifier(n_estimators=10,random_state=32) # не глубокий случайный лес
model_drf = RandomForestClassifier(n_estimators=40,random_state=32) # глубокий случайный лес
model_ocb = CatBoostClassifier(silent=True) # градиентный бустинг
from sklearn.metrics import accuracy_score
# вспомогательная функция для оценки модели
def evaluate_tfidf(X,vect,name,model):
print(f'case_id: :{name}')
print('==================================')
# train model
model.fit(X,target_tr)
y_model = model.predict(X)
print(f'train: {accuracy_score(target_tr,y_model)}')
X = vect.transform(corpus_te)
y_model = model.predict(X)
print(f'test: {accuracy_score(y_model,target_te)}')
print('==================================','\n')
Минимальная частота слов¶
Проверим как влияет фильтр минимальной частоты слов (min_df
) в документе на результат
v1
(min_df=3
фильтруем слова которые появляются в документов меньше 3 раз)v2
(min_df=2
фильтруем слова которые появляются в документов меньше 2 раз)v3
(min_df=1
)
Как мы видим, эффект от фильтрации часто встречающихся слов разный
evaluate_tfidf(X3,v3_vectoriser,'v3',model_srf)
evaluate_tfidf(X2,v2_vectoriser,'v2',model_srf)
evaluate_tfidf(X1,v1_vectoriser,'v1',model_srf)
case_id: :v3 ================================== train: 0.9866220735785953 test: 0.7288888888888889 ================================== case_id: :v2 ================================== train: 0.987736900780379 test: 0.7466666666666667 ================================== case_id: :v1 ================================== train: 0.9843924191750278 test: 0.7111111111111111 ==================================
Стоп Слова¶
- Попробуем повысить точность с помощью фильтрации не релевантных слов (стоп слова)
- При использовании разных настроек
min_df
точность модели меняется по разному;min_df=2
дает наиболее хороший результат
Вариатны:
v4
(фильтрация стоп слов сmin_df=3
)v5
(фильтрация стоп слов сmin_df=2
)v6
(фильтрация стоп слов сmin_df=1
)
evaluate_tfidf(X4,v4_vectoriser,'v6',model_srf)
evaluate_tfidf(X5,v5_vectoriser,'v5',model_srf)
evaluate_tfidf(X6,v6_vectoriser,'v4',model_srf)
case_id: :v6 ================================== train: 0.987736900780379 test: 0.7155555555555555 ================================== case_id: :v5 ================================== train: 0.9866220735785953 test: 0.76 ================================== case_id: :v4 ================================== train: 0.9866220735785953 test: 0.7111111111111111 ==================================
н-граммы¶
- Вариант
v5
дает нам точность 0.76 на тестовой выборке, используем этот вариант и проверим разные комбинации н-грамм - Как мы видим, улучшении никаких нет на тестовой выборке, при использовании bigram,trigram, точность сильно снизилось, базовые токены (unigram) являются критичным для высокой точности модели
Варианты:
v7
ngram_range=(1,1) (Только униграммы)v8
ngram_range=(1,2) (Униграммы и биграммы)v9
ngram_range=(1,3) (униграммы, биграммы и триграммы)
evaluate_tfidf(X9,v9_vectoriser,'v9',model_srf)
evaluate_tfidf(X8,v8_vectoriser,'v8',model_srf)
evaluate_tfidf(X7,v7_vectoriser,'v7',model_srf)
case_id: :v9 ================================== train: 0.987736900780379 test: 0.6622222222222223 ================================== case_id: :v8 ================================== train: 0.9888517279821628 test: 0.7155555555555555 ================================== case_id: :v7 ================================== train: 0.9866220735785953 test: 0.76 ==================================
Проверим еще варианты:
v10
ngram_range=(1,1) (Только униграммы)v11
ngram_range=(2,2) (Только биграммы)v12
ngram_range=(3,3) (Только триграммы)
evaluate_tfidf(X12,v12_vectoriser,'v12',model_srf)
evaluate_tfidf(X11,v11_vectoriser,'v11',model_srf)
evaluate_tfidf(X10,v10_vectoriser,'v10',model_srf)
case_id: :v12 ================================== train: 0.6622073578595318 test: 0.5466666666666666 ================================== case_id: :v11 ================================== train: 0.9119286510590858 test: 0.6 ================================== case_id: :v10 ================================== train: 0.9620958751393534 test: 0.6444444444444445 ==================================
Максимальная частота¶
В задании так же упоминается максимальная частота слов max_df
в векторизаторе, попробуем варианты:
v13
max_df=0.9
(фильтруем слова которые появляются в 90% документов)v14
max_df=0.8
(фильтруем слова которые появляются в 80% документов)v15
max_df=0.7
(фильтруем слова которые появляются в 70% документов)
Как мы видим, улучшении никаких нет, при уменьшении часто встречаюшихся слов, точность падает
evaluate_tfidf(X13,v13_vectoriser,'v13',model_srf)
evaluate_tfidf(X14,v14_vectoriser,'v14',model_srf)
evaluate_tfidf(X15,v15_vectoriser,'v15',model_srf)
case_id: :v13 ================================== train: 0.9866220735785953 test: 0.76 ================================== case_id: :v14 ================================== train: 0.9866220735785953 test: 0.76 ================================== case_id: :v15 ================================== train: 0.992196209587514 test: 0.7155555555555555 ==================================
Выбор модели¶
- Базовая модель случайного леса не очень глубокая, соответственно может не очень хорошо определить закономерности в данных, попробуем другие варианты перечислены в начале раздела
- Воспользуемся вариантом который показал наилучшую обобщающию способность на тестовой выборке (
v5
) - CatBoost хорошо оптимизируется под любые табличные данные, и как мы видим улучшения нет, обе модели показывают примерно идентичный результат
- Соответственно возможно что улучшении на тестовой выборке с TF-IDF (помимо токенизации) особо нет, и нужно проверить эмбеддинговый подход который мы рассмотрим в Part II
# 12000 признаков
X5.shape
(897, 11969)
evaluate_tfidf(X5,v5_vectoriser,'v5_deeprf',model_drf)
evaluate_tfidf(X5,v5_vectoriser,'v5_shallowcatboost',model_ocb)
case_id: :v5_deeprf ================================== train: 0.9988851727982163 test: 0.8044444444444444 ================================== case_id: :v5_shallowcatboost ================================== train: 0.9654403567447045 test: 0.7955555555555556 ==================================
PART I - TF-IDF GridSearchCV¶
Из требовании задания:
Обучите random forest или градиентный бустинг (LightGBM или catboost) на полученных векторах и подберите оптимальную комбинацию гиперпараметров с помощью GridSearch
- В ручную подборка гиперпараметров может оказаться не самым оптимальным вариантов, поэтому воспользуемся
GridSearchCV
методом кросс-валидации для подборки гиперпараметров модели - Какую конкретные параметры нужно оптимизировать не уточняется, попробуем найти более оптимальные параметры для модели случайного леса, так как CatBoost показал хуже результат
У случайного леса много параметров которые можно перепобовать, ограничимся:
max_depth
максимальная глубина разбиенияn_estimators
количество решаущих деревьевcriterion
критерии оценки разбиения в узлеmin_samples_leaf
минимальное количество образцов, необходимое для нахождения в листовом узле каждого дереваmin_samples_split
Минимальное количество образцов, необходимое для разбиения внутреннего узла каждого дерева
Из соображении времени, количество построенных деревьев ограничивается (e_stimators=100)
from sklearn.model_selection import GridSearchCV
from scipy.stats import randint
import numpy as np
def evaluate_cv(X,name,base_estimator):
print(f'case_id: :{name}')
print('==================================')
rs_space={'max_depth':list(np.arange(10,60,10)) + [None],
'n_estimators':np.arange(40,120, step=20),
'criterion':['gini','entropy'],
'min_samples_leaf':[1,2,3,4],
'min_samples_split':np.arange(2,6, step=2),
}
model = GridSearchCV(base_estimator,
rs_space,
scoring='accuracy',
n_jobs=-1,
cv=3)
# train model
model.fit(X,target_tr)
print('Best hyperparameters are: '+str(model.best_params_))
print('Best score is: '+str(model.best_score_))
print('==================================','\n')
Кросс валидация нам дает немного хуже результат чем на train/test разбиение; в итоге мы получаем точность 0.78 на тестовой выборке в cv, что не так уж и плохо
%%time
model_drf = RandomForestClassifier(random_state=32)
evaluate_cv(X5,'random_forest_cv',model_srf)
case_id: :random_forest_cv ================================== Best hyperparameters are: {'criterion': 'entropy', 'max_depth': 40, 'min_samples_leaf': 3, 'min_samples_split': 2, 'n_estimators': 100} Best score is: 0.7814938684503902 ================================== CPU times: user 3.13 s, sys: 604 ms, total: 3.73 s Wall time: 1min 55s
Проверим точность на на всей тестовой и отложенной тестовой выборке на больше объеме данных у нас модель показывает лучше результат
best_model = RandomForestClassifier(**{'criterion': 'entropy',
'max_depth': 40,
'min_samples_leaf': 3,
'min_samples_split': 2,
'n_estimators': 100,
'random_state':32})
evaluate_tfidf(X5,v5_vectoriser,'v5_rf_best',best_model)
case_id: :v5_rf_best ================================== train: 0.9821627647714605 test: 0.8133333333333334 ==================================
PART II - эмбеддинги¶
Из требовании задания:
Теперь воспользуйтесь предобученными word2vec/fasttext эмбеддингами для векторизации текста. Векторизуйте тексты с помощью метода word2vec/fasttext c весами tf-idf
- В Part II у нас меняется подход преобразования документов в числовой формат, от нас требуется воспользоваться ранее предобученные эмбеддинги (на другом корпусе) от rusvectores.org и сравнить с предыдущем подходом TF-IDF.
- Как и раньше будем ориентироваться на обобщающую способность модели на тестовой выборке
Начнем с выгрузки TF-IDF весов из весторизатора v5_vectoriser
# Не забываем про веса tfidf; создадим словарь
tfidf_weights = dict(zip(list(v5_vectoriser.vocabulary_.keys()),v5_vectoriser.idf_))
Загрузка эмбеддингов¶
Для нашей задачи возьмем предобученные эмбеддинги (варианты без тагсет)
- geowac_lemmas_none_fasttextskipgram_300_5_2020
import gensim
import urllib.request
import zipfile
# название и URL
we_models = {"geowac_lemmas_none_fasttextskipgram_300_5_2020": "http://vectors.nlpl.eu/repository/20/213.zip",}
# сохраняем модель
def get_models(model_url, model_name, path_to_save="/kaggle/working/"):
model_path = path_to_save + model_name + ".zip"
urllib.request.urlretrieve(model_url, model_path)
for model_name, model_url in we_models.items():
get_models(model_url, model_name)
# Функция для чтения word2vec / FastText
def open_model(model_name,model_path, is_fasttext = True):
# word2vec (model.bin)
if is_fasttext == False:
model_file = model_path + model_name + ".zip"
with zipfile.ZipFile(model_file, 'r') as archive:
stream = archive.open('model.bin')
model = gensim.models.KeyedVectors.load_word2vec_format(stream, binary=True)
# fasttext (model.model)
else:
model_file = model_path + model_name
model = gensim.models.KeyedVectors.load(model_file + "/model.model")
return model
Распакуем эмбеддинги и загружаем вектора
with zipfile.ZipFile("/kaggle/working/geowac_lemmas_none_fasttextskipgram_300_5_2020.zip", 'r') as zip_ref:
zip_ref.extractall("/kaggle/working/geowac_lemmas_none_fasttextskipgram_300_5_2020")
# загружаем KeyedVectors эмбеддинговый вектора
geowac_model = open_model('geowac_lemmas_none_fasttextskipgram_300_5_2020','/kaggle/working/')
Токенизация документов¶
- Токенизируем все документы в тренировочной выборке/копрусе, будем использовать стандартный метод из nltk
word_tokenize
- Токенизированные документы сохраним в
lst_corpus_tr
иlst_corpus_te
дляtrain
иtest
выборки
from nltk.tokenize import word_tokenize
# Preprocessing, returns list instead
def tokenise_for_word2vec(text):
text = text.lower() #changes to lower case
tokens = word_tokenize(text) #tokenize the text
clean_list = []
for token in tokens:
clean_list.append(token)
return clean_list
lst_corpus_tr = []
for doc in corpus_tr:
lst_corpus_tr.append(tokenise_for_word2vec(doc))
lst_corpus_te = []
for doc in corpus_te:
lst_corpus_te.append(tokenise_for_word2vec(doc))
Усредненные Вектора (без TF-IDF весов)¶
- Воспользуемся самым простым подходом; для каждого документа мы возьмем усредненный эмбеддинговый вектор всех слов которые в него входят
- Размерность данных с эмбеддингами получается намного ниже (300) чем в TF-IDF (11969) что должно сказаться на скорости обучения модели
- Сохраняем эмбеддинг для каждого документа в
X_tr
иX_te
дляtrain
иtest
выборки
# Get average embedding vector for each text
def doc_vectoriser(doc, model):
doc_vector = []
num_words = 0
for word in doc:
try:
if num_words == 0:
doc_vector = model[word]
else:
doc_vector = np.add(doc_vector, model[word])
num_words += 1
except:
pass # if embedding vector isn't found
return np.asarray(doc_vector) / num_words
X_tr = []
for doc in lst_corpus_tr:
X_tr.append(doc_vectoriser(doc,geowac_model))
X_te = []
for doc in lst_corpus_te:
X_te.append(doc_vectoriser(doc,geowac_model))
Усредненные Вектора (c TF-IDF весами)¶
- Мы сохранили веса значимости слов (токенов) в документах корпуса в
tfidf_weights
- Сохраняем взвешенные эмбеддинг для каждого документа в
Xw_tr
иXw_te
дляtrain
иtest
выборки
# Get average embedding vector for each text
def doc_vectoriser_tfidf(doc, model):
doc_vector = []
num_words = 0
for word in doc:
try:
if num_words == 0:
# if word is in tfidf dictionary
if(word in tfidf_weights):
doc_vector = model[word]*(tfidf_weights[word])
else:
doc_vector = model[word]
else:
# if word is in tfidf dictionary
if(word in tfidf_weights):
doc_vector = np.add(doc_vector, model[word]*(tfidf_weights[word]))
else:
doc_vector = np.add(doc_vector, model[word])
num_words += 1
except:
pass # if embedding vector isn't found
return np.asarray(doc_vector) / num_words
Xw_tr = []
for doc in lst_corpus_tr:
Xw_tr.append(doc_vectoriser_tfidf(doc,geowac_model))
Xw_te = []
for doc in lst_corpus_te:
Xw_te.append(doc_vectoriser_tfidf(doc,geowac_model))
PART II - Эмбеддинг модели¶
Варианты Моделей¶
Повторим то что мы делали в Part I:
- В плане моделей, попробуем два подхода ансаблья, случайный лес (который использует бэггинт и метод случайного пространства) и градиентный бустинг СatBoost
- Начнем с базового RandomForest (не очень глубокого)
model_srf
, а потом попробуем улучшить результат с помошьюmodel_drf
иmodel_ocb
from sklearn.ensemble import RandomForestClassifier
from catboost import CatBoostClassifier
model_srf = RandomForestClassifier(n_estimators=10,random_state=32)
model_drf = RandomForestClassifier(n_estimators=40,random_state=32)
model_ocb = CatBoostClassifier(silent=True)
from sklearn.metrics import accuracy_score
# Импортируем матричу фич (эмбеддинги) X_tr, X_te
# target_tr, target_te глоб
def evaluate_embedding(X_tr,X_te,name,model):
print(f'case_id: :{name}')
print('==================================')
# train model
model.fit(X_tr,target_tr)
y_model = model.predict(X_tr)
print(f'train: {accuracy_score(target_tr,y_model)}')
y_model = model.predict(X_te)
print(f'test: {accuracy_score(y_model,target_te)}')
print('==================================','\n')
Варианты Моделей (без TF-IDF весов)¶
- Модели без TF-IDF весами для эмбеддингов, как и раньше попробуем базовые модели, после чего мы попытаемся оптимизировать гиперпараметры
- Интересно отметить что точность модели очень редко превышает 0.804
evaluate_embedding(X_tr,X_te,'geowac_rf',model_drf)
case_id: :geowac_rf ================================== train: 1.0 test: 0.7422222222222222 ==================================
evaluate_embedding(X_tr,X_te,'geowac_cat',model_ocb)
case_id: :geowac_cat ================================== train: 1.0 test: 0.8044444444444444 ==================================
Варианты Моделей (c TF-IDF весами)¶
- Модели с TF-IDF весами для эмбеддингов
- Точность с весами не улучшилась
evaluate_embedding(Xw_tr,Xw_te,'geowac_rf_tfidf_weights',model_drf)
case_id: :geowac_rf_tfidf_weights ================================== train: 1.0 test: 0.6977777777777778 ==================================
evaluate_embedding(Xw_tr,Xw_te,'geowac_cat_tfidf_weights',model_ocb)
case_id: :geowac_cat_tfidf_weights ================================== train: 1.0 test: 0.7555555555555555 ==================================
- Для более низкого пространства задачи, мы видим что градиентный бустнинг работаел немного лучше чем случайный лес
- TF-IDF веса не помогли улучшить ансамлевые модели
- Интересно что модель CatBoost показала тот ту же самую точность как и случайный лес и Part I; возможно что сами данные являются источником неточности (проведем анализ результат в разделе 10)
%%time
model_drf = RandomForestClassifier(random_state=32)
evaluate_cv(np.array(X_tr),'geowac_rf_cv',model_drf)
case_id: :geowac_rf_cv ================================== Best hyperparameters are: {'criterion': 'entropy', 'max_depth': 10, 'min_samples_leaf': 1, 'min_samples_split': 4, 'n_estimators': 100} Best score is: 0.7725752508361204 ================================== CPU times: user 3.12 s, sys: 252 ms, total: 3.37 s Wall time: 2min 49s
best_model_rf = RandomForestClassifier(**{'criterion': 'entropy',
'max_depth': 10,
'min_samples_leaf': 1,
'min_samples_split': 4,
'n_estimators': 100,
'random_state':32})
evaluate_embedding(X_tr,X_te,'geowac_rf_tfidf_weights',best_model_rf)
case_id: :geowac_rf_tfidf_weights ================================== train: 1.0 test: 0.7822222222222223 ==================================
GridSearchCV (c TF-IDF весами)¶
- Повторяем оптимизацию гиперпараметров для эмбеддингов с весами TF-IDF
- Умножив веса из TF-IDF на эмбеддинги не улучшило модель
%%time
model_drf = RandomForestClassifier(random_state=32)
evaluate_cv(np.array(Xw_tr),'geowac_rf_cv_tfidf_weights',model_drf)
case_id: :geowac_rf_cv_tfidf_weights ================================== Best hyperparameters are: {'criterion': 'gini', 'max_depth': 10, 'min_samples_leaf': 2, 'min_samples_split': 2, 'n_estimators': 80} Best score is: 0.7647714604236343 ================================== CPU times: user 2.64 s, sys: 287 ms, total: 2.93 s Wall time: 2min 49s
Анализ результата¶
Анализ результатов¶
- По итогам построении моделей возникает некий вопрос, почему наши модели ограничиваются точностью в районе 80%
- Причин могут быть несколько, давайте сосредоточимся на самих данных и попытаемся понять почему точность модели стагнирует
- Вполне вероятно что проблема в самих данных и в постановке задачи бинарной классификации
# tts evaluation function & return model results
def evaluate_tfidf(X,vect,name,model):
print(f'case_id: :{name}')
print('==================================')
# train model
model.fit(X,target_tr)
y_model_tr = model.predict(X)
print(f'train: {accuracy_score(target_tr,y_model_tr)}')
X = vect.transform(corpus_te)
y_model_te = model.predict(X)
print(f'test: {accuracy_score(y_model_te,target_te)}')
return pd.Series(y_model_tr),pd.Series(y_model_te)
print('==================================','\n')
Вернемся к TF-IDF подходу:
ym_train,ym_test = evaluate_tfidf(X5,v5_vectoriser,'v5_rf_best',best_model)
case_id: :v5_rf_best ================================== train: 0.9821627647714605 test: 0.8133333333333334
X_train = X_train.reset_index()
X_test = X_test.reset_index()
X_train['ypred'] = ym_train
X_test['ypred'] = ym_test
Найдем по распределению рейтинга правленные и не правленные предсказание модели
X_test['correct'] = (X_test['conclusion'] == X_test['ypred'])
- Можно отметить что модель часто ошибается когда клиенты ставят рейтинг 3 (из 1-5)
- Средняя оценка вполне может обозначать что клиент достаточно нейтрален; может писать как и положительные, так и негативные комментарии в отзыве что может запутать модель; поэтому можно вполне уверено сказать что постановка задача не самая идеальная
- Посмотрим на статистику правильных (True) и не правильно (False) предсказанных отзывов
X_test.groupby(by=['correct','rating']).count()[['index']].T
correct | False | True | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
rating | 1 | 2 | 3 | 4 | 5 | 1 | 2 | 3 | 4 | 5 |
index | 10 | 6 | 12 | 4 | 10 | 73 | 17 | 15 | 13 | 65 |
- Как и ожидалось, клиент пишет отзыв достаточно сбалансированно и информативно, учитывает положительные так и негативный опыт
- Разница между тональностью нейтрального и явно негативно настроенного отзыва достаточно очевидна
- Для будущего, рекомендуется разделить метку на еще одну категорию (что-то нейтральное)
# нейтральный отзыв
X_test[(X_test['correct'] == False) & (X_test['rating'] == 3)]['review_cleaned'].reset_index(drop=True).iloc[2]
'Верю всему о чем написано в отзывах, но не со всем могу согласиться. У Сбербанка полно огрехов, недоработок, сбоев, как и у многих других банков. Никто силком в банк не загоняет, нам самим проще не идти против течения и не отстаивать свои права, сказали оформить социальное пособие или зарплату, или пенсию через Сбербанк, мы и оформляем, думаем что он надежнее, или не хочется идти в бухгалтерию права качать. Я точно также столкнулась с проблемой при оформлении своей социальной карты. Когда заказывала карту отказалась от услуги платного мобильного банка, но просила включить услугу бесплатного смс информирования при пользовании Online банком, для получения ключей входа и проводимых операциях в интернет банке. Это две разные услуги, одна платная, другая нет, но это я сейчас так складно излагаю, а когда предъявляла претензии банку, талдычила про мобильный банк вместо услуги бесплатного смс информирования при пользовании интернет банком. Короче они мне про Фому, а я им про Ярему. Теперь про интернет банк, пользуюсь уже два года, до этого пользовалось картой и интернет банком "промсвязьбанка" есть с чем сравнить, сначала напрягало большое количество страниц с повторяющимися реквизитами при оплате ЖКХ, причем первый раз нужно создать шаблон, затем его подтвердить и только потом произвести оплату. Здесь они накрутили будь здоров, в "промсвязьбанке" эта операция выглядит как заполнение обычной квитанции, никаких вопросов и проблем. Что понравилось у Сбербанка - это платить налоги за квартиру, дачу и т.д., по номеру извещения сразу высвечивается ваша квитанция, оплачиваете одним кликом. В "промсвязьбанке" например нужно заполнять все поля самостоятельно, пока заполнишь-состаришься. Также легко в Сбербанке оплатить домашний телефон, не надо ждать квитанции на оплату, набираешь номер телефона и сумма долга на экране. Когда более или менее освоишься и создашь шаблоны, проблем не возникает. Картой Сбербанка пользуюсь при оплате продуктов или товаров в любом магазине , где карты принимают к оплате уже два года. Сразу как появилась услуга "Спасибо от Сбербанка", подключилась и потихонечку копятся небольшие денежки, уже расплачивалась ими при покупке мелочевки в интернет магазинах, пустячок, а приятно. Если у вас в семье две карты Сбербанка, легко перебросить деньги с одной карты на другую бесплатно. Вы обращали внимания на огромные очереди в Сбербанках, причем эти очереди состоят не из одних старушек, не умеющих пользоваться интернетом, там полно и молодежи. Спрашивается, почему не провести необходимые платежи дома без очереди, сидя за компьютером с чашкой кофе. Почему молодые люди не могут обучить своих родителей элементарным навыкам работы в интернете, а заставляют стоять в очередях? Та же картина и в магазинах, единицы расплачиваются карточками. Жизнь не стоит на месте, а с каждым годом темп жизни увеличивается и надо стараться идти в ногу, иначе очереди будут только расти. Многих неприятностей можно было бы избежать, если бы наши люди внимательно читали тексты договоров, тарифов обслуживания, а проще инструкции по пользованию банком, картой и т.д. и т.п. И еще не забываете, что в банках работают не инопланетяне, а наши с вами знакомые, друзья, родственники и как научили их работать, так они и работают. Кому интересно, мой отзыв о банке "Хоум Кредит" по ссылке.'
# негативных отзыв
X_test[(X_test['correct'] == True) & (X_test['rating'] == 1)]['review_cleaned'].reset_index(drop=True).iloc[2]
'Наглость "Сбербанка" не знает границ! Снятие денежных средств без уведомления клиента, блокировка банковских карт без каких-либо на то причин и самое интересное, что причину блокировки карты нельзя узнать по телефону горячей линии, обязательно нужно посетить офис банка! Отвратительное обслуживание! Больше нет никакого доверия к Сбербанку, боишься хранить свои денежные средства на банковской карте, т.к. в один прекрасный момент их могут списать за какие-либо услуги, не уведомив клиента, либо уведомить только после списания и без вашего согласия. Могут заблокировать карту к примеру после пополнения баланса телефона, который кстати привязан к мобильному банку! В общем творят что хотят, больше вероятности сохранить свои деньги в какой-нибудь шарашкиной конторе, чем в "государственной организации" такой как Сбербанк!'