Lolstats Documentación Técnica
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.
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:
- Catálogo de temporadas: https://static.developer.riotgames.com/docs/lol/seasons.json
- Catálogo de colas: https://static.developer.riotgames.com/docs/lol/queues.json
- Catálogo de mapas: https://static.developer.riotgames.com/docs/lol/maps.json
- Catálogo de modos de juego: https://static.developer.riotgames.com/docs/lol/gameModes.json
- Catálogo de tipos de juego: https://static.developer.riotgames.com/docs/lol/gameTypes.json
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:
- 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 partida obtener los datos de los 9 invocadores restantes.
- 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()
Creación de Data Set
Una vez se tienen los datos crudos, es necesario crear el set de entrenamiento y pruebas. Este set solo debe contener los datos necesarios para el modelo.
Aunque hay varios formatos en que se puede crear el Data-set, usaremos CSV por su versatilidad.
Los datos que debemos llenar son:
- Estadísticas del jugador (Para cada jugador):
- Level
- Rank: Se omite por el momento ya que no es fácil obtener este dato.
- WinRate: Porcentaje de victoria en esa cola. Últimas 50 partidas.
- ChampionWinRate: Porcentaje de victorias con ese campeón. Solo se buscará en las últimas 50 partidas jugadas.
- ChampionMastery: Puntos de maestría con el campeón.
- ChampionGames: Partidas jugadas con el campeón.
- WinStreak: Número de victorias seguidas al momento.
- WinRateEnemy[1-5]: Winrate contra cada uno de los campeones enemigos. Solo se buscará en las últimas 50 partidas jugadas.
- WinRateAlly[1-4]: Winrate con cada uno de los campeones aliados. Solo se buscará en las últimas 50 partidas jugadas.
- MeanKDA: Promedio de KDA (Asesinatos, Muertes, Asistencias) en las últimas 50 partidas.
- ChampionMeanKDA: Promedio de KDA con el campeon.