Создание прогнозов для Numerai с помощью нейронных сетей
Обзор
По сути, идея турнира Numerai состоит в том, чтобы позволить специалистам по обработке данных использовать методы машинного обучения, чтобы зарабатывать деньги, создавая прогнозы для хедж-фонда Numerai.
Методами машинного обучения могут быть нейронные сети, случайные леса, опорные векторные машины и т. Д. В этой статье я остановлюсь на использовании нейронных сетей. Эти нейронные сети будут программироваться и обучаться с использованием Python, Keras и scikit-learn (sklearn).
Помимо создания прогнозов для регулярного турнира, также можно использовать наши прогнозы для участия в турнире со ставками. Мы можем поставить счетчик криптовалюты (NMR) на наши прогнозы, чтобы заработать дополнительные деньги. Однако в этой статье я расскажу только о том, как делать прогнозы на регулярный турнир.
Предварительные условия
В этой статье необходимо установить следующие программы и библиотеки:
- Python 3 (от Anaconda)
- Керас
- scikit-learn
- Панды
- Numpy
- Блокнот Jupyter
Нам также потребуется учетная запись на Numerai.
Изучение наборов данных
После загрузки текущих наборов данных мы можем просмотреть распакованный каталог. Актуальны два файла CSV:
numerai_training_data.csv
numerai_tournament_data.csv
Первый файл содержит данные тренировки, а второй файл данные турнира. Мы можем ввести следующие инструкции в Jupyter Notebook. Или взгляните на мой https://github.com/jfjensen/numerai_article/blob/master/NumeraiArticle.ipynb.
training_data = pd.read_csv('numerai_training_data.csv', header=0) tournament_data = pd.read_csv('numerai_tournament_data.csv', header=0)
Давайте быстро посмотрим на данные в файлах с помощью Pandas.
training_data.head()
Приведенный выше оператор показывает нам первые 5 строк обучающих данных. Здесь мы видим названия столбцов. Обратите внимание на столбцы эпоха и data_type.
print(training_data.era.unique()) print(training_data.data_type.unique())
Используя приведенные выше операторы, мы можем увидеть все уникальные имена для эпох и типов данных в наших данных. В обучающих данных представлено 85 эпох, и все данные действительно являются обучающими данными. Мы можем сделать что-то подобное с данными турнира:
tournament_data.head()
Теперь мы получаем первые 5 строк данных нашего турнира.
print(tournament_data.era.unique()) print(tournament_data.data_type.unique())
Приведенные выше утверждения показывают, что данные турнира содержат более разнообразные типы данных - валидационные, тестовые, живые. Содержащиеся эпохи находятся в диапазоне от 86 до 97 и еще одна, называемая «eraX». Давайте углубимся в это следующим образом:
print(tournament_data.era[tournament_data.data_type=='validation'].unique()) print(tournament_data.era[tournament_data.data_type=='test'].unique()) print(tournament_data.era[tournament_data.data_type=='live'].unique())
Теперь мы видим, что данные турниров типа «validation» содержат эпохи с 86 по 97, а данные типов «test» и «live» содержат эпоху «eraX».
В документации на странице справки Numerai упоминается, что данные проверки содержат цели (как и данные обучения). Давайте проверим это:
print(tournament_data.target[tournament_data.data_type=='validation'].unique()) print(tournament_data.target[tournament_data.data_type=='test'].unique()) print(tournament_data.target[tournament_data.data_type=='live'].unique())
Данные проверки действительно содержат цели [0., 1.], тогда как тестовые и живые данные содержат [nan].
Учитывая, что у нас никогда не может быть слишком много обучающих данных, объединение всех эр в один «полный» обучающий набор - обучающие данные плюс данные проверки - это то, что нужно сделать. Поэтому мы проигнорируем предупреждение Numerai:
Мы рекомендуем вам не тренироваться на данных проверки, даже если у вас есть цели.
Но разве это не вызовет у нас проблем с переобучением? Не обязательно, учитывая, что мы будем использовать перекрестную проверку для проверки производительности наших моделей нейронных сетей.
Давайте создадим этот «полный» обучающий набор:
validation_data = tournament_data[tournament_data.data_type=='validation'] complete_training_data = pd.concat([training_data, validation_data])
И давайте проверим, есть ли у нас правильные эпохи:
complete_training_data.era.unique()
Это дает нам все эпохи от 1 до 97, как и предполагалось.
Теперь мы можем создать наши функции (X) и метки (Y) для обучения нашей нейронной сети:
features = [f for f in list(complete_training_data) if "feature" in f] X = complete_training_data[features] Y = complete_training_data["target"]
Прогнозы с помощью Keras и scikit-learn
Используя библиотеку нейронной сети Keras для Python, мы можем определить простую модель нейронной сети, которую мы можем обучить на нашем полном обучающем наборе. Сначала мы определяем модель нейронной сети в функции:
def create_model(neurons=200, dropout=0.2): model = Sequential() model.add(Dense(neurons, input_shape=(50,), kernel_initializer='glorot_uniform', use_bias=False)) model.add(BatchNormalization()) model.add(Dropout(dropout)) model.add(Activation('relu')) model.add(Dense(1, activation='sigmoid', kernel_initializer='glorot_normal')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['binary_crossentropy', 'accuracy']) return model
Затем мы создаем оболочку для нейронной сети. Это необходимо для создания моста между Keras и scikit-learn. Мы скажем, что он запускается в течение 8 эпох с размером пакета 128. Детализация установлена на 0, потому что нам не нужно видеть, как далеко была обучена сеть.
model = KerasClassifier(build_fn=create_model, epochs=8, batch_size=128, verbose=0)
Теперь из документации Numerai мы можем узнать, что:
Для перекрестной проверки лучше использовать случайную выборку эпох, а не случайную выборку строк. Использование случайной выборки строк имеет тенденцию к переполнению.
Следовательно, мы должны использовать специальный метод перекрестной проверки, называемый group-k-fold. В нашем случае он настроен на k-кратное увеличение по отдельным эпохам, а не по отдельным строкам в полном обучающем наборе. Для этого мы будем использовать класс GroupKFold из scikit-learn. Сначала мы создаем экземпляр этого класса и приказываем ему создать 5 складок. Затем, используя метод разделения, мы говорим объекту разделить обучающие данные на основе эпох.
gkf = GroupKFold(n_splits=5) kfold_split = gkf.split(X, Y, groups=complete_training_data.era)
Используя GridSearchCV из scikit-learn, мы можем найти хорошие гиперпараметры для нашей модели нейронной сети. Здесь мы пытаемся выяснить, что работает лучше всего - 10 нейронов или 14 и вероятность выпадения 0,01 или 0,26. Это дает сетку параметров с 4 комбинациями, которые можно опробовать.
neurons = [10, 14] dropout = [0.01, 0.26] param_grid = dict(neurons=neurons, dropout=dropout)
Создайте экземпляр GridSearchCV с моделью нейронной сети, которую мы определили выше, сеткой параметров с 4 комбинациями, функцией оценки, которая соответствует функции потерь нейронной сети, одним потоком и уровнем подробности 3. Затем мы говорим, чтобы она соответствовала наши данные обучения.
grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=kfold_split, scoring='neg_log_loss',n_jobs=1, verbose=3) grid_result = grid.fit(X.values, Y.values)
Мы можем вывести конечный результат различных прогонов с помощью следующих операторов:
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_)) means = grid_result.cv_results_['mean_test_score'] stds = grid_result.cv_results_['std_test_score'] params = grid_result.cv_results_['params'] for mean, stdev, param in zip(means, stds, params): print("%f (%f) with: %r" % (mean, stdev, param))
Когда мы запускаем метод fit объекта GridSearchCV, нам сообщают следующее:
Примерка 5 складок для каждого из 4 кандидатов, всего 20 подходов
Это означает, что наша нейронная сеть будет обучена 20 раз. Для каждой из 4 комбинаций нашей таблицы параметров будет использоваться 5 различных сверток обучающих данных. Последнее относится к 5-кратной перекрестной проверке.
Да, этот процесс занимает много времени даже с новым высокопроизводительным графическим процессором. На моем ПК с графическим процессором Nvidia GTX 1080 каждая «подгонка» занимает примерно 3,1 минуты, что в сумме дает 64,0 минуты.
Проверка производительности
Теперь мы можем проверить производительность на турнире лучшей модели, найденной объектом GridSearchCV. Давайте рассмотрим пункты, упомянутые в документации Numerai:
Отображаемая таблица лидеров основана только на данных проверки. Чтобы попасть в таблицу лидеров, модели должны иметь согласованность, оригинальность и последовательность.
Чтобы мы могли зарабатывать деньги в обычном турнире, мы должны быть в таблице лидеров. Следовательно, наша модель должна удовлетворять следующим трем критериям - соответствие, оригинальность и последовательность:
Согласованность - это мера того, генерируются ли прогнозы по набору проверки, набору тестов и живому набору одной и той же моделью.
Для нашей модели это не должно быть проблемой.
Оригинальность - это мера того, не коррелирует ли набор прогнозов с уже представленными.
Это самая сложная часть. Если вы подадите заявку в начале раунда, очень высоки шансы, что ваша заявка будет оригинальной. С другой стороны, эта вероятность со временем падает. Вот почему я поставил значения 0,01 и 0,26 вместо 0,0 и 0,25 для показателей отсева в сетке параметров. Скорее всего, у кого-то еще есть эти значения для своей модели. Это увеличивает уникальность модели тура.
Согласованность измеряет процент эпох, в которых модель достигает логопотерь ‹-ln (0,5). … Только модели с согласованностью выше 75% считаются согласованными.
Мы можем проверить соответствие с нашей собственной функцией.
def check_consistency(model, valid_data): eras = valid_data.era.unique() count = 0 count_consistent = 0 for era in eras: count += 1 current_valid_data = valid_data[validation_data.era==era] features = [f for f in list(complete_training_data) if "feature" in f] X_valid = current_valid_data[features] Y_valid = current_valid_data["target"] loss = model.evaluate(X_valid.values, Y_valid.values, batch_size=128, verbose=0)[0] if (loss < -np.log(.5)): consistent = True count_consistent += 1 else: consistent = False print("{}: loss - {} consistent: {}".format(era, loss, consistent)) print ("Consistency: {}".format(count_consistent/count)) check_consistency(grid.best_estimator_.model, validation_data)
Результат проверки согласованности следующий:
Наша модель обеспечивает согласованность выше 75%, поэтому наша заявка соответствует этому критерию.
Отправка прогнозов
Мы будем использовать следующие инструкции для создания прогнозов и записи их в файл CSV.
x_prediction = tournament_data[features] t_id = tournament_data["id"] y_prediction = grid.best_estimator_.model.predict_proba(x_prediction.values, batch_size=128) results = np.reshape(y_prediction,-1) results_df = pd.DataFrame(data={'probability':results}) joined = pd.DataFrame(t_id).join(results_df) # path = "predictions_w_loss_0_" + '{:4.0f}'.format(history.history['loss'][-1]*10000) + ".csv" path = 'predictions_{:}'.format(strftime("%Y-%m-%d_%Hh%Mm%Ss", gmtime())) + '.csv' print() print("Writing predictions to " + path.strip()) # # Save the predictions out to a CSV file joined.to_csv(path,float_format='%.15f', index=False)
Теперь мы можем загрузить CSV-файл в Numerai и посмотреть, какой результат мы получим.
Дальнейшие исследования
Самое очевидное, что можно попробовать сейчас, - это, конечно, изменить списки значений гиперпараметров. Возможно, некоторые другие значения, такие как количество слоев в нейронной сети или тип используемого оптимизатора. Вы можете найти вдохновение для этого здесь: https://machinelearningmastery.com/grid-search-hyperparameters-deep-learning-models-python-keras/
Для перекрестной проверки вы можете попробовать 10-кратную перекрестную проверку вместо 5-кратной перекрестной проверки, которую я показал здесь. Учтите, что на пробег уйдет как минимум вдвое больше времени. Но стоит ли того дополнительное время работы?
Используйте RandomizedSearchCV из библиотеки scikit-learn вместо GridSearchCV. Или напишите свою собственную функцию поиска. Причина, по которой вы захотите это сделать, заключается в том, чтобы вы могли сохранять прогресс в качестве контрольных точек после каждого фолда. Подробнее об этом здесь: https://machinelearningmaster.com/check-point-deep-learning-models-keras/
Попробуйте другие методы машинного обучения, такие как случайные леса или виртуальные машины защиты.
использованная литература
3.1. Перекрестная проверка: оценка производительности оценщика - документация scikit-learn 0.19.1
При оценке различных настроек («гиперпараметров) для оценщиков, таких как настройка, которая должна быть установлена вручную… scikit-learn.org »