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 :)

lunes, 26 de febrero de 2024

La diferencia entre un ejercicio de regresión y uno de clasificación con Tensorflow

 Estaba revisando estos dos códigos:
The Hello World of Deep Learning
Beyond Hello World: A computer Vision Example


El tutorial no lo dice, al menos no con la claridad que yo desearía, pero el primero es un problema de regresión y el otro es un problema de clasificación.

En el caso de la regresión, Tensorflow predice un valor en función a una entrada. En el caso de un problema de clasificación, Tensorflow devuelve el índice con mayor probabilidad de corresponder a la etiqueta (o clasificación) de una entrada.

Ambos códigos son casi idénticos, voy a copair debajo los que tengo en mi máquina y con los que he estado jugando:

Éste es el de regresión:

import tensorflow as tf
import numpy as np
from tensorflow import keras

# Build a simple Sequential model
model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])
# Compile the model
model.compile(optimizer='sgd', loss='mean_squared_error')
 
# Declare model inputs and outputs for training
xs = np.array([-1.0,  0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)
 
# Train the model
model.fit(xs, ys, epochs=100)

# Make a prediction
print(model.predict([10.0]))
 

Éste es el código del problema de clasificación:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
 
 
# Load the training and test split of the Fashion MNIST dataset
(training_images, training_labels), (test_images, test_labels) = fmnist.load_data()

print(len(training_images))
print(len(test_images))
print(training_labels[0:10])
 

# You can put between 0 to 59999 here
index = 562

# Set number of characters per row when printing
np.set_printoptions(linewidth=128)

# Print the label and image
print(f'LABEL: {training_labels[index]}')
print(f'\nIMAGE PIXEL ARRAY:\n {training_images[index]}')

# Visualize the image
plt.imshow(training_images[index])

# Normalize the pixel values of the train and test images
training_images  = training_images / 255.0
test_images = test_images / 255.0
 
# Build the classification model
model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(256, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])
 

model.compile(optimizer = tf.optimizers.Adam(),
              loss = 'sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(training_images, training_labels, epochs=15)
 
# Evaluate the model on unseen data
model.evaluate(test_images, test_labels)


Aparte de las diferencias al construir la red neuronal o al predecir o evaluar un valor, o la data que se está usando, la diferencia principal, y lo que define si vamos a usar la red neuronal que estamos construyendo se usará para clasificar o predecir, es la función de pérdida o Loss Function. Durate el entrenamiento de la red, esta función mide cuánto se están acercando los valores calculador por la red a los valores verdaderos (usualmente usando el cuadrado de la diferencia entre ambos valores, pero hay funciones más complejas), esta información se usa en el siguiente bucle para ajustar los parámetros de la red y disminuir el valor de la función de pérdida. La red neuronal hace estos ajustes usando otra función, la de optimización, usualmente la del gradiente descendente. En el caso de las funciones cóncavas, cuando su gradiente se hace cero, se ha alcanzado el mínimo. Lo que hace la función de optimización es minimizar a la de pérdida usando este principio.

Una lista de distintas funciones de pérdida está en este enlace.

miércoles, 20 de diciembre de 2023

Entrada antigua: Acerca de la Recursividad

Este es un artículo que escribí en mi vieja web allá por los dosmiles (¿2007?) y me ha traído nostalgia: es de cuando batallaba intentando entender la recursividad.

Se me hace curioso leerme después de tantos años, He recordado cuando, debugueando, intentaba infructuosamente entender línea por línea cómo iba eso de la recursividad, y de cómo sabía el procesador, la máquina virtual o el runtime, cómo volver a llamar a la función para llegar a la respuesta correcta.

He terminado sonriendo, pues éste fue el inicio de lo que luego sería esto: http://programacionamartillazos.blogspot.com/2023/06/cuadrados-recursivos-con-la-tortuga-de.html

o ésto: https://programacionamartillazos.blogspot.com/2016/01/listar-los-archivo-en-una-carpeta-y-sus.html

Aquí el artículo, Enjoy!

 

"Para entender la recursividad, primero hay que entender la recursividad"

 

En mis ratos libres me gusta leer blogs sobre programación. Así me topé con http://www.variablenotfound.com/2007/02/dnde-se-han-ido-los-programadores.html y con http://www.codinghorror.com/blog/archives/000781.html donde dice "If you can successfully write a loop that goes from 1 to 10 in every language on your resume, can do simple arithmetic without a calculator, and can use recursion to solve a real problem, you’re already ahead of the pack!".

Entonces me faltaba la recursión o recursividad. Ya había leído sobre ella antes, incluso bajado tutoriales, pero mi error fue intentar entenderla.
Como es una función que se llama a sí misma, no hay por dónde "cogerla", lógicamente es algo autocontenido y que debería generar un bucle infinito:

El límite, o parada, sucede cuando "i" llega a 10. Si "i" es igual o mayor a 10 (partiendo de un valor inferior), simplemente sale de la función y retorna el resultado de la suma total. No se debe perder tiempo intentando entender la recursión basta saberla usar. Si se intenta entender o hallar la secuencia lógica de la función de arriba no se llegará a ninguna parte. La variable "i" recibe la respuesta de una función donde está la propia variable "i" (y lo peor: en este caso la operación que debe realizar la función sumaR está donde recibe los parámetros).

¿hay alguna manera de escapar de este bucle infernal?
Sí, en el momento en que "i" llega al valor donde se debe retornar cero (ya no se llama a la función sumaR).

Recién llegué a comprender la recursión cuando dejé de intentar comprenderla. Suena raro, pero así fue.

jueves, 9 de noviembre de 2023

Hilos con C#, corregido

Continuando con mi entrada anterior, el detalle que hay que corregir es el siguiente: no se puede modificar la interfaz gráfica desde otro hilo. Las interfaces gráficas de .Net y Java no son thread-safe, incluso estos frameworks arrojarán una excepción si detectan que se está intentando modificar la interfaz gráfica desde otro hilo que no haya sido el que la creó.

Como ejemplo, voy a añadir un par de líneas al final de las funciones Hilo1() e Hilo2() en el código que dejé como ejemplo:






En muchas aplicaciones que modifican la interfaz gráfica es necesario llamar a las funciones Refresh() de los controles. Después de este cambio, la aplicación arroja esta excepción:



La manera correcta, en C#, de modificar la interfaz gráfica desde otro hilo es usando las funciones Invoke() o BeginInvoke() de la plataforma .Net. 

Por otro lado, es una mala práctica llamar a la función Abort() de un hilo. Si se desea que un hilo finalice junto a la aplicación que lo creó se debe declarar como hilo de background, eso se hace en una línea de código:

p2.IsBackground = true;

El código corregido es:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading; 


namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

        

        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true; 


            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));


            p1.IsBackground = true;

            p2.IsBackground = true;


            p1.Start();

            p2.Start(); 

        }

        

        public void Hilo1() {

            while (true)

            {


                Thread.Sleep(10);


                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;

                }


                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

                        pictureBox1.Refresh();

                    }));

                }

            }

       }


        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);


                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;


                    if (g == 255)

                        b2 = true;

                }


                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;


                    if (g == 0)

                        b2 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox2.BackColor = Color.FromArgb(100, g, 80);

                        pictureBox2.Refresh();

                    }));

                }

            }

        }


        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

        }

    }

}

 

El nuevo ejemplo se puede descargar de aquí.

viernes, 29 de septiembre de 2023

Hilos con C# (C Sharp)

Otro tutorial viejito, muy viejito, hay un detalle qué arreglar en el código, pero eso lo voy a explicar en una siguiente entrada:

¡Hilos! Visual Basic 6 no los permite, y programarlos en Borland C++ es muy engorroso (personalmente odio tener que estar programando archivos .h y el Borland los necesita para poder usar los hilos). En este tutorial voy a usar C#.

Para el programita que voy a mostrar a continuación hay que abrir el Visual C# (yo uso el 2005), crear un nuevo proyecto tipo Windows Application y en el formulario que aparece, ponerle dos Pictureboxes: pictureBox1 y pictureBox2. Al final, debe verse así:

 

Y luego se le mete este código:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading;

 

namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

       

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true;

            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));

            p1.Start();

            p2.Start();

        }

       

        public void Hilo1() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;                   

                }

 

                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }

                pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

            }

       }

 

        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;

 

                    if (g == 255)

                        b2 = true;

                }

 

                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;

 

                    if (g == 0)

                        b2 = false;

                }     

                pictureBox2.BackColor = Color.FromArgb(100, g, 80); 

            }                  

        }

 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

            p1.Abort();

            p2.Abort();

        }

    }

}

 

Lo que se debe entender primero es que existen dos hilos: "p1" y "p2" (o dos objetos de la clase "Thread", en inglés "Hilo") cada uno asociado a un método: A "p1" le corresponde "Hilo1" y a "p2" le corresponde "Hilo2". Lo que "p1" y "p2" permiten es que ambos métodos (Hilo1 e Hilo2) se ejecuten de manera simultánea.

Todas las otras variables en el programa son de control. Los hilos empiezan a ejecutarse al llamar al método "Start" y se terminan con "Abort". El C# no permite llamar de nuevo a "Start" para un hilo que ya se está ejecutando.

Lo que hace el programa simplemente es variar el color de ambos PictureBoxes en un bucle que jamás termina, para ello se usa el comando CualquierPictureBox.BackColor = Color.FromArgb(100, g, 80);

También uso el método Thread.Sleep que detiene la ejecución del hilo durante el tiempo en milisegundos que va entre paréntesis. Esto es para que los colores varíen despacio y se puedan apreciar.

Cada vez que dentro de un método se usa la palabra "Thread" ésta se refiere al hilo que está asociado a él.

Una buena página para aprender hilos en C# es ésta: https://www.albahari.com/

Y acá se puede descargar mi ejemplo de los PictureBoxes.

sábado, 17 de junio de 2023

Cuadrados recursivos (y con colores) usando la tortuga de Python

Quise jugar un poco más con la recursividad en Python, así que cogí mi código de los arbolitos fractales y lo modifiqué para que dibujara cuadraditos (tuve que hacer unos cálculos en mi cabeza para que los cuadrados pequeños se ubicaran en medio de cada lado del cuadrado más grande):

from operator import length_hint
import turtle as t

def cuadrado(lenght):
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        if lenght < 20:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


Creo que no es necesario el import length_hint, (no recuerdo de qué tutorial lo copié) pero los dibujos tardan su buen rato en terminarse de dibujar, así que al final dejé el import.

El resultado es:



Se ve bien, pero no lo suficiente, hice un pequeño cambio al parámetro que se pasa a la función cuadrado():

from operator import length_hint
import turtle as t

def cuadrado(lenght):
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        if lenght < 20:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


El resultado es:


Mucho mejor, pero le falta color, el código para el color lo copié de aquí.


from operator import length_hint
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght):
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
colormode(255)
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


El resultado es:


No está mal, pero esas líneas gruesas del cuadrado grande molestan un poco. Voy a ponder un color distinto a cada línea:

from operator import length_hint
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght):
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(90)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(180)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(270)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
colormode(255)
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


Queda asi:


Creo que prefiero la versión en negro y gris.

Como se ve tan feo, encontré un dato en uno de los tutoriales: el módulo colorsys. Con la ayuda de este enlace y luego de probar un poco llegué a este código:

from colorsys import hsv_to_rgb
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght, n):
    t.pencolor(hsv_to_rgb(n / 36, 0.75, 0.75))
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght, n):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        n += 4
                
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10, n)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2, n + 1)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        
drawit2(cx, cy, lenght / 2, n + 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        
drawit2(cx, cy, lenght / 2, n + 3)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        
drawit2(cx, cy, lenght / 2, n + 4)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160, 12)
t.mainloop()


Se ve mucho mejor ahora: