Archivos de la categoría Programación

Introducción a TensorFlow

La web oficial de TensorFlow tiene muy buenos recursos. En esencia lo que hay en este post proviene del “get started” de la web oficial.

En el primer ejemplo importaremos TensorFlow. Crearemos dos constantes y las imprimiremos en pantalla.

import tensorflow as tf
node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0) # También tf.float32 de forma implícita
sess = tf.Session()
print(sess.run([node1, node2]))
node3 = tf.add(node1, node2)
print("node3: ", node3) #Esta linea muestra las propiedades del tensor
print("sess.run(node3): ",sess.run(node3)) # Aquí se imprime el resultado

También podemos aplicar operaciones a los tensors. Las operaciones producen más tensores. En el siguiente ejemplo sumamos dos variables (tensores) y luego las sumamos. Un tensor es capaz de procesar listas también como veremos.

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b
print(sess.run(adder_node, {a: 3, b:4.5})) # 7.5
print(sess.run(adder_node, {a: [1,3], b: [2, 4]})) # Itera sobre la lista: 3 y 7

Podemos multiplicar

add_and_triple = adder_node * 3.
print(sess.run(add_and_triple, {a: 3, b:4.5}))

E incluso podemos crear modelos lineales

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
linear_model = W * x + b

Las constantes son inicializadas con tf.constant, y su valor no puede ser cambiado. Contrariamente las variables son creadas usando tf.Variable y no son inicializadas en el inicio. Para inicializar las variables  tenemos que usar una función especial como en el siguiente ejemplo:

init = tf.global_variables_initializer()
sess.run(init)

Lo importante es usar init. Es un “handle” que inicia el grafo, inicializando las variables que hasta entonces habían permanecido sin inicializar.

Ya que la X es la variable podemos evaluar el modelo usando distintos valores simultáneamente de la siguiente manera:

print(sess.run(linear_model, {x:[1,2,3,4]}))
# Output: [ 0, 0.30000001, 0.60000002, 0.90000004]

La función de pérdida (loss function) puede medir la distancia entre el modelo actual y los datos proporcionados. Usaremos la función de pérdida estándar para hacer una regresión lineal que sume los cuadrados de las distancias entre el modelo actual y los datos. El modelo crea un vector en el que cada posición corresponde a una distancia de error. Cuando usamos tf.square estamos elevando al cuadrado el error. Luego sumamos los errores para crear un simple escalar que abstrae el error de todos los puntos usando tf.reduce_sum:

y = tf.placeholder(tf.float32)
squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))

El valor producido es:  23.66

Podríamos mejorar el modelo manualmente asignando nuevos valores a W para obtener resultados perfectos de -1 y 1. Una variables es inicializada por el valor proporcionado por tf.Variable pero puede ser cambiada si usamos operaciones como tf.assign. Por ejemplo, W=-1 y b=1 son los parámetros óptimos de nuestro modelo. Podemos cambiar W y b acordemente:

fixW = tf.assign(W, [-1.])
fixb = tf.assign(b, [1.])
sess.run([fixW, fixb])
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]})
#resultado: 0

Hemos adivinado el valor perfecto para W i b aunque este no es el objetivo de  machine learning. El objetivo es encontrar los parámetros correctos para el modelo automáticamente. Pero esto lo vais a encontrar en la próxima entrega 😉

Bonus: En matemáticas y en física, un tensor es cierta clase de entidad algebraica de varias componentes, que generaliza los conceptos de escalar, vector y matriz de una manera que sea independiente de cualquier sistema de coordenadas elegido. ( wikipedia )

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.

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.

Que formato es el mejor para guardar números con decimales (float)?

Hace ya algunos días que le estoy dando vueltas al crear un script que me cogiera determinados datos y los fuera guardando en un archivo. Como quiero guardar muchos datos durante un largo periodo de tiempo he pensado que quizás debería empezar por lo básico. Que formato de archivo es mejor para almacenar este tipo de datos.

Para averiguar que formato es el ideal para tal propósito he ideado un script en python que lo que hace es generar cuatro números aleatorios entre -10 y 10 por cada una de las 100.000 hileras disponibles (en total 400.000 valores). Luego, usando el mismo data set, he almacenado esta información usando distintos formatos. Los formatos que he usado son texto plano, CSV, TSV, JSON, SQLite y HDF5. Estos formatos son los que me han parecido adecuados para este tipo de tarea. He dejado fuera XML porque me pareció que tiene gran similitud con JSON y realmente no necesito la jerarquía ni flexibilidad que este formato me ofrece.

Véase que el mismo script indica el tamaño de cada fichero. El tiempo de ejecución es de unos 65 segundos en mi laptop.

El script:


import numpy as np
import os
from os import listdir
from os.path import isfile, join

import json
import csv
import sqlite3
import h5py #pip install h5py

#generated 4 rows with number_of_floats data
def data_generation(number_of_floats):
	ret = np.ndarray((number_of_floats, 4))
	for i in range(number_of_floats):
		ret[i] = np.random.uniform( -10, 10, 4 )
	return ret

#standard text saving
def save_text_file(data):
	f = open('text.txt', 'w')
	for el in data:
		f.write(str(el).strip('[]'))
		f.write('\n')
	f.close()
	return True

#saving with json files
def save_json(data):
	f = open('json.json', 'w')
	j=json.dumps(data.tolist())
	json.dump(j, f)
	f.close()
	return True

#saving numbers on csv
def save_csv(data):
	with open('csv.csv', 'w') as csvfile:
		fieldnames = ['Col_A', 'Col_B', 'Col_C', 'Col_D']
		writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
		for el in data:
			writer.writerow({'Col_A': str(el[0]), 'Col_B': str(el[1]),'Col_C': str(el[2]),'Col_D': str(el[3])})
	return True

#Tab Separated Values (TSV)
def save_tsv(data):
	with open('tsv.tsv', 'w') as tsvfile:
		writer = csv.writer(tsvfile, delimiter='\t')
		for el in data:
				writer.writerow(el)
	return True

#save data in sqlite
def save_sqlite(data):
	conn = sqlite3.connect('data.sqlite3')
	cur = conn.cursor()
	cur.execute('DROP TABLE IF EXISTS Data ')
	cur.execute('CREATE TABLE Data (Col_A REAL, Col_B REAL, Col_C REAL, Col_D REAL)')
	for el in data:
		cur.execute('INSERT INTO Data VALUES ('+str(el[0])+', '+str(el[1])+', '+str(el[2])+', '+str(el[3])+')')
	conn.commit()
	conn.close()

#save the data in a hdf file
def save_hdf(data):
	h = h5py.File('data.hdf5', 'w')
	dset = h.create_dataset('data', data=data)

#check the file size
def show_file_size():
	mypath = '.'
	onlyfiles = [ f for f in listdir(mypath) if isfile(join(mypath,f)) ]
	for fil in onlyfiles:
		statinfo = os.stat(fil)
		print 'name: '+str(fil)
		print 'size: '+str(statinfo.st_size)
		print ' '
	print onlyfiles

##############################
#
# Main
#
##############################

data = data_generation(100000)
save_text_file(data)
save_csv(data)
save_json(data)
save_csv(data)
save_tsv(data)
save_sqlite(data)
save_hdf(data)
show_file_size()
print 'Done'

Finalmente los resultados:

data-format-comparsion-plot

Formato Tamaño (Bytes)
Texto plano 4806060
CSV 5900742
TSV 7901330
JSON 8109034
SQLite 4468736
HDF5 3202144

Como se puede observar HDF5 es el ganador claramente siendo casi un 30% menor en tamaño que Sqlite que está ocupando el segundo lugar. No es de extrañar puesto que HDF es un formato de fichero diseñado especialmente para organizar y almacenar grandes cantidades de datos (ideado para supercomputadores). Para nuestra suerte las librerías tienen licencia BSD permitiendo mejoras y creación aplicaciones por parte de terceros. Lo que no me esperaba es que TSV obtuviera un tamaño similar a JSON y no a CSV. Sinceramente pensaba que CSV y TSV eran básicamente lo mismo.

Bonus: Para los interesados el script está en github.

Forzar GPy para que muestre el gráfico (plot)

Recientemente he estado programando con Gaussian processes framework in python (GPy) y no conseguía mostrar a través de un gráfico los resultados obtenidos. El problema es que al finalizar la ejecución del script la ventana con el gráfico también se cerraba automáticamente. Para solucionar esto se tiene que forzar la librería matplotlib que bloquee la ventana para así poder visualizar los datos. Si ubicamos la siguiente linea después del plot nos congelará la ventana evitando que se cierre al finalizar el la ejecución.

matplotlib.pylab.show(block=True)

Debugging con python

Para hacer debugging en python normalmente pongo prints pero a medida que el código crece los prints aumentan en número y al final me paso un buen rato buscando los sitios dónde los tengo. Por lo que al final he implementado una solución en la que puedes imprimir la linea y el mensaje deseado.

Lo que hace simplemente es usar el numero de linea que nos da la librería “inspect”. Aquí el código:

import inspect

def debug(message):
    print 'Debug in line', inspect.currentframe().f_back.f_lineno,':'
    print message

if __name__ == '__main__':
    debug('message')
    print '.'
    print '.'
    debug('message2')

Git Merging con Ubuntu (Linux)

Como muchos programadores de Linux ya os habréis dado cuenta, no hay una aplicación nativa de GitHub para Linux (a diferencia de Windows y Mac). Sinceramente me parece algo ilógico ya que los programadores tenemos una tendencia a gravitar hacia Linux. Pero a grandes males grandes remedios, no? Cuando en un repositorio tenemos conflictos (merge conflict) y hay que solventarlos, una herramienta de merge nos va a ser bastante útil más que resolver el conflicto a mano o usando la consola. Una GUI, aunque no lo parezca, es bastante útil en algunos casos.

He probado kdiff3 y meld. A mi parecer no tienen punto de comparación, claramente la mejor herramienta es meld por algunas simples razones.

Kdiff3:

Captura de pantalla de la GUI de kdiff3

  • Me parece que usa una GUI obsoleta y poco intuitiva.
  • No encontré la opción para cambiar el idioma (la instalé des de Alemania y estaba en alemán).

Meld

Captura de pantalla de la GUI de meld

  • Por el contrario su GUI es más intuitiva y algo más moderna.
  • Estaba en inglés des del principio.
  • No me gustó que creara dos ficheros, para el archivo remoto y otro para el local (además del que se sube a la rama).

Para usar estos merge tools usaremos esta linea en la consola (cambiando meld por kdiff3 si te gusta más este):

 git mergetool -t meld

Puede ser que no los tengas instalados, usando el siguiente comando lo instalaremos (o kdiff3 en vez de meld):

sudo apt-get install meld

Finalmente una vez convencido de la herramienta podremos usar la siguiente linea para guardar la herramienta deseada e iniciar el programa que más nos haya gustado cada vez que surja un merge conflict.

git config --global merge.tool meld

Si conoces una herramienta mejor no puedes en dejarlo en los comentarios. Me encantará conocerla! :)

Fuente e imágenes

Git avanzado: Etiquetas, logs y el botón del pánico

Una vez inicializado git, conocidos los primeros comandos y aprendido a hacer ramas con git ahora toca aprender un poco sobre etiquetas (tags), logs y que hacer cuando la lías parda.

Las etiquetas sirven básicamente para etiquetar commits. ¿Y para que quiere uno etiquetar commits? Bien, pues para indicar en que momento se definió una versión de la aplicación. Para crear una etiqueta usaremos:

 git tag NumeroVersion idCommit

El id del Commit son los primeros 10 caracteres del commit al que queremos etiquetar. Te preguntarás, ¿y como se esos 10 caracteres? Pues fácil te vas al log, y para ver el log usarás el comando:

 git log

Si quieres algo más sofisticado y con menos ruido puedes filtrar por autor:

 git log --author=NombreUsuario

Sólo que archivos fueron cambiados:

 git log –name-status

O si aun así quieres ver todo el árbol con todas las ramas y etiquetas puedes usar el comando:

 git log --graph --oneline --decorate --all

En cualquier caso si quieres indagar más profundamente échale una ojeada a:

 git log --help

En el hipotético caso de que la hayas liado parda y no sepas como solucionar el problema eliminando todos los cambios locales y commits hechos trayendo la versión de la rama master más reciente. Ten en cuenta que sobre-escribirás todos tus ficheros, pero si aun así lo deseas deberás usar los siguientes comandos:

 git fetch origin
 git reset --hard origin/master

Para los que hayáis llegado tan lejos decir que git tiene incorporado algo parecido a una interfaz gráfica. Recordar que no hay para linux una GUI que permita hacer push y toda la mandanga, solo está en Mac y WIndows. Pero para acceder a su sucedáneo se puede acceder escribiendo en la consola:

gitk

Pero si aun así preferís seguir usando la linea de comandos quizás os interese colorear un poco el output con el comando

git config color.ui true

o en los logs simplemente mostrar sólo una linea por commit con:

 git config format.pretty oneline

Como crear ramas (branches) con git

Ahora que ya tenemos git inicializado  y hemos aprendido unos primeros pasos tocará empezar con unos conceptos un poco más avanzados. En este post aprenderemos a crear ramas. Normalmente los proyectos usan ramas para mantener la rama master “más limpia”, evitar commits a medias y evitar conflictos.

Para que nos entendamos la rama master es la rama principal y en las otras ramas es dónde hacemos los cambios. Para crear una rama empezaremos con:

 git checkout -b NombreRama

Como ya vimos después de aplicar los cambios en nuestra rama tocará subir los cambios al directorio remoto haciendo un:

 git push

Una vez terminados todos los cambios y dar la rama por acabada realizaremos el merge. Que básicamente es mezclar nuestros cambios con la rama master. Para esto usaremos el comando:

git merge nombreRama

Algunas veces esto ocasionará conflictos que deberemos solventar. Para ver los conflictos podemos usar:

 git diff <source_branch> <target_branch>

Y si hemos intentado hacer un “merge” que ha sido fallido debido a los conflictos deberemos volver a hacer un:

git add nombreArchivo

Una vez terminado del todo lo que podemos hacer es volver a la rama master con:

git checkout master

Y finalmente borrar la rama con el comando:

git branch -d feature_x

Cabe mencionar que la rama “no está disponible para terceros” a menos que se haga un push al repositorio remoto. Esto se puede hacer con el comando:

git push origin

Para los que les haya interesado este post sobre como crear ramas (branches) en git quizás les interese indagar un poco más en este [EN] modelo de creación de ramas con git. Haciendo un resumen rápido, tienen la rama principal, luego la rama developer y a partir de aquí crean ramas de features.