Создание прогнозов для 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 »