採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

採用 Python 機器學習預測足球比賽結果

足球是世界上最火爆的運動之一,世界盃期間也往往是球迷們最亢奮的時刻。比賽狂歡季除了炸出了熬夜看球的鐵桿粉絲,也讓足球競猜也成了大家茶餘飯後最熱衷的話題。甚至連原來不怎麼看足球的人,也是暗中努力惡補了很多足球相關知識,想通過賽事競猜先賺一個小目標。今天我們將介紹如何用機器學習來預測足球比賽結果!

本 Chat 採用 Python 編程語言,使用 人工智能建模平臺 Mo 作為在線開發環境進行編程,通過獲取 2000 年到 2018 年共 19 年英超的比賽數據,然後基於監督學習中邏輯迴歸模型、支持向量機模型和 XGBoost 模型,對英超比賽結果進行預測。

Python學習交流群:1004391443,這裡是python學習者聚集地,有大牛答疑,有資源共享!小編也準備了一份python學習資料,有想學習python編程的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入學習。

下面我們一起來看看預測英超比賽結果的機器學習步驟:

主要流程步驟

  1. 獲取數據和讀取數據的信息
  2. 數據清洗和預處理
  3. 特徵工程
  4. 建立機器學習模型並進行預測
  5. 總結與展望

1. 獲取數據和讀取數據的信息

首先我們進入 Mo 工作臺 ,創建一個空白項目,點擊 開始開發 進入內嵌 JupyterLab 的 Notebook 開發環境。

採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

接著我們需要在項目中上傳 數據集 。

英超每年舉辦一個賽季,在每年的 8 月到第二年的 5 月進行,共有 20 支球隊,實行主客場雙循環賽制,每個賽季共 38 輪比賽(其中 19 場主場比賽,19 場客場比賽),每輪比賽共計 10 場比賽,所以每個賽季,英超共有 380 場比賽。

  • 數據集地址
  • 數據集中特徵說明文檔

如果您已經在 MO 平臺新建項目,可以在平臺直接導入數據集,流程如下:

採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

1.1 讀取 csv 數據接口解釋

  • 採用 Pandas 讀取、寫入數據 API 彙總網址

讀取 csv 數據一般採用 pandas.read_csv():

pandas.read_csv(filepath_or_buffer, sep =',' , delimiter = None)

  • filepath_or_buffer:文件路徑
  • sep:指定分隔符,默認是逗號
  • delimiter:定界符,備選分隔符(如果指定改參數,則sep失效)
  • usecols: 指定讀取的列名,列表形式
# 導入必須的包
import warnings
warnings.filterwarnings('ignore') # 防止警告文件的包
import pandas as pd # 數據分析包
import os
import matplotlib.pyplot as plt # 可視化包
import matplotlib
%matplotlib inline
import seaborn as sns # 可視化包
from time import time
from sklearn.preprocessing import scale # 標準化操作
from sklearn.model_selection import train_test_split # 將數據集分成測試集和訓練集
from sklearn.metrics import f1_score # F1得分
import xgboost as xgb # XGBoost模型
from sklearn.svm import SVC ## 支持向量機分類模型

from sklearn.linear_model import LogisticRegression # 邏輯迴歸模型
from sklearn.model_selection import GridSearchCV # 超參數調參模塊
from sklearn.metrics import make_scorer # 模型評估
import joblib # 模型的保存與加載模塊

下面開始我們的表演:

# 獲取地址中的所有文件
loc = './/football//' # 存放數據的路徑
res_name = [] # 存放數據名的列表
filecsv_list = [] # 獲取數據名後存放的列表
def file_name(file_name):
# root:當前目錄路徑 dirs:當前目錄下所有子目錄 files:當前路徑下所有非目錄文件
for root,dirs,files in os.walk(file_name):
files.sort() # 排序,讓列表裡面的元素有順序
for i,file in enumerate(files):
if os.path.splitext(file)[1] == '.csv':
filecsv_list.append(file)
res_name.append('raw_data_'+str(i+1))
print(res_name)
print(filecsv_list)
file_name(loc)
['raw_data_1', 'raw_data_2', 'raw_data_3', 'raw_data_4', 'raw_data_5', 'raw_data_6', 'raw_data_7', 'raw_data_8', 'raw_data_9', 'raw_data_10', 'raw_data_11', 'raw_data_12', 'raw_data_13', 'raw_data_14', 'raw_data_15', 'raw_data_16', 'raw_data_17', 'raw_data_18', 'raw_data_19']
['2000-01.csv', '2001-02.csv', '2002-03.csv', '2003-04.csv', '2004-05.csv', '2005-06.csv', '2006-07.csv', '2007-08.csv', '2008-09.csv', '2009-10.csv', '2010-11.csv', '2011-12.csv', '2012-13.csv', '2013-14.csv', '2014-15.csv', '2015-16.csv', '2016-17.csv', '2017-18.csv', '2018-19.csv']

1.2 時間列表

獲取每一年的數據後,將每一年的年份放入到 time_list 列表中:

time_list = [filecsv_list[i][0:4] for i in range(len(filecsv_list))]
time_list

['2000','2001','2002','2003','2004','2005','2006','2007','2008','2009','2010','2011','2012','2013','2014','2015','2016','2017','2018']

1.3 用 Pandas.read_csv() 接口讀取數據

讀取時將數據與 res_name 中的元素名一一對應。

for i in range(len(res_name)):
res_name[i] = pd.read_csv(loc+filecsv_list[i],error_bad_lines=False)
print('第%2s個文件是%s,數據大小為%s'%(i+1,filecsv_list[i],res_name[i].shape))
第 1個文件是2000-01.csv,數據大小為(380, 45)
第 2個文件是2001-02.csv,數據大小為(380, 48)
第 3個文件是2002-03.csv,數據大小為(316, 48)
第 4個文件是2003-04.csv,數據大小為(335, 57)
第 5個文件是2004-05.csv,數據大小為(335, 57)
第 6個文件是2005-06.csv,數據大小為(380, 68)
第 7個文件是2006-07.csv,數據大小為(380, 68)
第 8個文件是2007-08.csv,數據大小為(380, 71)
第 9個文件是2008-09.csv,數據大小為(380, 71)
第10個文件是2009-10.csv,數據大小為(380, 71)
第11個文件是2010-11.csv,數據大小為(380, 71)
第12個文件是2011-12.csv,數據大小為(380, 71)
第13個文件是2012-13.csv,數據大小為(380, 74)
第14個文件是2013-14.csv,數據大小為(380, 68)
第15個文件是2014-15.csv,數據大小為(381, 68)
第16個文件是2015-16.csv,數據大小為(380, 65)
第17個文件是2016-17.csv,數據大小為(380, 65)
第18個文件是2017-18.csv,數據大小為(380, 65)
第19個文件是2018-19.csv,數據大小為(304, 62)

1.4 刪除特定文件的空值

經過查看第 15 個文件讀取的第 381 行為空值,故採取刪除行空值操作。

1.4.1 刪除空值的接口

  • Pandas.dropna(axis=0,how='any')
  • axis: 0 表示是行;1表示是列
  • how:'all'表示只去掉所有值均缺失的行、列;any表示只去掉有缺失值的行、列

1.4.2 接口運用

res_name[14] = res_name[14].dropna(axis=0,how='all')
res_name[14].tail()

Div Date HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR ... BbAv<2.5 BbAH BbAHh BbMxAHH BbAvAHH BbMxAHA BbAvAHA PSCH PSCD PSCA 375 E0 24/05/15 Hull Man United 0.0 0.0 D 0.0 0.0 D ... 1.99 25.0 0.50 1.76 1.71 2.27 2.19 3.20 3.76 2.27 376 E0 24/05/15 Leicester QPR 5.0 1.0 H 2.0 0.0 H ... 2.41 28.0 -1.00 1.98 1.93 1.98 1.93 1.53 4.94 6.13 377 E0 24/05/15 Man City Southampton 2.0 0.0 H 1.0 0.0 H ... 2.66 28.0 -1.00 2.00 1.94 2.03 1.93 1.60 4.35 6.00 378 E0 24/05/15 Newcastle West Ham 2.0 0.0 H 0.0 0.0 D ... 2.25 25.0 -0.50 1.82 1.78 2.20 2.10 1.76 4.01 4.98 379 E0 24/05/15 Stoke Liverpool 6.0 1.0 H 5.0 0.0 H ... 1.99 25.0 0.25 2.07 2.02 1.88 1.85 3.56 3.60 2.17 5 rows × 68 columns

1.5 刪除行數不是 380 的文件名

考慮到英超一般是 19 個球隊,每個球隊需要打 20 場球,故把行數不是 380 的數據刪除掉,並找到器原 CSV 文件一一對應。

for i in range(len(res_name),0,-1): 
# 採用從大到小的遍歷方式,然後進行刪除不滿足條件的。
if res_name[i-1].shape[0] != 380:
key = 'res_name[' + str(i) + ']'
print('刪除的數據是:%s年的數據,文件名:%s大小是:%s'%(time_list[i-1],key,res_name[i-1].shape))

res_name.pop(i-1)
time_list.pop(i-1)
continue
刪除的數據是:2018年的數據,文件名:res_name[19]大小是:(304, 62)
刪除的數據是:2004年的數據,文件名:res_name[5]大小是:(335, 57)
刪除的數據是:2003年的數據,文件名:res_name[4]大小是:(335, 57)
刪除的數據是:2002年的數據,文件名:res_name[3]大小是:(316, 48)

1.6 查看某一個數據集前n行數據

  • 文件名.head(n)
  • n:默認是5,想獲取多少行數據就填寫數字值。

讀取數據前五行操作:

res_name[0].head()

Div Date HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR ... IWA LBH LBD LBA SBH SBD SBA WHH WHD WHA 0 E0 19/08/00 Charlton Man City 4 0 H 2 0 H ... 2.7 2.20 3.25 2.75 2.20 3.25 2.88 2.10 3.2 3.10 1 E0 19/08/00 Chelsea West Ham 4 2 H 1 0 H ... 4.2 1.50 3.40 6.00 1.50 3.60 6.00 1.44 3.6 6.50 2 E0 19/08/00 Coventry Middlesbrough 1 3 A 1 1 D ... 2.7 2.25 3.20 2.75 2.30 3.20 2.75 2.30 3.2 2.62 3 E0 19/08/00 Derby Southampton 2 2 D 1 2 A ... 3.5 2.20 3.25 2.75 2.05 3.20 3.20 2.00 3.2 3.20 4 E0 19/08/00 Leeds Everton 2 0 H 2 0 H ... 4.5 1.55 3.50 5.00 1.57 3.60 5.00 1.61 3.5 4.50 5 rows × 45 columns

讀取數據前10行:

res_name[0].head(10)

Div Date HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR ... IWA LBH LBD LBA SBH SBD SBA WHH WHD WHA 0 E0 19/08/00 Charlton Man City 4 0 H 2 0 H ... 2.7 2.20 3.25 2.75 2.20 3.25 2.88 2.10 3.20 3.10 1 E0 19/08/00 Chelsea West Ham 4 2 H 1 0 H ... 4.2 1.50 3.40 6.00 1.50 3.60 6.00 1.44 3.60 6.50 2 E0 19/08/00 Coventry Middlesbrough 1 3 A 1 1 D ... 2.7 2.25 3.20 2.75 2.30 3.20 2.75 2.30 3.20 2.62 3 E0 19/08/00 Derby Southampton 2 2 D 1 2 A ... 3.5 2.20 3.25 2.75 2.05 3.20 3.20 2.00 3.20 3.20 4 E0 19/08/00 Leeds Everton 2 0 H 2 0 H ... 4.5 1.55 3.50 5.00 1.57 3.60 5.00 1.61 3.50 4.50 5 E0 19/08/00 Leicester Aston Villa 0 0 D 0 0 D ... 2.5 2.35 3.20 2.60 2.25 3.25 2.75 2.40 3.25 2.50 6 E0 19/08/00 Liverpool Bradford 1 0 H 0 0 D ... 8.0 1.35 4.00 8.00 1.36 4.00 8.00 1.33 4.00 8.00 7 E0 19/08/00 Sunderland Arsenal 1 0 H 0 0 D ... 2.1 4.30 3.20 1.70 3.30 3.10 2.05 3.75 3.00 1.90 8 E0 19/08/00 Tottenham Ipswich 3 1 H 2 1 H ... 4.7 1.45 3.60 6.50 1.50 3.50 6.50 1.44 3.60 6.50 9 E0 20/08/00 Man United Newcastle 2 0 H 1 0 H ... 5.0 1.40 3.75 7.00 1.40 3.75 7.50 1.40 3.75 7.00 10 rows × 45 columns

讀取最後 5 行操作:

res_name[0].tail()

Div Date HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR ... IWA LBH LBD LBA SBH SBD SBA WHH WHD WHA 375 E0 19/05/01 Man City Chelsea 1 2 A 1 1 D ... 1.65 4.0 3.60 1.67 4.20 3.40 1.70 4.00 3.1 1.80 376 E0 19/05/01 Middlesbrough West Ham 2 1 H 2 1 H ... 3.20 1.8 3.25 3.75 1.90 3.20 3.50 1.83 3.4 3.50 377 E0 19/05/01 Newcastle Aston Villa 3 0 H 2 0 H ... 2.90 2.4 3.25 2.50 2.38 3.30 2.50 2.25 3.4 2.60 378 E0 19/05/01 Southampton Arsenal 3 2 H 0 1 A ... 2.35 2.5 3.25 2.37 2.63 3.25 2.30 2.62 3.5 2.20 379 E0 19/05/01 Tottenham Man United 3 1 H 1 1 D ... 2.10 2.6 3.20 2.37 2.60 3.25 2.35 2.62 3.3 2.25 5 rows × 45 columns

讀取最後 4 行操作:

res_name[0].tail(4)

Div Date HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR ... IWA LBH LBD LBA SBH SBD SBA WHH WHD WHA 376 E0 19/05/01 Middlesbrough West Ham 2 1 H 2 1 H ... 3.20 1.8 3.25 3.75 1.90 3.20 3.50 1.83 3.4 3.50 377 E0 19/05/01 Newcastle Aston Villa 3 0 H 2 0 H ... 2.90 2.4 3.25 2.50 2.38 3.30 2.50 2.25 3.4 2.60 378 E0 19/05/01 Southampton Arsenal 3 2 H 0 1 A ... 2.35 2.5 3.25 2.37 2.63 3.25 2.30 2.62 3.5 2.20 379 E0 19/05/01 Tottenham Man United 3 1 H 1 1 D ... 2.10 2.6 3.20 2.37 2.60 3.25 2.35 2.62 3.3 2.25 4 rows × 45 columns

1.8 獲取某一年主場隊伍的名稱

res_name[0]['HomeTeam'].unique()
array(['Charlton', 'Chelsea', 'Coventry', 'Derby', 'Leeds', 'Leicester',
'Liverpool', 'Sunderland', 'Tottenham', 'Man United', 'Arsenal',
'Bradford', 'Ipswich', 'Middlesbrough', 'Everton', 'Man City',
'Newcastle', 'Southampton', 'West Ham', 'Aston Villa'],
dtype=object)

1.9 解析數據集列表頭含義

數據集行數已經固定,一般都是 380 行,而列數可能每年統計指標有變化,不一定相等,而且我們也比較關心列數表表頭。由於比較小,可以直接看數據集列數,這樣比較快,也可以代碼實現,找到最大的列數,然後獲取列數的表頭進行一般性介紹解釋。

# 獲取列表頭最大的列數,然後獲取器參數
shape_list = [res_name[i].shape[1] for i in range(len(res_name))]
for i in range(len(res_name)):
if res_name[i].shape[1] == max(shape_list):
print('%s年數據是有最大列數:%s,列元素表頭:\n %s'%(time_list[i],max(shape_list),res_name[i].columns))
2012年數據是有最大列數:74,列元素表頭:
Index(['Div', 'Date', 'HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'FTR', 'HTHG',
'HTAG', 'HTR', 'Referee', 'HS', 'AS', 'HST', 'AST', 'HF', 'AF', 'HC',
'AC', 'HY', 'AY', 'HR', 'AR', 'B365H', 'B365D', 'B365A', 'BWH', 'BWD',
'BWA', 'GBH', 'GBD', 'GBA', 'IWH', 'IWD', 'IWA', 'LBH', 'LBD', 'LBA',

'PSH', 'PSD', 'PSA', 'WHH', 'WHD', 'WHA', 'SJH', 'SJD', 'SJA', 'VCH',
'VCD', 'VCA', 'BSH', 'BSD', 'BSA', 'Bb1X2', 'BbMxH', 'BbAvH', 'BbMxD',
'BbAvD', 'BbMxA', 'BbAvA', 'BbOU', 'BbMx>2.5', 'BbAv>2.5', 'BbMx<2.5',
'BbAv<2.5', 'BbAH', 'BbAHh', 'BbMxAHH', 'BbAvAHH', 'BbMxAHA', 'BbAvAHA',
'PSCH', 'PSCD', 'PSCA'],
dtype='object')

我們看到數據包括 Date(比賽的時間),Hometeam(主場隊伍名),Awayteam(客場隊伍名),FTHG(主場球隊全場進球數),HTHG(主場球隊半場進球數),FTR(全場比賽結果) 等等,更多關於數據集中特徵信息可以參考 數據集特徵說明文檔 。

2. 數據清洗和預處理

我們挑選 Hometeam,Awayteam,FTHG,FTAG,FTR 這五列數據,作為我們的原始的特徵數據,後面基於這些原始特徵,我們再構造一些新的特徵。

2.1 挑選信息列

  • HomeTeam: 主場球隊名
  • AwayTeam: 客場球隊名
  • FTHG: 全場 主場球隊進球數
  • FTAG: 全場 客場球隊進球數
  • FTR: 比賽結果 ( H= 主場贏, D= 平局, A= 客場贏)
# 將挑選的信息放在一個新的列表中 

columns_req = ['HomeTeam','AwayTeam','FTHG','FTAG','FTR']
playing_statistics = [] # 創造處理後數據名存放處
playing_data = {} # 鍵值對存儲數據
for i in range(len(res_name)):
playing_statistics.append('playing_statistics_'+str(i+1))
playing_statistics[i] = res_name[i][columns_req]
print(time_list[i],'playing_statistics['+str(i)+']',playing_statistics[i].shape)
2000 playing_statistics[0] (380, 5)
2001 playing_statistics[1] (380, 5)
2005 playing_statistics[2] (380, 5)
2006 playing_statistics[3] (380, 5)
2007 playing_statistics[4] (380, 5)
2008 playing_statistics[5] (380, 5)
2009 playing_statistics[6] (380, 5)
2010 playing_statistics[7] (380, 5)
2011 playing_statistics[8] (380, 5)
2012 playing_statistics[9] (380, 5)
2013 playing_statistics[10] (380, 5)
2014 playing_statistics[11] (380, 5)
2015 playing_statistics[12] (380, 5)
2016 playing_statistics[13] (380, 5)
2017 playing_statistics[14] (380, 5)

2.2 分析原始數據

我們首先預測所有主場球隊全都勝利,然後預測所有的客場都會勝利,對結果進行對比分析:

2.2.1 統計所有主場球隊都會勝利的準確率

def predictions_0(data):
"""
當我們統計所有主場球隊都贏,那麼我們預測的結果是什麼
返回值是預測值和實際值
"""
predictions = []
for _, game in data.iterrows():

if game['FTR']=='H':

predictions.append(1)
else:
predictions.append(0)
# 返回預測結果
return pd.Series(predictions)
# 那我們對19年全部主場球隊都贏的結果進行預測,獲取預測的準確率。
avg_acc_sum = 0
for i in range(len(playing_statistics)):
predictions = predictions_0(playing_statistics[i])
acc=sum(predictions)/len(playing_statistics[i])
avg_acc_sum += acc
print("%s年數據主場全勝預測的準確率是%s"%(time_list[i],acc))
print('共%s年的平均準確率是:%s'%(len(playing_statistics),avg_acc_sum/len(playing_statistics)))
2000年數據主場全勝預測的準確率是0.4842105263157895
2001年數據主場全勝預測的準確率是0.4342105263157895
2005年數據主場全勝預測的準確率是0.5052631578947369
2006年數據主場全勝預測的準確率是0.4789473684210526
2007年數據主場全勝預測的準確率是0.4631578947368421
2008年數據主場全勝預測的準確率是0.45526315789473687
2009年數據主場全勝預測的準確率是0.5078947368421053
2010年數據主場全勝預測的準確率是0.4710526315789474
2011年數據主場全勝預測的準確率是0.45
2012年數據主場全勝預測的準確率是0.4368421052631579
2013年數據主場全勝預測的準確率是0.4710526315789474
2014年數據主場全勝預測的準確率是0.45263157894736844
2015年數據主場全勝預測的準確率是0.4131578947368421

2016年數據主場全勝預測的準確率是0.4921052631578947
2017年數據主場全勝預測的準確率是0.45526315789473687
共15年的平均準確率是:0.46473684210526317

2.2.2 統計所有客場球隊都會勝利的準確率

def predictions_1(data):
"""
當我們統計所有客場球隊都贏,那麼我們預測的結果是什麼
返回值是預測值和實際值
"""
predictions = []
for _, game in data.iterrows():

if game['FTR']=='A':
predictions.append(1)
else:
predictions.append(0)
# 返回預測結果
return pd.Series(predictions)
# 那我們對19年客場球隊都贏的結果進行預測,獲取預測的準確率。
for i in range(len(playing_statistics)):
predictions = predictions_1(playing_statistics[i])
acc=sum(predictions)/len(playing_statistics[i])
print("%s年數據客場全勝預測的準確率是%s"%(time_list[i],acc))
2000年數據客場全勝預測的準確率是0.25
2001年數據客場全勝預測的準確率是0.3
2005年數據客場全勝預測的準確率是0.29210526315789476
2006年數據客場全勝預測的準確率是0.2631578947368421
2007年數據客場全勝預測的準確率是0.2736842105263158

2008年數據客場全勝預測的準確率是0.2894736842105263
2009年數據客場全勝預測的準確率是0.2394736842105263
2010年數據客場全勝預測的準確率是0.23684210526315788
2011年數據客場全勝預測的準確率是0.30526315789473685
2012年數據客場全勝預測的準確率是0.2789473684210526
2013年數據客場全勝預測的準確率是0.3236842105263158
2014年數據客場全勝預測的準確率是0.3026315789473684
2015年數據客場全勝預測的準確率是0.30526315789473685
2016年數據客場全勝預測的準確率是0.2868421052631579
2017年數據客場全勝預測的準確率是0.28421052631578947

綜上比較:我們可以看出主場勝利的概率相對於輸和平局來說,確實概率要大。

2.3 我們想知道 Arsenal 作為主場隊伍時,他們的表現,如何求出 2005-06 所有比賽累計進球數 ?

我們知道 2005-06 年數據在 playing_statistics[2] 中:

def score(data):
""" Arsenal作為主場隊伍時,累計進球數 """
scores=[]
for _,game in data.iterrows():
if game['HomeTeam']=='Arsenal':
scores.append(game['FTHG'])
return np.sum(scores)
Arsenal_score=score(playing_statistics[2])
print("Arsenal作為主場隊伍在2005年時,累計進球數:%s"%(Arsenal_score))

Arsenal 作為主場隊伍在2005年時,累計進球數:48

2.4 我們想知道各個球隊作為主場隊伍時,他們的表現如何 ?

先試試求 2005-06 所有比賽各個球隊累計進球數。

print(playing_statistics[5].groupby('HomeTeam').sum()['FTHG'])
HomeTeam
Arsenal 31
Aston Villa 27
Blackburn 22
Bolton 21
Chelsea 33
Everton 31
Fulham 28
Hull 18
Liverpool 41
Man City 40
Man United 43
Middlesbrough 17
Newcastle 24
Portsmouth 26
Stoke 22
Sunderland 21
Tottenham 21
West Brom 26
West Ham 23
Wigan 17
Name: FTHG, dtype: int64

3. 特徵工程

特徵工程指的是把原始數據轉變為模型的訓練數據的過程,它的目的就是獲取更好的訓練數據特徵,得到更好的訓練模型。特徵工程能使得模型的性能得到提升,有時甚至在簡單的模型上也能取得不錯的效果。特徵工程在機器學習中佔有非常重要的作用,一般認為括特徵構建、特徵提取、特徵選擇三大部分。

3.1 構造特徵

因為這個比賽是一年一個賽季,是有先後順序的,那我們就可以統計到截止到本場比賽之前,整個賽季內,主客場隊伍的淨勝球的數量。那麼對於每一個賽季的每一週,都統計出每個球隊到本週為止累計的進球數和丟球數之差,也就是淨勝球的數量。

3.1.1 計算每個隊周累計淨勝球數量

處理後的數據,我們可以通過看某一年的某幾條數據來體現,比如:05-06 年的後五條數據

def get_goals_diff(playing_stat):
# 創建一個字典,每個 team 的 name 作為 key
teams = {}
for i in playing_stat.groupby('HomeTeam').mean().T.columns:
teams[i] = []
# 對於每一場比賽
for i in range(len(playing_stat)):
# 全場比賽,主場隊伍的進球數
HTGS = playing_stat.iloc[i]['FTHG']
# 全場比賽,客場隊伍的進球數
ATGS = playing_stat.iloc[i]['FTAG']
# 把主場隊伍的淨勝球數添加到 team 這個 字典中對應的主場隊伍下
teams[playing_stat.iloc[i].HomeTeam].append(HTGS-ATGS)
# 把客場隊伍的淨勝球數添加到 team 這個 字典中對應的客場隊伍下
teams[playing_stat.iloc[i].AwayTeam].append(ATGS-HTGS)

# 創建一個 GoalsDifference 的 dataframe
# 行是 team 列是 matchweek,
# 39解釋:19個球隊,每個球隊分主場客場2次,共38個賽次,但是range取不到最後一個值,故38+1=39
GoalsDifference = pd.DataFrame(data=teams, index = [i for i in range(1,39)]).T
GoalsDifference[0] = 0
# 累加每個隊的周比賽的淨勝球數
for i in range(2,39):
GoalsDifference[i] = GoalsDifference[i] + GoalsDifference[i-1]
return GoalsDifference
def get_gss(playing_stat):
# 得到淨勝球數統計
GD = get_goals_diff(playing_stat)
j = 0
# 主客場的淨勝球數
HTGD = []
ATGD = []
# 全年一共380場比賽
for i in range(380):
ht = playing_stat.iloc[i].HomeTeam
at = playing_stat.iloc[i].AwayTeam
HTGD.append(GD.loc[ht][j])
ATGD.append(GD.loc[at][j])
if ((i + 1)% 10) == 0:
j = j + 1
# 把每個隊的 HTGD ATGD 信息補充到 dataframe 中
playing_stat.loc[:,'HTGD'] = HTGD
playing_stat.loc[:,'ATGD'] = ATGD
return playing_stat
for i in range(len(playing_statistics)):
playing_statistics[i] = get_gss(playing_statistics[i])
#### 查看構造特徵後的05-06年的後五條數據
playing_statistics[2].tail()

HomeTeam AwayTeam FTHG FTAG FTR HTGD ATGD 375 Fulham Middlesbrough 1 0 H -11 -9 376 Man United Charlton 4 0 H 34 -10 377 Newcastle Chelsea 1 0 H 4 51 378 Portsmouth Liverpool 1 3 A -23 30 379 West Ham Tottenham 2 1 H -4 16 通過以上數據:我們發現 376 行數據的特點, 截止到這一場比賽之前,本賽季主場曼聯隊的淨勝球數是 34 , 客場查爾頓隊的淨勝球數是 -10 。

3.1.2 統計主客場隊伍到當前比賽周的累計得分

統計整個賽季主客場隊伍截止到當前比賽周的累計得分。一場比賽勝利計 3 分, 平局計 1 分,輸了計 0 分。我們根據本賽季本週之前的比賽結果來統計這個值。我們繼續觀看 05-06 年的後五條數據:

# 把比賽結果轉換為得分,贏得三分,平局得一分,輸不得分
def get_points(result):
if result == 'W':
return 3
elif result == 'D':
return 1
else:
return 0

def get_cuml_points(matchres):
matchres_points = matchres.applymap(get_points)
for i in range(2,39):
matchres_points[i] = matchres_points[i] + matchres_points[i-1]
matchres_points.insert(column =0, loc = 0, value = [0*i for i in range(20)])
return matchres_points
def get_matchres(playing_stat):
# 創建一個字典,每個 team 的 name 作為 key
teams = {}
for i in playing_stat.groupby('HomeTeam').mean().T.columns:
teams[i] = []
# 把比賽結果分別記錄在主場隊伍和客場隊伍中
# H:代表 主場 贏
# A:代表 客場 贏
# D:代表 平局
for i in range(len(playing_stat)):
if playing_stat.iloc[i].FTR == 'H':
# 主場 贏,則主場記為贏,客場記為輸

teams[playing_stat.iloc[i].HomeTeam].append('W')
teams[playing_stat.iloc[i].AwayTeam].append('L')
elif playing_stat.iloc[i].FTR == 'A':
# 客場 贏,則主場記為輸,客場記為贏
teams[playing_stat.iloc[i].AwayTeam].append('W')
teams[playing_stat.iloc[i].HomeTeam].append('L')
else:
# 平局
teams[playing_stat.iloc[i].AwayTeam].append('D')
teams[playing_stat.iloc[i].HomeTeam].append('D')
return pd.DataFrame(data=teams, index = [i for i in range(1,39)]).T
def get_agg_points(playing_stat):
matchres = get_matchres(playing_stat)
cum_pts = get_cuml_points(matchres)
HTP = []
ATP = []
j = 0
for i in range(380):
ht = playing_stat.iloc[i].HomeTeam
at = playing_stat.iloc[i].AwayTeam
HTP.append(cum_pts.loc[ht][j])
ATP.append(cum_pts.loc[at][j])
if ((i + 1)% 10) == 0:
j = j + 1
# 主場累計得分
playing_stat.loc[:,'HTP'] = HTP
# 客場累計得分
playing_stat.loc[:,'ATP'] = ATP
return playing_stat
for i in range(len(playing_statistics)):
playing_statistics[i] = get_agg_points(playing_statistics[i])

#查看構造特徵後的05-06年的後五條數據
playing_statistics[2].tail()

HomeTeam AwayTeam FTHG FTAG FTR HTGD ATGD HTP ATP 375 Fulham Middlesbrough 1 0 H -11 -9 45 45 376 Man United Charlton 4 0 H 34 -10 80 47 377 Newcastle Chelsea 1 0 H 4 51 55 91 378 Portsmouth Liverpool 1 3 A -23 30 38 79 379 West Ham Tottenham 2 1 H -4 16 52 65 我們處理得到 HTP (本賽季主場球隊截止到本週的累計得分), ATP (本賽季客場球隊截止到本週的累計得分)。

我們再看 376 行,截止到這一場比賽,本賽季,曼聯隊一共積了80分, 查爾頓隊積了 47 分。

3.1.3 統計某支隊伍最近三場比賽的表現

前面我們構造的特徵反映了一隻隊伍本賽季的歷史總表現,我們看看隊伍在最近三場比賽的表現。

我們用:

HM1 代表主場球隊上一次比賽的輸贏,

AM1 代表客場球隊上一次比賽是輸贏。

同理,HM2 AM2 就是上上次比賽的輸贏, HM3 AM3 就是上上上次比賽的輸贏。

我們繼續觀看處理後 05-06 年的後 5 五條數據:

def get_form(playing_stat,num):
form = get_matchres(playing_stat)
form_final = form.copy()
for i in range(num,39):
form_final[i] = ''
j = 0
while j < num:
form_final[i] += form[i-j]
j += 1
return form_final
def add_form(playing_stat,num):
form = get_form(playing_stat,num)
# M 代表 unknown, 因為沒有那麼多歷史
h = ['M' for i in range(num * 10)]
a = ['M' for i in range(num * 10)]
j = num
for i in range((num*10),380):
ht = playing_stat.iloc[i].HomeTeam
at = playing_stat.iloc[i].AwayTeam
past = form.loc[ht][j]
h.append(past[num-1])

past = form.loc[at][j]
a.append(past[num-1])
if ((i + 1)% 10) == 0:
j = j + 1
playing_stat['HM' + str(num)] = h
playing_stat['AM' + str(num)] = a
return playing_stat
def add_form_df(playing_statistics):
playing_statistics = add_form(playing_statistics,1)
playing_statistics = add_form(playing_statistics,2)
playing_statistics = add_form(playing_statistics,3)
return playing_statistics
for i in range(len(playing_statistics)):
playing_statistics[i] = add_form_df(playing_statistics[i])
#查看構造特徵後的05-06年的後5五條數據
playing_statistics[2].tail()

HomeTeam AwayTeam FTHG FTAG FTR HTGD ATGD HTP ATP HM1 AM1 HM2 AM2 HM3 AM3 375 Fulham Middlesbrough 1 0 H -11 -9 45 45 L D W D W L 376 Man United Charlton 4 0 H 34 -10 80 47 D L L L W W 377 Newcastle Chelsea 1 0 H 4 51 55 91 D L W W W W 378 Portsmouth Liverpool 1 3 A -23 30 38 79 W W W W L W 379 West Ham Tottenham 2 1 H -4 16 52 65 W W L D L L 3.1.4 加入比賽周特徵(第幾個比賽周)

然後我們把比賽周的信息也放在裡面,也就是這一場比賽發生在第幾個比賽周。

特徵構造後的結果,我們可以直接查看 05-06 年的後 5 條數據:

def get_mw(playing_stat):
j = 1
MatchWeek = []
for i in range(380):
MatchWeek.append(j)
if ((i + 1)% 10) == 0:
j = j + 1
playing_stat['MW'] = MatchWeek
return playing_stat
for i in range(len(playing_statistics)):
playing_statistics[i] = get_mw(playing_statistics[i])

#查看構造特徵後的05-06年的後五條數據
playing_statistics[2].tail()

HomeTeam AwayTeam FTHG FTAG FTR HTGD ATGD HTP ATP HM1 AM1 HM2 AM2 HM3 AM3 MW 375 Fulham Middlesbrough 1 0 H -11 -9 45 45 L D W D W L 38 376 Man United Charlton 4 0 H 34 -10 80 47 D L L L W W 38 377 Newcastle Chelsea 1 0 H 4 51 55 91 D L W W W W 38 378 Portsmouth Liverpool 1 3 A -23 30 38 79 W W W W L W 38 379 West Ham Tottenham 2 1 H -4 16 52 65 W W L D L L 38 3.1.5 合併比賽的信息

我們打算把數據集比賽的信息都合併到一個表裡面,然後我們把我們剛才計算得到的這些得分數據,淨勝球數據除以週數,就得到了周平均後的值。結果就可以通過查看構造特徵後數據集的後 5 條數據。

# 將各個DataFrame表合併在一張表中
playing_stat = pd.concat(playing_statistics, ignore_index=True)
# HTGD, ATGD ,HTP, ATP的值 除以 week 數,得到平均分
cols = ['HTGD','ATGD','HTP','ATP']
playing_stat.MW = playing_stat.MW.astype(float)
for col in cols:
playing_stat[col] = playing_stat[col] / playing_stat.MW

#查看構造特徵後數據集的後5五條數據
playing_stat.tail()

HomeTeam AwayTeam FTHG FTAG FTR HTGD ATGD HTP ATP HM1 AM1 HM2 AM2 HM3 AM3 MW 5695 Newcastle Chelsea 3.0 0.0 H -0.289474 0.710526 1.078947 1.842105 L D L W L W 38.0 5696 Southampton Man City 0.0 1.0 A -0.473684 2.052632 0.947368 2.552632 W W D D W W 38.0 5697 Swansea Stoke 1.0 2.0 A -0.710526 -0.894737 0.868421 0.789474 L L L D L D 38.0 5698 Tottenham Leicester 5.0 4.0 H 0.973684 -0.078947 1.947368 1.236842 W W L L W L 38.0 5699 West Ham Everton 3.0 1.0 H -0.578947 -0.315789 1.026316 1.289474 D D W W L W 38.0 我們看到數據集最後一行的行數是 5699 ,加上第一行為 0 行,則一共 5700 條數據;我們總共統計了 15 年的數據,每一年有 380 條數據,計算後發現我們統計後的數據集大小是準確的。

3.2 刪除某些數據

前面我們根據初始的特徵構造出了很多的特徵。這其中有一部分是中間的特徵,我們需要把這些中間特徵拋棄掉。因為前三週的比賽,每個隊的歷史勝負信息不足,所以我們打算棄掉前三週的數據。

# 拋棄前三週的比賽
playing_stat = playing_stat[playing_stat.MW > 3]
playing_stat.drop(['HomeTeam', 'AwayTeam', 'FTHG', 'FTAG', 'MW'],1, inplace=True)
#我們查看下此時的數據的特徵
playing_stat.keys()
Index(['FTR', 'HTGD', 'ATGD', 'HTP', 'ATP', 'HM1', 'AM1', 'HM2', 'AM2', 'HM3','AM3'], dtype='object')

3.3 分析我們構造的數據

在前面,我們計算了每一的年主客場的勝率,現在我們看看有效數據中,是主場勝利的多呢,還是客場勝利的多呢?

# 比賽總數
n_matches = playing_stat.shape[0]
# 特徵數
n_features = playing_stat.shape[1] - 1
# 主場獲勝的數目
n_homewins = len(playing_stat[playing_stat.FTR == 'H'])
# 主場獲勝的比例
win_rate = (float(n_homewins) / (n_matches)) * 100
# Print the results
print("比賽總數: {}".format(n_matches))
print("總特徵數: {}".format(n_features))
print("主場勝利數: {}".format(n_homewins))
print("主場勝率: {:.2f}%".format(win_rate))
比賽總數: 5250
總特徵數: 10
主場勝利數: 2451
主場勝率: 46.69%

通過統計結果看到:我們主場勝率 46.69% 與我們第 2.2.1 小節原始數據分析的結果是一致的,說明我們前面構造的特徵是有效的,比較貼近實際的。

3.4 解決樣本不均衡問題

通過構造特徵之後,發現主場獲勝的比例接近 50% ,所以對於這個三分類的問題,標籤比例是不均衡的。

我們把它簡化為二分類問題,也就是主場球隊會不會勝利,這也是一種解決標籤比例不均衡的問題的方法。

# 定義 target ,也就是否 主場贏
def only_hw(string):
if string == 'H':
return 'H'
else:
return 'NH'
playing_stat['FTR'] = playing_stat.FTR.apply(only_hw)

3.5 將數據分為特徵值和標籤值

# 把數據分為特徵值和標籤值
X_all = playing_stat.drop(['FTR'],1)
y_all = playing_stat['FTR']
# 特徵值的長度
len(X_all)

3.6 數據歸一化、標準化

我們對所有比賽的特徵 HTP 進行最大最小值歸一化。

def convert_1(data):
max=data.max()
min=data.min()
return (data-min)/(max-min)

r_data=convert_1(X_all['HTGD'])
# 數據標準化
from sklearn.preprocessing import scale
cols = [['HTGD','ATGD','HTP','ATP']]
for col in cols:
X_all[col] = scale(X_all[col])

3.7 轉換特徵數據類型

# 把這些特徵轉換成字符串類型
X_all.HM1 = X_all.HM1.astype('str')
X_all.HM2 = X_all.HM2.astype('str')
X_all.HM3 = X_all.HM3.astype('str')
X_all.AM1 = X_all.AM1.astype('str')
X_all.AM2 = X_all.AM2.astype('str')
X_all.AM3 = X_all.AM3.astype('str')
def preprocess_features(X):
'''把離散的類型特徵轉為啞編碼特徵 '''
output = pd.DataFrame(index = X.index)
for col, col_data in X.iteritems():
if col_data.dtype == object:
col_data = pd.get_dummies(col_data, prefix = col)
output = output.join(col_data)
return output
X_all = preprocess_features(X_all)
print("Processed feature columns ({} total features):\n{}".format(len(X_all.columns), list(X_all.columns)))
Processed feature columns (22 total features):
['HTGD', 'ATGD', 'HTP', 'ATP', 'HM1_D', 'HM1_L', 'HM1_W', 'AM1_D', 'AM1_L', 'AM1_W', 'HM2_D', 'HM2_L', 'HM2_W', 'AM2_D', 'AM2_L', 'AM2_W', 'HM3_D', 'HM3_L', 'HM3_W', 'AM3_D', 'AM3_L', 'AM3_W']
# 預覽處理好的數據
print("\nFeature values:")
display(X_all.head())
Feature values:

HTGD ATGD HTP ATP HM1_D HM1_L HM1_W AM1_D AM1_L AM1_W ... HM2_W AM2_D AM2_L AM2_W HM3_D HM3_L HM3_W AM3_D AM3_L AM3_W 30 0.724821 0.339985 -0.043566 -0.603098 1 0 0 1 0 0 ... 0 0 0 1 0 0 1 0 1 0 31 -0.702311 -1.088217 -1.097731 -2.192828 0 1 0 1 0 0 ... 0 0 1 0 0 0 1 0 1 0 32 0.011255 0.339985 -0.570649 -0.603098 0 1 0 1 0 0 ... 0 0 0 1 0 0 1 0 1 0 33 -0.345528 -0.374116 -1.097731 -1.662918 0 1 0 1 0 0 ... 0 0 1 0 0 0 1 1 0 0 34 0.011255 1.054086 -0.570649 0.456723 1 0 0 0 0 1 ... 0 0 0 1 0 0 1 0 1 0 5 rows × 22 columns

3.8 皮爾遜相關熱力圖

我們生成一些特徵的相關圖,以查看特徵與特徵之間的相關性。 為此,我們將利用 Seaborn 繪圖軟件包,使我們能夠非常方便地繪製熱力圖,如下所示:

import matplotlib.pyplot as plt
import seaborn as sns
# 防止中文出現錯誤
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
#製成皮爾森熱圖
#把標籤映射為0和1
y_all=y_all.map({'NH':0,'H':1})
#合併特徵集和標籤
train_data=pd.concat([X_all,y_all],axis=1)
colormap = plt.cm.RdBu
plt.figure(figsize=(21,18))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sns.heatmap(train_data.astype(float).corr(),linewidths=0.1,vmax=1.0,
square=True, cmap=colormap, linecolor='white', annot=True)
<matplotlib.axes.>
/<matplotlib.axes.>
採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

通過上圖我們可以看出特徵 HTP 特徵和 HTGD 特徵相關性很強,同樣 ATP 特徵和 ATGD 特徵相關性很強,可以表明多重共線性的情況。這個我們也很容易理解,主場周平均得分數越高,那麼主場周平均淨勝球數也同樣越高。如果我們考慮這些變量,我們可以得出結論,它們給出了幾乎相同的信息,因此實際上發生了多重共線性,這裡我們會考慮刪除 HTP 和 'ATP' 這兩個特徵,保留 HTGD 和 ATGD 這兩個特徵。皮爾森熱圖非常適合檢測這種情況,並且在特徵工程中,它們是必不可少的工具。同時,我們也可以看出上上上次球隊的比賽結果對目前比賽的結果影響較小,這裡我們考慮保留這些特徵。

  • 考慮到樣本集特徵 HTP 和 HTGD,ATP 和 ATGD 的相關性都超過了 90% ,故我們刪除特徵 HTP , ATP :
X_all=X_all.drop(['HTP','ATP'],axis=1)
  • 看看與FTR最相關的10個特徵
#FTR correlation matrix
plt.figure(figsize=(14,12))
k = 10 # number of variables for heatmap
cols = abs(train_data.astype(float).corr()).nlargest(k, 'FTR')['FTR'].index
cm = np.corrcoef(train_data[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)

plt.show()
採用 Python 機器學習預測足球比賽結果!買誰贏就誰贏!

我們可以看出最相關的特徵是 HTGD ,表明一個球隊主場周平均淨勝球數越高,他們贏的概率也就越大。

4.建立機器學習模型並進行預測

4.1 切分數據

將數據集隨機分成為訓練集和測試集,並返回劃分好的訓練集測試集樣本和訓練集測試集標籤。我們直接採用 train_test_split 接口進行處理。

4.1.1 train_test_split API 接口介紹

  • X_train, X_test, y_train, y_test =cross_validation.train_test_split(train_data,train_target,test_size=0.3, random_state=0)
  • 參數解釋:
  • train_data:被劃分的樣本特徵集
  • train_target:被劃分的樣本標籤
  • test_size:如果是浮點數,在0-1之間,表示樣本佔比;如果是整數的話就是樣本的數量
  • random_state:是隨機數的種子。
  • 返回值解釋:
  • x_train:訓練集特徵值
  • x_test:測試集特徵值
  • y_train:訓練集目標值
  • y_test:測試集目標值

隨機數種子:其實就是該組隨機數的編號,在需要重複試驗的時候,保證得到一組一樣的隨機數。比如你每次都填1,其他參數一樣的情況下你得到的隨機數組是一樣的。但填0或不填,每次都會不一樣。隨機數的產生取決於種子,隨機數和種子之間的關係遵從以下兩個規則: 種子不同,產生不同的隨機數;種子相同,即使實例不同也產生相同的隨機數。

4.1.2 代碼處理分割數據

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_all, y_all,test_size = 0.3,random_state = 2,stratify = y_all)

4.2 相關模型及其接口介紹

下面我們分別使用邏輯迴歸、支持向量機和 XGBoost 這三種不同的模型,來看看他們的表現。我們先定義一些輔助函數,記錄模型的訓練時長和評估時長,計算模型的準確率和 f1 分數。我們首先介紹一下這三個模型聯繫與區別和相關的接口:

4.2.1 邏輯迴歸介紹

邏輯迴歸模型是:假設數據服從伯努利分佈,通過極大化似然函數的方法,運用梯度下降來求解參數,來達到將數據二分類的目的。該模型的主要優點是解釋性比較好;如果特徵工程做得好,模型效果也非常不錯;訓練速度也比較快;輸出結果也很容易調整。但是該模型的缺點也很突出,比如:準確率不是很高,比較難處理數據不均衡問題等。

4.2.2 邏輯迴歸模型接口介紹

API:sklearn.linear_model.LogisticRegression(penalty='l2', dual=False, tol=0.0001, C=1.0,fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None,solver='liblinear', max_iter=100, multi_class='ovr', verbose=0,warm_start=False, n_jobs=1)

  • 主要參數解析:
  • penalty:正則化參數,l1 or l2, default: l2;
  • C:正則化係數λ的倒數,default: 1.0;
  • fit_intercept : 是否存在截距, default: True
  • solver:損失函數的優化方法,有以下四種可供選擇{newton-cg, lbfgs, liblinear,sag}, default: liblinear
  • multi_class:分類方式選擇,一般有{ovr, multinomial}, default:ovr;
  • class_weight:類型權重參數,默認為None
  • random_state:隨機數種子,默認為無
  • tol:迭代終止判據的誤差範圍
  • n_jobs:並行數,為-1時跟CPU核數一致,默認值為1。

以上是主要參數的簡單解析,如果大家想深入瞭解,可以參看 官方網址 。

4.2.3 支持向量機介紹

SVM(Support Vector Machine) 是一種二類分類模型。它的基本模型是在特徵空間中尋找間隔最大化的分離超平面的線性分類器。

(1)當訓練樣本線性可分時,通過硬間隔最大化,學習一個線性分類器,即線性可分支持向量機;

(2)當訓練數據近似線性可分時,引入鬆弛變量,通過軟間隔最大化,學習一個線性分類器,即線性支持向量機;

(3)當訓練數據線性不可分時,通過使用核技巧及軟間隔最大化,學習非線性支持向量機。

4.2.4 支持向量機分類模型API

sklearn.svm.SVC(C=1.0,kernel='rbf',degree=3,gamma='auto',coef0=0.0,shrinking=True,probability=False,tol=0.001,cache_size=200,class_weight=None,verbose=False,max_iter=-1,decision_function_shape=None,random_state=None)

  • 主要參數解析:
  • C:C-SVC的懲罰參數C,默認值是1.0。C越大,相當於懲罰鬆弛變量,希望鬆弛變量接近0,即對誤分類的懲罰增大,趨向於對訓練集全分對的情況,這樣對訓練集測試時準確率很高,但泛化能力弱。C值小,對誤分類的懲罰減小,允許容錯,將他們當成噪聲點,泛化能力較強。
  • kernel :核函數,默認是rbf,可以是‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’
  • 0 – 線性:u'v
  • 1 – 多項式:(gamma_u'_v + coef0)^degree
  • 2 – RBF函數:exp(-gamma|u-v|^2)
  • 3 –sigmoid:tanh(gamma_u'_v + coef0)
  • degree :多項式poly函數的維度,默認是3,選擇其他核函數時會被忽略。
  • gamma :rbf,poly 和sigmoid的核函數參數。默認是auto,則會選擇1/n_features
  • coef0 :核函數的常數項。對於poly和 sigmoid有用。
  • max_iter :最大迭代次數。-1為無限制。
  • decision_function_shape :ovo, ovr or None, default=None。

主要調節的參數有:C、kernel、degree、gamma、coef0;參數詳解請參考 官網 。

4.2.5 XGBoost 原理介紹

XGBoost 是 Boosting算法的其中一種, Boosting 算法的思想是許多弱分類器集成在一起,形成一個強分類器,基本原理是下一棵決策樹輸入樣本會與前面決策樹的訓練和預測相關。以為 XGBoost 是一種提升樹模型,所以他是將許多樹模型集成在一起,形成一個很強的分類器。而所用到的樹模型則是 CART 迴歸樹模型。

4.2.6 XGBoost 接口介紹

XGBoost.XGBRegressor(max_depth=3, learning_rate=0.1, n_estimators=100, silent=True, objective='reg:linear', booster='gbtree', n_jobs=1, nthread=None, gamma=0, min_child_weight=1, max_delta_step=0, subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, base_score=0.5, random_state=0, seed=None, missing=None, **kwargs)

  • 主要參數解析:
  • booster:模型類別,主要有2種,gbtree 和 gbliner,默認是: gbtree ;
  • nthread:使用 CPU 個數,為 -1 時表示使用全部 CPU 進行並行運算(默認),等於 1 時表示使用1個 CPU 進行運算;
  • scale_pos_weight:正樣本的權重,在二分類任務中,當正負樣本比例失衡時,設置正樣本的權重,模型效果更好。例如,當正負樣本比例為 1:10 時,scale_pos_weight=10;
  • n_estimatores:總共迭代的次數,即決策樹的個數;
  • early_stopping_rounds:在驗證集上,當連續n次迭代,分數沒有提高後,提前終止訓練
  • max_depth:樹的深度,默認值為6,典型值3-10;
  • min_child_weight:值越大,越容易欠擬合;值越小,越容易過擬合(值較大時,避免模型學習到局部的特殊樣本),默認為1;
  • learning_rate:學習率,控制每次迭代更新權重時的步長,默認0.3;
  • gamma:懲罰項係數,指定節點分裂所需的最小損失函數下降值;
  • alpha:L1 正則化係數,默認為 1 ;
  • lambda:L2 正則化係數,默認為 1 ;
  • seed:隨機種子。

如想詳細學習該 API ,可以參考 官網網址 。

4.3 建立機器學習模型並評估

4.3.1 建立模型

from time import time
from sklearn.metrics import f1_score
def train_classifier(clf, X_train, y_train):
''' 訓練模型 '''
# 記錄訓練時長
start = time()
clf.fit(X_train, y_train)
end = time()
print("訓練時間 {:.4f} 秒".format(end - start))

def predict_labels(clf, features, target):
''' 使用模型進行預測 '''
# 記錄預測時長
start = time()
y_pred = clf.predict(features)
end = time()
print("預測時間 in {:.4f} 秒".format(end - start))
return f1_score(target, y_pred, pos_label=1), sum(target == y_pred) / float(len(y_pred))
def train_predict(clf, X_train, y_train, X_test, y_test):
''' 訓練並評估模型 '''
# Indicate the classifier and the training set size
print("訓練 {} 模型,樣本數量 {}。".format(clf.__class__.__name__, len(X_train)))
# 訓練模型
train_classifier(clf, X_train, y_train)
# 在測試集上評估模型
f1, acc = predict_labels(clf, X_train, y_train)
print("訓練集上的 F1 分數和準確率為: {:.4f} , {:.4f}。".format(f1 , acc))
f1, acc = predict_labels(clf, X_test, y_test)
print("測試集上的 F1 分數和準確率為: {:.4f} , {:.4f}。".format(f1 , acc))

4.3.2 分別初始化,訓練和評估模型

import xgboost as xgb
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
# 分別建立三個模型

clf_A = LogisticRegression(random_state = 42)
clf_B = SVC(random_state = 42, kernel='rbf',gamma='auto')
clf_C = xgb.XGBClassifier(seed = 42)
train_predict(clf_A, X_train, y_train, X_test, y_test)
print('')
train_predict(clf_B, X_train, y_train, X_test, y_test)
print('')
train_predict(clf_C, X_train, y_train, X_test, y_test)
print('')
訓練 LogisticRegression 模型,樣本數量 3675。
訓練時間 0.0050 秒
預測時間 in 0.0010 秒
訓練集上的 F1 分數和準確率為: 0.6232 , 0.6648。
預測時間 in 0.0010 秒
測試集上的 F1 分數和準確率為: 0.6120 , 0.6457。
訓練 SVC 模型,樣本數量 3675。
訓練時間 0.5755 秒
預測時間 in 0.3620 秒
訓練集上的 F1 分數和準確率為: 0.6152 , 0.6746。
預測時間 in 0.1486 秒
測試集上的 F1 分數和準確率為: 0.5858 , 0.6400.
訓練 XGBClassifier 模型,樣本數量 3675. . .
訓練時間 0.4079 秒
預測時間 in 0.0110 秒
訓練集上的 F1 分數和準確率為: 0.6652 , 0.7067.
預測時間 in 0.0060 秒
測試集上的 F1 分數和準確率為: 0.5844 , 0.6279。

通過運行結果,我們發現:

  • 在訓練時間上, 邏輯迴歸
    耗時最短,XGBoost 耗時最長,為 2 秒多。
  • 在預測時間上, 邏輯迴歸 耗時最短,支持向量機耗時最長。
  • 在訓練集上 F1 分數方面, XGBoost 得分最高,支持向量機得分最低,但是差距不是很大。
  • 在訓練集上準確率方面分析, XGBoost 得分最高,邏輯迴歸最低。
  • 在測試集上 F1 分數方面分析, 邏輯迴歸 的最好,其餘兩個模型基本相等,相對較低。
  • 在測試集上準確率方面分析, 邏輯迴歸 支持向量機** 2 個模型基本相等,稍微比 XBGoost 高一點。

4.4 超參數調整

我們使用 sklearn 的 GridSearch 來進行超參數調參。

from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer
import xgboost as xgb
# 設置想要自動調參的參數

parameters = { 'n_estimators':[90,100,110],
'max_depth': [5,6,7],
}
# 初始化模型
clf = xgb.XGBClassifier(seed=42)
f1_scorer = make_scorer(f1_score,pos_label=1)
# 使用 grdi search 自動調參
grid_obj = GridSearchCV(clf,
scoring=f1_scorer,
param_grid=parameters,
cv=5)
grid_obj = grid_obj.fit(X_train,y_train)
# 得到最佳的模型
clf = grid_obj.best_estimator_
# print(clf)
# 查看最終的模型效果
f1, acc = predict_labels(clf, X_train, y_train)
print("F1 score and accuracy score for training set: {:.4f} , {:.4f}。".format(f1 , acc))
f1, acc = predict_labels(clf, X_test, y_test)
print("F1 score and accuracy score for test set: {:.4f} , {:.4f}。".format(f1 , acc))
預測時間 in 0.0368 秒
F1 score and accuracy score for training set: 0.7991 , 0.8201。
預測時間 in 0.0149 秒
F1 score and accuracy score for test set: 0.5702 , 0.6133。

4.5 保存模型和加載模型

然後我們可以把模型保存下來,以供以後使用。

import joblib
#保存模型
joblib.dump(clf, 'xgboost_model.model')
#讀取模型
xgb = joblib.load('xgboost_model.model')
# 然後我們嘗試來進行一個預測
sample1 = X_test.sample(n=5, random_state=2)
y_test_1 = y_test.sample(n=5, random_state=2)
print(sample1)
# 進行預測
y_pred = xgb.predict(sample1)

print("實際值:%s \n預測值:%s"%(y_test_1.values,y_pred))
HTGD ATGD HM1_D HM1_L HM1_W AM1_D AM1_L AM1_W HM2_D \
70 0.189646 -1.088217 0 0 1 0 1 0 0
5529 -0.668332 -0.901190 0 1 0 1 0 0 0
4297 -0.702311 -0.136082 0 1 0 0 1 0 0
5230 -0.654740 -1.302447 0 0 1 0 1 0 0
1307 1.438387 -0.269101 1 0 0 0 0 1 0
HM2_L HM2_W AM2_D AM2_L AM2_W HM3_D HM3_L HM3_W AM3_D AM3_L \
70 0 1 0 1 0 1 0 0 1 0
5529 0 1 0 1 0 1 0 0 0 1
4297 1 0 0 1 0 1 0 0 1 0
5230 1 0 1 0 0 0 1 0 0 1
1307 0 1 0 0 1 1 0 0 0 0
AM3_W
70 0
5529 0
4297 0
5230 0
1307 1
實際值:[0 0 1 1 1]
預測值:[1 0 1 1 1]

通過以上,我們從 test 數據集中隨機挑選5個,預測值跟實際值相同的有 4 個,考慮到我們準確率不高,能夠得到這個結果來說還是比較幸運的。


分享到:


相關文章: