Reducción de dimensiones: Principal Component Analysis (PCA)

Principal Component Analysis o PCA en corto es un método de reducción de dimensiones bastante conocido y comúnmente usado. Este método transforma ortogonalmente las observaciones (quizás relacionadas) en un conjunto de puntos linealmente no relacionados. De esta forma se consigue que el primer componente tenga la varianza mayor. El siguiente componente será el que tendrá la varianza mayor de los restantes y es ortogonal al anterior componente, y así sucesivamente. Este método es sensible a la escala de las variables. Para evitar que los valores sean un problema tendremos que escalar las variables estandardizándolos antes de usar PCA.

Generar una barra de progreso en ipython notebooks

Manual con código para generar una barra de progreso en nuestro código para saber en que porcentaje de compleción estamos sin llenar el output con números. Muchos de los que usáis jupyter (el nuevo ipython notebooks) podéis imprimir por pantalla la iteración en la que vuestro loop reside. Eso es solo posible para una cantidad pequeña de iteraciones. Cuando llegamos a varios miles se puede generar un output bastante engorroso. Googleando un poco encontré la solución. Este código nos genera una clase que luego podemos llamar para obtener la barra de progreso junto con el porcentaje completado.

import sys, time
try:
    from IPython.core.display import clear_output
    have_ipython = True
except ImportError:
    have_ipython = False

class ProgressBar:
    def __init__(self, iterations):
        self.iterations = iterations
        self.prog_bar = '[]'
        self.fill_char = '*'
        self.width = 40
        self.__update_amount(int(0))
        if have_ipython:
            self.animate = self.animate_ipython
        else:
            self.animate = self.animate_noipython

    def animate_ipython(self, iter):
        try:
            clear_output()
        except Exception:
            # terminal IPython has no clear_output
            pass
        print '\r', self,
        sys.stdout.flush(),
        self.update_iteration(iter + 1)
        
    def update_iteration(self, elapsed_iter):
        self.__update_amount((elapsed_iter / float(self.iterations)) * 100.0)
        self.prog_bar += '  %d of %s complete' % (elapsed_iter, self.iterations)

    def __update_amount(self, new_amount):
        percent_done = int(round((new_amount / 100.0) * 100.0))
        all_full = int(self.width - 2)
        num_hashes = int(round((percent_done / 100.0) * all_full))
        self.prog_bar = '[' + self.fill_char * num_hashes + ' ' * (all_full - num_hashes) + ']'
        pct_place = int((len(self.prog_bar) / 2) - len(str(percent_done)))
        pct_string = '%d%%' % percent_done
        self.prog_bar = self.prog_bar[0:pct_place] + \
            (pct_string + self.prog_bar[pct_place + len(pct_string):])

    def __str__(self):
        return str(self.prog_bar)
        

Y el siguiente código vemos un ejemplo de como llamaremos la clase para obtener la funcionalidad deseada

c = ProgressBar(1000)
for i in range(1000):
    c.animate_ipython(i)

Para los interesados encontré el código aquí.

Usar listas en matplotlib en vez de un punto a la vez

TLTR: Cuando uses matplotlib en python pasa a la función dos listas con todos los puntos en vez de ir uno por uno. Así te ahorras el rendering cada vez que usas la función consiguiendo incrementos de velocidad de varias magnitudes.

Hasta hace poco lo que hacía mayoritariamente cada vez que tenia que hacer una gráfica con matlplotlib era poner un punto cada vez. ERROR!!! Hasta el momento asumía que el tiempo de ejecución del script era debido a los algoritmos que usaba. Pero no, era debido a una  malapraxis muy simple de resolver. Últimamente estoy trabajando con cantidades más grandes de datos y la ejecución de mi código en python me tardaba más de lo que debería hasta que descubrí que hacia un error de novato. Poner punto por punto a una gráfica en vez de pasarle la lista entera era lo que causaba una ejecución tan lenta de mi código. Iba punto por punto porque era como me llegaban los datos y no los tenia que guardar, pero resulta que si los pongo en una lista y luego paso la lista a la librería el resultado es infinitamente más veloz y eficaz. La librería se ahorra renderizar la gráfica cada vez si recibe una lista.

El siguiente código tiene una ejecución de más de 6 segundos y como veis es la forma incorrecta de hacer un scatter plot:

start_time = timeit.default_timer()
for x in range(100):
for y in range(10):
matplotlib.pyplot.scatter(x,y)

matplotlib.pyplot.show()
end_time = timeit.default_timer()
print(end_time - start_time)

El siguiente código es la manera rápida de hacer un scatter plot y tarda unos 300ms (si MILI-segundos).

xlist = []
ylist = []

start_time = timeit.default_timer()
for x in range(100):
for y in range(10):
xlist.append(x)
ylist.append(y)

matplotlib.pyplot.scatter(xlist, ylist)
matplotlib.pyplot.show()
end_time = timeit.default_timer()
print(end_time - start_time)

He subido un python notebooks (jupyter notebook) a github para que lo ejecutéis vosotros si queréis.

Reducción de dimensiones: Self-organizing feature map (SOFM)

Self-organizing map (SOM) o self-organizing feature map (SOFM) es un método que usa redes neuronales (neuronal networks) para reducir las dimensiones de un vector de datos. Para reducir las dimensiones lo que hace es usar los vecinos de un punto en concreto para moverlo al nuevo espacio dimensional manteniendo la misma topografía que en el espacio original.

Una de las ventajas de SOFM es que es un método unsupervised, esto quiere decir que no necesitamos un training dataset para entrenar nuestra red neuronal. Normalmente para la inteligencia artificial (y el machine learning) se requiere que el training dataset esté etiquetado. Un data set etiquetado son puntos del input que ya se sabe cual tiene que ser el resultado. De este modo la maquina es capaz de identificar que resultado tiene que producir.

Para explicar con más detalle este método voy a usar la foto de la wikipedia que lo hace más visual.

Somtraining
SOMF training

 

En la imagen vemos la malla que representa el input en un espacio multidimensional y la zona azul que representa un espacio 2D al que queremos extrapolar nuestros puntos. El primer paso es seleccionar nuestro punto en el espacio de origen que se acerque mas al espacio 2D. El segundo paso es mover ese punto en concreto al espacio 2D. El tercer paso, y ultimo, consiste en mantener las distancias entre puntos en el nuevo plano. Si se consigue mantener las distancias entre los puntos conseguiremos una reducción de dimensiones satisfactoria.

El paper original lo podéis encontrar aquí. Pero si no tenéis ninguna afiliación académica quizás os interese checkear este otro post.

Reducción de dimensiones: T-SNE

Como ya explicamos en el post anterior los ordenadores si que pueden procesar grandes cantidades de datos multidimensionales. Pero los humanos a veces necesitamos “ver” y entender los datos. Cuando estamos trabajando en un espacio multidimensional no podemos imaginarnos nuestro dataset. Para solventar este problema se ha desarrollado T-SNE. Éste es un algoritmo pensado especialmente en reducir dimensiones (a 2 o 3) para que podamos visualizar los datos. En el paper original se expresa de forma explícita que el algoritmo está pensado para la visualización de datos. Usar T-SNE para la reducción de dimensiones puede causar efectos desconocidos. Así que si lo usas para evitar “la maldición de la dimensionalidad”  allá tu 😉

El concepto principal consiste en que los puntos cercanos (en el espacio multidimensional) se atraen y los distantes se repelen. Para conseguir este objetivo el algoritmo tiene unos cuantos parámetros que se permiten alterar. La “perplexity” es la cantidad de vecinos que un simple punto puede afectar. Por lo que he visto hasta ahora un valor entre 30-50 suele ser el óptimo. Luego tenemos epsilon que nos sirve para determinar el tamaño de los pasos de aprendizaje. Valor pequeño el algoritmo le cuesta más a encontrar el óptimo. Con un valor grande te lo puedes pasar. Finalmente la cantidad de iteraciones o steps para conseguir la convergencia. A más iteraciones en teoría más cerca del valor óptimo, pero como es evidente como más iteraciones más tiempo de computación requieres.

Para los que queráis visualizar como funciona y un poco sus efectos cambiando los parámetros podéis verlo en vuestro navegador. Si por el contrario queréis implementarlo en Javascript aquí podéis encontrar una versión oficial del desarrollador principal (es la que estoy usando). Pero si sois tan malotes que queréis implementarlo vosotros mismos mejor que os leáis el paper original.

Bonus: En el paper para computar las distancias se usa la distancia euclidiana (la “normal”) quizás otro tipo de distancias puede ir mejor para vuestro problema.

Reducción de dimensiones: Introducción a los espacios multidimensionales

En inteligencia artificial y machine learning en la mayoría de ocasiones se usan espacios multidimensionales. Los espacios multidimensionales son espacios en los que los datos requieren más de un valor. Los espacios multidimensionales son espacios con puntos repartidos por el espacio. Un espacio 2D tiene dos dimensiones, las típicas X, Y. Un espacio 3D tiene tres dimensiones X, Y, Z. Las dimensiones las puedes llamar X, Y, Z pero también perro, gato, conejo. Como seres humanos estamos limitados a poder visualizar espacios a 3D máximo. Aunque no puedas visualizar dimensiones superiores a tres, puedes llegar a entenderlo. Imagínate que quieres ir a un restaurante a 10km máximo de tu casa. Esto en un mapa son dos dimensiones norte/sur y este/oeste. Ahora si decides que quieres ir a un restaurante italiano ya hemos añadido otra dimensión. Si no quieres pagar más de 10€ por la cena ya has añadido otra dimensión. Por lo que ya tenemos un espacio de 4D. Dos para las dimensiones del mapa norte/sur, este/oeste, otra para que tipo de restaurante y la cuarta para los precios. Se le podría sumar otra dimensión si añadiéramos en que planta esta el restaurante. Virtualmente podríamos añadir infinidad de dimensiones.

Un vector o un punto en nuestro espacio es un conjunto de valores para todas las dimensiones. Un valor (o dato) puede ser indefinido o ausente, no tiene porque tener un valor concreto. Un punto es asociado a la persona o el elemento de la acción. Tu puedes ser ese vector/punto. Con el ejemplo anterior nuestro punto sería (23, 31, italiano, 10) que hacen referencia a norte/sur, este/oeste, tipo de comida y precio máximo. Las variables pueden ser categóricas y cuantitativas. Las categóricas es “italiano” no tiene un valor númerico asociado. Otros ejemplos de variables categóricas pueden ser el sexo de una persona o su color preferido. Las variables cuantitativas son los números.

La pregunta del millón es: cómo podemos pensar en un espacio de más de tres dimensiones en el que una de ellas es “italiano”? Simplemente no podemos. Nuestro cerebro no puede visualizar más de tres dimensiones. Estos espacios sólo tienen sentido matemáticamente. Por lo que para trabajar en este tipo de espacios si queremos visualizarlos de algún modo tenemos que pensar en tres dimensiones máximo y extrapolarlo. Otra opción también puede ser usar distintas técnicas de reducción de dimensiones. En los próximos posts describiré distintos métodos para la reducción de dimensiones.

Propósitos para el 2017

Los propósitos de año nuevo sirven para hacer durante el nuevo año lo que no se estaba preparado para hacer el año anterior. En cierto modo creo que es cierto, uno empieza de 0 y tiene que avanzar, por lo que es año nuevo se tienen que coger los conocimientos del pasado y mejorarlos. Usar los cimientos del pasado para seguir construyendo. Vivir es seguir mejorando, ya hace algunos años que hago públicos mis objetivos de año nuevo. Para los curiosos aquí encontrareis los objetivos del año pasado revisados.

Este año toca nuevos propósitos y objetivos. Como cada año, los propósitos tienen algunas características, como que sean concretos, medibles y relevantes. Esta vez he intentado que los propósitos sean con la ilusión de un niño, la sensatez de un adulto, que contenga algo imposible para hacer lo inesperado.

Este año va a ser un año de cambios. Ahora si, sin más dilaciones aquí os dejo la lista de los propósitos del 2017:

Profesional (y formación):

  1. Esta vez si que si que si! Sacarme el B2 de alemán. Para esto voy a finalizar mi lista de 5.000 palabras alemanas y aprender la gramática del libro básico y intermedio. Voy hacer un par de amigos alemanes en Boston y solo consumir noticias en alemán.

  2. Asistir a un evento al mes en Boston. La idea es aprovechar para conocer la cultura, gente nueva,  el ecosistema de startups, el ecosistema técnico y probar nuevas actividades. Voy a buscar meetups y eventos que organicen MIT y Harvard.

  3. Implementar alguna de mis ideas. Voy a revisar mi lista de ideas y programar una de ellas los fines de semana.

  4. Mejorar mi inglés escrito. Escribir posts en inglés y buscar alguien con el que podamos hablar sobre el estilo y que me los corrija.

  5. Finalizar el máster. Escribir el trabajo de final de máster y entregarlo para dar por terminado el título.

  6. Escribir un research paper y publicarlo. Con el mero propósito de ganar puntos para posteriores andaduras (phd?)

Personal:

  1. Hacer deporte cada dos días. No hace falta correr un maratón cada vez que salga pero si que me gustaría mantener una buena forma física (4h body puede ser una idea).

  2. Siguiendo la “tradición” voy a leer por lo menos doce libros. Creo que es un punto muy importante que muchos deberían seguir. La calidad de los libros es difícilmente comparable a la de los posts o los tweets. Como siempre, leeré aproximadamente uno al mes.

  3. Hacer deliberadamente un buen acto cada día. El año pasado tres me pareció un número bueno pero es más difícil de lo que parece. Este año pongo uno como mínimo para seguir haciendo de este mundo un lugar mejor.

  4. Aprender una receta de tupper nueva cada mes. Quiero mejorar mi repertorio culinario ya que estoy en un país que la comida no es lo que más se cuida.

  5. Cocinar la mayoría de mis platos. La intención es evitar comer todas las porquerías que ponen en las comidas y los alimentos no básicos.

  6. Meditar por lo menos una vez al día. Preferiblemente antes de ir a dormir para calmar la mente y conseguir un estado de tranquilidad a lo largo del día.

Propósitos extras:

  1. Seguir con mis clases de salsa con la idea de seguir conociendo gente nueva y desconectar un poco.

  2. Seguir con “Bollywood”. Me gustó lo que probé en Munich y me gustaría seguir un poco para conocer un poquito más de gente.

  3. Asistir a un Burning Man. Tengo curiosidad para ver como es ya que se ha hablado mucho de ello

  4. Visitar 5 estados. Ya que estoy en USA por primera vez me gustaría hacer un poco de turismo por los distintos estados.

  5. Estirar cada día. Pretendo mejorar mi flexibilidad para evitar que se me acorten los músculos debido a la cantidad de tiempo que paso sentado.

  6.  Aprender italiano. Debido a la similitud con el español espero que aprendiendo unas 3000 palabras en italiano y haciendo un par de amigos italianos podré ganar un buen nivel con el idioma.

  7. Aprender a usar Tensor Flow. No sabía si poner esto en la lista o no ya que no veo de dónde voy a sacar el tiempo pero realmente esto es algo que me quema por dentro y me gustaría aprender. Quizás seria mejor keras (investigar).

Y para variar como cada año, ante la duda escoger aventura, ser feliz, sobrevivir otro año más y conquistar el mundo!

Feliz año nuevo! 😀

Revisión de los propósitos del 2016

No solamente uno se tiene que proponer objetivos en la vida, sino que tiene que pasar revisión de estos para ver dónde se ha estado fuerte y dónde se puede mejorar. Para los que queráis ver los propósitos en el post original los podéis ver pulsando en el siguiente enlace.

Profesional (y formación):

  1. Sacarme el B2 de alemán => No conseguido. No hay manera… Cuando quise ya no habían plazas para diciembre. My bad…

  2. Asistir a un mínimo de 12 eventos en Múnich => Conseguido! He asistido a distintos tipos de eventos. Éstos han sido más o menos interesantes pero todos han sido valiosos a su modo.

  3. Desarrollar una idea multimillonaria. => Conseguido! Aunque no se si vais a estar de acuerdo intenté desarrollar una idea para acertar el resultado de los partidos de fútbol. No ha funcionado pero lo he hecho.

  4. Empezar una lista de objetivos mensuales. => No conseguido. En el primer mes ya supe que no lo iba a conseguir. No termino de verlo útil.

  5. Crear algo cada día. => Conseguido! He cocinado nuevos platos, he programado algunos side projects sencillos  y escribir (esto ya no tanto).

  6. Practicar el ideas-sex / idea-machine de Altucher. Conseguido a medias. Empecé con mucha energía pero se me terminaron las ganas. Me ayudó a darle un boost a mi creatividad y quizás me sirva si realmente lo aplico a la vida real. Ya veré si sigo o no.

Personal:

  1. Salir de mi circulo de amistades habitual. => Conseguido! He hecho algunas nuevas amistades. Me ha costado algunas viejas con las que tenia poco en común pero creo que por regla general no me arrepiento. Cabe añadir que creo que me ha faltado tiempo para consolidar y que el idioma no me ha ayudado.

  2. Poder correr sin que me duela la rodilla. => Conseguido! Salgo a correr de vez en cuando, solo media horita o quarenta minutos. No me atrevo a hacer más pero me doy por satisfecho. Fui al especialista para que me mirara la rodilla y me dijo que no tenia nada.

  3. Los doce libros al año. Conseguido! Como siempre he leído bastantes más libros de los doce. Me doy por más que satisfecho.

  4. Aprender una tercera lengua extranjera (Francés o italiano) => Conseguido! Estoy en proceso de aprender italiano. La verdad es que me está costando poco. De momento he hecho dos semestres en la universidad llegando al nivel A2 de italiano. Es bastante básico pero es un inicio.

  5. Visitar Suiza y Liechtenstein No fui a Liechtenstein y suiza solo fue Zurich. Me lo esperaba bastante más distinto aun así mis anfitriones hicieron mi estancia muy agradable.

  6. Hacer, deliberadamente, tres cosas buenas cada día. => Conseguido a medias. Ciertamente he estado haciendo más bien que anteriormente pero tres cosas al día es más complicado de lo que parece. Otro de los problemas que me he encontrado es que a menudo es dificil encontrar soluciones a problemas más allá del dinero.

Propósitos extras:

  1. Ir al carnaval de Köln => No conseguido. Me cogió en exámenes y el 11 del 11 (Noviembre) no me acorde a tiempo

  2. Monetizar un proyecto => No conseguido. El único proyecto que he desarrollado que podría haber sido fructífero no lo ha sido. No es nada sorprendente, me decanté por la “putería” mas que por la pragmaticidad.

  3. Mejorar mis habilidades de escritura en inglés => Conseguido con creces! Me apunté a un curso de inglés del que esperaba conseguir una mejora lineal pero a pesar de ser un curso intensivo de una semana me ha dado la sensación que mis habilidades de escritura inglesa han mejorado substancialmente.

  4. Escribir un research paper y publicarlo. => No conseguido. Parece que el próximo año si que está al alcance de la mano.

  5. Estirar cada día. => Conseguido! Después de ir en bicicleta, a veces en momentos random he estirado un poco y la verdad es que he ganado flexibilidad aunque no estoy en mi cúsipide no estoy lejos.

  6. Hablar más frecuentemente con desconocidos. => Conseguido pero no del modo que pensaba. Algunas veces han sido gestos otros han sido conversaciones. Pero ha sido con gente a la que he visto varias veces y no completamente desconocidos.

Inesperado:

  1. Fui a una boda India en la India y viajé por distintas ciudades durante tres semanas.
  2. He empezado a bailar Bollywood.
  3. Me han contratado en Harvard durante un año.
  4. He empezado natación.
  5. Un profesor tiene intención de meterme como co-autor en un paper.
  6. He visitado amigos a otras ciudades alemanas.

Estoy bastante satisfecho con la cantidad de propósitos que he conseguido. Pero me molesta especialmente algún propósito en rojo que ya lleva en la lista bastante tiempo. Tocará mejorar para el próximo año! :) De lo que no me propuse y he realizado estoy bastante contento es muy variado y no lo hubiera esperado.

Feliz año nuevo!

Duchas de agua fría

Últimamente en internet está habiendo una especie de boom a favor de las duchas de agua fría. Así es. Agua. Fría… Un servidor se apuntó al movimiento para probar. Así que voy a dejaros una lista de los presuntos beneficios de ducharse con agua fría seguido de mi experiencia personal.

Las duchas de agua fría no es algo que venga de nuevo. En algunas sociedades, como los espartanos, las duchas de agua caliente no eran algo común. Los espartanos consideraban las duchas calientes para personas débiles. En otras sociedades, como la finlandesa, hay la costumbre de sudar la gota gorda en una sauna y seguidamente saltar en el agua del lago helado. Estos rituales se hacen bajo algunas premisas. Las premisas de mejorar la vida de las personas que los practiquen. Algunos de los beneficios son los siguientes.

Los beneficios se podrían separar en dos grupos. En un grupo podemos poner los beneficios a nivel fisiológico y otros a nivel mental/espiritual. A nivel fisiológico mejora tu piel y pelo, estimula la pérdida de peso, la circulación y el sistema inmune. También ayuda a incrementar la testosterona y la fertilidad. Para los más deportistas os interesará saber que además reduce inflamaciones y ayuda a la recuperación del musculo. A nivel mental incrementa la fuerza de voluntad, la resistencia emocional y el estado de alerta. Para los que tengáis mucho trabajado ayuda con el estrés y la depresión.

Ahora mi experiencia personal. Se hace más fácil con la práctica. Las primeras cuatro o cinco veces me costó meterme. Incluso me duché un par de veces con agua caliente queriendo hacer el cambio pero echándome para atrás en el último momento. La clave es decidir que uno se mete si o si sin muchas dilaciones. Como truco recomiendo ducharse normal y luego escoger pasar X segundos bajo el agua fría. Al principio estaba quieto pero más adelante ya me movía e iba cambiando de posiciones. Punto a favor si el aire del cuarto de baño no es frío. Si no no se hace muy agradable salir de la ducha frío para pasar un frío prolongado. A modo de curiosidad las primeras veces cuando el agua me caía por encima me salia una risilla tonta. No preguntes por que, no tiene explicación para mi. Supongo que me reía de la ironía del momento.

He intentado hacer como Moneyball para predecir el resultado de los partidos de fútbol y he fracasado

Hace ya algún tiempo leí un post interesante sobre como un data scientist usó el concepto presentado en el libro Moneyball para escoger mejor los jugadores del FIFA 2016 cuando jugaba con “Career Mode”. El concepto de Moneyball es básicamente hacer data mining para conseguir el mejor equipo de béisbol con el menor precio. El argumento principal del libro es que al fichar los jugadores de béisbol las personas usan sus propios inclinaciones que normalmente no coinciden con la realidad o que están ancladas al pasado. Como ejemplo en el libro pone que a menudo el resultado de una táctica ofensiva de un jugador se puede medir mejor usando la frecuencia con que el bateador llega a la base en vez de la velocidad con la que batea. La velocidad de bateo ha sido la métrica que se ha usado des del principio pero después de un análisis detallado los analistas descubrieron que otras métricas son más relevantes.

A partir de este concepto creé mi propio experimento. La idea era que normalmente los analistas usan datos reales de los partidos para definir la habilidad del jugador. El problema que tiene esto es que lo que se luce el jugador depende del nivel del contrincante. Por eso mi idea era usar una valoración más o menos neutra y absoluta para predecir la calidad de cada jugador. Estas métricas las usaría para predecir la calidad del once inicial y así poder determinar el resultado final del partido.

Lo primero que hice fue recopilar tanta información como pude de todos los jugadores que pude. Para determinar la calidad del jugador usé la que usan en el videojuego FIFA 2015. El valor de las apuestas que se habían hecho en distintos sitios web de apuestas lo saqué de football-data. La alineación inicial y el resultado final del partido lo saqué de 11v11.

Todo el scrapping que hice puede sonar muy divertido y rápido pero no fue así. Durante el proceso pasé por alto varias cosas importantes. Lo primero es que en la liga española tenemos muchos jugadores españoles y con nombres españoles. El problema de los nombres españoles es que usan caracteres especiales como acentos o la ñ causando problemas con el encoding. El segundo problema es que el scrapping es divertido si la página web es consistente y está bien ordenada. Resultó no ser el caso. Por algún motivo que desconozco algunas de las páginas de la web no tenían un formato consistente con el resto de la web y tuve que programar excepciones. El tercer y último punto es que las webs de dónde extraje la información tenían herramientas para evitar el scrapping. Por suerte no eran muy estrictos con el número de peticiones que un mismo ordenador podía hacer y conseguí toda la información que buscaba en relativamente poco tiempo.

El resultado final fue que no funcionó. Me pareció una idea innovadora pero quizás no muy realista. El fútbol se ha caracterizado por las sorpresas. Usando sólo la información de las apuestas puedo predecir en un 50% cual será el resultado final del partido. Curiosamente con regresión lineal y toda la información de los jugadores se puede predecir también con un 50% quién será el ganador del partido. Si quitamos el empate de la ecuación todos los algoritmos que probé siguen funcionando igual de “bien”. Básicamente podría tirar una moneda en el aire y decir que cara gana el equipo local y cruz el visitante y acertaría el mismo número de veces que todos los algoritmos probados. Una pena que no funcionara pero me alegra haberlo probado.