<jupyter_start><jupyter_text>Открытый курс по машинному обучению. Сессия № 2  Автор материала: <NAME> (@malugina)   Индивидуальный проект по анализу данных   Анализ новостей для предсказания фондового рынка<jupyter_code>import pandas as pd
import numpy as np
import re
 
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import r2_score, roc_auc_score
from sklearn.model_selection import train_test_split, GridSearchCV, validation_curve, StratifiedKFold, cross_val_score, learning_curve
from sklearn.feature_extraction.text import TfidfTransformer, CountVectorizer, TfidfVectorizer
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline, Pipeline
from sklearn.preprocessing import MaxAbsScaler
from sklearn.svm import SVC

%matplotlib inline
from matplotlib import pyplot as plt
import pylab 
from pylab import rcParams
import seaborn as sns

from tqdm import tqdm_notebook

from scipy import stats as stats
from scipy.stats.stats import pearsonr 

RANDOM_STATE = 100<jupyter_output><empty_output><jupyter_text>1. Описание набора данных и признаков В данном наборе данных предлагается рассмотреть связь между текстами ежедневных новостей и изменением индекса [Доу Джонса](https://ru.wikipedia.org/wiki/Промышленный_индекс_Доу_—_Джонса) (DJIA). Этот индекс отражает состояние американской экономики, и умение его предсказыть позволяет компаниям принимать правильные решения о покупке или продаже акций и ценных бумаг. Индекс расситывается как средняя цена акций 30 крупнейших американских компаний. Определенные новости об этих компаниях способны изменить стоимость их ценных бумаг, и, как следствие, изменить индекс. В данной работе предстоит проверить, насколько тексты новостей влияют на изменение этого индекса.Данные взяты с [Kaggle](https://www.kaggle.com/aaron7sun/stocknews/data), копия – [тут](https://drive.google.com/file/d/1OpKHm9iDcKJwSKUTbD0SFLDZ3MbiJBGB/view?usp=sharing).Давайте посмотрим на исходный набор файлов:  1) RedditNews.csv - Список новостей по датам- Date - дата выхода новости, - News - текст новости- Одна дата - несколько новостей<jupyter_code>news = pd.read_csv('RedditNews.csv')
news.head()<jupyter_output><empty_output><jupyter_text>2) DJIA_table.csv - Индекс Доу Джонса по дням- Date - дата- Open - значение индекса DJIA на момент открытия торгов- Close - значение индекса DJIA на момент закрытия торгов- High, Low - наибольшее и наименьшее значение индекса DJIA в течение указаного дня- Volume - объём торгов в течение дня- Adj Close - скорректированное значение индекса DJIA, в который были внесены изменения, включая любые распределения и корпоративные действия, которые произошли в любое время до открытия следующего дня. В этом наборе данных значение этого поля во всех строках равно Close, то есть оно не несет никакой новой информации. Сразу удалим его<jupyter_code>djia = pd.read_csv('DJIA_table.csv')
djia.head()
djia[djia['Adj Close'] - djia['Close'] !=0 ].shape
del djia['Adj Close']<jupyter_output><empty_output><jupyter_text>3) Combined_News_DJIA.csv Это аналог первой таблицы, уже сгруппированный по датам со следующими полями:- Date - дата- Label - метка соответствующая положиельному закрытию торгов (1 - положительное, 0 - отрицательное)- Top1 - Top25 - тексты новостей за указанную дату[Ссылка](https://drive.google.com/file/d/1_qMektcsrrRZrg6S1-dC3dCVJ-mgY_PQ/view?usp=sharing) на данные.<jupyter_code>news_comb = pd.read_csv('Combined_News_DJIA.csv')
news_comb.head()<jupyter_output><empty_output><jupyter_text>Метка в таблице Combined_News_DJIA.csv предполагает решение задачи классификации, в плюсе или минусе закроются торги. Но, на мой взгляд, было бы интереснее и логичнее решить задачу регрессии и оценить то, насколько велик будет плюс или минус, так как для принятия решения о финансовых операциях важно знать, поднимется ли этот индекс на половину пункта или на сотни.Поэтому соединим таблицы news_comb и djia, и подсчитаем разницу в закрытии и открытии. В дальнейшем попробуем рассмотреть абсолютную и относительную разницу в качестве целевой переменной. Рассматривать само значение индекса Доу Джонса в качестве целевой переменной не стоит, так как новости влияют на его изменение относительно начала торгов.<jupyter_code>data = pd.merge(djia, news_comb, on='Date')
data['Diff_abs'] = data['Close'] - data['Open']
data['Diff_rel'] = (data['Close'] - data['Open']) / data['Open']<jupyter_output><empty_output><jupyter_text>2. Первичный анализ данных<jupyter_code>data.describe()<jupyter_output><empty_output><jupyter_text>Проверим заполненность полей:<jupyter_code>data.isnull().sum()[data.isnull().sum()>0]<jupyter_output><empty_output><jupyter_text>Видим, что в трёх днях было меньше новостей, чем обычно. Можно было бы исключить эти записи, но, на мой взгляд, их надо оставить, так как тексты всё равно будем объединять, а также попробуем проверить влияние вчерашних новостей на сегодняшний курс.<jupyter_code># отсортируем по датам:
data.sort_values(by='Date', axis=0, ascending=True, inplace=True)
# переведем тип столбца
data['Date'] = data['Date'].astype(dtype='datetime64[ns]')
plt.figure(1, figsize=(20, 10))

plt.subplot(311)
plt.plot(data['Date'], data['Close'])

plt.subplot(312)
plt.plot(data['Date'], data['Diff_abs'])

plt.subplot(313)
plt.plot(data['Date'], data['Diff_rel'])

plt.show()<jupyter_output><empty_output><jupyter_text>По поводу выбросов сделаю предположение: их нет, данные собраны автоматически, ошибки при ручном вводе исключены, и график (первый) показывает адекватные данные Посмотрим, не пропущены ли какие либо дни, для этого посчитаем интервалы между текущим и предыдущим днём:<jupyter_code>intervals = (data['Date'] - data['Date'].shift(1)).apply(lambda x: x.days)
print(intervals.value_counts())
plt.figure(figsize=(20, 10))
plt.plot(data['Date'][:100], intervals[:100])<jupyter_output><empty_output><jupyter_text>Очевидно, что пропуски - это выходные и изредка праздники, то есть дни в которые торги на бирже не проходят.<jupyter_code>rcParams['figure.figsize'] = 5, 3
data['Diff_abs'].plot(kind='hist', bins=100)<jupyter_output><empty_output><jupyter_text>Если смотреть на ранные с точки зрения классификации. то распределение целевой переменной будет следующим:<jupyter_code>data['Label'].value_counts().plot(kind='bar', label='Рост DJIA')<jupyter_output><empty_output><jupyter_text>Присутствует небольшой дисбаланс классов  Проверка на нормальность Критерий Шапиро-Уилка:<jupyter_code>print(stats.shapiro(data['Close']))
print(stats.shapiro(data['Diff_abs']))
print(stats.shapiro(data['Diff_rel']))<jupyter_output><empty_output><jupyter_text>Q-Q plot:<jupyter_code>stats.probplot(data['Diff_abs'], dist="norm", plot=pylab)
pylab.show()<jupyter_output><empty_output><jupyter_text>Выводы:  3. Первичный визуальный анализ данных Частично визуальный анализ, касающийся целевой переменной, присутствовал в предыдущем пункте. Так как признаки - это тексты и даты, то применим к ним преобразования 1. Выделим из даты год, год-месяц, месяц, день недели.<jupyter_code>data['Year'] = data['Date'].apply(lambda x: x.year)
data['Month'] = data['Date'].apply(lambda x: x.month)
min_year = data['Year'].min()
data['Year_Month'] = data['Date'].apply(lambda x: (x.year - min_year)*12 + x.month)
data['weekday'] = data['Date'].apply(lambda x: x.weekday())
data.head()<jupyter_output><empty_output><jupyter_text>2.1. А теперь соединим все тексты по годам, посчитаем tf-idf и выберем топ-20 слов, наиболее характерных для каждого года.Для этого соединим все тексты дня в один, а затем сгруппируем по годам:<jupyter_code># очищаем тексты от всего, кроме слов
def filterNews(s):
    if type(s) == str:
        s = s.lower()
        s = re.sub(r'^b[\'\"]','',s)
        s = re.sub(r'[\n]','',s)
        s = re.sub(r'[^a-z ]','',s)
    return s
#Склеенные 25 новостей
data['Combined_news'] = data.filter(regex=('Top.*'))\
    .apply(lambda x: ''.join(x.apply(lambda y: filterNews(y)).values.astype(str)), axis=1) 
#новости, сгруппированные по годам
yearly_news = data.groupby(['Year'])['Combined_news'].apply(lambda x: ''.join(x.values.astype(str)))
yearly_news
# преобразуем текты в мешок слов
# max_df=4 необходимо, чтобы отсеить высокочастотные слова, встречающиеся во многих текстах
# иначе получим максимальный tf-idf у слов типа "the", "of", "to"
# Это связанно с тем, что при расчете idf в библиотеке sklearn используется формула, 
# в которой к логирифму частного прибаляется единица. Таким образом, у слов, которые часто
# встречаются в текстах будет максимальное значение tf-idf

def get_top_words(data, n_top=10, max_df=None):
    cnt = CountVectorizer(max_df=max_df)
    a = cnt.fit_transform(data)
    feature_array = np.array(cnt.get_feature_names())

    tfidf = TfidfTransformer()
    a = tfidf.fit_transform(a)
    #print(pd.DataFrame(a.toarray(),columns=feature_array))

    # посмотрим на n_top самых актуальных слов года
    ar = [] #  и запишем их в этот массив
    for i in range(len(data)):
        tfidf_sorting = np.argsort(a[i].toarray()).flatten()[::-1] # ключи для сортировки
        top_keys = feature_array[tfidf_sorting][:n_top] # сами слова
        top_vals = np.array([a[i].toarray()[0][tfidf_sorting][:n_top]]) # значения их tf-idf
        #print(i,': ',top_n, a[i].toarray()[0][tfidf_sorting][:n_top])
        ar.append(pd.DataFrame(top_vals,columns=top_keys,index=[data.index[i]])) # собираем в один массив

    top_words = pd.concat(ar).fillna(0) # и соединяем все в один датафрейм с общими колонками   
    return top_words

top_words = get_top_words(yearly_news, 10, 4) # посмотрим на 10 самых актуальных слов года
top_words<jupyter_output><empty_output><jupyter_text>Наконец, можно приступить к визуализации!   1. По годам.Построим тепловую карту самих коэффициентов и корреляции слов.<jupyter_code>rcParams['figure.figsize'] = 5, 30
sns.heatmap(top_words.T, annot=False)<jupyter_output><empty_output><jupyter_text>На этом графике очень наглядно видны тенденции: про Сноудена активно говорили в 2013 году, потом в 2014 меньше (но тоже часто упоминали), в 2015 еще реже, а в 2016 уже и не попало его имя в топ-10 слов. Одновременно идет чуть более слабая тенденция для слова Edward (это же имя Сноудена, и оно также упоминалось в новостях в 2014 и потом пошло на спад). Другое заметное пятно - это упоминание isis (исламское государство) в 2014-2016 годах. Если увеличить картинку, и вчитаться, то можно увидеть много знакомых новостей: Донецк обсуждали в 2014, Грузию, Грузию-Россию и Саакашвили в 2008. Вот тут возникает мысль построить матрицу коррелиции и посмотреть, какие новости обсуждали одновременно:<jupyter_code>rcParams['figure.figsize'] = 25, 25
sns.heatmap(top_words.corr(), annot=False)<jupyter_output><empty_output><jupyter_text>Но эта матрица кажется мне не слишком выразительной, многие слова, активно упоминаемые в один год, нельзя назвать взаимосвязанными. Год - достаточно большой период. поэтому тут много сильно скоррелированных слов. Попробуем построить по месяцам и дням.<jupyter_code>monthly_news = data.groupby(['Year_Month'])['Combined_news'].apply(lambda x: ''.join(x.values.astype(str)))

top_words = get_top_words(monthly_news, 3, max_df=0.9) # посмотрим на 1 самых актуальных слов месяца
top_words.head()
rcParams['figure.figsize'] = 25, 30
sns.heatmap(top_words.T, annot=False)
rcParams['figure.figsize'] = 25, 25
sns.heatmap(top_words.corr(), annot=False)<jupyter_output><empty_output><jupyter_text>Посмотрим, что предаставлюят из себя пары сильно скоррелированных слов:<jupyter_code>corr = top_words.corr()
corr = corr - np.tril(corr)
corr.stack().nlargest(20)<jupyter_output><empty_output><jupyter_text>Мы получили пары слов, упоминаемых одновременно: про апокалипсис говорили тогда же, когда и про Майя, Кадаффи - Ливия и т.д.  По дням: Строить графики нет смысла, т.к. визуально их не оценить. Тут можно перейти к связи слов и индекса ДоуДжонса: какие слова в новостях совпали с повышением и с понижением этого индекса?<jupyter_code>cnt = CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3) #min_df=3 - чтобы исключить слова с ошибками
words = cnt.fit_transform(data['Combined_news'])
feature_array = np.array(cnt.get_feature_names())

tfidf = TfidfTransformer()
words_tfidf = tfidf.fit_transform(words)

corr = np.empty(words_tfidf.shape[1])
targ = np.array([data['Diff_abs']]).T
for i in tqdm_notebook(range(words_tfidf.shape[1])):#a1.shape[1]-1
    corr[i] = pearsonr(words_tfidf[:,i].toarray(), targ)[0][0]
corr = pd.Series(corr, index=feature_array)
corr.nlargest(15)<jupyter_output><empty_output><jupyter_text>Обрантая корреляция (частое упротребление каких слов приводит к понижению индекса) :<jupyter_code>(-corr).nlargest(15)<jupyter_output><empty_output><jupyter_text>Корелляция частоты слов и изменений биржевого индекса слабая, менее 0.12. Значит, каждое отдельно взятое слово (или пара слов) не влияет существено на целевую переменную. Построим простую линейную модель и посмотрим на слова с самыми высокими по модую коэффициентами:<jupyter_code># функция взята из лекции по линейным моделям
def visualize_coefficients(classifier, feature_names, n_top_features=25):
    # get coefficients with large absolute values 
    coef = classifier.coef_.ravel()
    positive_coefficients = np.argsort(coef)[-n_top_features:]
    negative_coefficients = np.argsort(coef)[:n_top_features]
    interesting_coefficients = np.hstack([negative_coefficients, positive_coefficients])
    # plot them
    plt.figure(figsize=(15, 5))
    colors = ["red" if c < 0 else "blue" for c in coef[interesting_coefficients]]
    plt.bar(np.arange(2 * n_top_features), coef[interesting_coefficients], color=colors)
    feature_names = np.array(feature_names)
    plt.xticks(np.arange(1, 1 + 2 * n_top_features), feature_names[interesting_coefficients], rotation=60, ha="right");
from sklearn.svm import SVC
model =  LogisticRegression()
model.fit(words_tfidf, data['Label'])
visualize_coefficients(model, feature_array, 25)<jupyter_output><empty_output><jupyter_text>Так как модель линейная, то "синие" слова связаны с повышением биржевого индекса, а "красные" - с понижением. Посмотрим, насколько можно умньшить размерность, сохранив 90% дисперсии:<jupyter_code>pca = PCA().fit(words_tfidf.toarray())


plt.figure(figsize=(10,7))
plt.plot(np.cumsum(pca.explained_variance_ratio_), color='k', lw=2)
plt.xlabel('Number of components')
plt.ylabel('Total explained variance')
#plt.xlim(0, 1263)
plt.yticks(np.arange(0, 1.1, 0.1))
plt.axvline(1600, c='b')
plt.axhline(0.9, c='r')
plt.show();<jupyter_output><empty_output><jupyter_text>5. Выбор метрики Так как классы несколько несбалансированы, а также для оценки полезности модели будем использовать ROC-AUC   6. Выбор модели Так как обрабатывать будем разреженные данные, то для этого отлично подходят линейные модели. Для работы с текстом часто используют метод опорных векторов. Также сделаем предположение, что в дни с похожили новостями будут и похожие изменения, поэтому попробуем метод ближайших соседей. И градиентный бустинг, ну как его не проверить) Проверим все методы, сравним их эффективность.  7. Предобработка данных  7.1. Разобъём данные на обучающую и тестовую выборки: Как уже делалось ранее, склеим тексты новостей в одно поле, оставив лишь слова. В раздельном хранении нет смысла, так как новости (поля Top1-Top25) не упорядочены по значимости, категории или как то ещё. И отдельное преобразование не даст результата, так как в одном признаке будут разные ничем не связанные тексты.<jupyter_code>data['Combined_news'] = data.filter(regex=('Top.*'))\
    .apply(lambda x: ''.join(x.apply(lambda y: filterNews(y)).values.astype(str)), axis=1)<jupyter_output><empty_output><jupyter_text>Наиболее логичным разбиением для этой задачи будет разбиение по дате: до - обучающая, после - тестовая. Перемешивание может привести к утечке данных, в итоге получив завышенный результат.Треть данных оставим для тестовой выборки<jupyter_code>X_train, X_test, y_train, y_test = train_test_split(data.drop(['Label'],axis=1), data['Label'], test_size=0.33)<jupyter_output><empty_output><jupyter_text>"Мешок слов" следует обучать только на тренировочной выборке, применив преобразование к тестовой, чтобы не заглядывать вперед. И также с TF_IDF.<jupyter_code>count_vect = CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)
count_vect.fit(X_train['Combined_news'])
X_train_text = count_vect.transform(X_train['Combined_news'])
X_test_text = count_vect.transform(X_test['Combined_news'])

tfidf = TfidfTransformer()
tfidf.fit(X_train_text)
X_train_text = tfidf.transform(X_train_text)
X_test_text = tfidf.transform(X_test_text)<jupyter_output><empty_output><jupyter_text>В дальнейшем будем использовать подобное последовательное преобразование:<jupyter_code>pipe_cnt_tfidf = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])<jupyter_output><empty_output><jupyter_text>Стоит заметить, что данный датасет можно рассмативать и как временной ряд, прогнозируя значения последовательно, опираясь на предыдущие. Но основная задачу тут - рассмотреть свять между текстами новостей и их влиянием на индекс ДоуДжонса.  8. Кросс-валидация и настройка гиперпараметров модели Создадим Pipeline, чтобы отдельно преобразовывать в tfidf блоки, предназначенные для обучения и для валидации. Для масштаброввания использую MaxAbsScaler, который специально разработан для преобразования разреженных матриц.Я не удержалась и к логистической регрессии добавила метод опорных векторов и градиентный бустинг (ну как без него), чтобы сравнить результаты. Число фолдов равно 3, это почти минимум, чтобы оценить среднеквадратичное отклонение, а при большем числе фолдов на некоторых алгоритмах ноут перегревается и отключается ¯\\_(ツ)_/¯<jupyter_code>pipe_cnt_tfidf = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])
kf = StratifiedKFold(n_splits=3, shuffle=True, random_state=99)
param_grid = [
    {
        'classifier': [LogisticRegression()],
        'classifier__C': np.logspace(-6,2,12)
    },
    {
        'classifier': [LogisticRegression(solver='newton-cg')],
        'classifier__max_iter': [5,10,50,100]
    },
    {
        'classifier': [LinearSVC()],
        'classifier__C': np.logspace(-6,2,8)
    },
    {
        'classifier': [KNeighborsClassifier()],
        'classifier__n_neighbors': [3,7,15,30]
    }

]

grid = GridSearchCV(pipe_cnt_tfidf, cv=kf, n_jobs=1, param_grid=param_grid, scoring='roc_auc')
grid.fit(X_train['Combined_news'], y_train)
grid.best_score_<jupyter_output><empty_output><jupyter_text>На тренировочной части оценка близка к 1, а на валидационной - 0.5. Это говорит о переобучении модели, даже при очень малых значениях С (см. далее кривые валидации)<jupyter_code># преобразование разреженной матрицы в массив, чтобы его можно было встроить в pipeline
class ToArrayTransformer(BaseEstimator, TransformerMixin):

    def transform(self, X, y=None, **fit_params):
        return X.toarray()

    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
pipe_cnt_tfidf_gb = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('toarray', ToArrayTransformer()),
    #('PCA',PCA(n_components=1000)),
    ('classifier',GradientBoostingClassifier(max_features='log2', subsample=0.9, random_state=99))
])
param_grid = [
    {
        'classifier__n_estimators': [10,100,500,1000]
    },
]

grid = GridSearchCV(pipe_cnt_tfidf_gb, cv=kf, n_jobs=1, param_grid=param_grid, scoring='roc_auc')
grid.fit(X_train['Combined_news'], y_train)
grid.best_score_
grid.cv_results_['mean_test_score']
grid.cv_results_<jupyter_output><empty_output><jupyter_text>И построим сразу для бустринг кривую валидации, чтобы не запускать все заново:<jupyter_code>test_res = np.array([grid.cv_results_['split0_test_score'],grid.cv_results_['split1_test_score'],grid.cv_results_['split2_test_score']]).T;
train_res = np.array([grid.cv_results_['split0_train_score'],grid.cv_results_['split1_train_score'],grid.cv_results_['split2_train_score']]).T;
plot_with_err([10,100,500,1000], test_res, label='training scores')
plot_with_err([10,100,500,1000], train_res, label='validation scores')
plt.xlabel('N'); plt.ylabel('ROC AUC')
#plt.xscale('log')
plt.legend()<jupyter_output><empty_output><jupyter_text>Как видно, результаты неудовлетворительные, сравнимые с константным классификатором, т.к. roc_auc колеблется около 0.5 (+/- 0.03) Хотя на графике бустинга заметна небольшая тенденция по улучшению качества при увеличении числа деревьев.  9. Создание новых признаков и описание этого процесса  9.1. Текст вчерашней новости (Tfidf)В описании данных нет информации о точном времени выхода новости, и можно предположить, что новости, опубликованные вчера после закрытия торгов на бирже, повлияют на курс только сегодня. Также можно предположить, что эффект от новости будет распространяться и на следующий день<jupyter_code>data['Combined_news_prev'] = data['Combined_news'].shift(1)
pipe_cnt_tfidf = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])
kf = StratifiedKFold(n_splits=3, shuffle=True, random_state=99)
param_grid = [
    {
        'classifier': [LogisticRegression()],
        'classifier__C': np.logspace(-6,2,12)
    }]

grid = GridSearchCV(pipe_cnt_tfidf, cv=kf, n_jobs=1, param_grid=param_grid, scoring='roc_auc')
grid.fit(data['Combined_news_prev'][1:], data['Label'][1:])
grid.best_score_<jupyter_output><empty_output><jupyter_text>9.2. Месяц, год, день неделиМесяц, год, день недели уже были подготовлены в пункте 3 (при первичном визуальном анализе)<jupyter_code>data['DayOfMonth'] = data['Date'].apply(lambda x: x.day)
data['WeekOfYear'] = data['Date'].apply(lambda x: x.weekofyear)
data_dates = data[['Date','Year','Month','weekday','Year_Month','DayOfMonth','WeekOfYear']]
data_dates.head()
data_dates = pd.get_dummies(data=data_dates, columns=['Date','Year','Month','weekday','Year_Month','DayOfMonth','WeekOfYear'])
data_dates.shape
kf = StratifiedKFold(n_splits=3, shuffle=True, random_state=99)
gb = GradientBoostingClassifier(max_features='log2', subsample=0.9, random_state=99)
grid = GridSearchCV(gb, cv=kf, param_grid={'n_estimators':[100,500,1000]}, scoring='roc_auc')
grid.fit(data_dates, data['Label'])
grid.best_score_
kf = StratifiedKFold(n_splits=3, shuffle=True, random_state=99)
lr = LogisticRegression()
grid = GridSearchCV(lr, cv=kf, param_grid={'C':np.logspace(-6,2,10)}, scoring='roc_auc')
grid.fit(data_dates, data['Label'])
grid.best_score_<jupyter_output><empty_output><jupyter_text>Результаты тоже плохи. Смысла добавлять эти признаки нет. Вероятно, может улучшить ситуацию смысловая обработка текстов, разделение новостей на категории и отбор и использование только тех новостей, которые действительно как-то влияют на биржевой индекс. А также выделение сущностей: объекты, действия. Но это тоько перспектива и не входит в данную работу,   10. Построение кривых валидации и обучения<jupyter_code>def plot_with_err(x, data, **kwargs): # функция взята из лекции на Хабре
    mu, std = data.mean(1), data.std(1)
    lines = plt.plot(x, mu, '-', **kwargs)
    plt.fill_between(x, mu - std, mu + std, edgecolor='none',
    facecolor=lines[0].get_color(), alpha=0.2)<jupyter_output><empty_output><jupyter_text>Кривая валидации для логистической регрессии:<jupyter_code>pipe_cnt_tfidf_lr = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])
C_range = np.logspace(-7,5,10)
val_train, val_test = validation_curve(estimator=pipe_cnt_tfidf, 
                                       X=X_train['Combined_news'], 
                                       y=y_train, 
                                       param_name='classifier__C', 
                                       param_range=C_range, 
                                       cv=3, 
                                       scoring='roc_auc')


plot_with_err(C_range, val_train, label='training scores')
plot_with_err(C_range, val_test, label='validation scores')
plt.xlabel(r'$\alpha$'); plt.ylabel('ROC AUC')
plt.xscale('log')
plt.legend()<jupyter_output><empty_output><jupyter_text>Для метода опорных векторов:<jupyter_code>pipe_cnt_tfidf = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('classifier', SVC())
])
С_range = np.logspace(-6, 5, 10)
val_train, val_test = validation_curve(estimator=pipe_cnt_tfidf, 
                                       X=X_train['Combined_news'], 
                                       y=y_train, 
                                       param_name='classifier__C', 
                                       param_range=С_range, 
                                       cv=3, 
                                       scoring='roc_auc')
plt.subplot(122)
plot_with_err(С_range, val_train, label='training scores')
plot_with_err(С_range, val_test, label='validation scores')
plt.xlabel('C'); plt.ylabel('ROC AUC')
plt.xscale('log')
plt.legend()

gamma_range = np.logspace(-6, -1, 5)
val_train, val_test = validation_curve(estimator=pipe_cnt_tfidf, 
                                       X=X_train['Combined_news'], 
                                       y=y_train, 
                                       param_name='classifier__gamma', 
                                       param_range=gamma_range, 
                                       cv=3, 
                                       scoring='roc_auc')


plt.subplot(121)
plot_with_err(gamma_range, val_train, label='training scores')
plot_with_err(gamma_range, val_test, label='validation scores')
plt.xlabel(r'$\gamma$'); plt.ylabel('ROC AUC')
plt.legend()<jupyter_output><empty_output><jupyter_text>Для градиентного бустинга - выполнена выше.  Кривая обучения:<jupyter_code>pipe_cnt_tfidf_lr = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])

train_sizes, train_scores, valid_scores = learning_curve(estimator=pipe_cnt_tfidf_lr, 
                                                         X=X_train['Combined_news'], 
                                                         y=y_train, 
                                                         cv=3, 
                                                         scoring='roc_auc', 
                                                         train_sizes=np.linspace(0.1, 1.0, 5))

plt.figure()

plt.xlabel("Training examples")
plt.ylabel("Score")
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
valid_scores_mean = np.mean(valid_scores, axis=1)
valid_scores_std = np.std(valid_scores, axis=1)
plt.grid()

plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                 train_scores_mean + train_scores_std, alpha=0.1,
                 color="r")
plt.fill_between(train_sizes, valid_scores_mean - valid_scores_std,
                 valid_scores_mean + valid_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o-', color="r",
         label="Training score")
plt.plot(train_sizes, valid_scores_mean, 'o-', color="g",
         label="Cross-validation score")

plt.legend(loc="best")
plt.show()<jupyter_output><empty_output><jupyter_text>Результаты одинаково плохи, как при малом числе примеров, так и при большом.   11. Прогноз для тестовой или отложенной выборки<jupyter_code>pipe_cnt_tfidf_lr = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=2)),
    ('tfidf', TfidfTransformer()),
    ('scaler', MaxAbsScaler()),
    ('classifier',LogisticRegression())
])

pipe_cnt_tfidf_lr.fit(X_train['Combined_news'], y_train)
pred = pipe_cnt_tfidf_lr.predict_proba(X_test['Combined_news'])
roc_auc_score(y_test, pred[:,1])
pipe_cnt_tfidf_gb = Pipeline([
    ('count', CountVectorizer(ngram_range=(1,2), stop_words='english', min_df=3)),
    ('tfidf', TfidfTransformer()),
    ('toarray', ToArrayTransformer()),
    ('classifier',GradientBoostingClassifier(n_estimators=1200, max_features='log2', subsample=0.9, random_state=99))
])

pipe_cnt_tfidf_gb.fit(X_train['Combined_news'], y_train)
pred = pipe_cnt_tfidf_gb.predict_proba(X_test['Combined_news'])
roc_auc_score(y_test, pred[:,1])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11447106473536074, 'fraction_numerical': 0.014553442712981396, 'mean_word_length': 5.212153518123667, 'pattern_counts': {'":': 0, '<': 151, '<?xml version=': 0, '>': 151, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 29, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '37', 'dir': 'jupyter-structured-clean-dedup', 'id': '268817', 'license': 'Unlicense', 'n_tokens_mistral': 11524, 'n_tokens_neox': 12169, 'n_words': 2318, 'path': 'jupyter_russian/projects_individual/project_Dow_Jones_daily_news_malugina.ipynb', 'repo_name': 'salman394/AI-ml--course', 'size': '29146'}