Применение передового машинного обучения к ценам на сырьевые товары.
Будучи студентом 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/.