Acompaña el evento final de la Maratón Behind the Code 2020: 05/12 - Online ¡Inscríbete ahora!

Cree una red neuronal artificial con la infraestructura Neuroph Java

Este tutorial combina tres cosas que amo: el lenguaje Java, las redes neuronales artificiales y el torneo «I Men’s College Basketball» anual de la división NCAA (también conocido como, «March Madness»). Teniendo eso en cuenta, en este tutorial cubriré cuatro temas:

  • Conceptos de la red neuronal artificial (ANN)
  • El perceptrón multicapa (MLP)
  • La infraestructura de la red neuronal Neuroph Java
  • Estudio de caso: March Madness

Mi meta no es ofrecer un tratamiento completo (ni siquiera está cerca de estar completo) de la teoría de las redes neuronales artificiales. En la nube existen muchos recursos excelentes que explicarán este tema complejo de una forma mucho mejor que la mía (pondré un enlace hacia ellos donde sea apropiado). En vez de eso, lo que quiero hacer es ayudarle a que intuya qué es una ANN es y cómo funciona.

Le aviso: incluirá algo de matemáticas, pero sólo donde sea absolutamente necesario o cuando sea más conciso mostrar las matemáticas. A veces, una ecuación vale más que mil palabras, pero intentaré poner las mínimas.

Conceptos de la red neuronal artificial

Una red neuronal artificial es una construcción computacional — a menudo, un programa de computación, — que se inspira por las redes biológicas, en particular las que se encuentran en los cerebros animales. Está formada por capas de neuronas artificiales (a partir de aquí solo me referiré a ellas como neuronas), donde las neuronas de una capa se conectan a otras de las capas más próximas que las rodean.

En Internet hay tantas representaciones de este concepto que soy reacio a realizar otra representación de una red neuronal multicapa, pero aquí esta:

Figure 1. Representación de una capa de red neuronal artificial (fuente: Wikipedia)
Representación de una capa de red neuronal artificial

En este tutorial a veces me refiero a las capas como «previa» y «siguiente» en relación a sus capas adyacentes. Utilizando la Imagen 1 como guía, me referiré a la capa de entrada como la capa previa relativa a la capa oculta, que, a su vez, es la previa relativa a la capa de resultado. De igual manera, la capa de resultado es la capa siguiente en relación a la capa oculta, que, a su vez, es la capa siguiente en relación a la capa de resultado.

En general, funciona de la siguiente manera: una neurona artificial de una determinada capa de la red puede enviar una señal de salida (que es un número entre -1 y 1) a todas las otras neuronas de la capa siguiente. Si una neurona «se enciende», envía una señal de salida, si no es así, no lo hace. En este tipo de red de retroalimentación , la salida de las neuronas de una capa es la entrada para las neuronas de la siguiente. Las señales se propagan hacia adelante desde las neuronas de una capa para alimentar a las de la siguiente, hasta que finalmente, en la capa de salida de la red, se calcula la suma total de la salida, y la red lo calcula como su «respuesta».

Las redes neuronales son muy adecuadas para una clase de problemas conocido como reconocimiento de patrones. Una red neuronal se capacita utilizando datos que contienen entradas y salidas conocidas — llamados datos de capacitación — hasta que la red pueda reconocer de forma precisa los patrones de los datos de la capacitación.

Las redes neuronales son buenas para reconocer patrones por algo llamado plasticidad. Cada conexión de neurona a neurona tiene un peso asociado. Cuando se capacita la red, los datos de capacitación se envían a la red, y la salida de la red se calcula y compara por lo que se espera. Se calcula la diferencia entre lo real y lo esperado (llamada error). Después, empezando en la capa de salida, el programa de capacitación propaga hacia atrás minuciosamente el error, ajustando todos los pesos de las conexiones de las neuronas de las capas de la red, y después la vuelve a enviar los datos de la capacitación (a cada iteración se la llama época de capacitación).

Esta — plasticidad— cambiante de los pesos de las conexiones de las neuronas cambia la respuesta de la red tras cada ejecución a medida que aprende.

Este proceso continúa hasta que se logra un nivel aceptable de tolerancia a errores, en cuyo punto se considera que la red está capacitada. La red capacitada recientemente recibe datos de entrada similares (pero diferentes, claro) a los datos de capacitación, para ver qué respuestas proporciona la red y, así, validarla.

Esta técnica para capacitar una red neuronal se llama aprendizaje supervisado. La veremos con más detalle a continuación.

¿Han entendido todo? No pasa nada si no es así. Acabo de presentar un montón de conceptos, así que voy a explicarlos más detalladamente.

Neurona artificial

En el núcleo de una red neuronal está la neurona artificial. Tiene tres características principales:

  • Conexión: en los tipos de neuronas que voy a mostrar en este tutorial, cada neurona de una capa determinada está conectada a todas las otras neuronas de las capas más próximas que la rodean.
  • Señales de entrada: la colección de valores numéricos que se reciben de cada neurona de la capa anterior, o de una neurona de la capa de entrada, un único valor normalizado (más tarde hablaré sobre normalización).
  • Señal de salida: un único valor numérico que la neurona envía a todas las neuronas de la capa siguiente, o la «respuesta» si esta neurona está en la capa de salida.

Aprendizaje supervisado

En resumen, el aprendizaje supervisado ocurre cuando se toma un conjunto de ejemplos, y cada ejemplo está formado por una entrada y una salida deseada. Las entradas se introducen en la red neuronal hasta que pueda producir de forma fiable (es decir, dentro de una tolerancia a error que especifique) la salida deseada.

O, como se lo explicaría a mis hijos:

  1. alimentar la red con datos conocidos.
  2. P: ¿La red produjo las respuestas correctas? Sí (dentro de la tolerancia a error): vaya al paso 3. No:
     1. Ajustar los pesos de las conexiones de la red.
     2. Ir al paso 1
    
  3. Hemos acabado

(en este punto, mis hijos están poniendo sus ojos en blanco, pero lo han entendido completamente).

Para ver una discusión más formal sobre aprendizaje supervisado, eche un vistazo a este tutorial de IBM developerWorks sobre aprendizaje automático.

El perceptrón multicapa (MLP)

El perceptrón multicapa, o MLP, es un tipo de red neuronal que tiene una capa de entrada y una capa de salida, y una o más capas ocultas entre medias. Cada capa tiene un número de neuronas fijo, pero potencialmente diferente (es decir, después de que se defina la estructura de red, será fijo durante todas las épocas de la capacitación).

Como describí antes, cada conexión de neuronas tiene un peso, una función de entrada y una función de salida. En esta sección, los describiré más detalladamente, junto con algunos conceptos más que tendrá que entender sobre cómo se utiliza el MLP en el estudio de caso siguiente.

Pesos de las neuronas

Cuando una neurona de una red biológica, como la del cerebro de un mamífero, «se enciende», envía una señal eléctrica por su axón hasta las dendritas de neuronas a las que está conectada. La fuerza de la señal determina la cantidad de influencia que la neurona encendida tiene sobre las neuronas de «niveles inferiores».

El modelo computacional de ANN está diseñado para ser análogo a este tipo de red biológica, y simula la fuerza de la señal por medio del peso de la conexión de salida.

El peso de la neurona artificial representa dos conceptos, la plasticidad y la fuerza de la señal. Plasticidad es el concepto de que el peso se puede ajustar entre las épocas de la capacitación, y la fuerza de la señal es la cantidad de influencia que la neurona que se enciende tiene en las otras a las cuales está conectada en la siguiente capa.

En resumen, cada neurona de una capa de red predeterminada:

  1. está conectada a cada neurona de la capa anterior (excepto las neuronas de entrada, que no tienen una capa anterior);
  2. está conectada a cada neurona de la capa siguiente (excepto las neuronas de salida, que no tienen una capa siguiente);
  3. su conexión de salida tiene asociado un peso que simula la potencia de la señal de salida.

Función de entrada

Potencialmente hay muchas (digamos, n) señales que entran en una neurona artificial (vea la Imagen 1), y la red tiene que convertir todas esas señales en un único valor que se puede evaluar. Este es el trabajo de la función de entrada: transformar la colección de señales de entrada en un valor único, normalmente un número entre -1 y 1.

El tipo de función de entrada más habitual se llama suma ponderada. Suponga que una neurona de entrada N está conectada a n neuronas de la capa anterior de la red. La suma ponderada A de las entradas para N se calcula añadiendo el producto del valor de entrada de cada conexión por a veces el peso de la conexión w a lo largo de todas las entradas n . La fórmula se parece a esto:

Fórmula de suma ponderada

¡Ugh, matemáticas! Está bien. ¿Y si pongo un ejemplo? Suponga que la neurona N está conectada a tres neuronas de la capa anterior, así que tiene n = 3 valores de entrada, los valores de a de la ecuación anterior son { 0,3, 0,5 y 0,6 }, y cada uno de ellos tiene un peso w cuyos valores son { 0,4, -0,2 y 0,9 }, respectivamente. La suma ponderada A de N sería:

Ejemplo de suma ponderada

Función de salida (activación)

La función de salida determina si una neurona se enciende, es decir, pasa su señal de salida a todas las neuronas de la capa siguiente de la red.

El tipo de función de activación que más habitualmente se usa en las redes MLP es la Función sigmoidea. Para una suma ponderada A para una neurona determinada, el valor sigmoideo V de A es calculado por:

Sigmoid formula

La función sigmoidea es no lineal, lo que la hace muy adecuada para su uso en redes neuronales. Un vistazo rápido a la ecuación anterior muestra que para valores negativos cada vez mayores de A, el denominador crece en consecuencia, y el valor de la función se aproxima a cero. De igual manera, a medida que los valores positivos de A crecen cada vez más, el término exponencial se desvanece, y el valor de la función se aproxima a 1.

Es posible leer más acerca de la función sigmoidea aquí.

Función de error de red

El aprendizaje supervisado trata de la corrección lenta y sistemática de los errores de la salida de una red neuronal, lo que significa que el programa de capacitación primero necesita una forma de calcular el error.

La función de error más habitualmente usada con las redes MLP es la función de error cuadrático medio. Esencialmente, lo que hace es calcular la «distancia» media entre el valor real que el programa de capacitación calcula, y el valor esperado de los datos de la capacitación.

Matemáticamente, se parece a esto:

Mean squared formula

Dadas n neuronas de salida, para cada suma ponderada de las neuronas de salida A, el programa de capacitación calcula la diferencia entre el valor de los datos de capacitación y el valor de la red, lo eleva al cuadrado, suma esos valores de todas las neuronas de salida y lo divide por el número de neuronas de salida n para llegar al error de salida total E.

O, en términos más sencillos: suponga que su objetivo es tirar un dardo y dar en el centro de la diana. Cada vez que tira un dardo, se clava en un punto a cierta distancia d del centro de la diana (incluso cuando se clava en el propio centro de la diana, lo que significa d = 0).

Para descubrir lo buena que es su puntería, tiene que calcular la distancia media que usted está «desviado» y eso le proporciona una indicación de cuánto debe corregir los siguientes lanzamientos para tirar de forma más precisa.

Nuestra red neuronal no está lanzando dardos, pero la idea es la misma: en cada época de capacitación el entrenador calcula lo «lejos» (el error E) del centro de la diana está el valor (A esperado de los datos de la capacitación) calculado por la red y ajusta los pesos de la red en consecuencia. La idea es que la magnitud de la corrección sea proporcionar al error de la red. O, en el lenguaje de los dardos: si el dardo se clava más lejos del centro de la diana, se hacen correcciones mayores; cuando está más cerca del centro de la diana, se hacen correcciones menores.

Retropropagación de error

Cuando una época de capacitación acaba, el programa de capacitación calcula el error de la red neuronal, y modifica los pesos de la conexión a lo largo de la red. Como mencioné en una sección anterior, hace esto empezando en la capa de salida y trabajando hacia atrás (hacia la capa de entrada), ajustando el peso de cada conexión neuronal según avanza.

A esto se llama retropropagación de error y es una potente técnica para capacitar redes neuronales.

Si se pregunta cómo exactamente hace el programa de capacitación para ajustar los pesos, tengo buenas y malas noticias. Primero, las malas noticias: la técnica está formada por cuatro ecuaciones, y utiliza técnicas avanzadas para realizar el cálculo, las estadísticas y el álgebra lineal multivariable. Es decir, muchas matemáticas. No es para los débiles y se sale del ámbito de este tutorial.

Ahora, las buenas noticias: ¡para utilizar esta retropropagación, no necesita entender cómo se implementa! Mientras tenga una idea intuitiva de lo que está ocurriendo, podrá emplear la retropropagación para capacitar algunas redes bastante impresionantes (confíe en mí).

Sin embargo, si quiere conocer todas las matemáticas, eche un vistazo a esta maravillosa pieza de Michael Nielsen.

La infraestructura de la red neural Neuroph Java

Bueno, ya vale de teoría. Es la hora de hablar sobre cómo hacer este trabajo.

Afortunadamente para nosotros, existe una infraestructura basada en Java llamada Neuroph que implementa todos los detalles cruentos que (mayormente) pasé por alto en la sección anterior. En esta sección, me gustaría presentarle la infraestructura de Neuroph y explicarle un poco sobre por qué la elegí para mi estudio de caso.

Algunas razones por las que me gusta la infraestructura de Neuroph:

  • Puro código de Java: ¿necesito decir más? ¿Sí? Vale, el lenguaje Java es el lenguaje más popular del planeta (ahí lo dejo).
  • Es compatible con muchos tipos de redes: lo que incluye el perceptrón multicapa.
  • Las APIs de Neuroph son superfáciles de utilizar, lo que es bueno, porque no hay mucha documentación.

Descargue e instale Neuroph

Para descargar Neuroph, visite esta página y seleccione el archivo ZIP de la infraestructura de Neuroph.

Expanda el archivo .zip en su computadora, encuentre el archivo .jar principal de Neuroph, llamado (en el momento en el que escribo esto) neuroph-core-2.94.jar.

En el momento en el que estoy escribiendo esto, la versión más reciente de Neuroph (2.94) no está en Maven Central, así que, para usarla, es necesario instalarla en su repositorio local de Maven. En el video le muestro cómo hacer esto.

Si quiere utilizar la UI, seleccione la descarga para su plataforma. Es posible utilizar la UI para cargar y capacitar sus redes, aunque esta función me parece algo insuficiente (reconozco completamente que esto puede ser culpa mía).

Para las redes pequeñas (< diez neuronas de entrada), creo que la UI está bien, pero para las redes en las que estaba trabajando para el estudio del caso, tuve que estar constantemente desplazándome hacia adelante y hacia atrás para verlo todo. Incluso así, no podía ver toda mi red en la pantalla.

Además, me encanta escribir código, y las APIs de Neuroph APIs siguen muy de cerca sus conceptos teóricos, ¡así que lancé Eclipse y me puse a escribir algo de código!

Estudio de caso: Video

Estudio de caso : March Madness

Hace algunos años, tuve la idea de utilizar una ANN para predecir los ganadores del torneo «I Men’s College Basketball» anual de la división NCAA (también conocido como, «March Madness»). Pero, no sabía por dónde empezar.

Sabía que las redes neuronales son excelentes para encontrar relaciones «ocultas» entre los elementos de datos que no son inmediatamente obvias para los humanos. Soy aficionado al baloncesto desde que era un niño, así que puedo echar un vistazo a los resúmenes estadísticos de los rendimientos de los equipos en una temporada y más o menos saber quién de los dos será mejor en el torneo. Pero, después de algunas categorías estadísticas, me empieza a doler la cabeza. ¡Son demasiados datos para un ser humano normal como yo!

Pero, no podía olvidarme de la idea. «Hay algo en los datos», me decía a mí mismo. Tengo formación científica, hice aproximadamente (al parecer) un millón de proyectos de feria de ciencias con mis tres hijos. Así que decidí aplicar la ciencia al caso de estudio y me hice la siguiente pregunta: «¿es posible utilizar datos de la temporada regular para elegir de forma precisa (> 75 %) los ganadores de una competencia deportiva?»

Mi hipótesis: sí. Es posible.

Después de intentarlo intermitentemente durante dos años, creé el procedimiento de este estudio de caso para predecir los ganadores del torneo «I Men’s College Basketball» de la división NCAA.

Utilizando 5 temporadas de datos históricos que incluyen 21 categorías estadísticas, capacite más de 30 redes separadas y las ejecuté juntas como una «matriz» de redes (la idea era reducir paulatinamente los errores a lo largo de varias redes) para completar mi grupo de torneos.

Me impresionó lo bien que lo hicieron las redes. De 67 partidos, la matriz de mi red eligió correctamente 48 (~72 %). No está mal, pero, lamentablemente, se demostró que me hipótesis estaba equivocada. Por ahora. Eso es lo genial acerca de la ciencia: los fallos nos inspiran a seguir intentándolo.

Déjeme que le enseñe sin más demora el procedimiento que seguí para capacitar y ejecutar la matriz de la red. Le aviso: es avanzado. Lo presento aquí para mostrar que es posible, no como tutorial. Sin embargo, si encuentra errores en el código o en el procedimiento, o quiere sugerir mejoras (o compartir sus casos de éxito con el código), déjeme un comentario.

1

Cargar la base de datos

Para crear los datos para capacitar la red (los datos de la capacitación), primero tuve que obtener datos estadísticos para toda la temporada regular. Después de una amplia búsqueda de sitios web que proporcionan datos gratuitamente, llegué a ncaa.com, que proporciona datos históricos de las temporadas regulares para casi todos sus deportes, lo que incluye el baloncesto masculino (los datos solo son gratis para usos no comerciales, lea atentamente sus términos de servicio antes de descargar algo).

1 Descargar los datos

Ncaa.com proporciona una completa interfaz completa (aún que no totalmente intuitiva) para descargar todo tipo de datos excelentes. Decidí que lo que necesitaba eran datos del equipo (en vez de datos individuales) de las temporadas regulares desde ese momento hasta el 2010. En marzo de 2017, cuando estaba acabando este proyecto, eran 8 años de datos históricos, entre los que se incluía los de la temporada regular del 2017.

Descargué los datos navegando por varias pantallas (eche un vistazo al video si quiere ver más) y descargué los datos de los equipos hasta el 2010, incluidos los de categorías estadísticas como:

  • Número medio de puntos por partido
  • Porcentaje de canastas
  • Porcentaje de tiros libres
  • Robos por partido
  • Pérdidas de pelota por partido

y muchas más (eche un vistazo al código fuente en GitHub para saber los detalles completos). Estos datos cubrían las tres áreas principales: ataque (por ejemplo, porcentaje de canastas), defensa (por ejemplo, robos por partido) y errores (por ejemplo, pérdidas de pelota por partido).

Ncaa.com ofrece en varios formatos para la descarga, pero decidí utilizar el de variables separadas por comas (CSV), para poder utilizar OpenCSV para procesarlo. Todas las estadísticas para esa temporada están en un único archivo, con encabezados separados para indicar cuál es la categoría estadística que sigue los datos. Después es seguida por la siguiente combinación de cabecera/datos, y así sucesivamente. En el video, muestro cuál es la apariencia de los datos CSV, así que asegúrese de echarla un vistazo.

Las siguientes imágenes deberían dar una idea de cuál es el formato de los datos.

Primero, los datos de la parte superior del archivo. Hay varias líneas vacías, seguidas por una carretera de tres líneas — la categoría estadística (subrayada) es la línea 2 de la cabecera — seguida por más líneas vacías, seguida por los datos CSV (que incluyen una línea de título) para esa categoría estadística.

Figure 2. Datos CSV de la temporada 2011 al inicio del archivo
alt

Cuando se acaban los datos para esa categoría estadística, comienza la siguiente categoría. Este patrón se repite para el resto de las categorías estadísticas. La siguiente imagen muestra los datos para la categoría estadística «puntuación ofensiva» que sigue inmediatamente a los datos que se muestran en Figure 2.

Figure 3. Más datos CSV de la temporada 2011
2011 CSV data

Generar los scripts de carga en SQL

Como sabía cómo estaba compuesto el archivo CSV, pensé que podía procesar los datos de forma programática y decidí utilizar el patrón Strategy para procesarlos. De esa forma, si las personas de TI de Ncaa.com cambiasen el orden de las estadísticas en el archivo de un año para otro, mientras se mantuviese la tupla { { header, data }, { header, data } ...} , la implementación de mi patrón Strategy me inmunizaría contra cambios en el orden de los datos.

La interfaz de Strategy se define de esta manera:


                package com.makotojava.ncaabb.sqlgenerator;

                import java.util.List;

                public interface Strategy {
                  String generateSql(String year, List<String;
                
                  String getStrategyName();
                
                  int getNumberOfRowsProcessed();
                }
                

Hay una implementación de Strategy por cada categoría estadística. No le voy a aburrir con los detalles. Vea la función source code si quiere saber más.

El problema que genera el SQL se llama SqlGenerator, y su principal trabajo es analizar el archivo CSV buscando las cabeceras de la categoría estadística, seleccionar el Strategy apropiado para procesar esta sección, delegar el proceso a ese Strategy, repetirlo hasta que se llega al final del archivo y escribir el script de carga de SQL. Si no se puede encontrar un Strategy para una categoría estadística particular (ncaa.com brinda más categorías estadísticas de las que decidi utilizar) el programa salta a la siguiente cabecera.

El trabajo de cada Strategy es procesar los valores de los datos del detalle de la categoría y crear declaraciones SQL INSERT para ellos que yo pueda utilizar después para cargar los datos en la BB. DD. SqlGenerator escribe un script de carga al año que contiene todas las declaraciones SQL INSERT para todos los datos de la categoría estadística que tengan su correspondiente Strategy.

Después de escribir el programa (y todos los Strategies), escribí un script para lanzarlo llamado run-sql-generator.sh, y lo ejecuté para todos los años que quería cargar.

El script SQL (que llamé load_season_data-2011.sql) para el 2011 es así:


                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'San Diego St',32,2,0.941);
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Kansas',32,2,0.941);
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Ohio St',32,2,0.941);
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Utah St',30,3,0.909);
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Duke',30,4,0.882);
                .
                (más datos)
                .
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Toledo',4,28,0.125);
                INSERT INTO won_lost_percentage(YEAR, TEAM_NAME, NUM_WINS, NUM_LOSSES, WIN_PERCENTAGE)VALUES(2011,'Centenary LA',1,29,0.033);
                .
                INSERT INTO scoring_offense(YEAR, TEAM_NAME, NUM_GAMES, NUM_POINTS, AVG_POINTS_PER_GAME)VALUES(2011,'VMI',31,2726,87.9);
                INSERT INTO scoring_offense(YEAR, TEAM_NAME, NUM_GAMES, NUM_POINTS, AVG_POINTS_PER_GAME)VALUES(2011,'Oakland',34,2911,85.6);
                INSERT INTO scoring_offense(YEAR, TEAM_NAME, NUM_GAMES, NUM_POINTS, AVG_POINTS_PER_GAME)VALUES(2011,'Washington',33,2756,83.5);
                INSERT INTO scoring_offense(YEAR, TEAM_NAME, NUM_GAMES, NUM_POINTS, AVG_POINTS_PER_GAME)VALUES(2011,'LIU Brooklyn',32,2643,82.5);
                INSERT INTO scoring_offense(YEAR, TEAM_NAME, NUM_GAMES, NUM_POINTS, AVG_POINTS_PER_GAME)VALUES(2011,'Kansas',34,2801,82.3);
                .
                (ya se hace una idea)
                .
                

La línea 1 muestra el primer SQL INSERT para la categoría estadística de porcentaje de partidos ganados/perdidos, y la línea 12 muestra el primer SQL INSERT para la puntuación ofensiva. Compare este código con Figure 2 y Figure 3 para ver la transformación de datos CSV a SQL INSERT.

Después de generar todos los scripts SQL a partir de los datos CSV, estaba listo para cargar la base de datos con los datos.

2 Cargar los datos en la base de datos PostgreSQL

Escribí un script para cargar los datos en PostgreSQL. En los siguientes ejemplos, ejecuté el shell interactivo psql . Primero, creé la base de datos ncaabb , después salí del shell.


                Ix:~ sperry$ "/Applications/Postgres.app/Contents/Versions/9.6/bin/psql" ‑p5432 ‑d "sperry"
                psql (9.6.1)
                Escriba "help" para obtener ayuda.

                sperry=#create database ncaabb;
                CREATE DATABASE
                sperry=#\q
                Ix:~ sperry$ 
                

Después, conecté la base de datos ncaabb , y ejecute el script build_db.sql :


                Ix:~ sperry$ "/Applications/Postgres.app/Contents/Versions/9.6/bin/psql" ‑p5432 ‑d "ncaabb"
                psql (9.6.1)
                Escriba "help" para obtener ayuda.

                ncaabb=#begin;
                BEGIN
                ncaabb=#\i /Users/sperry/home/development/projects/developerWorks/NcaaMarchMadness/src/main/sql/build_db.sql
                BUILDING DB...
                Script variables:
                ROOT_DIR ==>  /Users/sperry/home/development/projects/developerWorks/NcaaMarchMadness/src/main
                SQL_ROOT_DIR ==>  /Users/sperry/home/development/projects/developerWorks/NcaaMarchMadness/src/main/sql
                DATA_ROOT_DIR ==>  /Users/sperry/home/development/projects/developerWorks/NcaaMarchMadness/src/main/data
                LOAD_SCRIPT_ROOT_DIR ==>  /Users/sperry/l/MarchMadness/data
                CREATING ALL TABLES...
                CREATING ALL VIEWS...
                LOADING ALL TABLES:
                YEAR: 2010...
                YEAR: 2011...
                YEAR: 2012...
                YEAR: 2013...
                YEAR: 2014...
                YEAR: 2015...
                YEAR: 2016...
                YEAR: 2017...
                LOADING TOURNAMENT RESULT DATA FOR ALL YEARS...
                FIND MISSING TEAM NAMES...
                DATABASE BUILD COMPLETE.
                ncaabb=#commit;
                COMMIT
                ncaabb=#
                

En ese punto, los datos estaban cargados y yo estaba listo para crear los datos de la capacitación.

2

Crear datos de la capacitación

Escribí un programa llamadoDataCreator que utilicé para crear los datos de la capacitación para años específicos. Como con las otras utilidades que había escrito, también escribí un script de shell script para manejar este programa, y lo llamé run-data-creator.sh.

Ejecuté DataCreator en un instante. Solo especifiqué los años para los que quería datos de capacitación, y el programa leyó la base de datos y generó los datos para mí. Claro que tuve que escribir algunas clases de soporte para hacer que eso funcionase con DataCreator, lo que incluye un conjunto de Objetos de Acceso a Datos (DAOs) que lee los datos de la base de datos, junto con los objetos del modelo Java para mantener los datos.

Asegúrese de verificar los que están en el código fuente, específicamente en los paquetes com.makotojava.ncaabb.dao y com.makotojava.ncaabb.model .

Para ejecutar run-data-creator.sh, abrí una ventana de Terminal en mi Mac y especifiqué que se creasen los años de 2010 a 2017:


                Ix:$ ./run‑data‑creator.sh 2010 2011 2012 2013 2014 2015 2015 2017
                
                Number of arguments: 8
                Script arguments: 2010 2011 2012 2013 2014 2015 2015 2017
                INFO: Properties file 'network.properties' loaded.
                2017‑09‑28 15:40:38 INFO  DataCreator:72 ‑ ∗∗∗∗∗∗∗∗∗∗∗ CREATING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:40:44 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:40:44 INFO  DataCreator:136 ‑ Saved 128 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2010.trn'
                2017‑09‑28 15:40:51 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:40:51 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2011.trn'
                2017‑09‑28 15:40:57 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:40:57 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2012.trn'
                2017‑09‑28 15:41:04 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:41:04 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2013.trn'
                2017‑09‑28 15:41:10 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:41:10 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2014.trn'
                2017‑09‑28 15:41:17 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:41:17 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2015.trn'
                2017‑09‑28 15:41:23 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:41:23 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2015.trn'
                2017‑09‑28 15:41:29 INFO  DataCreator:132 ‑ ∗∗∗∗∗∗∗∗∗∗∗ SAVING TRAINING DATA ∗∗∗∗∗∗∗∗∗∗∗∗∗∗
                2017‑09‑28 15:41:29 INFO  DataCreator:136 ‑ Saved 134 rows of training data 'NCAA‑BB‑TRAINING_DATA‑2017.trn'
                Ix:~/home/development/projects/developerWorks/NcaaMarchMadness/src/main/script sperry$ 
                

Después de crear los datos de la capacitación, era el momento de entrenar y validar las redes que utilizaría para realizar mis elecciones para el torneo de baloncesto de la NCAA.

Al principio de este tutorial dije que hablaría sobre normalización, y ahora ha llegado el momento.

La razón para que todos esos datos de entrada estén normalizados es que no todos los datos se crean iguales (numéricamente hablando). Por ejemplo, una categoría de datos estadística es el porcentaje de partidos ganados/perdidos de cada equipo, que es un porcentaje y, como se puede imaginar, tiene valores entre 0 y 1. En contraste, las faltas por partido, que es una media de todos los partidos jugados, tienen como límite inferior cero y no tiene límite superior.

No tiene sentido hacer una comparación en bruto de dos números cuyos rasgos son diferentes. Así que tenía que normalizar los datos (específicamente usando una técnica de normalización llamada Escalado de variables). La ecuación que usé fue sencilla:

Feature scaling formula

Para cualquier valor en bruto X calculé el valor normalizado X' sobre el rango de valores que van desde Xmin a Xmax. Naturalmente, eso significaba que tenía que calcular los valores mínimo y máximo para todas las categorías estadísticas. Pero, como tenía los datos en la base de datos, me fue fácil definir una vista para que PostgreSQL hiciese esto por mi (eche un vistazo a la vista v_season_analytics, y a su definición en create_views.sql).

DataCreator es el responsable de normalizar todos los datos de la capacitación. Finalmente estaba listo para comenzar con la capacitación de las redes.

3

Capacitar y validar la red

Utilizar el aprendizaje automático para capacitar y validar una ANN es mitad arte, mitad ciencia. Tenía que conocer bien los datos (ciencia), pero también tenía que adivinar (arte) cuáles eran las mejores estructuras de red hasta que encontrarse algunas que funcionasen. Tenía que responder preguntas como las siguientes:

  • ¿Cuántas de las 23 categorías estadísticas disponibles quiero utilizar?
  • ¿Cuántas capas ocultas quiero que la red tenga?
  • ¿Cuántas neuronas debe haber en cada capa oculta?
  • ¿Cuál debería ser la apariencia de la salida de la red?

Y resulta que acabé utilizando 21 de las 23 categorías. Así que hay 42 entradas (una para cada equipo). También decidí que el resultado debía ser una puntuación normalizada basada en los emparejamientos de ejemplo de los dos equipos: para cada uno de los juegos simulados, el equipo que tenía la mayor puntuación normalizada era el ganador.

En cuanto al número de neuronas y de capas ocultas, investigué un poco, pero, al final, todo se redujo a prueba y error. Ejecuté los datos de la capacitación por toda la red un MONTÓN de veces, y me quedé con las redes que tenían un rendimiento > 70 %.

Escribí un programa llamadoMlpNetworkTrainer cuyo único propósito era capacitar y validar redes, y guardé aquellas redes que tenían un rendimiento superior al umbral que especifiqué.

Esto es lo que hice: le di instrucciones a MlpNetworkTrainer para capacitar a las redes utilizando datos del 2010 al 2014, después validé las redes contra datos del 2015 y 2016, y me quedé con aquellas que tuvieron un rendimiento superior al umbral del 70 %.

Si la red tenía un rendimiento superior al umbral del 70 %, el programa guardaba esa red para utilizarla como parte de la matriz de redes para hacer las elecciones del torneo. Si no lo superaba, se descartaba.

Repetí este proceso hasta que tuve 31 redes que podía ejecutar como una «matriz» de redes del torneo simulado.

4

Simular el torneo

Después de que acabó la temporada regular (aproximadamente el 10 de marzo), descargué los datos CSV, los ejecuté a través de SqlGenerator, y cargué en la BB. DD.

Después de que el Comité de selección del torneo de la NCAA hiciese las selecciones para el torneo del 2017, creé un archivo llamado tourney_teams_file_2017.txt que contenía todo sobre los equipos que participarían en el torneo (esto ya lo había hecho, para las temporadas del 2010 al 2016).

Ejecuté un programa que había escrito que creaba SQL INSERT para poder cargar los participantes del 2017 en tournament_participant . Esto ya lo había hecho para los años anteriores, para crear los datos que necesitaba para validar las redes.

Todo estaba preparado. Había capacitado y validado 31 redes y tenía los datos de la temporada para el 2017. Ahora nada me podía detener. Excepto que necesitaba una forma de visualizar como se emparejarían los equipos.

Así que escribí un programa llamado TournamentMatrixPredictor para generar un archivo CSV que podría cargar en Open Office Calc para cada uno de los equipos que participaban en el torneo. Cada archivo contenía el resultado previsto de un equipo específico en cada partido simulado entre ese equipo y cada uno de los otros equipos del torneo.

El archivo para la Región Central del Estado de Tennessee se muestra en la siguiente imagen, que se carga en Open Office como una hoja de cálculo.

Figure 4. Las previsiones de la red para Middle Tennessee State
The network's predictions for Middle Tennessee State

La hoja de cálculo presenta simulaciones de Middle Tennessee State (TEAM) contra todos los otros equipos (VS OPPONENT) dentro de (incluido el propio Middle Tennessee), junto con las medias de las predicciones de las 31 redes con los resultados de victoria (WIN %), derrota (LOSS %) o empate (PUSH %) de Middle Tennessee contra sus oponentes, y el resultado individual de las redes.

Extendiéndonos hacia la derecha (fuera del marco) están los resultados individuales de las redes (solo las predicciones de ganador). Por ejemplo, la red cuya estructura es 42x42x107x49x10x14x2x2 — 42 neuronas de entrada, 6 capas ocultas (42 neuronas en la primera, 107 en la siguiente, etc.) y 2 neuronas de salida — muestra el ganador previsto para cada partido.

En la primera ronda, el oponente de Middle Tennessee State, que estaba 12º clasificado era claro favorito, el 5º clasificado, Minnesota (cuando más bajo sea el número de la clasificación más favorito es el equipo para el comité de selección de NCAA). La media de la matriz era un poco nebulosa: las 31 redes trabajando conjuntamente pronosticaron que Middle Tenn ganaría, perdería o empataría con el 37,14 %, 17,14 % y 45,71 % de probabilidad respectivamente.

No estaba completamente seguro de qué hacer. ¿La ciencia me había fallado? Admitámoslo, los eventos deportivos son caóticos (por eso el «madness» (locura) de «March Madness»). Así que, sabía que habría decisiones difíciles como estas que precisarían de algo de intuición. Aunque la mayoría de las redes de mi matriz no podían elegir claramente un ganador (45,71 %), las que lo eligieron fueron más del doble de las que no lo hicieron (37,14 % a 17,14 %).

Al final, hubo dos cosas que me hicieron elegir Middle Tenn para mi ronda: acababa de ganar en su conferencia (EE. UU. Central) y tenía una oferta automática para el torneo, y todas las redes de mis capas profundas (profundas por este esfuerzo) eligieron Middle Tenn de forma abrumadora. Así que, elegí Middle Tenn para vencer inesperadamente a Minnesota. Y resultó que la red estaba correcta. Realicé este proceso para todos los equipos del torneo, rellené todas las rondas y esperé lo mejor.

Como dije antes, la red (y yo) acertamos aproximadamente el 72 % de las elecciones. No está mal. Y fue muy divertido.

Conclusión

En este tutorial, se ha hablado sobre conceptos de Redes Neuronales Artificiales (ANN), después hablé sobre el perceptrón multicapa, y finalmente le mostré detalladamente un caso de estudio en el que capacité un matriz de redes MLP y las utilice para elegir ganadores del torneo «I Men’s Basketball» de la división NCAA.