Kobe 投篮预测比赛解题报告

题目数据分析

这道Kaggle题目还是蛮与时俱进的,科比刚刚退役,这道题目就上线了。这道题目主要是把科比在役这么多年以来的比赛投篮数据,主要是NBA的,拿过来,每一项数据就是一次投蓝,当然包括中或者不中,主要是预测科比这次投篮到底是中还是不中。中就是1,不中就是0。每个数据包含了科比的投篮方式,是在什么时间的什么比赛,是不是季后赛,以及投球时所剩余的时间等等信息。从这么多次投篮中,挖去了一些投球结果,比赛的目的就是让参赛选手通过分析没有挖去的训练数据中进行学习和挖掘,然后预测挖去的投球结果到底是中了还是没中。

所有的代码我已经放的我的Github上面。

首先,根据shot_made_flag来分数据集和训练集,不为空的数据为训练集,为空的数据为测试集。

在本文中,我使用的编程语言是Python3.4,使用的包有numpy,pandas,scikit-learn以及Keras

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def load_data():
if sample:
data = pd.read_csv("./data/data_min.csv")
else:
data = pd.read_csv("./data/data.csv")

print(data['shot_distance'].max())

train=data[data[target].notnull()].reset_index(drop=True)
test=data[data[target].isnull()].reset_index(drop=True)

print(train.info())
print(test.info())

return data,train,test

输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
79
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 25697 entries, 0 to 25696
Data columns (total 25 columns):
action_type 25697 non-null object
combined_shot_type 25697 non-null object
game_event_id 25697 non-null int64
game_id 25697 non-null int64
lat 25697 non-null float64
loc_x 25697 non-null int64
loc_y 25697 non-null int64
lon 25697 non-null float64
minutes_remaining 25697 non-null int64
period 25697 non-null int64
playoffs 25697 non-null int64
season 25697 non-null object
seconds_remaining 25697 non-null int64
shot_distance 25697 non-null int64
shot_made_flag 25697 non-null float64
shot_type 25697 non-null object
shot_zone_area 25697 non-null object
shot_zone_basic 25697 non-null object
shot_zone_range 25697 non-null object
team_id 25697 non-null int64
team_name 25697 non-null object
game_date 25697 non-null object
matchup 25697 non-null object
opponent 25697 non-null object
shot_id 25697 non-null int64
dtypes: float64(3), int64(11), object(11)
memory usage: 4.9+ MB
None
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5000 entries, 0 to 4999
Data columns (total 25 columns):
action_type 5000 non-null object
combined_shot_type 5000 non-null object
game_event_id 5000 non-null int64
game_id 5000 non-null int64
lat 5000 non-null float64
loc_x 5000 non-null int64
loc_y 5000 non-null int64
lon 5000 non-null float64
minutes_remaining 5000 non-null int64
period 5000 non-null int64
playoffs 5000 non-null int64
season 5000 non-null object
seconds_remaining 5000 non-null int64
shot_distance 5000 non-null int64
shot_made_flag 0 non-null float64
shot_type 5000 non-null object
shot_zone_area 5000 non-null object
shot_zone_basic 5000 non-null object
shot_zone_range 5000 non-null object
team_id 5000 non-null int64
team_name 5000 non-null object
game_date 5000 non-null object
matchup 5000 non-null object
opponent 5000 non-null object
shot_id 5000 non-null int64
dtypes: float64(3), int64(11), object(11)
memory usage: 976.6+ KB
None

从上表可以看出,数据比较完整,不需要做过多的清洗和整理,因此,我没有去做过多的数据预处理。

最后的数据提交的是判断科比投篮投中的概率,0~1之间的一个数值,使用了logLoss的进行评估。所以,提交的时候尽量交概率值,而不是分类值

特征构造

接下来就是数据的特征抽取和构造,这里,主要是对数据的提取和凝练,有些数据还是需要进行一些处理,比如将类别数据转换成哑变量,将数据归一化等等。

首先是对特征的构造,这里主要构造了时间相关的数据。

1
2
3
4
5
6
7
8
9
for data in [train,test]:
data['month']=data['game_date'].apply(lambda x: get_date(x,'month'))
data['year']=data['game_date'].apply(lambda x: get_date(x,'year'))
data['day']=data['game_date'].apply(lambda x: get_date(x,'day'))
data['range']=data['shot_zone_range'].map({'16-24 ft.':20, '8-16 ft.':12, 'Less Than 8 ft.':4, '24+ ft.':28, 'Back Court Shot':36})
data['season']=data['season'].apply(lambda x: int(x.split('-')[1]))
data['is_host']=data['matchup'].apply(lambda x: 1 if '@' in x else 0)
data['shot_type']=data['shot_type'].apply(lambda x: 1 if '2' in x else 0)
data['is_addTime']=data['period'].apply(lambda x:1 if x>4 else 0)

上述代码主要对时间数据进行了解析,然后提取了赛季数据,主客场,是否是2分投球,是否是加时赛等等。

另外,对多个类别的数据进行哑变量处理。

1
2
3
4
5
6
7
8
9
dummy_variables=['action_type','combined_shot_type','shot_zone_area','shot_zone_basic','opponent']
for var in dummy_variables:
encoded = pd.get_dummies(pd.concat([train,test], axis=0)[var],prefix=var)
train_rows = train.shape[0]
train_encoded = encoded.iloc[:train_rows, :]
test_encoded = encoded.iloc[train_rows:, :]
features+=encoded.columns.tolist()
train = pd.concat([train, train_encoded], axis=1)
test = pd.concat([test,test_encoded],axis=1)

也有一种方法是对这些类别的数据经行一个有序的编号,比如从0~n,但是这样的编号会给后面的分类过程引入多余的信息,也就是给他们一个顺序的信息,而大多数情况下,这些类别数据是没有顺序关系的。我之前使用的是编号方法,结果是0.607,而使用哑变量的结果大大提高,结果到达了0.60061。看来对于这种类别型的数据,使用哑变量还是有效果的。

然后加了一些原始的特征,如下:

1
features+=['loc_x','loc_y','period','minutes_remaining','seconds_remaining','shot_distance','month','year','day','season','range','is_host','shot_type','is_addTime']

接下来是数据的标准化或者归一化,我使用sklearn中的sklearn.preprocessing.StandardScalersklearn.preprocessing.MinMaxScaler两个不同的函数,发现这连个函数对结果的影响不大,最终选择了归一化到0~1之间。

使用模型

这是一道明显的二分类问题,一般使用分类算法来进行计算,我选择使用的算法有xgboostneural networks等等

xgboost

现在我感觉基本上xgboost都是数据竞赛的首选分类算法。这是文档。可以使用xgb.cv来进行交叉验证和调参,也可以添加watchlist来进行调参。调参的方法可以人工调,也可以通过使用网格搜索或者随机搜索来进行参数搜索。

我是用了以下的参数进行了计算,如果需要复现,可能需要根据具体情况进行调参。

1
2
3
4
params = {'max_depth':8, 'eta':0.02,'silent':1,
'objective':'binary:logistic', 'eval_metric': 'logloss',
'min_child_weight':3, 'subsample':0.5,'colsample_bytree':0.5, 'nthread':4}
num_rounds = 290

然后开始训练模型

1
2
3
watchlist = [(xgbdev,'eval'), (xgbtrain,'train')]
evals_result = {}
classifier = xgb.train(params, xgbtrain, num_rounds, watchlist)

最后进行测试和输出
当然,我也是用了其他的模型方法

neural networks

我主要使用了Keras来实现神经网络,Keras是一个python的神经网络库,它的backend可以选择为TheanoTensorFlow,最近出的functional api感觉非常不错。以下是我的keras的代码,我尝试着使用了1~2层隐藏层,隐藏层的节点数选择了50100等参数。

1
2
3
4
5
6
7
8
9
10
11
features_cnt = len(features)
inputs = Input(shape=(features_cnt,))
dense1 = Dense(100, activation='relu')(inputs)
dropout1 = Dropout(0.5)(dense1)
outputs = Dense(1, activation='sigmoid')(dropout1)
model = Model(input=inputs, output=outputs)
model.compile(loss='binary_crossentropy',
optimizer='sgd', metrics=['accuracy'])
print("Start Training", time.ctime())
model.fit(train[features].values, train[target].values,
batch_size=12, nb_epoch=100, validation_split=0.25)

组合模型

在这里,我只是用了bagging的方法,我用之前的模型获得了5个结果。结果列表如下

1
files=['xgboost-feature-submit_best.csv','xgboost-dummy-feature-best.csv','nn-feature-submit.csv','nn-tanh-feature-submit.csv','xgboost-linear-feature-submit.csv']

这五个模型分别是xgboost调参后最好的模型(没有将类别变量变为哑变量),变为哑变量后的最好结果,神经网络的结果(使用relu激活函数),使用tanh激活函数的结果,使用线性方法的xgboost结果

其权重以及Public Leaderboard结果:

  • weight=[0.15,0.6,0.1,0.15,0.1] 结果 0.62815
  • weight=[0.05,0.7,0.1,0.05,0.1] 结果 0.60009
  • weight=[0.05,0.75,0.1,0.05,0.05] 结果 0.59987
  • weight=[0.05,0.8,0.05,0.05,0.05] 结果 0.60008
  • weight=[0.04,0.73,0.15,0.04,0.04] 结果 0.59970
  • weight=[0.04,0.7,0.18,0.04,0.04] 结果 0.59962
  • weight=[0.04,0.6,0.28,0.04,0.04] 结果 0.59952
  • weight=[0.05,0.55,0.30,0.05,0] 结果 0.59959
  • weight=[0.01,0.55,0.36,0.04,0.04] 结果 0.59953
  • weight=[0.01,0.55,0.38,0.03,0.03] 结果 0.59951

结论,bagging的方法还是有一定作用的,毕竟单个模型还没有低于0.6

解题心得

学到的知识

  • 对于pandas的操作更熟练了
  • 对于哑变量有了更深刻的理解
  • 对于bagging的方法有了更深入的了解,bagging的确可以提高准确率

不足

  • 前期对于数据的分析还不够,感觉就是搬个模型在套,如果能够分析好数据,解题的过程会更加地有方向
  • 没有考虑其他集成学习的算法
  • 没有考虑其他的算法,如KNNSVM
文章作者: Victor Zhang
文章链接: http://cupdish.com/2016/06/15/kobe/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Cupdish.com