7 ene. 2018

Introducción al Perceptron con Python

Este artículo se basa en un artículo en inglés The Perceptron.

Un perceptron es un clasificador y uno de los más simples de la Red Neuronal Artificial. Lo que se busca es tomar las características de una red neuronal biológica, y replicarla como un algoritmo.

En la siguiente figura se muestra una red neuronal de dos entradas, cada entrada tiene su peso, luego se suman y la función de activación genera una salida.


La ecuación mátemática sería algo como:
y = FuncionActivacion(x1*w1+x2*w2)

La función de activación para una onda cuadrada es:


Donde la función de activación puede ser una onda cuadrada (hay más funciones de activación).

Acá otro perceptron con 5 señales de entrada.



A continuación se muestra el código:

#!/usr/bin/env python3

# coding: utf-8



# # Importar librerías

from pylab import rand,plot,show,norm

# # Clase Perceptron

class Perceptron:

 def __init__(self):

  """ inicialización del perceptron  """

  self.w = rand(2)*2-1 # pesos

  self.tasaAprendizaje = 0.1 



 def respuesta(self,x):

  """ saldia del perceptron """

  y = x[0]*self.w[0]+x[1]*self.w[1] # producto punto entre w y x

  if y >= 0:

   return 1

  else:

   return -1



 def actualizarPesos(self,x,iterError):

  """

  Actualizar estatus de los pesos, w en un tiempo t+1 es w(t+1)= w(t) + learningRate*(d-r)*x

  donde d es la salida deseada y r la respuesta del perceptro, iteError es la diferencia entre

  d y r (d-r).

  """

  self.w[0] += self.tasaAprendizaje*iterError*x[0]

  self.w[1] += self.tasaAprendizaje*iterError*x[1]



 def entrenamiento(self,data):

  """ 

  Entra todo el vector en los datos, cada vector en los datos debe tener 3 elementos,

  el tercer elemento (x[2]) debe ser etiquetado (salida deseada)

  """

  learned = False

  iteration = 0

  while not learned:

   globalError = 0.0

   for x in data: # por cada muestra

    r = self.respuesta(x)    

    if x[2] != r: # si tenemos un respuesta equivocada

     iterError = x[2] - r # respuesta deseada-respuesta actual

     self.actualizarPesos(x,iterError)

     globalError += abs(iterError)

   iteration += 1

   if globalError == 0.0 or iteration >= 100: # detiene por el criterio

    print ('iterations {}'.format(iteration))

    learned = True # detiene el aprendizaje

    



def datosGenerados(n):

    """

    genera un conjunto de datos, de dos dimensiones,  linealmente separados con

    n muestras. El tercer elemento de la muestra es la etiqueta.

    """

    xb = (rand(n)*2-1)/2-0.5

    yb = (rand(n)*2-1)/2+0.5

    xr = (rand(n)*2-1)/2+0.5

    yr = (rand(n)*2-1)/2-0.5

    inputs = []

    for i in range(len(xb)):

        inputs.append([xb[i],yb[i],1])

        inputs.append([xr[i],yr[i],-1])

    return inputs



trainset = datosGenerados(30) # generación de datos para entrenar

perceptron = Perceptron()   # Instancia del perceptron

perceptron.entrenamiento(trainset)  # Entrenamiento con el conjunto de datos

testset = datosGenerados(20)  # conjunto de datos para el test.



# Prueba del perceptron

for x in testset:

 r = perceptron.respuesta(x)

 if r != x[2]: # Si la respuesta no es correcta

  print ('error')

 if r == 1:

  plot(x[0],x[1],'ob')  

 else:

  plot(x[0],x[1],'or')



# Se gráfica una línea de separación, la cual es ortogonal a w.

n = norm(perceptron.w)

ww = perceptron.w/n

ww1 = [ww[1],-ww[0]]

ww2 = [-ww[1],ww[0]]

plot([ww1[0], ww2[0]],[ww1[1], ww2[1]],'--k')

show()





# ## 





Al ejecutar el script se tiene la siguiente gráfica:

Los puntos azules pertenecen a la primera clase y los rojos pertenecen a la segunda. La línea punteada es la línea de separación que el perceptrón aprendió durante el entrenamiento.

En siguientes artículos se seguirá trabajando con el perceptron pero usando librerías como scikit-learn y  tensorflow.

El script en python y el notebook de jupyter lo pueden descargar de github.

Introducción a Pandas

Pandas es una librería de python para analizar datos, permite multiples entrada de datos.

El artículo se basa en un artículo en inglés Building a neural network with python, y en un tutorial de pandas.

Para este tutorial se usará un conjunto de datos en formato csv  sobre vinos (análisis químico para saber el origen de los vinos).


A continuación de describe paso a paso la forma de manejar los datos a partir del archivo wine_data.csv.

El archivo Pandas.py y el Pandas.ipynb lo pueden descargar de github.

Se importa Pandas como pd:

>>> import pandas as pd

Se extrae los datos del archivo csv y se definen el nombres de las columnas:
>>> wine = pd.read_csv('wine_data.csv', names = ["Cultivator", "Alchol", "Malic_Acid", "Ash", "Alcalinity_of_Ash", "Magnesium", "Total_phenols", "Falvanoids", "Nonflavanoid_phenols", "Proanthocyanins", "Color_intensity", "Hue", "OD280", "Proline"])

Se muestran los primeros datos:
>>> wine.head()


Se muestra las dimensiones de la tabla:
>>> wine.shape
(178, 14)

Se tiene 178 filas y 14 columnas.

Se muestra las 2 primeras filas de la tabla:
wine.loc[[0,1]]


Se muestra las dos primeras filas y las columnas Cultivator y Alchol

>>> wine.loc[[0,1]][["Cultivator","Alchol"]]
 


Está introducción es necesaria ya que en futuros artículos se trabajará mucho con la librería pandas.

Graficar líneas de campo eléctrico con matplotlib y Python

Este artículo se basa en un artículo en inglés del blog scipython, con nombre Visualizing a vector field with matplotlib.

Matplotlib provee una función llamada streamplot, para crear flujos que puede usarse para representar vectores de campo.  El código python muestra la representación de un campo eléctrico a partir de multiples cargas. Las multiples cargas son seleccionadas como potencia de 2 (1 dipolo, 2 cuadrupolo, etc).

Se tiene una variable count la cual define la cantidad de cargas, en el primer caso vale 1 (dipolo).

A continuación el código:



#!/usr/bin/env python3

# coding: utf-8



# # Se importan los módulos necesarios para la graficación

import numpy as np

import matplotlib.pyplot as plt

from matplotlib.patches import Circle



# # Función que retorna el campo Eléctrico.

def E(q, r0, x, y):

    """Retorna el vector de campo eléctrico E=(Ex,Ey) de una carga q en r0"""

    den = np.hypot(x-r0[0], y-r0[1])**3

    return q * (x - r0[0]) / den, q * (y - r0[1]) / den



# # puntos de los ejes x e y.

nx, ny = 64, 64

x = np.linspace(-2, 2, nx)

y = np.linspace(-2, 2, ny)

X, Y = np.meshgrid(x, y)



# # Crear un multipolo con nq cargas

# count = número de q. En ese caso es 1 dipolo

count = 1

nq = 2**int(count)

charges = []

for i in range(nq):

    q = i%2 * 2 - 1

    charges.append((q, (np.cos(2*np.pi*i/nq), np.sin(2*np.pi*i/nq))))



# # Vector de campo eléctrico como componentes separados (Ex,Ey)

Ex, Ey = np.zeros((ny, nx)), np.zeros((ny, nx))

for charge in charges:

    ex, ey = E(*charge, x=X, y=Y)

    Ex += ex

    Ey += ey



fig = plt.figure()

ax = fig.add_subplot(111)





# # Dibujar las líneas de flujo con mapa de colores y estilos apropiados.

color = 2 * np.log(np.hypot(Ex, Ey))

ax.streamplot(x, y, Ex, Ey, color=color, linewidth=1, cmap=plt.cm.inferno,

              density=2, arrowstyle='->', arrowsize=1.5)





# # Agregar circulos para las cargas.

charge_colors = {True: '#aa0000', False: '#0000aa'}

for q, pos in charges:

    ax.add_artist(Circle(pos, 0.05, color=charge_colors[q>0]))





# # Graficar

ax.set_xlabel('$x$')

ax.set_ylabel('$y$')

ax.set_xlim(-2,2)

ax.set_ylim(-2,2)

ax.set_aspect('equal')

plt.show()



Al ejecutar el código se tiene la siguiente gráfica:

La siguiente gráfica es con count con valor a 4, osea 8 polos:

Y la última gráfica es con count con 10, osea 20 polos:


El código python  y el notebook de jupyter lo pueden obtener de un repositorio en github.

Gráfica de curvas en 3D con Python

El artículo de hoy se explicará como crear una gráfica 3D con Python.

Este artículo se basa en el artículo en inglés 3D Stem plot.

Para este caso se tiene dos archivos en un repositorio en github, uno python y otro un notebook de jupyter.

Se generará una variable x que define el eje del tiempo de dos gráficas (seno y coseno) para luego graficarlo en forma de gráfica 3D.

A continuación el código:


#!/usr/bin/env python3



# # Se importan las librerías necesarias



from numpy import linspace, sin, cos

from pylab import figure, show

from mpl_toolkits.mplot3d import Axes3D





# # generando algunos datos

x = linspace(-10,10,100);

y = sin(x);

z = cos(x);





# # Se crea la instancia de la figura y se asocia a la figura 3D

fig = figure()

ax = Axes3D(fig)





# # graficando los datos

for i in range(len(x)):

  ax.plot([x[i], x[i]], [y[i], y[i]], [0, z[i]], 

          '--', linewidth=2, color='b', alpha=.5)





# # graficando un circulo en el tope de cada curva

# plotting a circle on the top of each stem

ax.plot(x, y, z, 'o', markersize=8, 

        markerfacecolor='none', color='b',label='ib')

ax.set_xlabel('x')

ax.set_ylabel('y')

ax.set_zlabel('z')

show()



Al ejecutar el script se tiene la siguiente gráfica.



19 nov. 2017

Árbol de decisión hecho en Python

Tenía algo de tiempo sin escribir (la situación en Venezuela no está facil).

Voy a ir retomando poco a poco los artículos en el blog,  tengo algunas cosas sobre Inteligencia Artificial (Redes Neuronales, Lógica Difusa, machine learning y deep learning), patrones de diseño con Python.

Este artículo trata de un modeo de predicción llamado árbol de decisión, según wikipedia es: Es un modelo de predicción utilizado en diversos ámbitos que van desde la inteligencia artificial hasta la economía. Dado un conjunto de datos se fabrican diagramas de construcciones lógicas, muy similares a los sistemas de predicción basados en reglas, que sirven para representar y categorizar una serie de condiciones que ocurren de forma sucesiva, para la resolución de un problema.

En youtube hay un canal dedicado a la ciencia de datos principalmente con Python, el youtuber es Siraj Raval . En el siguiente vídeo explica como hacer un clasificador de género hecho con un Árbol de decisión, el vídeo lo pueden ver en el siguiente enlace.


El código fuente del ejercicio del vídeo se puede ver en github.

La idea es tener los datos de altura, peso y talla de zapato y si es hombre o mujer, se construye el árbol de decisión, se pasa los datos y de ahí al pasar otro dato se genera una salida de si es hombre o mujer.

A continuación el código:



#!/usr/bin/env python

"""

https://www.youtube.com/watch?v=T5pRlIbr6gg

https://github.com/llSourcell/gender_classification_challenge/blob/master/demo.py



"""

#Se importa la librería sklearn el módulo tre

from sklearn import tree



#Se crea la instancia del árbol de decisión.

clf = tree.DecisionTreeClassifier()



#[altura, peso, talla de zapato]

X = [[181, 80, 44], [177, 70, 43], [160, 60, 38], [154, 54, 37], [166, 65, 40],

     [190, 90, 47], [175, 64, 39],

     [177, 70, 40], [159, 55, 37], [171, 75, 42], [181, 85, 43]]



#La salida donde se dice si es hombre o mujer

Y = ['hombre', 'hombre', 'mujer', 'mujer', 'hombre', 'hombre', 'mujer', 'mujer',

     'mujer', 'hombre', 'hombre']



#Se le pasa los datos  X y Y

clf = clf.fit(X, Y)



#Se definen los datos 1 y 2

dato1 = [190, 70, 43]

dato2 = [185, 62, 37]

prediction = clf.predict([dato1])

#Se muestra el resultado de la predicción de dato1

print(prediction)



prediction = clf.predict([dato2])


#Se muestra el resultado de la predicción de dato 2
print(prediction)


Al ejecutar el script se tiene:

python3 arboldecision.py 
['hombre']
['mujer']

Significa que, para una altura de 1.90 mts, peso 70 kilograms, y talla de 43, el resultado es que es un hombre, y para altura de 1.85 mts, peso 62 kilogramos y talla de 37, el resultado es que es una mujer.

Recomiendo ver el vídeo completo para tener una mejor explicación del funcionamiento del árbol de decisión. 


24 jul. 2017

Almacenar los datos de los eventos sismológicos de Funvisis con Python3

En el artículo sobre la captura de datos de eventos sismológicos de funvisis solamente se trabajó el orenamiento de los datos, pero hace falta almacenar la información en una base de datos, este es el tema del artículo.

Ahora se tiene varios módulos:

  • sismux_getdata.py: Es el módulo que hace el webscraping de la página de funvisis.
  • sismux_mongo.py: Es el módulo que implementa un CRUD para mongoDB a MongoLab.
  • sismux_main.py: Es el módulo principal que consulta cada 5 min la página de Funvisis para guardar la información en la base de datos si no existe.
  • sismux_apirest.py: Es el módulo que implementa un API rest(próximo artículo).
  • sismux_graphql.py: Es el módulo que implementa un API con GraphQL (próximo artículo).

Del artículo anterior se muestra el código de sismux_getdata.py:



#!/usr/bin/env python3





#Se importa beautifulSoup

from bs4 import BeautifulSoup

#Se importa la fecha

import datetime

import requests

import sys

import json



class Sismo(object):

    def __init__(self,url="http://www.funvisis.gob.ve/",home="index.php",referer='http://www.cantv.com.ve'):

        headers = {'User-agent': 'Mozilla/5.0',\

            'SSL_VERIFYHOST': 'False',\

            'FRESH_CONNECT':'True',\

            'RETURNTRANSFER':'True',\

            'SSL_VERIFYPEER': 'False',\

            'Referer': referer

            }

        self.__url = url

        self.__home = home

        self.__urlhome = self.__url + self.__home

        self.__session = requests.Session()

        self.__session.headers.update(headers)



    def GetData(self):

        #Se  obtiene la pagina por medio de session.

        try:

            self.__r = self.__session.get(self.__urlhome)

            self.__page = self.__r.content

        except (requests.exceptions.SSLError):

            print("SSL Error")

            sys.exit(0)

        except (requests.exceptions.ConnectionError):

            print("Connection Error")

            sys.exit(0)

        #Se le pasa la pagina a beautifulsoup usando lxml de parser.

        self.__soup = BeautifulSoup(self.__page,"lxml")

        #Se crea el diccionario que almacena los datos

        self.__sismo = {}



        #SE obtiene el primer  div que tengan class module

        for row in self.__soup('div', {'class': 'module'})[0]:

            #Se obtiene el tag a para luego obtener el href y tener el url

            #del gif del sitio de funvisis que tiene la imagen del sitio donde

            #fue el sismo.

            trs = row.find('a')

            if trs == -1:

                continue

            self.__sismo['urlref'] = self.__url  + trs.get('href',None)



            trs = row.find('tr')

            if trs == -1:

                continue

            #Obtiene los datos del sismo del sitio de funvisis

            datos = trs.find('td').getText().split(' ')[0].split('\n\t')

            self.__sismo['fecha'] = datos[0].split('\xa0')[1]

            date = self.__sismo['fecha'].split("/")

            self.__sismo['hora'] = datos[2].split(" ")[-2]

            time= self.__sismo['hora'].split(":")

            self.__sismo['datetime'] = datetime.datetime(int(date[2]),int(date[1]),int(date[0]), int(time[0]), int(time[1]))

            self.__sismo['magnitud'] = datos[4].split(" ")[-1]

            mag = datos[6].split(" ")[-1].split('\xa0')

            self.__sismo['profundidad'] = mag[0] + " "+ mag[1]

            lat = datos[8].split(" ")

            self.__sismo["latitud"] = lat[-2] + " " + lat[-1]

            lon =  datos[10].split(" ")

            self.__sismo['longitud'] = lon[-2] + " "+ lon[-1]

            self.__sismo['epicentro'] = datos[11].split(":")[1].split('\xa0')[-1]

            self.__sismo['loc'] = {'type':'Point','coordinates' : [ float(lat[-2]) , float(lon[-2]) ]}

        return self.__sismo



El código del módulos sismux_mongo.py se muestra a continuación: 




#!/usr/bin/env python3
import pymongo

from pymongo import MongoClient


#Se define el uri de la conexion a mongolab

uri = 'mongodb://usuario:clave@ds045064.mlab.com:45064/basedatos'

#Se define la base de datos y la cole

#ccion


basedatos = "sismux"

coleccion = "sismos"


#Se crea la clase BaseDatos que simplemente implemente un crud.
class BaseDatos(object):

    #Se define la intancia de mongoclient, se define la base de datos y

    #la coleccion

    def __init__(self,uri=uri,basedatos=basedatos,coleccion=coleccion):

        self.__client = MongoClient(uri)

        self.__db = self.__client[basedatos]

        self.__coleccion = self.__db[coleccion]


  def ConsultarTodos(self):

        #Traer todos los elementos de la consulta.

        elementos = []

        for i in self.__coleccion.find():

            elementos.append(i)

        return i


  def Consultar(self,patron):

        #Se devuelve la consulta de un elemento

        return self.__coleccion.find_one(patron)


  def Insertar(self,documento):

        #Se inserta un documento

        self.__coleccion.insert(documento)


  def Finalizar(self):

        #Se cierra la conexion con la base de datos
        self.__client.close()



A continuación se muestra sismux_main.py:



#!/usr/bin/env python3
from sismux_getdata import Sismo
import json
import time
import sys

from sismux_mongo import BaseDatos
import datetime
import logging

bd = BaseDatos()
sismo  = Sismo()
datos = sismo.GetData()
#bd.Insertar(datos)
#datos['datetime']
datime = datetime.datetime(2017,5,25, 20, 51)
#print(bd.Consultar({'datetime': datie}m))
def main():

   #Se crea la instancia a la base de datos
   bd = BaseDatos()
   #Se crea la instancia del webscraping
   sismo = Sismo()
   #Se optiene los datos de la pagina de funvisis
   datos = sismo.GetData()
   #Se consulta si ya existe el dato guardado en la base de datos
   query1 = bd.Consultar({'datetime': datos['datetime']})
   query2 = bd.Consultar({'loc': {'coordinates': datos['loc']['coordinates']}})
   if (query1 != None):
        return False
   else:
       #Si no existe se inserta en la base de datos.
       bd.Insertar(datos)
       return True

#Se crea un ciclo para consultar cada 5 min

def ciclo(tim=300):
    #Se define el log
    logging.basicConfig(filename="sismux.log",level=logging.DEBUG,format='%(asctime)s %(message)
s')
 
 
    
while True:
        #Se ejecuta main.
       result = main()
       #Se guarda el resultado de main en el log
       logging.info(result)
       #Se espera 5 min (por defecto)
       time.sleep(tim)



if __name__ == '__main__':
   #Se ejecuta la funcion ciclo
   ciclo()


Para ejecutar el programa se corre:

python3 sismux_main.py 

Al ejecutarlo se crea un archivo log y en el se tiene lo siguiente:

tail -f sismux.log 
2017-07-24 11:42:43,149 Starting new HTTP connection (1): www.funvisis.gob.ve
2017-07-24 11:42:43,821 http://www.funvisis.gob.ve:80 "GET /index.php HTTP/1.1" 200 14319
2017-07-24 11:42:56,560 False
2017-07-24 11:47:56,637 Starting new HTTP connection (1): www.funvisis.gob.ve
2017-07-24 11:47:57,466 http://www.funvisis.gob.ve:80 "GET /index.php HTTP/1.1" 200 14319
2017-07-24 11:48:12,086 False
2017-07-24 11:53:12,209 Starting new HTTP connection (1): www.funvisis.gob.ve
2017-07-24 11:53:12,976 http://www.funvisis.gob.ve:80 "GET /index.php HTTP/1.1" 200 14319
2017-07-24 11:53:18,516 False
2017-07-24 11:58:18,605 Starting new HTTP connection (1): www.funvisis.gob.ve
2017-07-24 11:58:30,984 http://www.funvisis.gob.ve:80 "GET /index.php HTTP/1.1" 200 14319
2017-07-24 11:58:57,034 False


Como muestra el log ya se tiene el último sismo en la base de datos.

En la siguiente figura se muestra un documento almacenado en la base de datos:


Para el siguiente artículo se desarrollará el API. 

Nota: En un futur artículo se muestra la creación de un demonio y como ponerlo a funcionar con SystemD y el empaquetado de todo los módulos desarrollados.