Diferencia entre revisiones de «Lolstats Documentación Técnica»

De WikiGarcia
 
(No se muestran 16 ediciones intermedias del mismo usuario)
Línea 127: Línea 127:
password=
password=
database=lolstats
database=lolstats
</pre>
== Keras ==
Keras es la librería de Inteligencia Artificial basada en Tensorflow que usaremos para entrenar los modelos de aprendizaje.
=== Instalar CUDA ===
Es una plataforma de desarrollo creada por Nvidia para realizar computo en paralelo, ya sea con CPU o con una GPU.
https://askubuntu.com/a/799185
=== Instalar CUDNN ===
Es una librería para crear Deep Neural Networks con la ayuda de GPUs.
https://developer.nvidia.com/cudnn
https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html
=== Instalar Tensorflow ===
<pre>
pip install tensorflow-gpu
</pre>
=== Instalar Keras ===
<pre>
pip install keras
</pre>
</pre>


Línea 212: Línea 241:
Por la naturaleza y funcionamiento de la API de Riot, es necesario realizar una estrategia como la siguiente:
Por la naturaleza y funcionamiento de la API de Riot, es necesario realizar una estrategia como la siguiente:


# Obtener los nombres e identificadores de los 1,000 primeros invocadores en el ranking (Challenger).
# Obtener los nombres e identificadores de los 1,000 primeros invocadores en el ranking (Challenger, Grandmaster, Master, etc.).
# Para cada invocador obtener su historial de las últimas 100 partidas clasificatorias. Esto para tener la referencia de otros invocadores en los mismos rangos.
# Para cada invocador obtener su historial de las últimas 100 partidas clasificatorias. Esto para tener la referencia de otros invocadores en los mismos rangos.
# Para cada partida obtener los datos de los 9 invocadores restantes.
# Para cada partida obtener los datos de los 9 invocadores restantes.
Línea 258: Línea 287:
</pre>
</pre>


Para obtener la lista de los invocadores en una cola clasificatoria se utiliza el método League-exp-v4. La URL que se usa para dicha tarea es:
A continuación podemos comenzar a hacer la recopilación de los datos. El proceso para obtener la lista de 1000 invocadores con sus partidas se describe en el código mostrado a continuación, donde se consultan los siguientes métodos de la API:
 
* League-exp-v4 (getSummonersFromAPI): Devuelve la lista de los invocadores en una cola clasificatoria. La cola en la que se busca es ''RANKED_SOLO_5x5'' en la división ''CHALLENGER I'', ''GRANDMASTER I'', ''MATER I'' y así sucesivamente hasta completar los 1,000.


https://la1.api.riotgames.com/lol/league-exp/v4/entries/RANKED_SOLO_5x5/CHALLENGER/I?page=1
https://la1.api.riotgames.com/lol/league-exp/v4/entries/RANKED_SOLO_5x5/CHALLENGER/I?page=1
* Summoner-v4 (getSummonerById): Devuelve la información del invocador identificado con el ID de Invocador encriptado. Este dato es el que se obtiene de League-exp-v4.
https://la1.api.riotgames.com/lol/summoner/v4/summoners/{encryptedSummonerId}
* Match-v5 (getSummonerGames): Devuelve el historial de partidas de un invocador identificado con su PUUID. Este dato se obtiene de Summoner-v4. Solo obtenemos las partidas de la cola clasificatoria.
https://la1.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?queue=420&start=0&count=100
* Match-v5 (getGameData): De este método también se consultan los detalles de cada partida identificada con su ID de partida.
https://la1.api.riotgames.com/lol/match/v5/matches/{matchid}


<pre>
<pre>
def get_summoners():
def getSummoners():
     summoners = getSummonersFromAPI();
     summoners = getSummonersFromAPI();
     for sid in summoners:
     for sid in summoners:
         if not summonerInDB():
         if not summonerInDB():
             saveSummoner();
             summoner = getSummonerById();
            summoner.save();
         games = getSummonerGames(100)
         games = getSummonerGames(100)
         for game in games:
         for game in games:
             if not gameInDB():
             if not gameInDB():
                 game_data = getGameData(game)
                 game_data = getGameData(game)
                 saveGameData()
                 game_data.save()
</pre>
 
Los tres elementos principales que se almacenan son:
 
* Datos del invocador: Se almacena en base de datos los datos generales, mientras que se guarda un archivo json (identificado con el ID del registro) en '''/datos/lolstats/summoners''' con los detalles.
* Datos generales de la partida: Se almacena en base de datos el identificador de Riot de la partida. Y se guarda un archivo json (identificado con el ID del registro) en '''/datos/lolstats/games''' con los detalles de la partida.
* Relación de Invocador/Partida: En una tabla de base de datos se almacena la relación de cuáles jugadores participaron en las partidas y a qué equipo pertenecieron.
 
Las matemáticas indicarían que al finalizar el proceso tendríamos 100,000 partidas guardadas, sin embargo, muchas partidas se repiten ya que más de uno de los jugadores del top 100 participan en ellas.
 
=== Obtención de los invocadores restantes ===
 
A continuación se deben buscar los datos de los invocadores restantes, así que para cada partida guardada buscamos si ya se encuentra el invocador o si no lo buscamos.
 
Se consultan los siguientes métodos de la API:
 
* Summoner-v4 (getSummoner): Devuelve la información del invocador identificado con el PUUID. Este dato es el que se obtiene del JSON de la partida.
 
https://la1.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}
 
<pre>
function fillSummonerss():
    matches = getSavedMatches()
    for match in matches:
        participants = match.getParticipants()
        for participant in participants:
            if not participantInDB():
                summoner = getSummoner()
                summoner.save()
</pre>
 
=== Obtención de las partidas restantes ===
 
El último paso consiste en que para cada invocador se obtienen sus últimas 50 partidas.
 
* Match-v5 (getSummonerGames): Devuelve el historial de partidas de un invocador identificado con su PUUID. Solo obtenemos las partidas de la cola clasificatoria.
 
https://la1.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?queue=420&start=0&count=100
 
* Match-v5 (getGameData): De este método también se consultan los detalles de cada partida identificada con su ID de partida.
 
https://la1.api.riotgames.com/lol/match/v5/matches/{matchid}
 
<pre>
function fillMatches():
    summoners = getSavedSummoners()
    for summoner in summoners:
        matches = getSummonerGames(50)
        for game in games:
            if not gameInDB():
                game_data = getGameData(game)
                game_data.save()
</pre>
 
= Modelos de predicción =
 
A continuación se enumeran los modelos y pruebas que se hicieron para obtener un modelo que pudiera predecir lo que queremos.
 
== Redes Neuronales ==
 
La primera aproximación fue hacer una red neuronal simple (perceptrón multi-capa).
La idea es crear una red con tres capas:
 
# Capa de entrada: 80 neuronas
# Capa intermedia: 160 neuronas
# Capa de salida: 2 neuronas
 
Esta sería un prueba simple y rápida para probar si una aproximación tan simple podría entregar algún resultado
 
=== Creación de Data Set ===
 
Con los datos crudos que se tienen, es necesario crear el set de entrenamiento y pruebas. Para este modelo, la intención inicial es incluir datos estadísticos de cada jugador, para ello se analiza su historial y se generan los vectores de pruebas.
 
Para cada invocador tenemos su historial de al menos 50 partidas, sin embargo, no podemos extraer los datos estadísticos de las 50 partidas para hacer nuestro dataset, debemos conservar algunas partidas para el entrenamiento y las pruebas. Por lo que se hace una distinción:
 
* Partidas del dataset: Para cada invocador se considerarán sus últimas 10 partidas para ser analizadas, es decir, de esas 10 partidas se extraeran las estadísticas de cada jugador y formarán parte del dataset de entrenamiento y pruebas.
* Partidas de estadísticas: Para cada una de las partidas del dataset se considerarán sus 40 partidas anteriores para extraer las estadísticas del jugador.
 
Para cada partida del dataset, los datos que debemos llenar son (Todos los valores son normalizados con base en su máximo):
 
* Estadísticas del jugador (Para cada jugador):
** '''Level''': Máximo permitido 1200.
** '''WinRate''': Porcentaje de victoria en esa cola. 40 partidas anteriores.
** '''WinStreak''': Número de victorias seguidas al momento. 40 partidas anteriores.
** '''MeanKDA''': Promedio de KDA (Asesinatos, Muertes, Asistencias) en las 40 partidas anteriores.
** '''ChampionMastery''': Puntos de maestría con el campeón. Máximo permitido 6,000,000.
** '''ChampionWinRate''': Porcentaje de victorias con ese campeón. Solo se buscará en las 40 partidas anteriores.
** '''ChampionMeanKDA''': Promedio de KDA con el campeon.
** '''ChampionGames''': Partidas jugadas con el campeón. 40 partidas anteriores.
 
Una vez que se obtienen los datos de cada jugador se tienen que unir en un arreglo que será nuestro vector de entrada, dicho arreglo se conformará de la concatenación de los vectores de cada uno de los 10 jugadores. Es importante destacar que siempre se colocarán primero las estadísticas del equipo 1 y luego los del equipo 2, sin revolver los vectores de los equipos.
 
* Estadísticas jugador 1 equipo 1
* Estadísticas jugador 2 equipo 1
* Estadísticas jugador 3 equipo 1
* Estadísticas jugador 4 equipo 1
* Estadísticas jugador 5 equipo 1
* Estadísticas jugador 1 equipo 2
* Estadísticas jugador 2 equipo 2
* Estadísticas jugador 3 equipo 2
* Estadísticas jugador 4 equipo 2
* Estadísticas jugador 5 equipo 2
 
'''Ejemplo:'''
 
# Para el invocador Invocador_00001 se buscan sus últimas 50 partidas: LAN_000001, ..., LAN_000050
# Se toman sus últimas 10 partidas para el dataset: LAN_000041, ..., LAN_000050
# Para cada una de las 10 partidas se sacan las estadísticas del jugador utilizando sus 40 partidas anteriores, ejemplo:
## Partida LAN_000041 se sacan estadísticas de partidas LAN_000039, ..., LAN_00001
## Partida LAN_000042 se sacan estadísticas de partidas LAN_000040, ..., LAN_00002
## Partida LAN_000043 se sacan estadísticas de partidas LAN_000041, ..., LAN_00003
## Y así sucesibamente
 
'''Resultado:'''
* Total de muestras: 41885
 
=== Entrenamiento ===
 
De nuestro dataset de 41,885 registros se usará el 80% para entrenamiento y 20% para probar el modelo.
 
Se utilizó la configuración más básica de Keras para entrenamiento:
 
* Batch size: 1000
* Epochs: 1000
 
El modelo a utilizar fue el siguiente:
<pre>
model = keras.Sequential(
    [
        keras.Input(shape=(80)),
        layers.Dense(160, activation="relu"),
        layers.Dense(2, activation="sigmoid")
    ]
)
</pre>
</pre>


Los datos se almacenan en tres partes diferentes:
'''Nota:''' Como no se obtuvo ningún resultado interesante se probaron algunas otras configuraciones de capas, epochs y batch size, pero ninguna hizo la diferencia (como era de esperar pues no es tan simple).
 
=== Resultados ===
 
Se pudo observar que el modelo no superó el 54% de exactitud, es decir, el modelo es inútil.
 
=== Discusión ===
 
Era de esperar que una aproximación tan aleatoria no diera ningún tipo de resultados, es por eso que ahora procederemos a implementar modelos y metodologías que han sido utilizados para situaciones similares.
 
== Aproximación de Semenov et al. ==
 
En un artículo publicado por Semenov et al. <ref>Semenov, A., Romov, P., Korolev, S., Yashkov, D., & Neklyudov, K. (2016, April). Performance of machine learning algorithms in predicting game outcome from drafts in dota 2. In International Conference on Analysis of Images, Social Networks and Texts (pp. 26-37). Springer, Cham.</ref> propone una forma de predecir resultados de partidas de Dota 2 (un juego muy similar a League of Legends) utilizando las composiciones de equipos.
 
=== Notas ===
<references />
 
=== Creación del dataset ===
 
Para esta prueba en su forma básica, solo se necesita extraer para cada partida el ID de campeón que cada invocador utilizó.
 
Para esta prueba consideraremos los equipos como "equipo 100" y "equipo 200"
 
Utilizamos un "bag of champions" donde cada draft se representa como un vector binario de 2 x N, donde N es el número total de campeones del juego, donde:
 
xi = 1 si i <= N y el campeón i estaba en el equipo 100 o si i > N y el campeón i - N estaba en el equipo 200.
xi = 0 en cualquier otro caso.
 
Nuestra base de datos contiene registros de aproximadamente 225,000 partidas, sin embargo, no todos están completos (cosas que pasan con la API de Riot, así que solo pudieron recolectarse datos completos de 97,725 para generar el dataset.


- Summoner: Datos del invocador. En la tabla se almacenan los datos generales, mientras que se guarda un archivo json en '''/datos/lolstats/summoners''' con los detalles, identificado con el ID del registro.
Para determinar el índice del campeón en el vector usaremos el id de base de datos, de forma que el campeón con el id 1 estará en la posición 0 y N del vector.
- Games: Datos generales de la partida. En base de datos se guarda el identificador de Riot de la partida. Y se guarda un archivo json en '''/datos/lolstats/games''' con los detalles de la partida, identificado el ID del registro.
- GameData: Es la tabla referencia de qué jugadores jugaron cuáles partidas.

Revisión actual del 02:25 7 jul 2022

En este apartado se detalla el proceso de configuración, planeación y codificación del proyecto Lolstats.

Entorno y Herramientas

Dada su facilidad de uso, configuración y versatilidad para la IA, usaremos Python como lenguaje de programación principal.

La idea final del proyecto es poner el modelo disponible en una página web donde los usuarios puedan consultarlo, es por ello que usaremos Django como framework de desarrollo. También usaremos la facilidad que tiene para crear y administrar una base de datos.

Usaremos además el sistema operativo Linux para hacer todo el proyecto por su facilidad para trabajar en la Web. Las instrucciones siguientes aplican para cualquier distribución basada en Debian.

Configuración del entorno

Necesitaremos dos directorios principales:

  • Directorio de código: Aquí se colocará el proyecto Django y los cripts python necesarios. Ej: ~/lolstats/
  • Directorio de datos: Aquí se colocarán los datos extraídos de la API de Riot y del repositorio de recursos. Ej: ~/datos/

MariaDB (MySQL)

MySQL es un sistema de gestión de bases de datos basado en lenguaje SQL, es gratuito aunque es propiedad de Oracle, por eso mejor usaremos la versión de código libre llamada MariaDB la cuál tiene exactamente las mismas características.

Instalación

sudo apt update
sudo apt install mariadb-server
sudo mysql_secure_installation

A mi me gusta desactivar la validación de password porque es muy restrictiva.

mysql -u root -p
uninstall plugin validate_password;

Creación de Base de datos

mysql -u root -p
CREATE DATABASE lolstats;
CREATE USER 'usuario'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON `lolstats`.* TO 'usuario'@'localhost';

Herramientas extras (Opcinales)

  • Git: Para control de versiones.
  • Tmux: Para navegar entre ventanas de terminal.
  • Vim: Para editar archivos desde consola.

Django

Django es un framework de desarrollo web en Python basado en MVC, proporciona métodos convenientes para crear páginas web, servicios web y administrar bases de datos.

Instalación de Python

Las distribuciones Linux ya traen instalada una versión de Python. Asumiremos que con esa funciona.

Nótese que algunas veces el comando para usar python 3.x es python3 en lugar de python.

TODO: Agregar guía de instalación para la versión de Python usada.

Entorno virtual

Es recomendable trabajar en un entorno virtual para que futuras actualizaciones no afecten nuestro proyecto.

Instalamos el paquete.

pip install venv

Nos movemos al directorio de código y creamos el entorno virtual.

cd ~/lolstats/
python -m venv venv

Activamos el entorno:

source venv/bin/activate

A partir de ahora todo lo que hagamos en Python se hará con el entorno virtual. Recuerda siempre activar el entorno antes de ejecutar Python o pip.

Instalación Django

Para instalar el release oficial:

pip install django

Creación del proyecto

Solo cubriremos lo relacionado con el modelo de aprendizaje y no lo relativo a la página web.

Para crear el proyecto utilizamos el comando:

django-admin startproject lolstats

Para poder empezar a recopilar nuestros datos necesitamos configurar el acceso a base de datos. Me gusta colocar el usuario y contraseña en un archivo separado y usar el mecanismo nativo de MySQL para cargar las credenciales desde ahí.

Editar el archivo ~/lolstats/lolstats/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '~/lolstats/.my.cnf',
        },
    }
}

Y generar el archivo en la ruta correspondiente ~/lolstats/.my.cnf:

[client]
user=lolstats
password=
database=lolstats

Keras

Keras es la librería de Inteligencia Artificial basada en Tensorflow que usaremos para entrenar los modelos de aprendizaje.

Instalar CUDA

Es una plataforma de desarrollo creada por Nvidia para realizar computo en paralelo, ya sea con CPU o con una GPU.

https://askubuntu.com/a/799185

Instalar CUDNN

Es una librería para crear Deep Neural Networks con la ayuda de GPUs.

https://developer.nvidia.com/cudnn https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html

Instalar Tensorflow

pip install tensorflow-gpu

Instalar Keras

pip install keras

Diseño de almacenamiento de datos

Dada la naturaleza de los datos y las fuentes de donde se obtienen, usaremos dos técnicas distintas para almacenarlos.

  • Base de datos: La idea es almacenar en tablas los datos generales de los jugadores y sus partidas, ej: IDs de jugadores o partidas, niveles, rangos, etc. El objetivo es poder utilizar consultas SQL para filtrar de forma eficiente y correcta los datos generales sin tener que parsear archivos.
  • Archivos JSON: Se usa JSON porque es el formato que usa el repositorio de recursos y la API. Se busca almacenar en texto plano el grueso de la información de los invocadores y las partidas, ya que es mucha información como para organizarla en una Base de datos (e innecesario). La idea es filtrar los datos con SQL y luego acceder a sus detalles buscando el archivo JSON correspondiente.

Diseño de Base de Datos

Las tablas que contendrá la base de datos se muestran en la siguiente figura.

Diagrama de Base de Datos

Estructura de Archivos

Se dividirán los archivos en dos directorios, dependiendo del tipo de archivo que se trate.

Data Dragon

  • Ubicación: /datos/data_dragon

Se trata de los documentos de dicho repositorio. Son documentos que contienen los datos del juego y solo se actualizan cada parche. Aquí se encuentran los catálogos del juego, como son datos de campeones, mapas, objetos, etc.

La estructura interna del directorio se mantiene igual a la original.

/datos/data_dragon
├── css
├── data
├── img
├── js
├── manifest.js
└── manifest.json

Documentos de información

  • Ubicación: /datos/lolstats

Les llamaremos así a los documentos que almacenan las respuestas de las peticiones a la API de Riot. Cuando se realiza una petición a la API (ej: Obtener los datos de una partida) se guarda la respuesta JSON en un archivo para su posterior procesamiento.

Los achivos se dividen por su tipo:

  • games: Información de los juegos.
  • summoners: Información de los invocadores.
  • tiers: Información de las colas clasificatorias.
/datos/lolstats/
├── games
├── summoners
└── tiers


Recopilación de datos

Data Dragon

La documentación completa sobre el repositorio puede encontrarse en la documentación de la API Riot para League of Legends.

El link directo de descarga se forma de la siguiente manera:

https://ddragon.leagueoflegends.com/cdn/dragontail-<version>.tgz

La información adicional que se encuentra disponible es:

Datos dinámicos

Entramos a donde el grueso de la información se concentra. La estrategia consiste en obtener la mayor cantidad de datos posible, empezando por los rangos más altos de clasificatorias.

Experimentalmente todos los datos obtenidos serán de la región de LAN.

Por la naturaleza y funcionamiento de la API de Riot, es necesario realizar una estrategia como la siguiente:

  1. Obtener los nombres e identificadores de los 1,000 primeros invocadores en el ranking (Challenger, Grandmaster, Master, etc.).
  2. Para cada invocador obtener su historial de las últimas 100 partidas clasificatorias. Esto para tener la referencia de otros invocadores en los mismos rangos.
  3. Para cada partida obtener los datos de los 9 invocadores restantes.
  4. Para cada uno de los invocadores restantes obtener los datos de las últimas 50 partidas clasificatorias (ir más allá traería datos irrelevantes).

Obtención top 1000 invocadores

Como trabajaremos con una API de pruebas con limitantes de cantidades de peticiones por minuto y por segundo, primero generaremos un algoritmo para delimitar cada petición.

global maxPerSecond = 20;
global maxPer2Minutes = 100;
global countPerSecond = 0;
global countPer2Minutes = 0;
global lastSecond = 0;
global lastMinute = 0;
function sendRequest:
    while true:
        if currentSecond = lastSecond:
            if countPerSecond < maxPerSecond:
                MaqueRequest()
                break;
            else:
                sleep 1
        else if abs(currentMinute - lastMinute) <= 2:
            if countPer2Minutes < maxPerMinute:
                MakeRequest()
                break;
            else:
                sleep 1
        else:
            MaqueRequest()
            break;

function MakeRequest():
    countPer2Minutes++;
    countPerSecond++;
    Send();
    if currentSecond != lastSecond:
        countPerSecond = 1;
        lastSecond = currentSecond;
        if not abs(currentMinute - lastMinute) <= 2:
            countPer2Minutes = 1;
            lastMinute = currentMinute;

A continuación podemos comenzar a hacer la recopilación de los datos. El proceso para obtener la lista de 1000 invocadores con sus partidas se describe en el código mostrado a continuación, donde se consultan los siguientes métodos de la API:

  • League-exp-v4 (getSummonersFromAPI): Devuelve la lista de los invocadores en una cola clasificatoria. La cola en la que se busca es RANKED_SOLO_5x5 en la división CHALLENGER I, GRANDMASTER I, MATER I y así sucesivamente hasta completar los 1,000.

https://la1.api.riotgames.com/lol/league-exp/v4/entries/RANKED_SOLO_5x5/CHALLENGER/I?page=1

  • Summoner-v4 (getSummonerById): Devuelve la información del invocador identificado con el ID de Invocador encriptado. Este dato es el que se obtiene de League-exp-v4.

https://la1.api.riotgames.com/lol/summoner/v4/summoners/{encryptedSummonerId}


  • Match-v5 (getSummonerGames): Devuelve el historial de partidas de un invocador identificado con su PUUID. Este dato se obtiene de Summoner-v4. Solo obtenemos las partidas de la cola clasificatoria.

https://la1.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?queue=420&start=0&count=100

  • Match-v5 (getGameData): De este método también se consultan los detalles de cada partida identificada con su ID de partida.

https://la1.api.riotgames.com/lol/match/v5/matches/{matchid}

def getSummoners():
    summoners = getSummonersFromAPI();
    for sid in summoners:
        if not summonerInDB():
            summoner = getSummonerById();
            summoner.save();
        games = getSummonerGames(100)
        for game in games:
            if not gameInDB():
                game_data = getGameData(game)
                game_data.save()

Los tres elementos principales que se almacenan son:

  • Datos del invocador: Se almacena en base de datos los datos generales, mientras que se guarda un archivo json (identificado con el ID del registro) en /datos/lolstats/summoners con los detalles.
  • Datos generales de la partida: Se almacena en base de datos el identificador de Riot de la partida. Y se guarda un archivo json (identificado con el ID del registro) en /datos/lolstats/games con los detalles de la partida.
  • Relación de Invocador/Partida: En una tabla de base de datos se almacena la relación de cuáles jugadores participaron en las partidas y a qué equipo pertenecieron.

Las matemáticas indicarían que al finalizar el proceso tendríamos 100,000 partidas guardadas, sin embargo, muchas partidas se repiten ya que más de uno de los jugadores del top 100 participan en ellas.

Obtención de los invocadores restantes

A continuación se deben buscar los datos de los invocadores restantes, así que para cada partida guardada buscamos si ya se encuentra el invocador o si no lo buscamos.

Se consultan los siguientes métodos de la API:

  • Summoner-v4 (getSummoner): Devuelve la información del invocador identificado con el PUUID. Este dato es el que se obtiene del JSON de la partida.

https://la1.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/{encryptedPUUID}

function fillSummonerss():
    matches = getSavedMatches()
    for match in matches:
        participants = match.getParticipants()
        for participant in participants:
            if not participantInDB():
                summoner = getSummoner()
                summoner.save()

Obtención de las partidas restantes

El último paso consiste en que para cada invocador se obtienen sus últimas 50 partidas.

  • Match-v5 (getSummonerGames): Devuelve el historial de partidas de un invocador identificado con su PUUID. Solo obtenemos las partidas de la cola clasificatoria.

https://la1.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids?queue=420&start=0&count=100

  • Match-v5 (getGameData): De este método también se consultan los detalles de cada partida identificada con su ID de partida.

https://la1.api.riotgames.com/lol/match/v5/matches/{matchid}

function fillMatches():
    summoners = getSavedSummoners()
    for summoner in summoners:
        matches = getSummonerGames(50)
        for game in games:
            if not gameInDB():
                game_data = getGameData(game)
                game_data.save()

Modelos de predicción

A continuación se enumeran los modelos y pruebas que se hicieron para obtener un modelo que pudiera predecir lo que queremos.

Redes Neuronales

La primera aproximación fue hacer una red neuronal simple (perceptrón multi-capa). La idea es crear una red con tres capas:

  1. Capa de entrada: 80 neuronas
  2. Capa intermedia: 160 neuronas
  3. Capa de salida: 2 neuronas

Esta sería un prueba simple y rápida para probar si una aproximación tan simple podría entregar algún resultado

Creación de Data Set

Con los datos crudos que se tienen, es necesario crear el set de entrenamiento y pruebas. Para este modelo, la intención inicial es incluir datos estadísticos de cada jugador, para ello se analiza su historial y se generan los vectores de pruebas.

Para cada invocador tenemos su historial de al menos 50 partidas, sin embargo, no podemos extraer los datos estadísticos de las 50 partidas para hacer nuestro dataset, debemos conservar algunas partidas para el entrenamiento y las pruebas. Por lo que se hace una distinción:

  • Partidas del dataset: Para cada invocador se considerarán sus últimas 10 partidas para ser analizadas, es decir, de esas 10 partidas se extraeran las estadísticas de cada jugador y formarán parte del dataset de entrenamiento y pruebas.
  • Partidas de estadísticas: Para cada una de las partidas del dataset se considerarán sus 40 partidas anteriores para extraer las estadísticas del jugador.

Para cada partida del dataset, los datos que debemos llenar son (Todos los valores son normalizados con base en su máximo):

  • Estadísticas del jugador (Para cada jugador):
    • Level: Máximo permitido 1200.
    • WinRate: Porcentaje de victoria en esa cola. 40 partidas anteriores.
    • WinStreak: Número de victorias seguidas al momento. 40 partidas anteriores.
    • MeanKDA: Promedio de KDA (Asesinatos, Muertes, Asistencias) en las 40 partidas anteriores.
    • ChampionMastery: Puntos de maestría con el campeón. Máximo permitido 6,000,000.
    • ChampionWinRate: Porcentaje de victorias con ese campeón. Solo se buscará en las 40 partidas anteriores.
    • ChampionMeanKDA: Promedio de KDA con el campeon.
    • ChampionGames: Partidas jugadas con el campeón. 40 partidas anteriores.

Una vez que se obtienen los datos de cada jugador se tienen que unir en un arreglo que será nuestro vector de entrada, dicho arreglo se conformará de la concatenación de los vectores de cada uno de los 10 jugadores. Es importante destacar que siempre se colocarán primero las estadísticas del equipo 1 y luego los del equipo 2, sin revolver los vectores de los equipos.

  • Estadísticas jugador 1 equipo 1
  • Estadísticas jugador 2 equipo 1
  • Estadísticas jugador 3 equipo 1
  • Estadísticas jugador 4 equipo 1
  • Estadísticas jugador 5 equipo 1
  • Estadísticas jugador 1 equipo 2
  • Estadísticas jugador 2 equipo 2
  • Estadísticas jugador 3 equipo 2
  • Estadísticas jugador 4 equipo 2
  • Estadísticas jugador 5 equipo 2

Ejemplo:

  1. Para el invocador Invocador_00001 se buscan sus últimas 50 partidas: LAN_000001, ..., LAN_000050
  2. Se toman sus últimas 10 partidas para el dataset: LAN_000041, ..., LAN_000050
  3. Para cada una de las 10 partidas se sacan las estadísticas del jugador utilizando sus 40 partidas anteriores, ejemplo:
    1. Partida LAN_000041 se sacan estadísticas de partidas LAN_000039, ..., LAN_00001
    2. Partida LAN_000042 se sacan estadísticas de partidas LAN_000040, ..., LAN_00002
    3. Partida LAN_000043 se sacan estadísticas de partidas LAN_000041, ..., LAN_00003
    4. Y así sucesibamente

Resultado:

  • Total de muestras: 41885

Entrenamiento

De nuestro dataset de 41,885 registros se usará el 80% para entrenamiento y 20% para probar el modelo.

Se utilizó la configuración más básica de Keras para entrenamiento:

  • Batch size: 1000
  • Epochs: 1000

El modelo a utilizar fue el siguiente:

model = keras.Sequential(
    [
        keras.Input(shape=(80)),
        layers.Dense(160, activation="relu"),
        layers.Dense(2, activation="sigmoid")
    ]
)

Nota: Como no se obtuvo ningún resultado interesante se probaron algunas otras configuraciones de capas, epochs y batch size, pero ninguna hizo la diferencia (como era de esperar pues no es tan simple).

Resultados

Se pudo observar que el modelo no superó el 54% de exactitud, es decir, el modelo es inútil.

Discusión

Era de esperar que una aproximación tan aleatoria no diera ningún tipo de resultados, es por eso que ahora procederemos a implementar modelos y metodologías que han sido utilizados para situaciones similares.

Aproximación de Semenov et al.

En un artículo publicado por Semenov et al. [1] propone una forma de predecir resultados de partidas de Dota 2 (un juego muy similar a League of Legends) utilizando las composiciones de equipos.

Notas

  1. Semenov, A., Romov, P., Korolev, S., Yashkov, D., & Neklyudov, K. (2016, April). Performance of machine learning algorithms in predicting game outcome from drafts in dota 2. In International Conference on Analysis of Images, Social Networks and Texts (pp. 26-37). Springer, Cham.

Creación del dataset

Para esta prueba en su forma básica, solo se necesita extraer para cada partida el ID de campeón que cada invocador utilizó.

Para esta prueba consideraremos los equipos como "equipo 100" y "equipo 200"

Utilizamos un "bag of champions" donde cada draft se representa como un vector binario de 2 x N, donde N es el número total de campeones del juego, donde:

xi = 1 si i <= N y el campeón i estaba en el equipo 100 o si i > N y el campeón i - N estaba en el equipo 200. xi = 0 en cualquier otro caso.

Nuestra base de datos contiene registros de aproximadamente 225,000 partidas, sin embargo, no todos están completos (cosas que pasan con la API de Riot, así que solo pudieron recolectarse datos completos de 97,725 para generar el dataset.

Para determinar el índice del campeón en el vector usaremos el id de base de datos, de forma que el campeón con el id 1 estará en la posición 0 y N del vector.