機器學習競賽分享:通用的團隊競技類的數據分析挖掘方法


前言

  1. 該篇分享來源於NFL競賽官方的R語言版本,我做的主要是翻譯為Python版本;
  2. 分享中用到的技巧、構建的特征、展示數據的方式都可以應用到其他領域,比如籃球、足球、LOL、雙人羽毛球等等,只要是團隊競技,都可以從中獲益;
  3. 分享基於kaggle上的NFL大數據碗,也就是基於橄欖球;
  4. 泰森多邊形的概念最好可以去了解一下,可以不用糾結於公式,看看它對一些實際問題的抽象建模表示即可;

分享目的

言簡意賅的分享下在團隊競技類問題中一些有用的數據可視化、分析方法,不同的領域下對數據的處理確實千差萬別,每次遇到都深感自己的不足,幸好有各位大佬們的分享,跪謝;

分享目錄

  1. 使用matplotlib對比賽實況進行繪制,直觀理解某一時刻下的球場狀態;
  2. 使用泰森多邊形可視化各個球員的控制區域,借以理解、量化當前的形勢;
  3. 結合球員們的當前位置、速度、加速度、方向等信息繪制行進路線圖,可視化在N秒后的狀態;
  4. 分享一篇去年關於足球球員控制區域熱圖相關的論文中的信息,這部分沒有在項目里,大家感興趣可以看看這里

競賽鏈接

https://www.kaggle.com/c/nfl-big-data-bowl-2020

項目鏈接,該項目代碼已經public,大家可以copy下來直接運行

https://www.kaggle.com/holoong9291/nfl-tracking-wrangling-voronoi-and-sonars-python

github倉庫鏈接,更多做的過程中的一些思考、問題等可以在我的github中看到

https://github.com/NemoHoHaloAi/Competition/tree/master/kaggle/Top61%-0.01404-zzz-NFL-Big-Data-Bowl

一些橄欖球相關的基本概念

  • 美式足球:進攻方目的是通過跑動、傳球等盡快抵達對方半場,也就是達陣,而防守方的目的則是相反,盡全力去阻止對方的前進以及盡可能斷球;
  • 球場長120碼(109.728米),寬53碼(48.768米),周長是361.992米;
  • 球員:雙方場上共22人,進攻方11人,防守方11人,進攻方持球;
  • 進攻機會:進攻方共有四次機會,需要推進至少十碼;
  • 進攻方:進攻方的職責是通過四次機會,盡可能的向前推進10碼或者達陣,以獲得下一個四次機會,否則就需要交出球權;
  • 防守方:防守方則是相反,盡可能的阻止對方前進,如果能夠斷球那更好,直接球權交換;
  • handoff:傳球;
  • snap:發球;
  • 橄欖球基本知識點我了解
  • QB:四分衛,通常是發球后接球的那個人,一般口袋陣的中心,但是也不乏有像拉馬爾-傑克遜這樣的跑傳結合的QB,目前古典QB代表是新英格蘭愛國者NE湯姆-布雷迪
  • RB:跑衛,通常發球后進行沖刺、擺脫等,試圖接住本方QB的傳球后盡可能遠的沖刺;

分享正式開始

繪制比賽實況

繪制的必要性:想象這樣一種情況,我們拿到的都是比賽方的表格數據,不僅枯燥,而且不夠直觀,即便我們足夠了解橄欖球,依然無法通過數據感受到場上緊張的氛圍,進攻方的戰術安排,防守方的防守計划等等,而這些實際上都是隱藏在數據中的,這就好像是玩LOL或者Dota(我個人兩個都玩過,目前主要玩Dota),我給你十個英雄的坐標、移動速度、朝向、裝備,你很難理解當前的情況,但是如果看看游戲中的小地圖(假設小地圖能看到全部10個英雄),我相信大部分玩家都能看出當前是在爭奪肉山(搶大龍)、上高地、團戰、局部團戰等,因此繪制一個類似游戲中的小地圖是非常有用的,會幫助我們更深刻的了解比賽;

繪制代碼思路

  1. 區分進攻方和防守方,進攻方為紅色,防守方為綠色(因為進攻方和防守方會交替,所以進攻方可能是球隊A可能是球隊B);
  2. 將持球人用黑色特別標示出來;
  3. 將橄欖球場特有的碼線繪制出來,這一特點在籃球和足球中是沒有的,不過球隊半場的概念是通用的;
  4. 將得分線加粗繪制出來,得分線就是橄欖球中的TouchDown的區域,進攻方持球過了這條線得6分;

下面是相關代碼:

plt.figure(figsize=(30, 15))
plt.suptitle("Sample plays, standardized, Offense moving left to right")
plt.xlabel("Distance from offensive team's own end zone")
plt.ylabel("Y coordinate")

i=1
for gp,chance in sample_chart_v2.groupby('PlayId'):
    play_id = gp
    rusher = chance[chance.NflId==chance.NflIdRusher].iloc[0]
    offense = chance[chance.IsOnOffense]
    defense = chance[~chance.IsOnOffense]
    
    plt.subplot(3,2,i)
    i+=1
    plt.xlim(0,120)
    plt.ylim(-10,63)
    
    plt.scatter(offense.X_std,offense.Y_std,marker='o',c='red',s=55,alpha=0.5,label='OFFENSE')
    plt.scatter(defense.X_std,defense.Y_std,marker='o',c='green',s=55,alpha=0.5,label='DEFENSE')
    plt.scatter([rusher.X_std],[rusher.Y_std],marker='o',c='black',s=30,label='RUSHER')
    
    for line in range(10,130,10):
        plt.plot([line,line],[-100,100],c='silver',linewidth=0.8,linestyle='-')
    
    plt.plot([rusher.YardsFromOwnGoal,rusher.YardsFromOwnGoal],[-100,100],c='black',linewidth=1.5,linestyle=':')
    plt.plot([10,10],[-100,100],c='black',linewidth=2)
    plt.plot([110,110],[-100,100],c='black',linewidth=2)
    
    plt.title(play_id)
    plt.legend()

plt.show()

下面是效果圖:

可以看到,通常對比賽實況的可視化,可以清晰的看到當前處於哪個半場,距離達陣還有多遠,進攻方、防守方的站位分別是怎樣,持球人周圍的隊友、對手數量、距離等,這非常有利於后續的分析挖掘;

繪制動態比賽實況

繪制的目的:上面的繪制能看出是靜態的,而且並沒有用上球員的速度、加速度、面向、移動方向等數據,而我們知道球員總是處於不斷運動當中的,他們的當前狀態很重要,但是1s后,2s后可能更重要,這就是這一部分繪制的目的,強調每個球員在一段時間后的狀態,當然,這部分繪制有一個前提假設,那就是球員當前的速度、加速度、面向、移動方向等信息在短時間內是不變的,這一點也符合實際情況(),當然繪制與現實會有一些出入,但是這些差異不影響我們分析比賽;

繪制的代碼

plt.figure(figsize=(12, 8))
plt.suptitle("Playid:20170910001102")
plt.xlabel("Distance from offensive team's own end zone")
plt.ylabel("Y coordinate")

for gp,chance in sample_20170910001102.groupby('PlayId'):
    play_id = gp
    rusher = chance[chance.NflId==chance.NflIdRusher].iloc[0]
    offense = chance[chance.IsOnOffense]
    defense = chance[~chance.IsOnOffense]
    
    plt.subplot(1,1,1)
    i+=1
    
    x_min, x_max = chance.X_std.min()-5, chance.X_std.max()+5
    y_min, y_max = chance.Y_std.min()-5, chance.Y_std.max()+5
    plt.xlim(x_min,x_max)
    plt.ylim(y_min,y_max)
    
    plt.scatter(offense.X_std,offense.Y_std,marker='o',c='green',s=55,alpha=0.5,label='OFFENSE')
    plt.scatter(defense.X_std,defense.Y_std,marker='o',c='red',s=55,alpha=0.5,label='DEFENSE')
    plt.scatter([rusher.X_std],[rusher.Y_std],marker='o',c='black',s=30,label='RUSHER')
    
    for idx, row in chance.iterrows():
        _color='black' if row.IsBallCarrier else('green' if row.IsOnOffense else 'red')
        plt.arrow(row.X_std,row.Y_std,row.X_std_end-row.X_std,row.Y_std_end-row.Y_std,width=0.05,head_width=0.3,ec=_color,fc=_color)
    
    for line in range(10,130,10):
        plt.plot([line,line],[-100,100],c='silver',linewidth=0.8,linestyle='-')
    
    plt.plot([rusher.YardsFromOwnGoal,rusher.YardsFromOwnGoal],[-100,100],c='black',linewidth=1.5,linestyle=':')
    plt.plot([10,10],[-100,100],c='black',linewidth=2)
    plt.plot([110,110],[-100,100],c='black',linewidth=2)
    
    plt.title(play_id)
    plt.legend()

plt.show()

下面是效果圖:

繪制球員的泰森多邊形

繪制的必要性:百度百科定義點泰森多邊形-馮洛諾伊圖,簡單理解就是在一個球場中,每個球員都是一個個不重合的點,那么將整個球場划分到這些點上,那么可以認為每個點都有自己的一片控制區域,這也經常用於獅群領土划分、機場划分等問題,抽象出來都是同一個問題;

泰森多邊形的局限

  1. 沒有考慮球員與球員的差異;
  2. 沒有考慮球員的移動方向速度;
  3. 沒有考慮球的位置和影響;

相對來說,泰森多邊形是對這一類問題的簡單抽象,沒有考慮一些復雜因素,但是也揭示了很多信息;

繪制代碼如下

from scipy.spatial import Voronoi

plt.figure(figsize=(12, 8))
plt.suptitle("Sample plays, standardized, Offense moving left to right")
plt.xlabel("Distance from offensive team's own end zone")
plt.ylabel("Y coordinate")

sample_20171120000963 = train_1[train_1.PlayId==20171120000963].copy()
for gp,chance in sample_20171120000963.groupby('PlayId'):
    play_id = gp
    rusher = chance[chance.NflId==chance.NflIdRusher].iloc[0]
    offense = chance[chance.IsOnOffense]
    defense = chance[~chance.IsOnOffense]
    
    plt.subplot(1,1,1)
    i+=1
    
    x_min, x_max = chance.X_std.min()-2, chance.X_std.max()+2
    y_min, y_max = chance.Y_std.min()-2, chance.Y_std.max()+2
    #plt.xlim(8,50) # 特定
    plt.xlim(x_min,x_max)
    #plt.ylim(5,40) # 特定
    plt.ylim(y_min,y_max)
    #plt.plot([x_min,x_min,x_max,x_max,x_min],[y_min,y_max,y_max,y_min,y_min],c='black',linewidth=1.5)
    
    vor = Voronoi(np.array([[row.X_std,row.Y_std] for index, row in chance.iterrows()]))
    regions, vertices = voronoi_finite_polygons_2d(vor)
    for region in regions:
        polygon = vertices[region]
        plt.plot(*zip(*polygon),c='black',alpha=0.8)
    
    plt.scatter(offense.X_std,offense.Y_std,marker='o',c='green',s=55,alpha=0.5,label='OFFENSE')
    plt.scatter(defense.X_std,defense.Y_std,marker='o',c='red',s=55,alpha=0.5,label='DEFENSE')
    plt.scatter([rusher.X_std],[rusher.Y_std],marker='o',c='black',s=30,label='RUSHER')
    
    for line in range(10,130,10):
        plt.plot([line,line],[-100,100],c='silver',linewidth=0.8,linestyle='-')
    
    plt.plot([rusher.YardsFromOwnGoal,rusher.YardsFromOwnGoal],[-100,100],c='black',linewidth=1.5,linestyle=':')
    plt.plot([10,10],[-100,100],c='black',linewidth=2)
    plt.plot([110,110],[-100,100],c='black',linewidth=2)
    
    plt.title(play_id)
    plt.legend()

plt.show()

運行效果圖:

從該圖中,能清晰的看到各個球員的控制區域,有一個量化因子是將這部分區域相加,量化每個球隊的控制區域大小以及分布;

球員控制區域熱圖

這部分的分享目的:這部分分享來自這篇論文,我也還沒看完,所以分享內容會比較少,簡單概述一下。首先大家應該能看到泰森多邊形的不足,首先它沒有考慮速度等動態因素,其次它是針對每個球員而不是球隊的,但是我們知道球隊的信息更重要,因為這是團隊競技,因此缺乏對球員進行疊加的過程,而這些都是這篇論文重點探討的地方;

  1. 論文以足球數據為基礎,量化了某個時刻的球場控制熱圖,且考慮了球在其中的影響,注意此時還是假設每個球員的影響在球場中都是一個圓形區域:
  2. 但是理想狀態每個球員的影響可能是圓可能是橢圓,這里我想象一個球員是一顆石子,如果垂直丟入水中(球員靜置不動時),那么波紋就是一個圓形,如果是斜着拋入水中,那么波紋應該是一個與石子方向上的橢圓:
  3. 那么引入速度、方向后的球場控制熱圖,就應該是下面這樣:

實際上這篇論文還有很多內容,且主要內容是關於如何量化球員影響區域的,也就是如何抽象為一些數學公式上,當然這部分我目前也算不上理解,所以處於外行看熱鬧的階段,不過大家應該可以從中感受到數學建模的威力,以及這些東西的廣泛應用,希望這篇分享能夠幫到大家一點點;

最后

大家可以到我的Github上看看有沒有其他需要的東西,目前主要是自己做的機器學習項目、Python各種腳本工具、數據分析挖掘項目以及Follow的大佬、Fork的項目等:
https://github.com/NemoHoHaloAi


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM