使用深度學(xué)習(xí)來預(yù)測NBA比賽結(jié)果
這篇文章,我們來使用深度學(xué)習(xí)來預(yù)測 NBA 比賽結(jié)果。通過本文,我們可以學(xué)習(xí)到:
- 如何爬取 NBA 技術(shù)統(tǒng)計數(shù)據(jù);
- 如何預(yù)處理數(shù)據(jù);
- 如何搭建簡單的深度網(wǎng)絡(luò)模型;
- 如何預(yù)測比賽結(jié)果。
最終我們得到一個預(yù)測第二天比賽準確率 100% 的模型。
技術(shù)統(tǒng)計數(shù)據(jù)收集
要用深度學(xué)習(xí)來預(yù)測比賽結(jié)果,需要有大量技術(shù)統(tǒng)計數(shù)據(jù)作為學(xué)習(xí)樣本。
來看下官方的技術(shù)統(tǒng)計網(wǎng)站:http://stats.nba.com/schedule
打開瀏覽器的開發(fā)者工具,點擊每場比賽右邊的 BOX SCORE,我們就能看到會請求這樣的一個 json 文件:
具體到我們要找的數(shù)據(jù)統(tǒng)計,是這個 json 里面的 hls (主隊數(shù)據(jù)) 和 vls (客隊數(shù)據(jù)):
url 是這種格式:
https://data.nba.com/data/10s/v2015/json/mobile_teams/nba/2017/scores/gamedetail/0021700228_gamedetail.json
多嘗試幾次就可以發(fā)現(xiàn)規(guī)律:
- https://data.nba.com/data/10s/v2015/json/mobile_teams/nba/ 這個是固定的;
- 2017 是賽季開始年份,比如上賽季則是 2016;
- /scores/gamedetail/ 和 ***的 _gamedetail.json 也是固定的;
- 0021700228 則是比賽的 id,規(guī)律為 002 是規(guī)定的,17 則是賽季開始年份的后兩位,如上賽季是 16;00228 則是 5 位的數(shù)字,從 1 開始,不足補零,比如該賽季***場是 00001,而 00228 就是第 228 場比賽;
- 抓到的 url 是 https,其實 http 也是支持的,抓取時比 https 快點。
收集腳本比較簡單,就是循環(huán)獲取,然后存 redis。
對于我們要用來跑訓(xùn)練的數(shù)據(jù),需要整理成 主隊數(shù)據(jù) - 客隊數(shù)據(jù)的方式,并增加一個 win or lose 的 label (籃球比賽沒有平局)。
- 127.0.0.1:6379> HGET gamedetaildiff 0021700228_gamedetail.json
- "{u'ast': 2, 'win': 1.0, u'fbptsa': 6, u'tf': 1, u'bpts': -4, 'away': u'LAC', u'pip': -2, 'home': u'CHA', u'dreb': 4, u'fga': 4, u'tmtov': 0, u'scp': 14, 'date': u'2017-11-19', u'fbptsm': 5, u'tpa': -3, u'fgm': 1, u'stl': 2, u'fbpts': 10, u'ble': 13, u'tov': -6, u'oreb': 1, u'potov': 16, u'fta': 10, u'pipm': -1, u'pf': -6, u'tmreb': -2, u'blk': 3, u'reb': 5, u'pipa': -4, u'ftm': 10, u'tpm': 3}"
***一共收集了,2015、2016、2017 至 2017-11-19 三個賽季的有效數(shù)據(jù)共 2699 條。
數(shù)據(jù)預(yù)處理
我們用 Pandas 來做數(shù)據(jù)處理,非常方便。
先直接從 redis 里讀入數(shù)據(jù):
- import pandas as pd
- import redis
- import ast
- cli = redis.Redis()
- data = cli.hgetall("gamedetaildiff")
- df = pd.DataFrame([ast.literal_eval(data[k]) for k in data])
- df = df.fillna(value=0.0) # 用 0 填補空白數(shù)據(jù)
- df.head()
輸入數(shù)據(jù)去掉無關(guān)項,整理成訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù):
- dataX = df.drop(["win", "date", "home", "away"], axis=1)
- dataY = df["win"]
- train_x = np.array(dataX)[::2] # train set
- train_y = np.array(dataY)[::2]
- test_x = np.array(dataX)[1::2] # test set
- test_y = np.array(dataY)[1::2]
處理后的數(shù)據(jù)維度:
搭建深度網(wǎng)絡(luò)
這部分其實反而是這篇文章中最簡單的部分,因為我們有 Keras:
- from keras.models import Sequential
- from keras.layers.core import Dense
- model = Sequential()
- model.add(Dense(60, input_dim=train_x.shape[1], activation='relu'))
- model.add(Dense(30, activation='relu'))
- model.add(Dense(1, activation='sigmoid'))
- model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
最簡單的三層全連接層網(wǎng)絡(luò)。
因為網(wǎng)絡(luò)的輸出維度是 1,所以***一層的激活函數(shù)是 sigmoid,損失函數(shù)為 binary_crossentropy。
模型訓(xùn)練以及驗證
可以看到 10 個 epochs 之后,模型對于訓(xùn)練數(shù)據(jù)的準確度已經(jīng)達到了 98.89%
再使用測試數(shù)據(jù)對該模型進行驗證:
訓(xùn)練數(shù)據(jù)的準確度也達到了 95.40%,說明這個模型還是比較靠譜的。雖然訓(xùn)練花不了幾秒鐘,但我們還是保存下吧:
- model.save("nba-model.hdf5")
新數(shù)據(jù)的預(yù)測
我們有模型可以來預(yù)測比賽結(jié)果了?,F(xiàn)在我們的問題就在于如何模擬對陣雙方的技術(shù)統(tǒng)計了。
我們用主隊上五場主場技術(shù)統(tǒng)計均值,和客隊上五場客場技術(shù)統(tǒng)計均值,兩者相減作為模型的預(yù)測輸入。
先從 redis 獲取下完整的數(shù)據(jù):
- game_detail_data = cli.hgetall("gamedetail")
- game_detail_json = []
- for k in game_detail_data:
- di_v = {}
- di_h = {}
- j = json.loads(game_detail_data[k])
- vls = j["g"]["vls"]
- hls = j["g"]["hls"]
- di_v.update(vls["tstsg"])
- di_v.update({"date": j["g"]["gdtutc"], "name": vls["ta"], "home": 0})
- game_detail_json.append(di_v)
- di_h.update(hls["tstsg"])
- di_h.update({"date": j["g"]["gdtutc"], "name": hls["ta"], "home": 1})
- game_detail_json.append(di_h)
- game_detail_df = pd.DataFrame(game_detail_json)
- game_detail_df = game_detail_df.fillna(value=0.0)
用 Pandas 可以一行代碼實現(xiàn) 找到主隊上五場主場數(shù)據(jù)均值 的功能:
- def predict(home=None, away=None):
- home_data = game_detail_df[(game_detail_df['name']==home) & (game_detail_df['home']==1)].sort_values(by='date', ascending=False)[:5].mean()
- away_data = game_detail_df[(game_detail_df['name']==away) & (game_detail_df['home']==0)].sort_values(by='date', ascending=False)[:5].mean()
- home_data = home_data.drop(['home'])
- away_data = away_data.drop(['home'])
- new_x = np.array(home_data - away_data)
- return model.predict_classes(new_x[np.newaxis,:], verbose=0)[0][0]
預(yù)測效果
數(shù)據(jù)只收集到美國時間 2017-11-19:
我們來看下 2017-11-20 那天的比賽結(jié)果:
跑下我們模型的預(yù)測結(jié)果:
11 場全部正確,amazing !!