Previsão de resultados de futebol

Um modelo de aprendizado de máquina em Python usando a biblioteca Scikit-learn para prever os resultados de partidas de futebol na Russian Premier League (RPL).

Entrada


Eu fui inspirado a escrever este artigo pelo artigo Aprendizado de máquina: prever a matemática do EPL de 2018 . Nosso modelo de aprendizado de máquina será treinado nas estatísticas das partidas da Liga Russa (RPL) a partir da temporada 2015/2016 para prever os resultados dos próximos jogos. Dados extraídos do site de estatísticas de futebol do wyscout.com.
Código e dados estão disponíveis no github .

Dados


Conectamos as bibliotecas necessárias:

import pandas as pd import numpy as np import collections 

Os dados da correspondência estão no github .

 data = pd.read_csv("RPL.csv", encoding = 'cp1251', delimiter=';') data.head() 

imagem
O que significa xG e PPDA?
xG (objetivos esperados) é um modelo de objetivos esperados. É baseado no indicador de chutes no gol, com base nos quais podemos estimar quantos gols a equipe realmente teve que marcar se levarmos em conta todos os chutes que ele realizou. Saiba mais sobre o xG.
PPDA (passes permitidos por ação defensiva) é um indicador de estatística de futebol que permite determinar a intensidade da pressão em uma partida. Quanto menor o valor do PPDA, maior a intensidade do jogo na defesa. Saiba mais sobre o PPDA
PPDA = número de passes feitos pela equipe atacante / número de ações em defesa


Vamos prever os resultados das partidas para a segunda parte da temporada 2018/2019 (ou seja, partidas disputadas em 2019). A lista de times que jogam nesta temporada (sem levar em consideração Arsenal, Orenburg, Dínamo, Krylia Sovetov e Yenisei, porque eles não têm estatísticas para as temporadas anteriores ou existem poucas estatísticas sobre eles):

 RPL_2018_2019 = pd.read_csv('Team Name 2018 2019.csv', encoding = 'cp1251') teamList = RPL_2018_2019['Team Name'].tolist() teamList 

imagem

Removemos partidas com equipes que não participam da temporada 2018/2019:

 deleteTeam = [x for x in pd.unique(data['']) if x not in teamList] for name in deleteTeam: data = data[data[''] != name] data = data[data[''] != name] data = data.reset_index(drop=True) 

Função que retorna estatísticas da equipe para uma temporada:

 def GetSeasonTeamStat(team, season): goalScored = 0 #  goalAllowed = 0 #  gameWin = 0 # gameDraw = 0 # gameLost = 0 # totalScore = 0 #   matches = 0 #   xG = 0 #  shot = 0 # shotOnTarget = 0 #   cross = 0 # accurateCross = 0 #  totalHandle = 0 #  averageHandle = 0 #     Pass = 0 # accuratePass = 0 #  PPDA = 0 #    for i in range(len(data)): if (((data[''][i] == season) and (data[''][i] == team) and (data[''][i] == 2)) or ((data[''][i] == season-1) and (data[''][i] == team) and (data[''][i] == 1))): matches += 1 goalScored += data[''][i] goalAllowed += data[''][i] if (data[''][i] > data[''][i]): totalScore += 3 gameWin += 1 elif (data[''][i] < data[''][i]): gameLost +=1 else: totalScore += 1 gameDraw += 1 xG += data['xG'][i] shot += data[''][i] shotOnTarget += data['  '][i] Pass += data[''][i] accuratePass += data[' '][i] totalHandle += data[''][i] cross += data[''][i] accurateCross += data[' '][i] PPDA += data['PPDA'][i] averageHandle = round(totalHandle/matches, 3) #      return [gameWin, gameDraw, gameLost, goalScored, goalAllowed, totalScore, round(xG, 3), round(PPDA, 3), shot, shotOnTarget, Pass, accuratePass, cross, accurateCross, round(averageHandle, 3)] 

Exemplo de uso de função:

 GetSeasonTeamStat("", 2018) #    2017/2018 

imagem

Por conveniência, podemos adicionar o código:

 returnNames = ["", "", "", "\n ", " ", "\n ", "\nxG ( )", "PPDA ( )", "\n", "  ", "\n", " ", "\n", " ", "\n (   )"] for i, n in zip(returnNames, GetSeasonTeamStat("", 2018)): print(i, n) 

imagem

Por que nossas estatísticas são diferentes das estatísticas reais
Estatísticas reais de Spartak na temporada 2017/2018:

imagem

As estatísticas são diferentes porque Levamos em consideração os jogos das equipes que não jogam no RPL na temporada 2018/2019. Ou seja, não levamos em consideração as partidas de Spartak - SKA, Spartak - Tosno, etc.

Função que retornará estatísticas de todas as equipes para a temporada:

 def GetSeasonAllTeamStat(season): annual = collections.defaultdict(list) for team in teamList: team_vector = GetSeasonTeamStat(team, season) annual[team] = team_vector return annual 

Modelo de treinamento


Escreveremos uma função que retornará dados de treinamento. Ela cria um dicionário com vetores de equipe para todas as estações. Para cada jogo, a função calcula a diferença entre os vetores das equipes para uma determinada temporada e a grava no xTrain. A função define yTrain como 1 se a equipe da casa vencer e 0 caso contrário.

 def GetTrainingData(seasons): totalNumGames = 0 for season in seasons: annual = data[data[''] == season] totalNumGames += len(annual.index) numFeatures = len(GetSeasonTeamStat('', 2016)) #     xTrain = np.zeros(( totalNumGames, numFeatures)) yTrain = np.zeros(( totalNumGames )) indexCounter = 0 for season in seasons: team_vectors = GetSeasonAllTeamStat(season) annual = data[data[''] == season] numGamesInYear = len(annual.index) xTrainAnnual = np.zeros(( numGamesInYear, numFeatures)) yTrainAnnual = np.zeros(( numGamesInYear )) counter = 0 for index, row in annual.iterrows(): team = row[''] t_vector = team_vectors[team] rivals = row[''] r_vector = team_vectors[rivals] diff = [a - b for a, b in zip(t_vector, r_vector)] if len(diff) != 0: xTrainAnnual[counter] = diff if team == row['']: yTrainAnnual[counter] = 1 else: yTrainAnnual[counter] = 0 counter += 1 xTrain[indexCounter:numGamesInYear+indexCounter] = xTrainAnnual yTrain[indexCounter:numGamesInYear+indexCounter] = yTrainAnnual indexCounter += numGamesInYear return xTrain, yTrain 

Aprendemos dados de treinamento para todas as estações de 2015/2016 a 2018/2019.

 years = range(2016,2019) xTrain, yTrain = GetTrainingData(years) 

Para prever a probabilidade de vitória, usaremos o algoritmo de aprendizado de máquina LinearRegression da biblioteca Scikit-Learn.

 from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(xTrain, yTrain) 

Escreveremos uma função que retornará previsões. Ele retornará um valor entre 0 e 1, onde 0 é a perda e 1 é o ganho.

 def createGamePrediction(team1_vector, team2_vector): diff = [[a - b for a, b in zip(team1_vector, team2_vector)]] predictions = model.predict(diff) return predictions 

Resultados


Por exemplo, vejamos as previsões do algoritmo para a partida Zenit - Spartak

 team1_name = "" team2_name = "" team1_vector = GetSeasonTeamStat(team1_name, 2019) team2_vector = GetSeasonTeamStat(team2_name, 2019) print (',   ' + team1_name + ':', createGamePrediction(team1_vector, team2_vector)) print (',   ' + team2_name + ':', createGamePrediction(team2_vector, team1_vector)) 

imagem

No jogo Zenit - Spartak, a probabilidade de vitória para Zenit é de 47% (17/03/2019 Spartak 1-1 Zenit).

Proponho fazer uma previsão considerando o seguinte:
Até 40% - o time não ganha (perda ou empate)
De 40% a 60% - alta probabilidade de empate
De 60% - a equipe definitivamente não perderá (vencerá ou empatará)

Obter previsões para o CSKA contra todos os outros clubes

 for team_name in teamList: team1_name = "" team2_name = team_name if(team1_name != team2_name): team1_vector = GetSeasonTeamStat(team1_name, 2019) team2_vector = GetSeasonTeamStat(team2_name, 2019) print(team1_name, createGamePrediction(team1_vector, team2_vector), " - ", team2_name, createGamePrediction(team2_vector, team1_vector,)) 

imagem

O algoritmo forneceu uma previsão correta para quase todas as correspondências que não terminaram em empate. A única previsão imprecisa: CSKA Moscou - Zenit. A probabilidade de vitória do CSKA é maior em 0,001; pode-se supor que as equipes tenham força igual e disputarão um empate, mas no final o Zenit venceu (3-1).

Conclusão


Nosso algoritmo é muito primitivo. Ele leva em conta apenas as estatísticas dos jogos (e depois apenas 15 parâmetros básicos), e o resultado no futebol depende de muitos fatores. Até a condição do campo ou o clima podem afetar o resultado do jogo.

Em seguida, gostaria de aumentar o número de sinais, criar uma amostra de teste, experimentar vários algoritmos, configurar o modelo e obter previsões mais precisas.

Ficaria muito grato se você deixar suas idéias e comentários.

Source: https://habr.com/ru/post/pt456226/


All Articles