Найти в Дзене

Модель классификации от дилетанта

Мы продолжаем цикл статей: И в этот раз мы с вами займемся классификацией. Помните, мы как-то обсуждали, что точно спрогнозировать определенное значение почти невозможно, зато можно с определенной вероятностью попасть в диапазон значений. Теперь мы делаем следующий шаг - вместо диапазона значений используем классификацию. Например, прогноз на падение в пределах одного стандартного отклонения можно обозначить цифрой буквой, словом и т.д., таким образом, мы будем прогнозировать какого класса будет следующее значение. В статистике традиционно используется 6-классовое разделение: 0 - вниз более 2 стандартных отклонений, 1 - от -2 до -1 стандартных отклонений, 2 - от -1 стандартного отклонения до 0, 3 - от 0 до 1 стандартного отклонения, 4 - от 1 до 2 стандартных отклонений, 5 - более 2 стандартных отклонений. Что такое стандартное отклонение, как оно считается, как обозначается в статистике и другие вопросы вы можете нати самостоятельно в интернете, потому что ресурсов, в том числе на русс

Мы продолжаем цикл статей:

  1. введение,
  2. получение и обработка данных,
  3. продолжение обработки,
  4. анализ временных рядов,
  5. разбор недостатков решения,
  6. долгосрочный анализ данных,
  7. получение и обработка данных для машинного обучения,
  8. линейная регрессия.

И в этот раз мы с вами займемся классификацией. Помните, мы как-то обсуждали, что точно спрогнозировать определенное значение почти невозможно, зато можно с определенной вероятностью попасть в диапазон значений. Теперь мы делаем следующий шаг - вместо диапазона значений используем классификацию. Например, прогноз на падение в пределах одного стандартного отклонения можно обозначить цифрой буквой, словом и т.д., таким образом, мы будем прогнозировать какого класса будет следующее значение.

В статистике традиционно используется 6-классовое разделение:

0 - вниз более 2 стандартных отклонений,

1 - от -2 до -1 стандартных отклонений,

2 - от -1 стандартного отклонения до 0,

3 - от 0 до 1 стандартного отклонения,

4 - от 1 до 2 стандартных отклонений,

5 - более 2 стандартных отклонений.

Что такое стандартное отклонение, как оно считается, как обозначается в статистике и другие вопросы вы можете нати самостоятельно в интернете, потому что ресурсов, в том числе на русском языке предостаточно. А мы по традиции создаем новый блокнот, подсмотреть мой вариант можно здесь. Первым делом объявляем библиотеки и функции:

import pandas as pd
import pandas_datareader as pdr
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import MinMaxScaler

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

dt_start = '2006-01-10'
df = pdr.get_data_fred(['DTWEXEMEGS', 'DCOILBRENTEU'], start=dt_start)
df.replace(0.0, np.NaN, inplace=True)
df.fillna(method='ffill', axis=0, inplace=True)
df.rename(columns={'DTWEXEMEGS': 'dollar', 'DCOILBRENTEU': 'brent'}, inplace=True)
df.info()

Получаем данные по USD000UTSTOM:

df1 = pdr.get_data_moex('USD000UTSTOM', start=dt_start)
df1 = df1[df1.BOARDID == 'CETS']
df1.drop_duplicates(inplace=True)
df1.replace(0.0, np.NaN, inplace=True)
df1.fillna(method='ffill', axis=0, inplace=True)

Вот теперь начинаются изменения, а именно сформируем новый столбец, который будет отражать относительную разницу между ценой закрытия и открытия:

df1['result'] = (df1.CLOSE - df1.OPEN) / df1.HIGH
df1 = df1[['result']]
v_std = df1.result.std()
df1['cl'] = 0

Почему относительную? Да потому что абсолютная разница сильно меняется в течение времени, по крайней мере, в этом случае. Это будет сильно сказываться на результате. Кроме этого столбца из остальных значений нам больше ничего не потребуется, поэтому оставим в наборе данных только его. Вычислим то самое стандартное отклонение, и добавим новую колонку, заполнив её нулями. Как мы говорили выше, нулю соответствует значения отклоняющиеся вниз более 2 стандартных отклонений. Далее мы будем использовать методику последовательного переписывания значений попадающих в необходимый диапазон:

df1.loc[df1.result > v_std * -2, 'cl'] = 1
df1.loc[df1.result > v_std * -1, 'cl'] = 2
df1.loc[df1.result > 0, 'cl'] = 3
df1.loc[df1.result > v_std, 'cl'] = 4
df1.loc[df1.result > v_std * 2, 'cl'] = 5
df1.cl.value_counts()

Последняя строчка выводит количество значений в каждой категории:

Распределение по категориям
Распределение по категориям

Мы получили нужную колонку заполненную значениями классификации относительного результата дня. Теперь нужно её добавить в наш набор данных:

df = df.join(df1.cl)
df.cl.fillna(method='ffill', axis=0, inplace=True)
df.cl = df.cl.astype('int8')
df.info()

Третьей строчкой мы заменяем тип данных для уменьшения расходования памяти, конкретно в этом случае такой необходимости нет. Далее выполняем уже знакомый нам цикл:

ls_ind = ['IMOEX', 'RGBITR', 'RUCBITR']
for el_ind in ls_ind:
df1 = pdr.get_data_moex(el_ind, start=dt_start)
df1 = df1[['CLOSE']]
df1.drop_duplicates(inplace=True)
df1.rename({'CLOSE': el_ind}, axis=1, inplace=True)
df = df.join(df1)
df.fillna(method='ffill', axis=0, inplace=True)
df.info()

Также знакомая нам процедура выделения целевого значения и выборки данных:

y = df.cl
x = df.drop('cl', axis=1)

Дальше опять знакомая процедура нормализации и выделения тренировочной и тестовой выборки:

scaler = MinMaxScaler()
x_scaled = scaler.fit_transform(x)
x_train, x_test, y_train, y_test = train_test_split(x_scaled, y, test_size=0.1, shuffle=False)

Как видите многие действия очень скучны и однообразны, разобравшись в их назначении, вам придется их в буквальном смысле копипастить. Вот следующий участок кода хоть и отличается от участка с предыдущей статьи, но многие могут даже не увидеть эти отличия:

model = LogisticRegression()
model.fit(x_train, y_train)
model.score(x_train, y_train)

Первая строчка создает модель логистической регрессии, которую можно также назвать моделью классификации. Вторая строчка обучает модель, а третья выдает долю попаданий. С одной стороны мы видим, что доля попаданий ниже 0,5, но надо учитывать, что у нас не бинарная классификация, где попаданий долно быть выше 50%. Проверим на тестовой выборке:

model.score(x_test, y_test)

По сравнению с тренировочной выборкой результат отличный. Проблему с прогнозом решим таким же способом, как и прошлый раз:

y = df.cl[5:]
x = df.drop('cl', axis=1)[:-5]

Заново выполните ячейки с нормализацией, разделением, созданием и тренировкой модели. Сравните долю попаданий, она хоть и уменьшилась но не значительно. Теперь получаем прогноз:

x = df.drop('cl', axis=1)[-5:]
x_scaled = scaler.transform(x)
pred = model.predict(x_scaled)
pred

Попробуйте самостоятельно интерпретировать этот прогноз. На всякий случай, предупреждаю, что данный материал не является инвестиционной рекомендацией, а следовательно использовать его можно только на свой страх и риск.