NBA Basketball Prediction
Uma previsão que não dá para auditar é um palpite com uma porcentagem em cima.
58.84%
melhor taxa de acerto
632
partidas usadas no treino
9
métricas ponderadas do time
12.5-15k
gerações analisadas
Por que existe
Um Projeto Integrador: o trabalho de conclusão do Curso Técnico Integrado em Informática do IFSC câmpus Gaspar, escrito com três colegas e defendido em 2021. O tema veio de um mercado que crescia rápido no Brasil naquele momento e não sofria de falta de gente confiante — o de apostas esportivas. Todo mundo tinha um método. Ninguém publicava um.
Algoritmos genéticos eram a lacuna. Redes neurais e regressão já tinham sido apontadas para o basquete várias vezes; um vetor de pesos evoluído contra dados reais de partidas, não. E a pergunta que de fato nos interessava nunca foi se aquilo ganharia dinheiro. Era o que aquilo diria sobre o jogo: quais números do primeiro quarto de um time carregam o primeiro quarto, e o quanto.
O problema
Prever uma partida inteira da NBA é um problema disputado, com muito trabalho anterior. Prever só o primeiro quarto é mais estreito e mais duro — menos posses de bola, menos tempo para o time melhor se impor, mais ruído por minuto jogado.
A entrada é deliberadamente magra. De cada time, as médias dos seus próprios primeiros quartos anteriores: pontos, saldo, rebotes, porcentagens de acerto, turnovers, vitórias. Coletadas do Basketball Reference com Selenium e BeautifulSoup para um esquema SQLite de times, partidas e participações por time. Sem lesões, sem escalações, sem odds, sem dias de descanso. Se nove pesos sobre esses números conseguem superar o acaso, isso é uma afirmação sobre os próprios números.
Arquitetura
Um algoritmo genético, e em volta dele apenas o que alimenta um. Um indivíduo são nove pesos sorteados no intervalo [-100, 100], um por métrica. Seu fitness é a fração das partidas da janela de treino cujo primeiro quarto ele acerta. As gerações são elitistas — os melhores indivíduos seguem intactos, o resto é recombinado e sofre mutação — e é por isso que um log de fitness só sobe, e por isso que ele é uma escada, não uma curva.
A linha de comando dirige quatro verbos: coletar, treinar, prever e validar. O último é o que importa. Ele passa o algoritmo e dois controles pela mesma função de fitness, para que uma boa pontuação tenha contra o que ser boa.
Decisões que merecem explicação
- 01
Não existe conjunto de teste separado, e esta página não vai fingir que existe. O fitness é medido nas mesmas 632 partidas contra as quais os pesos são treinados, então 58,84% é um número dentro da amostra. A única evidência fora da amostra que produzimos foi mais tosca e mais pública: previsões de partidas futuras publicadas em uma conta no Twitter conforme os jogos aconteciam.
- 02
Os controles vieram antes das conclusões. Um vetor de pesos totalmente aleatório e um vetor constante de uns passam pela função de fitness idêntica, porque 58% sozinho é um número sem nada com que ser comparado.
- 03
Os parâmetros foram calibrados um de cada vez contra uma configuração fixa, o que os trata como independentes quando eles claramente não são. O artigo diz isso em vez de apresentar a varredura como encerrada — e a única varredura que voltou plana foi relatada como inconclusiva, não maquiada de descoberta.
- 04
Todo eixo de fitness começa em 50%. Um previsor abaixo do acaso não é um previsor ruim, é um previsor útil lido ao contrário; então 50% é o piso que carrega algum significado.
- 05
O requisito pedia 70% de acerto. Nunca foi atingido, e permaneceu no documento como um requisito falhado em vez de ser silenciosamente reescrito para baixo, até o que de fato alcançamos.
Melhor fitness por geração
Duas execuções de treino versionadas no repositório, redesenhadas a partir dos próprios logs. As duas sobem rápido e param: a última melhoria da execução mais longa acontece na geração 405, e as mil gerações seguintes não produzem nada. Nenhuma das duas chega perto dos 70% que os requisitos pediam.
- Execução A — 1.410 gerações, 56,41% a 59,06%
- Execução B — 515 gerações, 59,06% a 60,47%
Melhor fitness: a porcentagem de primeiros quartos previstos corretamente.
Fonte: src/data/json/gen/teste1.json e teste2.json, amostrados a cada 5 gerações. O algoritmo é elitista, então o melhor fitness nunca cai e a linha é uma escada.
Taxa de acerto por método
Onde o algoritmo fica em relação aos trabalhos correlatos com que o artigo se compara, e em relação a um previsor aleatório. Com 58.84%, ele supera o acaso e a referência do futebol, e fica atrás de todos os métodos de basquete acima dele. As barras não são equivalentes e não devem ser lidas como se fossem: só este projeto prevê um primeiro quarto — os três acima preveem partidas inteiras, dois deles em outra liga.
Regressão linear
NBB, partida inteira · Michelon et al., 2017
75%
Rede neural
NBB, partida inteira · Michelon et al., 2017
74%
Mineração de dados, aprendizado de máquina
Playoffs da NBA, partida inteira · Fukano et al., 2018
73.19%
Este projeto — algoritmo genético
NBA, primeiro quarto · 2021
58.84%
Gradient boosting
Futebol, partida inteira · Fernandes, 2019
52.78%
Previsor aleatório
Controle, 50% por construção
50%
Fonte: o artigo, seção 4.3, figuras 18 e 19.
O vetor de pesos do melhor indivíduo
Os nove multiplicadores em que o cromossomo vencedor parou, aplicados às médias de primeiro quarto do próprio time. A magnitude é o quanto o algoritmo decidiu que a métrica importava; o sinal negativo significa que ele encontrou a métrica mais alta em times que acabam perdendo o quarto. Dois deles contrariam a intuição — a média de pontos do time recebe peso negativo — o que o artigo sinaliza em vez de explicar.
won
+98.4
points
-59.5
spread
+78.5
offensive_rebounds
+81.3
defensive_rebounds
+4.9
field_goals_percentage
-54.9
three_point_field_goals_percentage
+98.0
turnover
-36.2
turnover (como impresso)
+72.5
Fonte: o artigo, seção 5.1. Ele imprime turnover como rótulo dos dois últimos genes; reproduzido aqui como publicado, já que o esquema do banco tem apenas uma coluna com esse nome.
Varredura de calibração
Cinco parâmetros, cada um movido sozinho contra uma configuração fixa, cinco ciclos de cem gerações por valor — entre 12.500 e 15.000 gerações distintas no total. A última linha é a que vale ler: a fração de indivíduos gerados aleatoriamente não fez diferença mensurável em nenhum ponto entre 0 e 40, o que não é um resultado. É uma varredura que não resolveu.
| Parâmetro | Valores testados | Escolhido |
|---|---|---|
| mutation_chance | 1 · 5 · 15 · 30 · 60 · 95 | 15 |
| mutation_magnitude | [0, 0] · [-1, 1] · [-10, 10] · [-100, 100] · [-1000, 1000] | [-1, 1] |
| population_size | 15 · 50 · 100 · 200 · 500 | 500 |
| persistent_individuals | 0 · 5 · 10 · 25 · 40 | 5 |
| random_individuals | 0 · 5 · 10 · 25 · 40 | 0 / 25inconclusivo |
Fonte: o artigo, seção 4.2, figuras 12 a 16.
Stack
Situação
Terminado e congelado. Trabalho acadêmico, defendido em 2021 no IFSC Gaspar por Bernardo Pires Mesko, Grégori Sabel, João Vitor Waldrich e Rafael Guilherme Onesko, sob orientação de Thiago Lipinski-Paes e Raphael Silvano Ferreira Silva. O código e o artigo são públicos; nenhum dos dois recebe manutenção.
O resumo honesto é que funcionou e não foi suficiente. 58,84% supera o acaso com folga e fica bem abaixo dos 70% que haviam sido pedidos. Todo número desta página vem do artigo ou de um log de treino versionado no repositório — e os que favorecem o projeto não estão aqui sem os que não favorecem.