Введение в анализ данных, ИАД-1

НИУ ВШЭ, 2018-19 учебный год

Домашнее задание №3. Обработка текстов. Линейная классификация.

Задание выполнил(а): Подчезерцев Алексей

Общая информация

Дата выдачи: 28.04.2019

Дедлайн: 23:59 12.05.2019

Оценивание и штрафы

За сдачу задания позже срока на итоговую оценку за задание накладывается штраф в размере 1 балл в день, но получить отрицательную оценку нельзя.

Внимание! Домашнее задание выполняется самостоятельно. «Похожие» решения считаются плагиатом и все задействованные студенты (в том числе те, у кого списали) не могут получить за него больше 0 баллов.

Формат сдачи

Стирать условия нельзя!

Загрузка файлов с решениями происходит в системе Anytask.

Формат названия файла: homework_03_Фамилия_Имя.ipynb

In [1]:
import numpy as np
import pandas as pd

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

Необходимо построить алгоритм, который будет по тексту документа определять тип источника:

  • Микроблоги
  • Новости
  • Отзывы
  • Форумы
  • Блоги
  • Видео
  • Мессенджеры

Часть 1. Работа с текстовыми данными

1. Исследование данных

[2 балла]

Скачаем данные отсюда: https://yadi.sk/d/o3cPgFAq5gALiw

In [459]:
D = pd.read_csv('texts_dataset.csv', sep=';', index_col=0)
C:\Users\alex1\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py:3020: DtypeWarning: Columns (2) have mixed types. Specify dtype option on import or set low_memory=False.
  interactivity=interactivity, compiler=compiler, result=result)
In [460]:
D.head()
Out[460]:
Дата ID сообщения Заголовок Текст Дублей Тип источника
0 27.04.2019 13:18 1396238 NaN -Здравствуйте, тинькофф банк\r\n-Мать твою еба... 1.0 Микроблоги
1 27.04.2019 13:15 1396239 NaN Почему Немагия сделала обзор на Тинькова? #нем... 1.0 Микроблоги
2 27.04.2019 13:10 1396248 Forbes назвал самые экстравагантные инвестиции... 13:01, 27.04.2019 \r\n\r\nПоделиться:\r\n\r\n ... 2.0 Новости
3 27.04.2019 13:06 1396243 Forbes назвал самые экстравагантные инвестиции... 13:01, 27.04.2019 \r\n\r\nПоделиться:\r\n\r\n ... 2.0 Новости
4 27.04.2019 13:03 1396186 Тинькофф Страхование: Не купить полис без подп... Первый отзыв без оценки: \r\nhttps://www.... 1.0 Отзывы

Далее будем использовать лишь поля "Текст", "Тип источника"

In [461]:
D = D[[ "Текст", "Тип источника"]]
In [462]:
D.head()
Out[462]:
Текст Тип источника
0 -Здравствуйте, тинькофф банк\r\n-Мать твою еба... Микроблоги
1 Почему Немагия сделала обзор на Тинькова? #нем... Микроблоги
2 13:01, 27.04.2019 \r\n\r\nПоделиться:\r\n\r\n ... Новости
3 13:01, 27.04.2019 \r\n\r\nПоделиться:\r\n\r\n ... Новости
4 Первый отзыв без оценки: \r\nhttps://www.... Отзывы
In [463]:
for i in D['Тип источника'].unique():
    print (i)
Микроблоги
Новости
Отзывы
Форумы
Блоги
Видео
Мессенджеры
nan
In [464]:
D.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 200206 entries, 0 to 200205
Data columns (total 2 columns):
Текст            199578 non-null object
Тип источника    199993 non-null object
dtypes: object(2)
memory usage: 4.6+ MB

Удалим объекты с пропусками

In [465]:
D.dropna(axis = 0, inplace=True)

1.1 Выведите среднюю длину текстов в каждом классе. Что можно сказать о данных? Что можно сказать о каждом классе?

In [466]:
for i in D['Тип источника'].unique():
    print(i, '\t\t', D[D['Тип источника'] == i]['Текст'].str.len().mean())
Микроблоги 		 170.67440462427746
Новости 		 2611.6309011749895
Отзывы 		 552.476661643401
Форумы 		 465.8052389493173
Блоги 		 3503.4882308276387
Видео 		 916.9406722689075
Мессенджеры 		 950.9282234491718

Наименьшая длина текстов в категории Микроблогов $\approx 170$, далее идут форумы и отзывы $\approx 500$, после Видео и Мессенджеры $ \approx 930$.

Наибольшее количество символов категории Новости $-$ 2611 и Блоги $-$ 3500.

Результаты метрик не противоречат смыслу и жизненому опыту.

1.2 Приведите тексты к нижнему регистру и токенезируйте их

используйте word_tokenize из nltk.tokenize

In [467]:
from nltk.tokenize import word_tokenize
In [470]:
%%time
D['Текст'] = D['Текст'].str.lower()
D['tokens'] = D.apply(lambda x: word_tokenize(x['Текст']), axis=1)
Wall time: 5min 28s
In [471]:
D.head()
Out[471]:
Текст Тип источника tokens
0 -здравствуйте, тинькофф банк\r\n-мать твою еба... Микроблоги [-здравствуйте, ,, тинькофф, банк, -мать, твою...
1 почему немагия сделала обзор на тинькова? #нем... Микроблоги [почему, немагия, сделала, обзор, на, тинькова...
2 13:01, 27.04.2019 \r\n\r\nподелиться:\r\n\r\n ... Новости [13:01, ,, 27.04.2019, поделиться, :, 57, forb...
3 13:01, 27.04.2019 \r\n\r\nподелиться:\r\n\r\n ... Новости [13:01, ,, 27.04.2019, поделиться, :, 44, forb...
4 первый отзыв без оценки:&nbsp;\r\nhttps://www.... Отзывы [первый, отзыв, без, оценки, :, &, nbsp, ;, ht...

1.3 Оставьте в каждом документе токены содержащие только буквы русского или английского алфавита.

In [473]:
%%time
D['tokens'] = D.apply(lambda x : [w for w in x['tokens'] if w.isalpha()], axis=1)
Wall time: 8.54 s
In [474]:
D.head()
Out[474]:
Текст Тип источника tokens
0 -здравствуйте, тинькофф банк\r\n-мать твою еба... Микроблоги [тинькофф, банк, твою, ебал, досвидания]
1 почему немагия сделала обзор на тинькова? #нем... Микроблоги [почему, немагия, сделала, обзор, на, тинькова...
2 13:01, 27.04.2019 \r\n\r\nподелиться:\r\n\r\n ... Новости [поделиться, forbes, назвал, самые, экстравага...
3 13:01, 27.04.2019 \r\n\r\nподелиться:\r\n\r\n ... Новости [поделиться, forbes, назвал, самые, экстравага...
4 первый отзыв без оценки:&nbsp;\r\nhttps://www.... Отзывы [первый, отзыв, без, оценки, nbsp, https, крат...

1.4 Выведите 20 слов, которые встечаются в наибольшем числе документов. Что можно сказать об этих словах?

In [475]:
import collections
In [476]:
words = collections.Counter()
for line in D["tokens"]:
    for word in set(line):
        words[word] += 1
In [477]:
for i in words.most_common(20):
    print(*i)
в 144033
и 136262
на 117638
не 110081
тинькофф 105112
с 100489
банк 93858
по 84583
что 82808
а 71779
за 68298
банка 62513
как 60769
это 59170
у 55321
для 55128
так 50070
от 49675
но 47131
к 45944

большая часть слов является предлогами и союзами и не несут смысловой нагрузки. Из 20 популярных слов смысл есть только у 3, причем 2 из них отличаются только на окончание

1.5 Выведите 20 слов, которые встечаются в наименьшем числе документов

In [478]:
for i in words.most_common()[len(words)-20:]:
    print(*i)
вардер 1
охеренный 1
фейсу 1
jagger 1
психую 1
устанлвлено 1
вылазящий 1
кэшбжком 1
повалится 1
netesov 1
отреклось 1
сварганили 1
кредитчику 1
выписного 1
наличгыми 1
pandeglol 1
пэйпассу 1
заказывый 1
советсткую 1
сверкало 1

2. Подготовка данных

[3 балла]

2.1 Разделите выборку на обучающую и тестовую в соотношении 70:30

In [2]:
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
In [480]:
D['Text'] = D.apply(lambda x: ' '.join(x['tokens']), axis=1)
D_train, D_test = train_test_split(D, test_size=0.3, random_state=42)

2.2 Обучите BOW векторы на обучающей выборке и примените преобразование к обучающей и тестовой

In [481]:
%%time
cnt_vec = CountVectorizer()
D_train_bow = cnt_vec.fit_transform(D_train["Text"])
D_test_bow = cnt_vec.transform(D_test["Text"])
Wall time: 35.1 s
In [482]:
D_train_bow, D_test_bow
Out[482]:
(<139697x278913 sparse matrix of type '<class 'numpy.int64'>'
 	with 11639034 stored elements in Compressed Sparse Row format>,
 <59871x278913 sparse matrix of type '<class 'numpy.int64'>'
 	with 4904259 stored elements in Compressed Sparse Row format>)

2.3 Обучите TFIDF векторы на обучающей выборке и примените преобразование к тестовой

In [483]:
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vec = TfidfVectorizer()
In [484]:
%%time
X_TFIDF = tfidf_vec.fit_transform(D_train["Text"])
Wall time: 24.4 s
In [485]:
X_test_TFIDF = tfidf_vec.transform(D_test["Text"])
In [486]:
X_TFIDF
Out[486]:
<139697x278913 sparse matrix of type '<class 'numpy.float64'>'
	with 11639034 stored elements in Compressed Sparse Row format>
In [487]:
X_test_TFIDF
Out[487]:
<59871x278913 sparse matrix of type '<class 'numpy.float64'>'
	with 4904259 stored elements in Compressed Sparse Row format>

2.4 Примените стемминг к текстам обучающей и тестовой выборки. Обучите TFIDF векторы на полученных данных.

In [488]:
from nltk.stem.snowball import SnowballStemmer
stemmer = SnowballStemmer('russian')
In [489]:
%%time
D_train['stemmed'] = D_train.apply(lambda x: ' '.join([stemmer.stem(w) for w in x['tokens']]), axis=1)
Wall time: 21min 13s
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  """Entry point for launching an IPython kernel.
In [490]:
D_train.head()
Out[490]:
Текст Тип источника tokens Text stemmed
51329 лучшее мобильное приложение лучшего мобильного... Отзывы [лучшее, мобильное, приложение, лучшего, мобил... лучшее мобильное приложение лучшего мобильного... лучш мобильн приложен лучш мобильн банк да и б...
88722 20 часов назад, мурчака сказал: мы всегда п... Форумы [часов, назад, мурчака, сказал, мы, всегда, пр... часов назад мурчака сказал мы всегда примерно ... час назад мурчак сказа мы всегд примерн счита ...
106055 цитатаmmit пишет:\r\nвойдет ли этот платеж в н... Форумы [цитатаmmit, пишет, войдет, ли, этот, платеж, ... цитатаmmit пишет войдет ли этот платеж в необх... цитатамм пишет войдет ли этот платеж в необход...
86205 москва, россия — 9 ноября 2018 г.\r\nтинькофф ... Новости [москва, россия, ноября, тинькофф, банк, сообщ... москва россия ноября тинькофф банк сообщает о ... москв росс ноябр тинькофф банк сообща о введен...
194642 контактные данные. 3.3. администрация осуществ... Блоги [контактные, данные, администрация, осуществля... контактные данные администрация осуществляет с... контактн дан администрац осуществля сбор стати...
In [491]:
%%time
D_test['stemmed'] = D_test.apply(lambda x: ' '.join([stemmer.stem(w) for w in x['tokens']]), axis=1)
Wall time: 8min 48s
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  """Entry point for launching an IPython kernel.
In [492]:
D_test.head()
Out[492]:
Текст Тип источника tokens Text stemmed
179313 ориентир доходности бессрочных субординированн... Новости [ориентир, доходности, бессрочных, субординиро... ориентир доходности бессрочных субординированн... ориентир доходн бессрочн субординирова еврообл...
162765 у меня была кредитная карта банка тинькофф. в ... Отзывы [у, меня, была, кредитная, карта, банка, тиньк... у меня была кредитная карта банка тинькофф в и... у мен был кредитн карт банк тинькофф в июн год...
158372 rt @andr_11_92: если вы нуждаетесь в деньгах,и... Микроблоги [rt, если, вы, нуждаетесь, в, деньгах, и, не, ... rt если вы нуждаетесь в деньгах и не знаете гд... rt есл вы нужда в деньг и не знает где их взят...
80873 фото: omskregion.info втб и тинькофф-... Новости [фото, втб, и, изучают, вопрос, переоборудован... фото втб и изучают вопрос переоборудования бан... фот втб и изуча вопрос переоборудован банкомат...
56717 тинькофф банк,\r\nотправил. там было более 3 м... Форумы [тинькофф, банк, отправил, там, было, более, м... тинькофф банк отправил там было более млн един... тинькофф банк отправ там был бол млн единствен...
In [165]:
tfidf_vec_stem = TfidfVectorizer()
In [493]:
%%time
X_stem_TFIDF = tfidf_vec_stem.fit_transform(D_train["stemmed"])
X_test_stem_TFIDF = tfidf_vec_stem.transform(D_test["stemmed"])
Wall time: 31.1 s

2.5 Сравните размеры полученных матриц

In [494]:
D_train_bow.shape, D_test_bow.shape
Out[494]:
((139697, 278913), (59871, 278913))
In [495]:
X_TFIDF.shape, X_test_TFIDF.shape
Out[495]:
((139697, 278913), (59871, 278913))
In [496]:
X_stem_TFIDF.shape, X_test_stem_TFIDF.shape
Out[496]:
((139697, 123887), (59871, 123887))

После стемминга количество уникальных слов уменьшилось примерно в 2 раза.

3. Обучение модели и оценка результатов

[2 балла]

3.1 Обучите логистическую регрессию SGDClassifier на данных, полученных в пунктах 2.2, 2.3 и 2.4.

Оцените качество на отложенной выборке по метрике accuracy

In [3]:
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import accuracy_score
In [499]:
lr1 = SGDClassifier()
lr1.fit(D_train_bow, D_train["Тип источника"])
y_predict = lr1.predict(D_test_bow)
print("BOW algo accuracy:", accuracy_score(D_test["Тип источника"], y_predict))
BOW algo accuracy: 0.8353626964640644
In [501]:
lr2 = SGDClassifier()
lr2.fit(X_TFIDF, D_train["Тип источника"])
y_predict = lr2.predict(X_test_TFIDF)
print("TFIDF algo accuracy:", accuracy_score(D_test["Тип источника"], y_predict))
TFIDF algo accuracy: 0.8321892067946084
In [502]:
lr3 = SGDClassifier()
lr3.fit(X_stem_TFIDF, D_train["Тип источника"])
y_predict = lr3.predict(X_test_stem_TFIDF)
print("TFIDF with stem algo accuracy:", accuracy_score(D_test["Тип источника"], y_predict))
TFIDF with stem algo accuracy: 0.819912812546976

3.2 Какой алгоритм показал наилучшее качество классификации? Как это можно объяснить?

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

Лучший результат показал BOW алгоритм.

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

После стэминга качество упало на 0.012 по сравнению с обычным TFIDF. Возможная причина - потеря смысла некоторых слов при откидывании окончаний и суффиксов (смысл слова в работе не восстанавливался).

3.3 Выведите несколько документов из тестовой выборки, на которых были допущены ошибки. Что можно о них сказать?

In [504]:
_D_test = D_test.reindex()
_D_test.index=np.arange(_D_test.shape[0])
for i,r in _D_test[_D_test["Тип источника"] != y_predict][:10].iterrows():
    print("Text:")
    print(r["Текст"])
    print("Stammed text:")
    print(r["stemmed"])
    print("Predicted: ", r["Тип источника"])
    print("Actual: ", y_predict[i])
    print("="*60)
Text:
тинькофф банк,
отправил. там было более 3 млн. единственное, чем можно объяснить - списанием средств при покупке акций и не моментальным зачислением самих акций на счёт. у меня ситуация может быть аналогичной.
при этом в чате сообщали. что проблемой это не будет.
Stammed text:
тинькофф банк отправ там был бол млн единствен чем можн объясн списан средств при покупк акц и не моментальн зачислен сам акц на счет у мен ситуац может быт аналогичн при эт в чат сообща что проблем эт не будет
Predicted:  Форумы
Actual:  Отзывы
============================================================
Text:
спасибо тинькофф банку https://www.tinkoffinsurance.ru/ins/osago-product/
за быстрое и простое оформление е-осаго, все понятно и интуитивно, также цена ниже чем у конкурентов! оплатили картой и в момент полис на почте), правда теперь вся семья заставила делать им полисы). ну и по оформлению другого полиса осаго надеюсь решится успешно, в прошлый раз видимо не все данные передали в базу, и теперь коэффициент 1, но полис на руках есть.
Stammed text:
спасиб тинькофф банк https за быстр и прост оформлен все понятн и интуитивн такж цен ниж чем у конкурент оплат карт и в момент полис на почт правд тепер вся сем застав дела им полис ну и по оформлен друг полис осаг над реш успешн в прошл раз видим не все дан переда в баз и тепер коэффициент но полис на рук ест
Predicted:  Форумы
Actual:  Отзывы
============================================================
Text:
спасибо  за совет!  я так и поступил, разговор  записал... . от этих товарищей можно всего ожидать... потом скажут, что вы нам  может и говорили про закрытие договора, но мы не слышали....   в любом случае, карта истекает через месяц,  и получать новую я не буду в любом случае!
Stammed text:
спасиб за совет я так и поступ разговор записа от эт товарищ можн всег ожида пот скажут что вы нам может и говор про закрыт договор но мы не слыша в люб случа карт истека через месяц и получа нов я не буд в люб случа
Predicted:  Отзывы
Actual:  Форумы
============================================================
Text:
+themaxrus пользуюсь тинькофф и втб. и как банк тинькофф гораздо удобнее и понятнее. тинькофф рулит
Stammed text:
польз тинькофф и втб и как банк тинькофф горазд удобн и понятн тинькофф рул
Predicted:  Видео
Actual:  Микроблоги
============================================================
Text:
на самом деле у тинькофф очень высокие штрафы за просрочку минимального платежа, если не брать в расчеты премиальные карты, то выходит [b]29,9%+19%=48,9% + 590 руб[/b], если сравнивать со сбером, то у них 36%
Stammed text:
на сам дел у тинькофф очен высок штраф за просрочк минимальн платеж есл не брат в расчет премиальн карт то выход b руб есл сравнива со сбер то у них
Predicted:  Отзывы
Actual:  Форумы
============================================================
Text:
безнаказанноехищение денежных средств с расчетного счета организации в банке втб
26марта 2018 года с расчетного счета нашей организации в банке втб с
использованием системы &laquo;банк-клиент&raquo; через сеть интернет был похищен весь
остаток денежных средств в размере 972&nbsp;000 рублей. в связи с редкими
платежами по счету хищение было обнаружено на 3-й день.
28марта была предпринята попытка войти в систему &laquo;банк-клиент&raquo;, однако, служба
техподдержки банка сообщила, что пароль от системы &laquo;банк-клиент&raquo; был изменен 26
марта.
выяснилось,что в этот день были осуществлены 5 платежей, несанкционированных руководством
и физически не проводившихся с нашего компьютера. все платежи были произведены
в адрес одного получателя в тинькофф банк &mdash; ип вашин николай олегович
(красноярский край, р-н партизанский, с. партизанское).
29марта 2018 года были поданы официальное заявление об отмене платежей и
заявление в полицию. как выяснилось в банке, пароль доступа был изменен без
участия клиента, хотя в соответствии с принятой в банке процедурой, такой
пароль меняется только в течение 2-х дней и только при личной явке клиента в
банк.
ни службабезопасности банка, ни руководитель дополнительного офиса, ни руководство
головного офиса, не оказали никакой реальной помощи в разрешении сложившейся
ситуации, ограничившись бесполезной перепиской. не была проведена ни экспертиза
компьютера, с которого клиент заходил в банк, ни проверка сотрудников банка на
детекторе лжи, не подано даже заявление банка в полицию. при этом очевидно, что
мошенники осуществляли платежи, зная, что в ближайшее время пропажу денег не обнаружат,
т.е. они, вероятнее всего, обладали информацией о периодичности платежей с
расчетного счета.
только после 2-хписем на имя костина а.л. нам соизволили назначить встречу с директором
управления по москве и мо департамента корпоративной сети банка втб &ndash; полинко
татьяной александровной. нам было заявлено, что у нас будет не больше 30-40
минут в связи с плотным графиком такого высокого руководителя. фактически с нами
поговорили 4 других сотрудника банка, а сама госпожа полинко, подойдя в
завершение встречи, со слов своих коллег за 10 минут поняла, что мы сами
виноваты в случившемся. директор управления объяснила, что наши претензии к
банку вызваны нашим &laquo;субъективным восприятиемпроблемы&raquo;, банк считает своё поведение правильным, затем покинула встречупосле нашего замечания на удивительную &laquo;клиентоориентированность&raquo; собеседницы.
после такихвстреч нам стало понятно, как банк втб относится к своим клиентам и их денежным
средствам.
вызываетполнейшее непонимание и возмущение равнодушная позиция сотрудников одного из
крупнейших банков страны к произошедшему инциденту при том, что денежные
средства похищены у некоммерческой организации, не имеющей иных источников
доходов кроме взносов членов и выполняющей государственные задачи по защите и
представлению интересов всей отрасли.
&nbsp;
поискав винтернете, мы обнаружили, что пропажа денег в банке втб &ndash; нередкое явление и
такое поведение его сотрудников - типично.
вот первые обнаруженныессылки на другие случаи хищения, попавшие в сми:
&nbsp;
https://life.ru/1145312&nbsp; молчание &mdash; золото. почему втб не ищетпропавшие со вклада клиента миллионы?
http://www.banki.ru/forum/?page_name=read&amp;fid=61&amp;tid=327302в втб-24 украли деньги со вклада.
исполнительный директор национальной ассоциации звероводов зубкова н.а.
Stammed text:
безнаказанноехищен денежн средств с расчетн счет организац в банк втб год с расчетн счет наш организац в банк втб с использован систем laqu raqu через сет интернет был похищ ве остаток денежн средств в размер nbsp рубл в связ с редк платеж по счет хищен был обнаруж на ден был предпринят попытк войт в сист laqu raqu однак служб техподдержк банк сообщ что парол от систем laqu raqu был измен март выясн что в этот ден был осуществл платеж несанкционирова руководств и физическ не провод с наш компьютер все платеж был произвед в адрес одн получател в тинькофф банк mdash ип вашин никола олегович красноярск кра партизанск партизанск год был пода официальн заявлен об отмен платеж и заявлен в полиц как выясн в банк парол доступ был измен без участ клиент хот в соответств с принят в банк процедур так парол меня тольк в течен дне и тольк при личн явк клиент в банк ни службабезопасн банк ни руководител дополнительн офис ни руководств головн офис не оказа никак реальн помощ в разрешен слож ситуац огранич бесполезн переписк не был провед ни экспертиз компьютер с котор клиент заход в банк ни проверк сотрудник банк на детектор лжи не пода даж заявлен банк в полиц при эт очевидн что мошенник осуществля платеж зна что в ближайш врем пропаж денег не обнаружат он вероятн всег облада информац о периодичн платеж с расчетн счет тольк посл на им костин нам соизвол назнач встреч с директор управлен по москв и мо департамент корпоративн сет банк втб ndash полинк татьян александровн нам был заявл что у нас будет не больш минут в связ с плотн график так высок руководител фактическ с нам поговор друг сотрудник банк а сам госпож полинк подойд в завершен встреч со слов сво коллег за минут поня что мы сам виноват в случ директор управлен объясн что наш претенз к банк вызва наш laqu субъективн восприятиемпроблем raqu банк счита сво поведен правильн зат покинул встречупосл наш замечан на удивительн laqu клиентоориентирован raqu собеседниц посл такихвстреч нам стал понятн как банк втб относ к сво клиент и их денежн средств вызываетполн непониман и возмущен равнодушн позиц сотрудник одн из крупн банк стран к произошедш инцидент при том что денежн средств похищ у некоммерческ организац не имеющ ин источник доход кром взнос член и выполня государствен задач по защ и представлен интерес все отрасл nbsp поиска винтернет мы обнаруж что пропаж денег в банк втб ndash нередк явлен и так поведен ег сотрудник типичн вот перв обнаруженныессылк на друг случа хищен попа в сми nbsp https nbsp молчан mdash золот поч втб не ищетпропа со вклад клиент миллион http amp amp укра деньг со вклад исполнительн директор национальн ассоциац зверовод зубков
Predicted:  Отзывы
Actual:  Новости
============================================================
Text:
если у вам часто надоели звонки из банка тинькофф и других банков....
Stammed text:
есл у вам част надоел звонк из банк тинькофф и друг банк
Predicted:  Видео
Actual:  Отзывы
============================================================
Text:
19 часов назад, sever сказал:    большое спасибо за ответы.
попробую тоже - пойти, сказать свои пожелания, может они подумают ))) и дадут что мне бы хотелось.
тинькоф - ушла я от них, закрыла кредитку и на все их звонки - "мягко" посылала. рассталась, т.к.по мне они ужасно навязчивые (ясное дело - работа такая) - но предлагали страховку на машину - телефон просто оборвали, потом дебетовую - "но я вам все-таки пришлю нашего менеджера  с картой. и точка", потом телефоны друзей "сдать", агрессивные - ужс.     именно из тинькова по страховке телефон оборвали? у меня первые 2 года после покупки машины тоже ад был со звонками на счет авто страховки. звонить начинали месяца за 3 до даты продления, не по одному разу за день.  только тиньков тут не причем. звонят разные брокеры, и агенты, телефон берут из базы страховщиков. тиньков по поводу страховки звонил 1 раз, мы посчитали полис и у них вышло дороже, после чего больше не звонили. и вообще, с 12 года с этим банком в навязчивой рекламе ни разу не замечены. они вообще с рекламой не звонили мне, вот когда сама им звонишь, тогда после решения вопроса обычно спрашивают есть ли минутка и успевают какие либо свои услуги предложить. но сами не звонят. 
Stammed text:
час назад sever сказа больш спасиб за ответ попроб тож пойт сказа сво пожелан может он подума и дадут что мне бы хотел тинькоф ушл я от них закр кредитк и на все их звонк мягк посыла расста мне он ужасн навязчив ясн дел работ так но предлага страховк на машин телефон прост оборва пот дебетов но я вам пришл наш менеджер с карт и точк пот телефон друз сдат агрессивн ужс имен из тиньков по страховк телефон оборва у мен перв год посл покупк машин тож ад был со звонк на счет авт страховк звон начина месяц за до дат продлен не по одн раз за ден тольк тиньк тут не прич звон разн брокер и агент телефон берут из баз страховщик тиньк по повод страховк звон раз мы посчита полис и у них вышл дорож посл чег больш не звон и вообщ с год с эт банк в навязчив реклам ни раз не замеч он вообщ с реклам не звон мне вот когд сам им звон тогд посл решен вопрос обычн спрашива ест ли минутк и успева как либ сво услуг предлож но сам не звон
Predicted:  Форумы
Actual:  Отзывы
============================================================
Text:
гарек сказал(а): &uarr;  ты. &quot;всем 100% кому выписали должны штрафы отменить и вернуть деньги&quot;
все платят разными способами
я например - через приложение тинькова
банк вообще не имеет никакого отношения к моим теркам с дептрансом и гибдд
потому и не представляю себе как можно просто &quot;отменить и вернуть&quot;
это в любом случае процедура непростая, даж если виновники падут ниц в раскаяниинажмите, чтобы раскрыть...  ну непростая и что дальше? я разве сказал что она простая? &nbsp;
Stammed text:
гарек сказа а uarr ты quot всем ком выписа должн штраф отмен и вернут деньг quot все плат разн способ я например через приложен тиньков банк вообщ не имеет никак отношен к мо терк с дептранс и гибдд пот и не представля себ как можн прост quot отмен и вернут quot эт в люб случа процедур непрост даж есл виновник падут ниц в раскаяниинажм чтоб раскр ну непрост и что дальш я разв сказа что он прост nbsp
Predicted:  Форумы
Actual:  Отзывы
============================================================
Text:
для любопытных: сама табличка на [url=https://docs.google.com/spreadsheets/d/1c_cywpjuezgxshvqgf-ykcxtzvkywsscnjq51stiwea/edit#gid=1620489015]гуглодиск[/url]е; статья про бюджет - [url=https://journal.tinkoff.ru/spreadsheet/]тут[/url]
Stammed text:
для любопытн сам табличк на гуглодиск е стат про бюджет тут
Predicted:  Отзывы
Actual:  Форумы
============================================================

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

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

Возможно, стоило добавить анализ тегов и разметки, но это бы было в какой-то мере читерством)

3.4 Постройте матрицу ошибок. Проанализируйте ее.

In [14]:
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from sklearn.utils.multiclass import unique_labels
%matplotlib inline

Всмомогательная статистика по текстам:

In [506]:
_D_test[_D_test["Тип источника"] ==y_predict].groupby("Тип источника")["Text"].describe()
Out[506]:
count unique top freq
Тип источника
Блоги 113 101 информационный портал новости банков является ... 11
Видео 2142 1640 https регистрация в letyshops https расширение... 170
Мессенджеры 81 39 друзья совместно с тинькофф банк мы запустили ... 24
Микроблоги 5304 2971 rt если вы нуждаетесь в деньгах и не знаете гд... 227
Новости 10556 7818 в связи с новым годом набираем новую команду и... 759
Отзывы 12762 12268 текст отсутствует 57
Форумы 18131 17765 оно не тонет 40
In [507]:
_D_test[_D_test["Тип источника"] !=y_predict].groupby("Тип источника")["Text"].describe()
Out[507]:
count unique top freq
Тип источника
Блоги 1440 1405 рубрика бытовуха а вот скажите мне кто в курсе... 6
Видео 1419 1392 в таком quot дупле quot как у юльке что ей quo... 5
Мессенджеры 852 779 лучших банков для ип в году к менее значимым н... 5
Микроблоги 1160 1154 вы можете помочь в развитии нашему фонду перев... 3
Новости 824 811 говорю же что поступления были с эквайринга ти... 3
Отзывы 2486 2441 11
Форумы 2601 2537 в любом бизнесе который вы бы не начали вам ну... 6
In [508]:
cm = confusion_matrix(D_test["Тип источника"], y_predict)
classes = unique_labels(D_test["Тип источника"], y_predict)

fig, ax = plt.subplots(figsize=(10,8))
im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Oranges)
ax.figure.colorbar(im, ax=ax)
ax.set(xticks=np.arange(cm.shape[1]),yticks=np.arange(cm.shape[0]), xticklabels=classes, yticklabels=classes,
       title='Матрица ошибок',
       ylabel='Истинное значение',
       xlabel='Предсказанное значение')
fmt = 'd'
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(j, i, format(cm[i, j], fmt),
                ha="center", va="center",
                color="white" if cm[i, j] > thresh else "black")
fig.tight_layout()
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
classes = unique_labels(D_test["Тип источника"], y_predict)

fig, ax = plt.subplots(figsize=(10,8))
im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Oranges)
ax.figure.colorbar(im, ax=ax)
ax.set(xticks=np.arange(cm.shape[1]), yticks=np.arange(cm.shape[0]), xticklabels=classes, yticklabels=classes,
       title='Матрица ошибок',
       ylabel='Истинное значение',
       xlabel='Предсказанное значение')
fmt = '.3f'
thresh = cm.max() / 2.
for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        ax.text(j, i, format(cm[i, j], fmt),
                ha="center", va="center",
                color="white" if cm[i, j] > thresh else "black")
fig.tight_layout()

Из 2 диаграммы видно, что модель хорошо предсказывает категории микроблоги, новости, отзывы и форумы. Хорошим остается предсказание для видео.

Модель совершенно не угадывает блоги и мессенджеры и относит их чаще к новостям.

Кроме того, модель часто неверно определяет другие категории не только как новости, но и форумы.

Часть 2. Логистическая регрессия.

[3 балла]

Для наших экспериентов возьмём обучающую выборку отсюда.

In [4]:
train = pd.read_csv('train.csv')

Решается задача многоклассовой классификации — определение ценовой категории телефона. Для простоты перейдём к задаче бинарной классификации — пусть исходные классы 0 и 1 соответствуют классу 0 новой целевой переменной, а остальные классу 1. ​ Замените целевую переменную, отделите её в отдельную переменную и удалите из исходной выборки.

In [5]:
y = train["price_range"].apply(lambda x: 0 if x == 0 or x == 1 else 1)
X = train.drop("price_range", axis=1)
X.head()
Out[5]:
battery_power blue clock_speed dual_sim fc four_g int_memory m_dep mobile_wt n_cores pc px_height px_width ram sc_h sc_w talk_time three_g touch_screen wifi
0 842 0 2.2 0 1 0 7 0.6 188 2 2 20 756 2549 9 7 19 0 0 1
1 1021 1 0.5 1 0 1 53 0.7 136 3 6 905 1988 2631 17 3 7 1 1 0
2 563 1 0.5 1 2 1 41 0.9 145 5 6 1263 1716 2603 11 2 9 1 1 0
3 615 1 2.5 0 0 0 10 0.8 131 6 9 1216 1786 2769 16 8 11 1 0 0
4 1821 1 1.2 0 13 1 44 0.6 141 2 14 1208 1212 1411 8 2 15 1 1 0

Разделите выборку на обучающую и тестовую части в соотношении 7 к 3. Для этого можно использовать train_test_split из scikit-learn. Не забудьте зафиксировать сид для разбиения.

In [6]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

В этой части вы будете обучать самый простой бинарный классификатор — логистическую регрессию. Будем использовать готовую реализацию LogisticRegression из scikit-learn.

Логистическая регрессия — линейный метод, то есть в нём предсказание алгоритма вычислаяется как скалярное произведение признаков и весов алгоритма:

$$ b(x) = w_0 + \langle w, x \rangle = w_0 + \sum_{i=1}^{d} w_i x_i $$

Для вычисления вероятности положительного класса применяется сигмода. В результате предсказание вероятности принадлежности объекта к положительному классу можно записать как:

$$ P(y = +1 | x) = \frac{1}{1 + \exp(- w_0 - \langle w, x \rangle )} $$

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

In [7]:
from sklearn.preprocessing import StandardScaler
In [8]:
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\preprocessing\data.py:625: DataConversionWarning: Data with input dtype int64, float64 were all converted to float64 by StandardScaler.
  return self.partial_fit(X, y)
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\base.py:462: DataConversionWarning: Data with input dtype int64, float64 were all converted to float64 by StandardScaler.
  return self.fit(X, **fit_params).transform(X)
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: DataConversionWarning: Data with input dtype int64, float64 were all converted to float64 by StandardScaler.
  This is separate from the ipykernel package so we can avoid doing imports until

Обучите логистическую регрессию. Сделайте предсказания для тестовой части, посчитайте по ним ROC-AUC и Accuracy (порог 0.5). Хорошо ли удаётся предсказывать целевую переменную? Не забывайте, что метод predict_proba вычисляет вероятности обоих классов выборки, а в бинарной классификации нас интересует в первую очередь вероятность принадлежности к положительному классу.

In [9]:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
In [10]:
lr = LogisticRegression()
lr.fit(X_train, y_train)
y_predict = lr.predict_proba(X_test)[:,1]
C:\Users\alex1\Anaconda3\lib\site-packages\sklearn\linear_model\logistic.py:433: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
  FutureWarning)
In [11]:
print("ROC-AUC: ", roc_auc_score(y_test, y_predict))
print("Accuracy: ", accuracy_score(y_test, np.round(y_predict)))
ROC-AUC:  0.9997555311086664
Accuracy:  0.9866666666666667

У обученной логистической регрессии есть два аттрибута: coef_ и intercept_, которые соответствуют весам $w$ и $w_0$. Это и есть результат обучения логистической регрессии. Попробуйте с помощью них (с помощью всё той же обученной ранее логистической регрессии) посчитать "сырое" предсказание алгоритма $b(x)$.

Постройте гистограмму полученных значений и ответьте на вопросы:

  • Какие значения принимает такое предсказание?
  • Похожи ли эти значения на вероятность классов?
In [12]:
b_x = lr.intercept_[0] + lr.coef_[0].dot(X_test.T)
In [540]:
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('"сырое" предсказание алгоритма b(x)')
plt.xlabel('Предсказание')
plt.ylabel('Количество')
plt.hist(b_x, bins=np.int(np.sqrt(b_x.shape[0])))
plt.show()

Данные распределены примерно одинаково относительно 0 (303 и 297 записей).

Заметны некоторые отколонения колчества записей в некоторых диапазонах (например -11..-10), но в целом количество записей у краев распределения уменьшается.

Значения похожи на вероятность принадлежности к классу. Диапазон значений больше единицы на количество признаков - 20.

Реализуйте сигмоиду и постройте её график. Что вы можете сказать об этой функции?

In [18]:
def sigma(x):
    return 1/(1 + np.exp(-x))
In [19]:
plt.figure(figsize=(20,10))
_x = np.linspace(-6, 6, 1000)
_y = sigma(_x)
plt.grid(True)
plt.title('Сигмоида')
plt.xlabel('X')
plt.ylabel('Сигмоида')
plt.plot(_x, _y)
plt.show()

Примените реализованную сигмоиду к $b(x)$. Вы должны получить вероятности принадлежности к положительному классу. Проверьте, что ваши значения совпали с теми, которые получены с помощью predict_proba.

In [20]:
b_xx = sigma(b_x)
In [21]:
sum(b_xx != y_predict)
Out[21]:
0

Значения совпали

Таким образом, обучение логистической регрессии — настройка параметров $w$ и $w_0$, а применение — подсчёт вероятностей принадлежности положительному классу как применение сигмоды к скалярному произведению признаков и параметров.

Постройте для обученной логистической регрессии ROC-кривую roc_curve и PR-кривую precision_recall_curve.

In [22]:
from sklearn.metrics import precision_recall_curve,  roc_curve
In [23]:
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(14,7))
ax1.grid(True)
ax1.set(title='ROC-кривая', xlabel ='False positive rate',ylabel ='True positive rate')
fpr, tpr, thresholds = roc_curve(y_test, y_predict)
ax1.plot(fpr, tpr)
fpr, tpr, thresholds = precision_recall_curve(y_test, y_predict)
ax2.grid(True)
ax2.set(title='Precision-recall-кривая', xlabel ='Recall',ylabel ='Precision')
ax2.plot(fpr, tpr)
Out[23]:
[<matplotlib.lines.Line2D at 0x1a6ba4a34e0>]

4. Бонусное задание. Обучение логистической регрессии.

[2 бонусных балла]

В этой части вы будете обучать самый простой бинарный классификатор — логистическую регрессию. Будем использовать готовую реализацию LogisticRegression из scikit-learn.

Логистическая регрессия — линейный метод, то есть в нём предсказание алгоритма вычислаяется как скалярное произведение признаков и весов алгоритма:

$$ b(x) = w_0 + \langle w, x \rangle = w_0 + \sum_{i=1}^{d} w_i x_i $$

Для вычисления вероятности положительного класса применяется сигмода. В результате предсказание вероятности принадлежности объекта к положительному классу можно записать как:

$$ P(y = +1 | x) = \frac{1}{1 + \exp(- w_0 - \langle w, x \rangle )} $$

Если выше вручную мы только применяли логистическую регрессию, то здесь предлагается реализовать обучение с помощью полного градиентного спуска. Если кратко, то обучение логистической регрессии с $L_2$-регуляризацией можно записать следующим образом:

$$ Q(w, X) = \frac{1}{l} \sum_{i=1}^{l} \log (1 + \exp(- y_i \langle w, x_i \rangle )) + \frac{\lambda_2}{2} \lVert w \rVert _2^2 \to \min_w $$

Считаем, что $y_i \in \{-1, +1\}$, а нулевым признаком сделан единичный (то есть $w_0$ соответствует свободному члену). Искать $w$ будем с помощью градиентного спуска:

$$ w^{(k+1)} = w^{(k)} - \alpha \nabla_w Q(w, X) $$

В случае полного градиентного спуска $\nabla_w Q(w, X)$ считается напрямую (как есть, то есть, используя все объекты выборки). Длину шага $\alpha > 0$ в рамках данного задания предлагается брать равной некоторой малой константе. Градиент по объекту $x_i$ считается по следующей формуле:

$$ \nabla_w Q(w, x_i) = - \frac{y_i x_i}{1 + \exp(y_i \langle w, x_i \rangle)} + \lambda_2 w $$

На самом деле неправильно регуляризировать свободный член $w_0$ (то есть при добавлении градиента для $w_0$ не надо учитывать слагаемое с $\lambda_2$). Но в рамках этого задания мы не обращаем на это внимания и работаем со всеми вектором весов одинаково.

В качестве критерия останова необходимо использовать (одновременно):

  • проверку на евклидовую норму разности весов на двух соседних итерациях (например, меньше некоторого малого числа порядка $10^{-6}$) — параметр tolerance
  • достижение максимального числа итераций (например, 10000) — параметр max_iter.

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

Реализуйте обучение логистической регрессии. Для удобства ниже предоставлен прототип с необходимыми методами. В loss_history необходимо сохранять вычисленное на каждой итерации значение функции потерь.

In [136]:
from sklearn.base import BaseEstimator

class LogReg(BaseEstimator):
    def __init__(self, lambda_2=1.0, tolerance=1e-4, max_iter=100, alpha=0.005):
        """
        lambda_2: L2 regularization param
        tolerance: for stopping gradient descent
        max_iter: maximum number of steps in gradient descent
        alpha: learning rate
        """
        self.lambda_2 = lambda_2
        self.tolerance = tolerance
        self.max_iter = max_iter
        self.alpha = alpha
        self.w = None
        self.loss_history = None
    
    def fit(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: self
        """
        if type(X) is pd.core.series.Series:
            X = X.values
        
        if type(y) is pd.core.series.Series:
            y = y.values
        self.loss_history = []
        
        shape = X.shape
        self.w = np.zeros(shape[1])
        for step in range(self.max_iter):
            self.w_curr = self.w - self.alpha * self.calc_gradient(X, y)
            self.loss_history.append(self.calc_loss(X,y))
            if np.linalg.norm(self.w_curr - self.w) < self.tolerance:
                break
            self.w = self.w_curr
            
        return self
    
    def predict_proba(self, X):
        """
        X: np.array of shape (l, d)
        ---
        output: np.array of shape (l, 2) where
        first column has probabilities of -1
        second column has probabilities of +1
        """
        if self.w is None:
            raise Exception('Not trained yet')
        proba = sigma(np.dot(X_test, self.w))
        return np.array([proba, 1 - proba]).T
    
    def calc_gradient(self, X, y):
        """
        X: np.array of shape (l, d) (l can be equal to 1 if stochastic)
        y: np.array of shape (l)
        ---
        output: np.array of shape (d)
        """
        g = 0
        for i in range(X.shape[0]):
            g += y[i] * X[i] * sigma(y[i] * self.w.dot(X[i]))
        g /= X.shape[0] 
        g += self.lambda_2 * self.w
        return g

    def calc_loss(self, X, y):
        """
        X: np.array of shape (l, d)
        y: np.array of shape (l)
        ---
        output: float 
        """         
        return sum([sigma(y[i] * self.w.dot(X[i]))  for i in range(X.shape[0]) ]) / X.shape[0] +  self.lambda_2 / 2 * (np.linalg.norm(self.w) ** 2)
In [137]:
lr = LogReg()
lr.fit(X_train, y_train)
Out[137]:
LogReg(alpha=0.005, lambda_2=1.0, max_iter=100, tolerance=0.0001)
In [138]:
y_predict = lr.predict_proba(X_test)[:,1]
In [139]:
print("ROC-AUC: ", roc_auc_score(y_test, y_predict))
print("Accuracy: ", accuracy_score(y_test, np.round(y_predict)))
ROC-AUC:  0.9956106721783289
Accuracy:  0.96
In [140]:
plt.figure(figsize=(20,10))
plt.grid(True)
plt.title('LogReg')
plt.xlabel('Итерация')
plt.ylabel('Loss')
plt.plot(range(len(lr.loss_history)), lr.loss_history)
plt.show()
  • Примените логистическую регресиию на той же выборке.
  • Посчитайте качество по тем же метрикам.
  • Визуализируйте изменение значений функции потерь от номера итераций.