Prevendo a pontuação final do Campeonato Brasileiro — Parte III

Gabriel Aparecido Fonseca
Data Hackers
Published in
12 min readDec 14, 2019

E aí pessoal, essa é a terceira e última parte do tutorial sobre previsão da pontuação do campeonato brasileiro. Nas duas primeiras partes eu mostrei qual banco de dados utilizei e como obtive ele além das etapas de pré-processamento que foram realizadas. Se você ainda não viu as outras partes, corre lá primeiro para você entender tudo o que foi feito:

Prevendo a pontuação final do Campeonato Brasileiro — Parte I

Prevendo a pontuação final do Campeonato Brasileiro — Parte II

Apenas gostaria de relembrar que esse material foi utilizado pelo Clube da Aposta em uma série de postagens no nosso blog prevendo a pontuação e chance de rebaixamento e título para times de diversos campeonatos:

Veja a nossa análise para título e classificação para os principais campeonatos: https://clubedaposta.com/probabilidade-titulo-classificacao/

E confira também a nossa análise de rebaixamento: https://clubedaposta.com/probabilidade-rebaixamento/

Na terceira e última parte desse tutorial irei mostrar os modelos e técnicas utilizadas na predição da pontuação. Acredito que essa seja a etapa mais esperada por todos, é aqui que a magia realmente acontece. Mas é importante ter em mente que o tratamento dos dados é uma parte considerável do trabalho de classificação, predição ou mesmo análise de dados.

Antes de colocar a mão na massa, devo mencionar que quem conduziu esse trabalho sobre os modelos de machine learning em sua maior parte foi o Betão, que é um dos mandachuvas do clube e entende muito de ciência de dados e apostas esportivas. Se quiser saber mais sobre ele, dá uma passada lá no linkedin: http://bit.ly/HumbertoLinkedin.

Agora chega de papo e vamos ao código!

1 — Divisão do conjunto de dados em treinamento e teste

A primeira etapa antes de apresentar os dados aos modelos é realizar a divisão dele. Uma parte deve ser usada no treinamento enquanto a outra é usada no teste ou validação do modelo. Isso serve para verificar se o modelo treinado é capaz de generalizar com uma precisão satisfatória para dados desconhecidos. Para fazer isso no scikit-learn é bem simples, basta usar o código:

Você apenas precisa indicar o tamanho do conjunto de teste que você quer usar, no caso usamos 20% para teste e 80% para treino.

2 — Métricas de avaliação

Antes de proceder com as técnicas utilizadas devemos saber como avaliamos se um modelo é bom ou ruim. Existem diversas métricas que podem ser utilizadas em trabalhos de regressão. Nesse projeto foram usados o erro quadrático médio e o coeficiente de determinação.

2.1 — Erro Quadrático Médio

O erro quadrático médio (mean squared error — MSE) é uma métrica que verifica quão distante do valor real (ou desejado) a estimativa do modelo se encontra. É um método muito utilizado na comparação entre técnicas de regressão e é calculado utilizando a equação abaixo:

onde y é o valor real (ou desejado), ŷ é o valor estimado pelo modelo e n é o número de amostras.

Como é possível imaginar, quanto menor o valor do MSE melhor será o modelo. Uma métrica conjuntamente utilizada com o MSE é a raiz do erro quadrático médio (RMSE), ela permite retornar o erro para a grandeza física com a qual se está trabalhando.

2.2 — Coeficiente de determinação (R²)

O coeficiente de determinação (também chamado de R², “r quadrado”) representa a proporção da variância (de y) que é explicada pelas variáveis independentes no modelo. Ele fornece uma indicação de quão bom foi o treinamento e assim é uma medida de como o modelo irá predizer exemplos não vistos. O melhor valor possível é 1,0 e pode ser negativo (porque o modelo pode ser arbitrariamente pior). Um modelo constante que sempre prediz o valor esperado de y desconsiderando as variáveis de entrada teria um R² de 0,0. Esse texto foi traduzido da documentação do scikit-learn e maiores informações podem ser encontradas lá.

Para calcular o coeficiente de determinação utiliza-se a seguinte equação:

3 —Árvores de decisão

Antes de se falar sobre o modelo que foi precisamente usado nesse projeto, é necessário falar sobre árvores de decisão. Esse é um método não paramétrico de aprendizado supervisionado usado tanto em problemas de classificação quanto regressão. O objetivo é criar um modelo que prediz o valor de uma variável alvo a partir do aprendizado de simples regras de decisão inferidas das características dos dados.

Geralmente são simples de serem compreendidos e interpretados por serem baseados em regras de decisão SE-ENTÃO e por ser possível criar visualizações em árvores que facilitam verificar como o modelo se comporta. Uma árvore de decisão é composta por um nó raiz, nós interiores e nós terminais (ou folhas) conforme o diagrama a seguir.

3.1 — Ensembles

Árvores de decisão podem ser combinadas para criar modelos mais elaborados, complexos e robustos, eles são geralmente chamados de ensembles, alguns exemplos são: random forest (florestas aleatórias), AdaBoost, Gradient Boosting, etc.

Para entender como os ensembles funcionam é importante compreender dois conceitos: bagging e boosting.

3.1.1 — Bagging

Bagging (uma abreviação de “bootstrap aggregating”) considera as árvores de decisão independentemente uma das outras, então ele funciona de forma paralela possibilitando realizar o treinamento delas simultaneamente.

É importante também definir o que é “bootstrapping”. Essa é uma técnica que gera amostras menores a partir de um conjunto de dados maior sorteadas aleatoriamente com reposição. Isso faz com que o novo conjunto gerado tenha boas propriedades estatísticas, sendo composto por amostras representativas e independentes da distribuição real dos dados.

Voltando ao conceito de bagging, o objetivo é ajustar vários modelos independentes e calcular a média de suas predições para obter um modelo com uma variância menor. No entanto, como na prática não é viável realizar o ajuste de modelos totalmente independentes porque exigiria muitos dados, são utilizadas amostras bootstrap.

Esse processo pode ser resumido então da seguinte maneira:

Um modelo que utiliza o método bagging é o random forest.

3.1.2 — Boosting

Boosting também é uma técnica para construir famílias de modelos (como árvores de decisão) e agregar elas para obter um modelo mais robusto, com melhor desempenho. Ela consiste em ajustar múltiplos modelos sequencialmente de um modo adaptativo, ou seja, cada modelo na sequência é ajustado dando mais importância a observações no conjunto de dados que são mal manipulados pelos modelos anteriores da sequência. Isso permite que o modelo final obtido tenha menor bias (viés). Um ponto de atenção é que em geral modelos baseados em boosting são computacionalmente mais custosos do que baseados em bagging uma vez que não podem ser executados paralelamente. Um resumo das etapas da técnica boosting pode ser vista a seguir.

Os dois principais algoritmos de boosting são o Adaptative Boosting (AdaBoost) e Grandient Boosting, eles se diferem quanto ao método de criação e agregação dos modelos simples. Aqui iremos focar apenas no Gradient Boosting.

4 — Gradient Boosting

No Gradient Boosting, o modelo final a ser construído é baseado na soma ponderada dos modelos simples, conforme a fórmula abaixo:

Encontrar o modelo ótimo sob essa forma é difícil e uma abordagem iterativa é necessária. Para isso, o Gradient Boosting utiliza a técnica de gradiente descendente, em cada iteração um modelo simples é ajustado ao oposto do gradiente do erro de ajuste atual em relação ao modelo atual do conjunto. Resumidamente os modelos são ajustados a partir da minimização do erro de ajuste utilizando a técnica de gradiente descendente. Pode ser meio confuso, mas essa é uma técnica bem famosa de otimização, e muito utilizada no ajuste dos pesos de redes neurais. Não quero aprofundar muito para esse texto não ficar maior do que ele já está, então recomendo que você leia esse artigo maravilhoso que o @joseph.rocca escreveu para o Towards Data Science, e que eu traduzi algumas partes e adicionei outras para escrever essas seções teóricas.

5 — XGBoost

Tudo isso que falei até agora foi uma breve (nem tão breve assim, pelo jeito) introdução para falar sobre o método de predição que utilizamos no nosso modelo de predição de pontuação, o XGBoost. Essa é uma técnica baseada no Gradiente Boosting de onde vem o seu nome “Extreme Gradient Boosting”, que combina métodos de otimização de software e hardware para conseguir resultados superiores.

Talvez você já tenha ouvido falar sobre essa técnica porque ela virou a queridinha dos usuários do Kaggle nos últimos anos. Acredito que o background que dei até aqui foi o suficiente para entender essas técnicas, se você quiser maiores informações sobre o XGBoost dê uma olhada nos links abaixo:

Paper oficial dos autores sobre o algoritmo: https://arxiv.org/abs/1603.02754

Documentação oficial do XGBoost: https://xgboost.readthedocs.io/en/latest/

Texto interessante do blog Machine Learning Mastery falando sobre essa técnica: https://machinelearningmastery.com/gentle-introduction-xgboost-applied-machine-learning/

O que nos interessa aqui é como usar o XGBoost em Python. Para isso é necessário utilizar a classe XGBRegressor(como estamos interessados em realizar uma regressão). O código completo de treinamento e teste está abaixo.

Observe que são utilizados 4 hiper-parâmetros:

learning_rate que é a taxa de aprendizado;

n_estimators que é a quantidade de árvores que o modelo final irá ter;

max_depth que é a profundidade máxima das árvores;

min_child_weight que é a soma mínima do peso da instância necessária em um filho.

Você deve ter observado que esses hiper-parâmetros são chaves de um dicionário que possuem como correspondência uma lista de valores. Isso é feito porque será utilizada uma técnica de hiper-parametrização para escolher dentre eles a combinação que irá gerar o melhor modelo possível.

Para isso, será utilizada uma técnica chamada Randomized Search. Ela realiza esse teste com os valores dos hiper-parâmetros retornando aqueles que gerarem o melhor modelo considerando os dados em questão. Para utilizá-la basta usar a classe RandomizedSearchCV. O primeiro atributo a ser passado para ela deve ser o modelo com o qual se deseja realizar a hiper-parametrização. O segundo é o dicionário contendo os hiper-parâmetros e seus respectivos valores. O terceiro é a quantidade de divisões que será realizada no conjunto de treinamento, como o valor passado foi 5, uma parte é utilizada para teste e 4 para treino e isso é feito recursivamente até todas as partes serem usadas tanto para treinamento quanto para teste. O último atributo n_jobs serve somente para indicar quantas tarefas serão executas em paralelo.

Inicializada a classe, o ajuste é realizado com o método fit() sendo necessário passar para ele o conjunto de dados de treinamento (X_train) e o target (y_train). Após isso o conjunto de testes é utilizado para se calcular o R² do modelo usando o método score(). Para os nosso dados obtivemos 0,7508 que é um valor consideravelmente bom dada a natureza do problema. Analisando o atributo best_params_ é possível verificar quais são os melhores parâmetros encontrados pelo Randomized Search, para o nosso caso foram:{'n_estimators':500,'min_child_weight':3,'max_depth':4,'learning_rate':0.1}

Além disso, a variável y_pred irá armazenar a predição para o conjunto de teste realizada com o método predict(), isso será importante na modelagem do erro.

6 — Modelando o Erro

Para esse projeto também foi necessário realizar a modelagem/predição do erro, pois ele será necessário para realizar várias simulações e gerar os inúmeros cenários possíveis.

Esse processo é bastante similar ao que foi apresentado na seção anterior, a diferença reside do fato de que em vez de se utilizar o conjunto de treinamento no método fit(), são utilizados os dados de teste e como target utiliza-se o erro quadrático médio entre os valores preditos e desejados. Portanto, esse é um modelo que visa prever os erros para os dados de entrada.

Nesse caso, obteve-se um R² de 0,7508 e o melhor modelo gerado é produzido com os seguintes hiper-parâmetros: {'n_estimators':400,'min_child_weight':3,'max_depth':3,'learning_rate':0.1}. O código utilizado para isso é apresentado a seguir.

7 — Realizando predições

Realizar alguma predição agora é extremamente simples.

Primeiro é necessário ir ao site WorldSoccer de onde retiramos os dados e escolher o campeonato e rodada atual do campeonato de interesse. Por exemplo, se escolhermos o Brasileirão na rodada 22, usaríamos o seguinte código para pegar os dados (isso vem do primeiro tutorial que publiquei, se estiver perdido dá uma passada lá): df_bra_2019 = get_table(22,38,'bra-serie-a',2019. Após isso temos que normalizar os dados e aplicar PCA como apresentei no segundo tutorial:

Então, finalmente podemos realizar a predição e será gerada a tabela final do campeonato esperada pelo modelo.

Mas agora como podemos garantir que esse modelo é confiável? Primeiro algumas suposições deve ser feitas, a primeira delas é que os dados possuem uma distribuição normal. Essa suposição é suficiente na maioria dos casos, mas provavelmente vai revirar os olhos de alguns estatísticos, porque o certo seria realizar testes estatísticos para verificar essa condição.

A curva normal (ou Gaussiana) é muito usada em diversas situações e encontrada em diversas situações práticas. Ela possui formato de sino e algumas propriedade interessantes:

Média = Mediana = Moda.

A curva é simétrica: 50% dos valores são menores do que a média (𝜇) e 50% dos valores são maiores do que a média (𝜇).

A área total sob o gráfico é 1.

68% dos valores estão a um desvio padrão da média: 𝜇±𝜎.

95% dos valores estão a dois desvios padrão da média: 𝜇±2𝜎.

99.7% dos valores estão a três desvios padrão da média: 𝜇±3𝜎.

Imagem extraída do artigo: https://towardsdatascience.com/understanding-the-68-95-99-7-rule-for-a-normal-distribution-b7b7cbf760c2

Maiores informações sobre a curva/distribuição normal podem ser encontradas nesse artigo do Sonik Mishra: An introduction to Normal Distribution using Python. A ideia aqui é considerar a distribuição dos pontos ao final do campeonato sendo normal.

Na figura abaixo é possível observar a distribuição dos dados, e nota-se que ela não segue perfeitamente a distribuição normal. Apesar disso, essa é uma aproximação coerente e suficiente para esse problema. Caso, outra distribuição fosse considerada a realização das simulações poderiam se tornar complexas, trabalhosas ou inviáveis. Dessa maneira, considerando os resultado obtidos, a suposição realizada foi satisfatória.

Feitas as devidas suposições, pode-se utilizar a média da distribuição (que será fornecida pelo modelo de predição que foi desenvolvido) e o desvio padrão (proveniente do modelo de predição do erro) com a função de geração de números aleatórias do Numpy para gerar as previsões de cada time, como no código abaixo:

Por fim, é realizado um loop paralelo para repetir essa simulação 100.000 vezes, sendo que em cada um desses cenários cada time terá um valor simulado dentro de uma distribuição normal que considera o valor predito para pontuação e a predição do erro obtida.

A repetição desses processos de simulação é comum em problemas que possuem a natureza aleatória já que isso leva a resultados mais fidedignos e reais. Entender isso é muito simples, imagine que você lance uma moeda e veja o resultado, existem duas possibilidades: cara ou coroa. Então você tem 50% de chance de tirar um ou outro, mas às vezes você é muito sortudo e em 10 lançamentos, 7 foram cara e 3 coroa, opa isso não é o 50% que se espera correto? E se você lançar a moeda 100, 1.000, 10.000, 100.000 vezes? Quanto maior o número de lançamentos mais próximo do resultado 50/50 é obtido. Dessa maneira, a repetição do evento torna o resultado do modelo mais próximo da realidade.

Termina aqui essa série de tutoriais! Espero que tenham gostado e entendido os conceitos básicos utilizados. Se tiverem dúvidas, comentários ou críticas podem entrar em contato!

Achou interessante esse trabalho e quer ver o resultado prático? Clica nos banners abaixo para ir para o blog do Clube da Aposta e conferir.

Se tiver dúvidas, questões ou críticas pode me chamar nas redes sociais:

Twitter: https://twitter.com/gabriel1991

Instagram: https://www.instagram.com/gabrielfonseca1991/

Linkedin: https://www.linkedin.com/in/gabrielfonseca91/

GitHub: https://github.com/gabriel19913

--

--

Gabriel Aparecido Fonseca
Data Hackers

Data Scientist — Master Degree @ UFLA — Bachelor degree in Mechatronics Engineer @ CEFET — Python and data scientist enthusiast