Не отслеживаете ли вы данные по незнанию при обучении моделей машинного обучения?

Почти каждый специалист по машинному обучению понимает принцип отделения данных обучения от данных проверки и тестирования при построении моделей машинного обучения. Полезность модели определяется тем, насколько хорошо модель работает или обобщает данные, на которых модель не была обучена прогнозировать или классифицировать. Большинство платформ машинного обучения предоставляют некоторые утилиты для разделения данных, такие как sklearn.model_selection.train_test_split.

Однако простое разделение данных на подмножества для обучения и тестирования может недостаточно предотвратить влияние данных тестирования или проверки на обучение модели.

Рассмотрим помеченный набор данных X, состоящий из всех выборок данных, доступных для разработки модели, такой как классификатор логистической регрессии. Метки классификации для каждого образца хранятся в отдельном векторе Y. Каждая выборка данных состоит из числовых и категориальных признаков. Мы разделили набор данных X на три подмножества вместе с соответствующими метками:

  1. X_train и y_train (70% от X)
  2. X_validate и y_validate (15% от X)
  3. X_test и y_test (15% от X)

Мы будем использовать X_train для обучения базовой модели, а затем оптимизируем параметры модели с помощью X_validate. Наконец, мы оценим прогностическую силу модели, используя X_test и соответствующие метки y_test.

Некоторый Python-подобный псевдокод:

# Assume X is Panda data frame with all of our data samples
numeric_feature_labels = ['f1', 'f2', 'f3']
categorical_feature_labels = ['f4', 'f5']
# Scale the numeric data by removing the mean and scaling to unit 
# variance
sklearn.preprocessing.StandardScaler(copy=True, with_mean=True, with_std=True)
scaler.fit(X[numeric_feature_labels])
X[numeric_feature_labels]= scaler.transform(X[numeric_feature_labels])
# Split X into 70/30 partitions
X_train, X_remain, y_train, y_remain = train_test_split( X, y, test_size = 0.3)
# Split X_remain into X_validate and X_test
X_validate, X_test, y_validate, y_test = train_test_split(X_remain,     y_remain, test_size=0.5)
# We now have a 70/15/15 split of X data
# Train a Logistic Regression Model
log_reg = LogisticRegression(solver='liblinear')
result = logreg.fit(X_train, y_train)
# Presume we have some custom function to optimize the model
optimized_model = custom_optimizer(log_reg, X_validate, y_validate)
# Finally, use the test data to evaluate the model
y_predict = optimized_model.predict(X_test)
# Compute the accuracy 
prediction_accuracy = sklearn.metrics.accuracy_score(y_test, y_pred)
print("Accuracy: ", prediction_accuracy)
=====
Accuracy = 0.7960

Кажется, все в порядке, верно? К сожалению, приведенный выше фрагмент кода является примером непреднамеренного включения отслеживания данных. ( «5.3 Отслеживание данных». Изучение данных: краткий курс, Ясер С. Абу-Мостафа и др., AMLbook, 2012, стр. 174–175. )Я нашел этот справочный материал после того, как эмпирическим путем обнаружил проблему в своем проекте.

Все доступные данные X были предоставлены стандартному масштабатору, который использовал среднее, максимальное и минимальное значение числовых данных для масштабирования числовых данных по всему набору данных X. Подмножество масштабированных данных (X_train) использовалось для обучения модели. Масштабированные данные включали данные, которые должны были стать частью тестовых данных, используемых для создания прогнозов для оценки. Скейлер «отслеживал» данные, которые не должны быть доступны для обучения. Тестовые данные влияют на среднее, максимальное и минимальное значения, используемые во время операции масштабирования.

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

# Split X into 70/30 partitions
X_train, X_remain, y_train, y_remain = train_test_split( X, y, test_size = 0.3)
# Split X_remain into X_validate and X_test
X_validate, X_test, y_validate, y_test = train_test_split(X_remain,     y_remain, test_size=0.5)
# We now have a 70/15/15 split of X data
# Scale only the training data
sklearn.preprocessing.StandardScaler(copy=True, with_mean=True, with_std=True)
scaler.fit(X_train[numeric_feature_labels])
X_train[numeric_feature_labels]= scaler.transform(X_train[numeric_feature_labels])
# Train a Logistic Regression Model
log_reg = LogisticRegression(solver='liblinear')
result = logreg.fit(X_train, y_train)
# Scale the validation data
scaler.transform(X_validate[numeric_feature_labels])
optimized_model = custom_optimizer(log_reg, X_validate, y_validate)
# Now scale the test data
scaler.transform(X_test[numeric_feature_labels])
# Finally, use the test data to evaluate the model
y_predict = optimized_model.predict(X_test)
# Compute the accuracy 
prediction_accuracy = sklearn.metrics.accuracy_score(y_test, y_pred)
print("Accuracy: ", prediction_accuracy)
=====
Accuracy = 0.7806

В моем реальном сценарии точность модели упала с 79,60% до 78,06%, когда тестовые данные не были включены в масштабирование обучающих данных. Хотя это и разочаровывает, результаты гораздо более тесно связаны с результатами, достигнутыми моделью, с новыми данными, которые были собраны с момента разработки модели.

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