投注者和博彩者沒有太多共同點——人們可以把他們的關係描述為一場競爭、決鬥、戰爭。但在夢中,他們卻為同樣的幻想而垂涎三尺:一個完美的預測模型,使用它能夠精確地預測出未來遊戲的結果。通過深入學習,這或許是可能的——或者至少比以前的數據科學技術更容易。
基本假設是NBA市場效率低下(價格或投注線並不能反映出所有可用信息),而且可能比大多數市場效率更低,因為鐵桿球迷傾向於只賭他們最喜歡的球隊。如果你能對市場的低效率下賭注,你就能賺錢。我們識別低效率的方法之一是通過數據分析。
儘管許多嘗試這一挑戰的模型都是準確的,但大多數模型離盈利還差得很遠。原因很簡單:博彩公司也非常準確。即使你能達到博彩公司的準確性,你也會因為5%的投注費而失敗。
左邊的圖表是365net的預測線與實際的贏取百分比。一個成功的模型必須能夠通過完美的迴歸分析預測博彩公司的微小波動。
我的模型是用帶有Tensorflow的Python構建的,它分析了過去11個NBA賽季,並且在很多方面與其他的深度學習模型相似(後者經常被試圖用於解決這個問題)。但是我們的模型有一個關鍵的區別——它使用了一個自定義的損失函數來剔除與博彩公司的相關性。我們正在挑選博彩公司錯誤預測獲勝百分比的遊戲。
去相關損失公式-這很重要!!!!!!!
源碼
模型結構
我用nba_api Python庫抓取了得分記錄。數據存儲在MongoDB集合中。在過去的11個賽季中,每名球員每局共存儲42個統計數據,從罰球率到防守得分再到偷球次數不等。下注數據是從betexplorer中收集的。找到高質量的投注線比訓練模型困難得多。你可以向斯洛文尼亞盧布雅那大學的什特魯姆貝爾教授尋求幫助。
對於每一場比賽,樣本是用賽季初每名球員最近8場比賽的平均數計算出來的。根據平均比賽時間選出前8名選手。
模型
預處理
<code>import os import numpy as np from tqdm import tqdm from pymongo import MongoClient bookies = { "44":"Betfair", "16":"bet365", "18":"Pinnacle", "5":"Unibet" } client = MongoClient() db = client.nba games = db.games seasons = [f"002{str(i).zfill(2)}" for i in range(8, 20)] # load the samples into memory x, y = [], [] def normalize_sample(nparr): for feature in range(nparr.shape[-1]): # iterate over the features f = np.nan_to_num(nparr[:, :, feature]) nparr[:, :, feature] = (f-f.min())/(f.max()-f.min()) return nparr for season in seasons: for filename in tqdm(os.listdir(f"samples/{season}")[120:]): if ".npy" in filename: game = list(games.find({"GAME_ID":filename.strip(".npy"), "bet365":{"$exists":"True"}})) if not game: continue game = game[0] closing_odds = 1/float(game["bet365"].split()[1].split("v")[0]) home_win = int(game["HOME"] == game["WINNER"]) sample = np.load(f"samples/{season}/{filename}") x.append((normalize_sample(sample), closing_odds)) y.append(home_win) x = np.array(x) y = np.array(y) import random print(x.shape, y.shape) diff = len(y)//2 - np.count_nonzero(y == 0) for i in tqdm(range(diff)): while True: a = random.randint(1, len(y)-1) if y[a] == 1: x = np.delete(x, a, 0) y = np.delete(y, a, 0) break print(len(x), len(y)) /<code>
模型
<code>from keras import backend as K from keras.models import Model from keras.models import Sequential from keras.layers import Input, Dense, Dropout, Conv2D, Flatten, Activation, concatenate from keras.optimizers import Adam c = 0.6 def decorrelation_loss(neuron): def loss(y_actual, y_predicted): return K.mean( K.square(y_actual-y_predicted) - c * K.square(y_predicted - neuron)) return loss # split the two input streams box_scores_train, odds_train = map(list, zip(*x_train)) box_scores_test, odds_test = map(list, zip(*x_test)) # box model turns stats into a vector box_model = Sequential() shape = box_scores_train[0].shape print(shape) box_model.add(Conv2D(filters=32, kernel_size=(1, 8), input_shape=shape, data_format="channels_first", activation="relu")) box_model.add(Flatten()) box_input = Input(shape=shape) box_encoded = box_model(box_input) odds_input = Input(shape=(1,), dtype="float32") #(opening or closing weight) merged = concatenate([odds_input, box_encoded]) output = Dense(32, activation="relu")(merged) output = Dropout(0.5)(output) output = Dense(8, activation="relu")(output) output = Dropout(0.5)(output) signal = Dense(1, activation="sigmoid")(output) opt = Adam(lr=0.0001) nba_model = Model(inputs=[box_input, odds_input], outputs=signal) print(nba_model.summary()) nba_model.compile(optimizer=opt, #loss="binary_crossentropy", loss=decorrelation_loss(odds_input), # Call the loss function with the selected layer metrics=['accuracy']) nba_model.fit([box_scores_train, odds_train], y_train, batch_size=16,validation_data=([box_scores_test, odds_test], y_test), verbose=1,epochs=20)/<code>
該模型是Conv2D和稠密層的組合,具有大量的dropout。模型獨一無二的部分是去相關性損失函數,在我的第一篇論文中提到過。儘管Keras本身並不支持具有神經元值的損失函數,但將函數包裝在函數中是一種有用的解決方法。我在GTX 1660Ti上訓練了20個世代的網絡,直到網絡收斂。
結果
使用一種非常原始的賭博策略,即10%的平衡*模型置信度,並且僅在模型的置信度大於0.6的遊戲上賭博,我們就產生了向上的平衡趨勢。有趣的是,這個模型只賭了大約10%的遊戲。除了整個2017-18賽季的慘敗,我們的模型表現非常好,從最初的100美元投資到現在的136美元,峰值為292美元。
展望與未來
這只是這個模型的開始。有了令人鼓舞的結果,我想制定一個更具活力的投注策略。如果你想嘲笑/同情我寫的一個行的Jupyter筆記本,完整的未經編輯的筆記本可以在這裡找到:
唯一有用的可能是下注代碼和模型。
使用NoSQL是一個錯誤,我應該堅持使用SQLite,但是學習一種新技術是很好的。編寫一個自定義損失函數是一個非常寶貴的經驗,並將在未來的深入學習項目中派上用場。
作者:Caleb Cheng
翻譯人:tensor-zhang