通过定时数据和交叉验证避免数据泄漏

Posted

技术标签:

【中文标题】通过定时数据和交叉验证避免数据泄漏【英文标题】:avoiding data leakage with timed data and cross validation 【发布时间】:2019-12-24 01:04:57 【问题描述】:

我正在使用Kobe Bryant Dataset。 我希望用KnnRegressor 预测shot_made_flag。 我试图通过按seasonyearmonth 对数据进行分组来避免数据泄漏。 season 是预先存在的列,yearmonth 是我添加的列,如下所示:

kobe_data_encoded['year'] = kobe_data_encoded['game_date'].apply(lambda x: int(re.compile('(\d4)').findall(x)[0]))
kobe_data_encoded['month'] = kobe_data_encoded['game_date'].apply(lambda x: int(re.compile('-(\d+)-').findall(x)[0]))

这是我的功能预处理代码的完整代码:

import re
# drop unnecesarry columns
kobe_data_encoded = kobe_data.drop(columns=['game_event_id', 'game_id', 'lat', 'lon', 'team_id', 'team_name', 'matchup', 'shot_id'])

# use HotEncoding for action_type, combined_shot_type, shot_zone_area, shot_zone_basic, opponent
kobe_data_encoded = pd.get_dummies(kobe_data_encoded, prefix_sep="_", columns=['action_type'])
kobe_data_encoded = pd.get_dummies(kobe_data_encoded, prefix_sep="_", columns=['combined_shot_type'])
kobe_data_encoded = pd.get_dummies(kobe_data_encoded, prefix_sep="_", columns=['shot_zone_area'])
kobe_data_encoded = pd.get_dummies(kobe_data_encoded, prefix_sep="_", columns=['shot_zone_basic'])
kobe_data_encoded = pd.get_dummies(kobe_data_encoded, prefix_sep="_", columns=['opponent'])

# covert season to years
kobe_data_encoded['season'] = kobe_data_encoded['season'].apply(lambda x: int(re.compile('(\d+)-').findall(x)[0]))

# covert shot_type to numeric representation
kobe_data_encoded['shot_type'] = kobe_data_encoded['shot_type'].apply(lambda x: int(re.compile('(\d)PT').findall(x)[0]))

# add year and month using game_date
kobe_data_encoded['year'] = kobe_data_encoded['game_date'].apply(lambda x: int(re.compile('(\d4)').findall(x)[0]))
kobe_data_encoded['month'] = kobe_data_encoded['game_date'].apply(lambda x: int(re.compile('-(\d+)-').findall(x)[0]))
kobe_data_encoded = kobe_data_encoded.drop(columns=['game_date'])

# covert shot_type to numeric representation
kobe_data_encoded.loc[kobe_data_encoded['shot_zone_range'] == 'Back Court Shot', 'shot_zone_range'] = 4
kobe_data_encoded.loc[kobe_data_encoded['shot_zone_range'] == '24+ ft.', 'shot_zone_range'] = 3
kobe_data_encoded.loc[kobe_data_encoded['shot_zone_range'] == '16-24 ft.', 'shot_zone_range'] = 2
kobe_data_encoded.loc[kobe_data_encoded['shot_zone_range'] == '8-16 ft.', 'shot_zone_range'] = 1
kobe_data_encoded.loc[kobe_data_encoded['shot_zone_range'] == 'Less Than 8 ft.', 'shot_zone_range'] = 0

# transform game_date to date time object
# kobe_data_encoded['game_date'] = pd.to_numeric(kobe_data_encoded['game_date'].str.replace('-',''))

kobe_data_encoded.head()

然后我使用MinMaxScaler 缩放数据:

# scaling
min_max_scaler = preprocessing.MinMaxScaler()
scaled_features_df = kobe_data_encoded.copy()
column_names = ['loc_x', 'loc_y', 'minutes_remaining', 'period',
                'seconds_remaining', 'shot_distance', 'shot_type', 'shot_zone_range']
scaled_features = min_max_scaler.fit_transform(scaled_features_df[column_names])
scaled_features_df[column_names] = scaled_features

并按上述seasonyearmonth 分组:

seasons_date = scaled_features_df.groupby(['season', 'year', 'month'])

我的任务是使用KFold 使用roc_auc 分数找到最佳 K。 这是我的实现:

neighbors = [x for x in range(1,50) if x % 2 != 0]
cv_scores = []
for k in neighbors:
    print('k: ', k)
    knn = KNeighborsClassifier(n_neighbors=k, n_jobs=-1)
    scores = []
    accumelated_X = pd.DataFrame()
    accumelated_y = pd.Series()
    for group_name, group in seasons_date:
        print(group_name)
        group = group.drop(columns=['season', 'year', 'month'])
        not_classified_df = group[group['shot_made_flag'].isnull()]
        classified_df = group[group['shot_made_flag'].notnull()]

        X = classified_df.drop(columns=['shot_made_flag'])
        y = classified_df['shot_made_flag']
        accumelated_X = pd.concat([accumelated_X, X])
        accumelated_y = pd.concat([accumelated_y, y])
        cv = StratifiedKFold(n_splits=10, shuffle=True)
        scores.append(cross_val_score(knn, accumelated_X, accumelated_y, cv=cv, scoring='roc_auc'))
    cv_scores.append(scores.mean())

#graphical view
#misclassification error
MSE = [1-x for x in cv_scores]
#optimal K
optimal_k_index = MSE.index(min(MSE))
optimal_k = neighbors[optimal_k_index]
print(optimal_k)
# plot misclassification error vs k
plt.plot(neighbors, MSE)
plt.xlabel('Number of Neighbors K')
plt.ylabel('Misclassification Error')
plt.show()

我不确定在这种情况下我是否正确处理了数据泄漏 因为如果我正在积累上一季的数据,然后将其传递给cross_val_score,我可能会遇到数据泄漏,因为 cv 可以按照它所适应的新赛季数据和上个赛季的数据经过测试,我在吗? 如果是这样,我想知道如何处理这种情况,我想使用K-Fold 使用这个定时数据找到最好的k 而不会出现数据泄漏。 使用K-Fold分割数据而不是按游戏日期分割以避免数据泄漏是否明智?

【问题讨论】:

【参考方案1】:

简而言之,当你想做一些听起来像时间序列的事情时,你不能使用标准的 k-fold 交叉验证。

你会使用未来的一些数据来预测过去,这是被禁止的。

您可以在这里找到一个好方法:https://stats.stackexchange.com/questions/14099/using-k-fold-cross-validation-for-time-series-model-selection

fold 1 : training [1], test [2]
fold 2 : training [1 2], test [3]
fold 3 : training [1 2 3], test [4]
fold 4 : training [1 2 3 4], test [5]
fold 5 : training [1 2 3 4 5], test [6]

这些数字按数据时间的时间顺序排列

【讨论】:

以上是关于通过定时数据和交叉验证避免数据泄漏的主要内容,如果未能解决你的问题,请参考以下文章

使用带有管道和 GridSearch 的 cross_val_score 进行嵌套交叉验证

交叉验证和模型选择

Python 中的逻辑回归和交叉验证(使用 sklearn)

神经网络中啥是交叉验证,为啥要进行交叉验证?

libsvm交叉验证与网格搜索(参数选择)

交叉验证(Cross Validation)比较