Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

sábado, 26 de octubre de 2024

Tutorial Viejito: Creando array de Controles con sus Eventos en C#

Para una aplicación necesité tener siete CheckBoxes en un formulario indicando los días de la semana. También tengo un array de variables booleanas llamado "dias" cuyos índices (0,1,2,3,4,5,6) representan los días de la semana (L,M,M,J,V,S,D) respectivamente (en C# los índices de los arrays empiezan siempre desde cero). Si selecciono el CkeckBox de los días lunes y miércoles solamente, las variables dias[0] y dias[2] toman el valor de true, el resto de variables "dias" serán false (en la práctica necesitaba el array "dias" para pasárselo a otra clase y luego usarlo para modificar unos campos en una BD...)

Esta vez, en lugar de crear cada CheckBox con el diseñador del Visual Studio quise crearlos como elementos de un array de CheckBoxes. Para este tutorial también voy a crear un array de TextBoxes y otro de Buttons.

La declaración para crear los arrays es como sigue:


Mirando cómo el diseñador del Visual Studio declara y asigna las propiedades a los controles, hice lo mismo para mis arrays de controles, pero dentro de un bucle. Aquí muestro la función que crea los CheckBoxes:

Dentro del bucle asigno los delegados para los eventos respectivos de cada control. Para saber cuál control del array es el que lanza el evento debo examinar el objeto "sender" que es el control quien "envía" o lanza el evento. Para saber el índice del control dentro del array lo almaceno en una propiedad del control: puede ser el nombre, el TabIndex, o el Tag. En realidad puede ser cualquier propiedad cuyo valor no vamos a modificar a lo largo de la aplicación, ya que desde la cual vamos a saber el índice del control dentro del array.

Un dato interesante acerca de los eventos es que estos no se ejecutan al declararlos dentro del bucle que crea los controles. Aquí sólo declaramos el evento y lo asignamos a su respectivo control, el evento recién se ejecuta cuando el control lo "lanza" (es decir: cuando se ejecuta la acción que llama al evento. Esto me ha recordado un poco a la "Evaluación Perezosa" de la programación funcional, no sé si será exactamente lo mismo, pero se le parece mucho).

Las dos líneas con el comentario "poned un breakpoint acá" hacen exactamente lo mismo: Si deseo leer el índice guardado en el nombre del control que lanza el evento, utilizo la variable "n", o más directamente puedo leer el índice usando la propiedad "Tag". Ambas líneas de código cambian el valor del elemento con índice "n" ó índice "Tag" (ambos tienen el mismo valor) del array "dias" a verdadero ó falso, según sea el valor de la propiedad "Checked" del CheckBox con índice "n" ó "Tag" dentro del array de ChackBoxes.

Y esta es la función de los TextBoxes:


En el caso de los TextBoxes, mandarán un saludo al texto de su formulario (el cual se llama "Form1") al recibir el cursor (el índice del respectivo TextBox se lee desde la propiedad "Tag"):



Si se pone un punto de quiebre en las líneas donde dice "poned un breakpoint acá" se podrá ver cómo la ejecución del programa "salta" a la declaración del evento para cambiar los valores a los elementos del array "dias" cuando se pone o quita el "check" de los CheckBoxes.

¿Y qué sucede si deseo enviar mensajes a otros formularios? Debo crear un delegado y su respectivo evento:

También debo crear el otro formulario que va a recibir los mensajes. Lo he llamado Form2, sólo posee un label llamado "label1" y éste es su código:

Ahora voy a crear un array de botones en Form1 con los que le mandaré mensajes a Form2:

Debo tener cuidado de evaluar que mi evento "Enviar" no sea nulo o lanzará una excepción. Esto ocurre si Form2 no se ha cargado. Luego añado un botón desde la barra de herramientas del Visual Studio con el que abriré Form2:


El código del nuevo botón (llamado btnForm2) es::

 Si ejecuto la aplicación, y abro también Form2, se verá cómo recibe los mensajes desde los botones creados en el array:

 

Esta aplicación está compilada con Visual Studio Express 2008 y se puede descargar de aquí.

jueves, 22 de agosto de 2024

Probando el Algoritmo Perceptron de la web Machine Learning from Scratch

Continuación de https://programacionamartillazos.blogspot.com/2024/07/correccion-del-algoritmo-perceptron-de.html

Ahora toca probar los algoritmos. Para esto hice una pequeña modificación en el código de la web Machine Learning from Scratch y al que yo modifiqué:

Código de Machine Learning from Scratch:

from tkinter import Y
import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets

class Perceptron:
    
    def sign(self, a):
        return (-1)**(a < 0)

    def to_binary(self, y):
            return y > 0

    def standard_scaler(self, X):
        mean = X.mean(0)
        sd = X.std(0)
        return (X - mean)/sd

    def fit(self, X, y, n_iter = 10**3, lr = 0.001, add_intercept = True, standardize = True):
        
        # Add Info #
        if standardize:
            X = self.standard_scaler(X)
        if add_intercept:
            ones = np.ones(len(X)).reshape(-1, 1)
        self.X = X
        self.N, self.D = self.X.shape
        self.y = y
        self.n_iter = n_iter
        self.lr = lr
        self.converged = False
        
        # Fit #
        beta = np.random.randn(self.D)/5
        for i in range(int(self.n_iter)):
            
            # Form predictions
            yhat = self.to_binary(self.sign(np.dot(self.X, beta)))
            
            # Check for convergence
            if np.all(yhat == self.sign(self.y)):
                self.converged = True
                self.iterations_until_convergence = i
                break
                
            # Otherwise, adjust
            for n in range(self.N):
                yhat_n = self.sign(np.dot(beta, self.X[n]))
                if (self.y[n]*yhat_n == -1):
                    beta += self.lr * self.y[n]*self.X[n]

        # Return Values #
        self.beta = beta
        self.yhat = self.to_binary(self.sign(np.dot(self.X, self.beta)))
        
        print(self.y[:50])
        print(self.yhat[:50])
        
        accuracy_true = self.y == self.yhat
        self.accuracy = np.count_nonzero(accuracy_true)/self.N * 100
        
        print("accuracy: ", self.accuracy, "%")
        
    def predict(self, X, y, N):
        accuracy_true = 0
        for n in range(N):
            yhat_n = self.to_binary(self.sign(np.dot(self.beta, X[n])))
        
            if (y[n] == yhat_n):
                accuracy_true += 1
                
        accuracy_true = accuracy_true/N * 100
        print("accuracy: ", accuracy_true, "%")
        
    
def main():
    # import data
    cancer = datasets.load_breast_cancer()
    X = cancer['data']
    y = cancer['target']
    perceptron = Perceptron()
    perceptron.fit(X[:-10], y[:-10], n_iter = 2e2, lr = 0.01)
    yy= np.atleast_1d(y[-10:])
    perceptron.predict(X[-10:], yy,  yy.size)
    
      
if __name__ == "__main__":
    main()


Código que modifiqué:

from tkinter import Y
import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets

class Perceptron:
    
    def sign(self, a):
        if a > 0:
            return 1
        else:
            return 0
    

    def standard_scaler(self, X):
        mean = X.mean(0)
        sd = X.std(0)
        return (X - mean)/sd

    def fit(self, X, y, n_iter = 10**3, lr = 0.001, add_intercept = True, standardize = True):
        
        # Add Info #
        if standardize:
            X = self.standard_scaler(X)
        if add_intercept:
            ones = np.ones(len(X)).reshape(-1, 1)
            
        self.X = X
        self.N, self.D = self.X.shape
        self.y = y
        self.n_iter = n_iter
        self.lr = lr
        self.converged = False
        self.accuracy = 0

        vsign = np.vectorize(self.sign)
        
        # Fit #
        beta = np.random.randn(self.D)/5
        
        for i in range(int(self.n_iter)):
            # Form predictions
            
            yhat = np.dot(self.X, beta)
            yhat = vsign(yhat)

            # Check for convergence
            if np.all(yhat == self.y):
                self.converged = True
                self.iterations_until_convergence = i
                break
                
            # Otherwise, adjust
            for n in range(self.N):
                yhat_n = self.sign(np.dot(beta, self.X[n]))
        
                if (self.y[n] != yhat_n):
                    beta += self.lr * self.y[n]*self.X[n]

        # Return Values #
        self.beta = beta
        self.yhat = yhat
        
        print(self.y[:50])
        print(self.yhat[:50])
        
        accuracy_true = self.y == self.yhat
        self.accuracy = np.count_nonzero(accuracy_true)/self.N * 100
        
        print("accuracy: ", self.accuracy, "%")
        
    def predict(self, X, y, N):
        accuracy_true = 0
        for n in range(N):
            yhat_n = self.sign(np.dot(self.beta, X[n]))
        
            if (y[n] == yhat_n):
                accuracy_true += 1
                
        accuracy_true = accuracy_true/N * 100
        print("accuracy: ", accuracy_true, "%")
        
    
def main():
    # import data
    cancer = datasets.load_breast_cancer()
    X = cancer['data']
    y = cancer['target']
    perceptron = Perceptron()
    perceptron.fit(X[:-10], y[:-10], n_iter = 2e2, lr = 0.01)
    yy= np.atleast_1d(y[-10:])
    perceptron.predict(X[-10:], yy,  yy.size)
    
      
if __name__ == "__main__":
    main()



El cálculo de la exactitud se hace en dos momentos, con la data de entrenamiento (dentro de la función fit), y con la data de validación (también de prueba en este caso) dentro de la función predict.

Dentro de la función main, lo que se hace es partir la data en dos pedazos: la data de entrenamiento que no incluye los 10 últimos elementos que se pasan a la función fit, y luego los diez últimos elementos que serán la data de evaluación o prueba, que se pasarán como parámetro a la función predict.

En el algoritmo perceptrón el único parámetro que se debe optimizar es beta (es un vector de valores en realidad), el cual luego se usará en la predicción. Los resultados son:

Código de Machine Learning from Scratch:


 

 

 

 

 

 

Código que modifiqué:


 

 

 

 

El primer valor "accuracy" es el calculado para la data de entrenamiento, el segundo valor es el resultado con la data de testeo.

Ahora otra prueba pero con menos data de entrenamiento, para esto se cambia en la función main donde dice 10 por cualquier otro valor (debe ser el mismo). Para este ejemplo, voy a coger 100 elementos para que sean los de testeo:


 

 

 

 

 

El resultado es:

Código de Machine Learning from Scratch:


Código Modificado:


Ambos códigos son equivalentes, la diferencia entre ambos es menor al 1%, siendo el código original el que calcula el parámetro beta un poquito mejor. 

Por otro lado, esto demuestra que el resultado depende de la cantidad de data que tengamos para entrenar el algoritmo. En este ejemplo es muy poca data. Una forma de compensarlo es aumentando el número de iteraciones; pero poner demasiadas, con apenas unos pocos cientos de elementos, se corre el riesgo de "overfitting": es decir sobreentrenar nuestro algoritmo de forma que sólo tenga la precisión necesaria con la data de entrenamiento.

viernes, 19 de julio de 2024

Corrección del Algoritmo Perceptrón de la web Machine Learning from Scratch

 Revisaba el algoritmo percentrón, como se explica en los tutoriales que hay por internet, es un algoritmo que recibe una entrada binaria (1 o 0), que suele ser un vector, donde cada elemento del vector está asociado a un peso. Además cada elemento del vector tiene asociado una etiqueta binaria. Lo que se desea con el algoritmo perceptrón es hallar parámetros (también llamados "pesos") que predigan correctamente la etiqueta si se ingresa un nuevo vector.

El perceptrón sólo actualiza sus pesos si predice mal una etiqueta. No siempre es necesario un condicional (un if... else...), si una etiqueta ha sido predicha correctamente, el error será cero y los pesos mantendrán su valor. Esta idea se usa en How To Implement The Perceptron Algorithm From Scratch In Python - MachineLearningMastery.com

El algoritmo implementado en esta web https://dafriedman97.github.io/mlbook/content/c3/s2/perceptron.html es uno de los más simples que he encontrado, pero hay un error. Teóricamente el algoritmo Perceptrón debería aproximarse bastante a una clasificación correcta, pero casi nunca del 100%. En esta web siempre resulta que el algoritmo no converge.

Hallé dos errores. una es en la función sign. No es necesario que devuelva {-1, +1}. Las etiquetas ya están clasificadas como {0, 1} así que las predicciones del perceptrón pueden dejarse con esos valores. Al cambiar la función sign también tuve que cambiar estas líneas:

 En mi caso basta evaluar cada elemento del vector de etiquetas:

 

El otro error es al evaluar si el algoritmo converge. Pide una exactitud del 100% para la convergencia:

Esto casi nunca se consigue. No eliminé estas líneas porque en casos muy raros sí puede cumplirse que las etiquetas predichas y las reales sean todas iguales.  

El código corregido es:

from tkinter import Y
import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets

class Perceptron:
    
    def sign(self, a):
        if a > 0:
            return 1
        else:
            return 0
    

    def standard_scaler(self, X):
        mean = X.mean(0)
        sd = X.std(0)
        return (X - mean)/sd

    def fit(self, X, y, n_iter = 10**3, lr = 0.001, add_intercept = True, standardize = True):
        
        # Add Info #
        if standardize:
            X = self.standard_scaler(X)
        if add_intercept:
            ones = np.ones(len(X)).reshape(-1, 1)
            
        self.X = X
        self.N, self.D = self.X.shape
        self.y = y
        self.n_iter = n_iter
        self.lr = lr
        self.converged = False
        self.accuracy = 0

        vsign = np.vectorize(self.sign)
        
        # Fit #
        beta = np.random.randn(self.D)/5
        
        for i in range(int(self.n_iter)):
            # Form predictions
            
            yhat = np.dot(self.X, beta)
            yhat = vsign(yhat)

            # Check for convergence
            if np.all(yhat == self.y):
                self.converged = True
                self.iterations_until_convergence = i
                break
                
            # Otherwise, adjust
            for n in range(self.N):
                yhat_n = self.sign(np.dot(beta, self.X[n]))
        
                if (self.y[n] != yhat_n):
                    beta += self.lr * self.y[n]*self.X[n]

        # Return Values #
        self.beta = beta
        self.yhat = yhat
        
        print(self.y[:50])
        print(self.yhat[:50])
        
        accuracy_true = self.y == self.yhat
        self.accuracy = np.count_nonzero(accuracy_true)/self.N * 100
        
        print("accuracy: ", self.accuracy, "%")
        
    
def main():
    # import data
    cancer = datasets.load_breast_cancer()
    X = cancer['data']
    y = cancer['target']
    perceptron = Perceptron()
    perceptron.fit(X, y, n_iter = 1e2, lr = 0.01)
      
if __name__ == "__main__":
    main() 


El resultado es:



martes, 25 de junio de 2024

Tutorial viejito: Creando una dll en Borland C++ para luego usarla en Visual Basic 6

 Otro tutorial de los viejitos, de hace casi 20 años! Enjoy!

Visual Basic es un lenguaje de programación con claras ventajas: Es fácil y rápido de programar, cuenta con abundante documentación, recursos y herramientas gratuitos botados por toda la Internet, permite redimensionar arrays, jalar datos desde archivos en Office, se lleva bien con las Apis, etc, etc. 

Pero también tiene sus desventajas: sus aplicaciones tienden a ser un tanto más lentas y pesadas que las programadas en otros lenguajes, depende de librerías que no todo el mundo tiene, es un compilador propietario de Microsoft, no maneja sentencias en asm, no permite declarar variables binarias tipo word o byte y manipularlas bit por bit, etc, etc.  

Es decir: todas esas cosas que tiene el lenguaje C++ pero que le faltan al Visual Basic. No digo que uno sea mejor que el otro, sino que uno tiene lo que al otro le falta. Si tuviera que elegir, haría las GUIs de mis programas en Visual, pero la parte del manejo de arrays, bucles, cálculo matemático, lectura/escritura en el puerto paralelo, entre otras cosas más, se lo dejaría al C++. Al Visual Basic le dejaría las Apis y el puerto serial, además de intercambiar datos con archivos de Office o leer imágenes desde el disco duro. 

En realidad, existe una manera de combinar las bondades de ambos lenguajes: hacer las GUIs del programa en Visual Basic y ordenarle que jale las funciones que necesita desde Dlls hechas y compiladas en C++. El uso de Dlls tiene claras ventajas: si todo el código está metido en un exe éste será más pesado y, a la hora de ejecutarlo, se cargará todo en la memoria RAM. Pero si hace llamadas a Dlls, éstas sólo se cargarán en la RAM en el momento de ser usadas, lo cual liberará espacio en memoria y hará que el programa corra más rápido.  

1. Cómo hacer la Dll:

Primero hay que bajar el compilador gratuito que ofrece Borland.  Incluye un ide, pero este tutorial está hecho para la versión original sin ide. La carpeta Borland se debe copiar a la partición C:, por lo que la ruta del ejecutable que realiza la compilación (el bcc32.exe) queda en C:\Borland\BCC55\Bin\.

Luego se debe ir a la carpeta C:\Borland\BCC55\Lib\ y copiar todos los archivos (excepto la carpeta PSDK) a la carpeta Bin, donde está bcc32.exe. Esto es porque a la hora de compilar, el compilador busca los archivos obj de la carpeta Lib, si no los copiamos a la misma carpeta donde está bcc32.exe nos botará error. 

En la misma carpeta Bin, desde el block de notas, se debe crear el archivo que será nuestra Dll, debe tener extensión cpp y el nombre que llevará la dll. Yo la llamaré inicio.cpp. El código es:

// inicio.cpp

#include <windows.h>
#include "inicio.h"


BOOL WINAPI DllEntryPoint(HINSTANCE, DWORD, LPVOID)
{
return TRUE;
}

extern "C"
int __declspec (dllexport) WINAPI suma (int a, int b) {
return a+ b;
}

La primera función WINAPI es "el puntero de entrada" que necesita Visual Basic para poder usar la Dll. Borland C++ permite declarar esta función como "BOOL WINAPI dllMain(HINSTANCE, DWORD, LPVOID)", pero en este caso Visual Basic generará error al no hallar el puntero de entrada. (Hay que notar que no existe una función main o winMain en la dll)

De la forma que sea, esta función DllEntryPoint deberá retornar siempre el valor de TRUE o se generará error.  

La siguiente función es la que programé y hace una simple sumatoria de dos enteros. La sintaxis en general es:

extern "C"
tipo __declspec (dllexport) WINAPI nombre_funcion (argumentos) { ... }

Hay que observar que antes de "declspec" pueden ir uno o dos guiones largos. 

Se debe notar que el archivo "inicio.cpp" también llama a un archivo "inicio.h", para crear la dll ambos son necesarios. El código de "inicio.h" es:

#ifndef _INICIO_H
#define _INICIO_H

#ifdef BUILD_DLL // en la construcción de la librería
#define EXPORT __declspec(dllexport)
#else // en la construcción del ejecutable
#define EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus /* if in a C++ program */
extern "C"
#endif

int __declspec(dllexport) WINAPI suma(int a, int b);

#endif 

Lo importante aquí está en la penúltima línea de código: la declaración de la función que se "exporta" desde la dll y que devolverá los valores al programa principal. 

La Compilación:

Ambos archivos, "inicio.cpp" e "inicio.h" deberán estar en la misma carpeta donde está el ejecutable compilador, la carpeta deberá tener estos archivos:


Luego se va a Inicio->Ejecutar->cmd (para windows 2000 hacia arriba, para windows 98 es Inicio->Ejecutar->Command), nos situamos en la carpeta del compilador y se ingresa el siguiente comando (va todo seguido):


El compilador necesita varios parámetros para hacer lo que queremos, ahora los voy a explicar:

"-einicio": todo lo que va después de "-e" y sin espacios, será el nombre de la dll.

-Q: Es para que nos dé la información de la compilación y si se generó algún error.

-I: este comando, seguido de una ruta o varias rutas separadas por punto y coma sin espacios, le indica al compilador dónde buscar las cabeceras que se están usando. Como "windows.h" está en la carpeta Include, debemos decirle que busque allí o botará error. 

-WD: es el comando para generar una dll y no un exe. 

Después de todo esto, le decimos qué archivo se quiere compilar.

Para más información, recomiendo revisar el archivo de ayuda que viene con el compilador y que está en la carpeta Help.

Si todo sale bien, deberá aparecer:


Después de esto, la dll deberá aparecer en la carpeta donde tenemos los archivos "inicio¡.cpp" e "inicio.h" con el nombre de "inicio.dll". Esta dll ya podemos copiarla a donde la necesitemos para poder usarla (se recomienda ponerla en la carpeta system32).

 

Probando la dll:

Abrimos el visual con un proyecto Exe Estándar. Le añadimos un módulo .bas nuevo y le ponemos este código:

Public Declare Function suma Lib "inicio.dll" (ByVal a As Integer, ByVal b As Integer) As Integer

Dentro de la Dll está la función "suma" (pueden ser varias funciones, entonces tenemos que jalarlas una por una), con los dos argumentos tipo Entero. La función devuelve un entero, por ello se la declara "As Integer". Si la función en C++ fuera del tipo "void" no se pone al final el tipo de dato pues no devuelve ningún valor (incluso en algunas webs vi que las funciones tipo "void" de C++ eran llamadas como "Sub" en lugar de "Function" desde Visual Basic, pero esto no lo he probado).

En el formulario, el único código que puse para probar si devolvía un valor correcto fue:

Private Sub Form_Load()
    Dim c As Integer
    c = suma(8, 4)
    Form1.Caption = c
End Sub

Y lo que salió fue:

Ya funciona :)

Y ahora, una web donde explican un poco más esto de las Dlls generadas con el Borland C++:

http://www.zator.com/Cpp/E1_4_4b2a.htm