Применение передового машинного обучения к ценам на сырьевые товары.

Будучи студентом MOOC¹ «Машинное обучение для программистов» fast.ai и интересовавшимся сельским хозяйством, первое, что пришло на ум, было предсказание цен на сою на основе исторических данных. Соевые бобы являются глобальным товаром, и их цена за бушель сильно изменилась за последнее десятилетие.

Истории цен на отдельные сырьевые товары доступны в виде простых структурированных табличных данных, доступных в Интернете бесплатно, что делает эту тему простой для начала. Вот код - обратите внимание, что мы используем Python 3 и fast.ai 0.7, поэтому следуйте инструкциям по установке².

Во-первых, нам нужно импортировать наши пакеты, прежде чем мы начнем: fast.ai, quandl, pandas, sklearn - обычные библиотеки науки о данных.

%load_ext autoreload
%autoreload 2
%matplotlib inline
import quandl
from fastai.imports import *
from fastai.structured import *
from pandas_summary import DataFrameSummary
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from IPython.display import display
from sklearn import metrics

Данные взяты из бесплатного набора данных Quandl TFGRAIN / SOYBEANS. У Quandl есть простой Python SDK для своего API, инструкции по началу работы. ³ Вытягивание всего набора данных - это одна (короткая) строка:

data = quandl.get("TFGRAIN/SOYBEANS", authtoken="<your token")

Это возвращает фрейм данных Pandas (который мы называем «данными»), аdata.info() показывает, что существует 4535 строк данных, проиндексированных по событию Datetime. Мы можем использовать data.tail(), чтобы показать формат:

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4535 entries, 2000-12-01 to 2019-01-14
Data columns (total 4 columns):
Cash Price    4535 non-null float64
Basis         4535 non-null float64
Fall Price    4516 non-null float64
Fall Basis    4516 non-null float64
dtypes: float64(4)
memory usage: 177.1 KB

Поскольку у нас есть время, это отличный шанс воспользоваться возможностями разработки функций обработки дат библиотеки fast.ai. Я переименовал индекс в «данные» и создал новый столбец с теми же значениями, которые могут обрабатываться функцией add_datepart():

data.rename_axis('Date')
data['Date'] = data.index
add_datepart(data, 'Date')
data.info()

Новые столбцы ниже. Джереми Ховард подробно объясняет, как и почему создаются эти новые столбцы, в уроке 1 ML1.

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4535 entries, 2000-12-01 to 2019-01-14
Data columns (total 18 columns):
id                  4535 non-null int64
Cash Price          4535 non-null float64
Basis               4535 non-null float64
Fall Price          4516 non-null float64
Fall Basis          4516 non-null float64
Year                4535 non-null int64
Month               4535 non-null int64
Week                4535 non-null int64
Day                 4535 non-null int64
Dayofweek           4535 non-null int64
Dayofyear           4535 non-null int64
Is_month_end        4535 non-null bool
Is_month_start      4535 non-null bool
Is_quarter_end      4535 non-null bool
Is_quarter_start    4535 non-null bool
Is_year_end         4535 non-null bool
Is_year_start       4535 non-null bool
Elapsed             4535 non-null int64
dtypes: bool(6), float64(4), int64(8)

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

col_list = ['Cash Price', 'Basis', 'Year', 'Month', 'Week', 'Day', 'Dayofweek', 'Dayofyear']
dfdata = dfdata[col_list]

И чтобы очистить форматирование, я беру значения из фрейма данных Pandas и повторно импортирую:

df = dfdata.values
df = pd.DataFrame.from_records(df)
df.columns = col_list

Теперь, когда у нас снова есть фреймворк Pandas, я использую другую удобную функцию fast.ai, которая (как объясняется в документации) меняет столбцы строк на столбцы категориальных значений и делает это на месте. Почему? Чтобы позволить случайному регрессору леса разбираться в табличных данных. Опять же, это более подробно рассматривается в курсе.

train_cats(df)

Другой препроцессор fast.ai, который (как отмечается в документации) принимает фрейм данных, разделяет переменную ответа и преобразует df в полностью числовой фрейм данных. Наша зависимая («y») переменная - это дневная цена на соевые бобы на этом элеваторе, а все остальное - независимые переменные.

df, y, nas = proc_df(df, 'Cash Price')

И теперь мы готовы подогнать наши данные! Это так просто:

m = RandomForestRegressor(n_jobs=-1)
m.fit(df, y)
m.score(df,y)
Score: 0.9991621993655437

Вполне нормально! Fast.ai оценивает это с точностью 99,91%, поэтому теперь мы можем бросить тестовый фрейм данных (с тем же форматом) в регрессор и получить реальный прогноз. Вот наш однорядный тестовый фрейм данных:

df1 = pd.DataFrame({'Basis':[-.85],
                    'Year':[2019],
                    'Month':[1],
                    'Week':[4],
                    'Day':[25],
                    'Dayofweek':[6],
                    'Dayofyear':[25],})

А для получения единого прогноза цены на 25 января 2019 года из расчета 85 центов:

train_cats(df1)
m.predict(df1) 
Return: array([8.465])

Бушель соевых бобов оценивается в 8,465 долларов.

Ради интереса я нарисовал график того, как изменения в базисе влияют на регрессию для одного дня (25 января 2019 г.). Код:

df1.iloc[0:, 0] = -.95
iterpreds = list()
a = list()
for x in range(0,10):
    df1.iloc[0, 0] += .1
    b = df1.at[0, 'Basis']
    iterpreds.append(m.predict(df1).astype(float).item(0))
    a.append(b)
plt.plot(a, iterpreds)

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

Использованная литература:

[1] https://course.fast.ai/ml.html

[2] https://forums.fast.ai/t/fastai-v0-7-install-issues-thread/24652

[3] https://docs.quandl.com/docs/getting-started

Дополнительный код находится на моем github: www.github.com/matthewarthur, а мой LinkedIn - https://www.linkedin.com/in/matt-a-8208aaa/.