Saltar navegación

Deep Learning con Keras e Tensorflow

Librería Keras
Copilot. Librería Keras (CC BY-SA)

TensorFlow é un marco (framework) desenvolto por Google que proporciona todas as ferramentas necesarias para construír, adestrar e despregar modelos de aprendizaxe automática. É moi poderoso e flexible, pero pode ser algo complicado para principiantes porque require moitos coñecementos técnicos.

Keras é unha biblioteca que funciona sobre TensorFlow . O seu propósito é facer máis fácil e accesible o desenvolvemento de redes neuronais.

Actividades de aprendizaxe

Actividade 1: O teu primeiro modelo con Keras

Obxectivo: Aprender como funciona un modelo de rede neuronal con Keras. 
Instrución: Crear e adestrar unha rede neuronal básica que prediga se un número é maior ou menor que 5.

Que é unha rede neuronal?

Unha rede neuronal é un tipo de modelo matemático inspirado no funcionamento do cerebro humano. Está composta por unidades chamadas neuronas artificiais, que procesan datos e toman decisións baseándose en exemplos previos.

Imaxina que queres ensinar a un cativo a distinguir entre gatos e cans. Primeiro, amósalle fotos e explícalle cales son gatos e cales son cans. Pouco a pouco, comezará a recoñecelos por si mesmo.

Unha rede neuronal aprende de xeito similar: recibe datos, analiza exemplos anteriores e mellora as súas predicións co tempo.

Sequential é unha forma fácil de construír redes neuronais en Keras, onde as capas se engaden en orde, unha tras outra.

Exemplo : Imaxina unha liña de montaxe nunha fábrica de coches. O coche pasa por distintas etapas (chasis, pintura, motor...), unha tras outra, ata completarse.

En Sequential, as capas de neuronas funcionan do mesmo xeito: cada capa recibe información da anterior e pásaa á seguinte.

Código básico: 

from tensorflow.keras.models import Sequential   # poñemos a disposición o tipo de modelo Sequential

modelo = Sequential()   # Creamos un modelo Sequential

Dense engade capas densamente conectadas a unha rede neuronal, onde cada neurona está conectada con todas as neuronas da capa anterior.

Exemplo : Imaxina unha aula onde cada estudante fala con todos os demais. Así, todos teñen acceso á mesma información.

En Dense, cada neurona ten conexión directa con todas as da capa anterior.

Código básico: 

from tensorflow.keras.layers import Dense # poñemos a disposición o tipo de capas Dense

modelo.add(Dense(10, input_shape=(1,), activation='relu')) #engadimos ó modelo unha capa Dense con esas características

Capa con 10 neuronas Aquí:

10 → Número de neuronas na capa.

input_shape=(1,) → Indica que cada entrada é un número único.

activation='relu' → Función de activación que explicarei a continuación.

A función de activación decide como responderán as neuronas aos datos. É como un filtro que transforma os valores de saída.

Tipos comúns de activación:

ReLU (relu) → Mantén só valores positivos.  

Sigmoid (sigmoid) → Converte valores nun rango de 0 a 1, útil para clasificación. 

Código básico:

modelo.add(Dense(10, activation='relu')) # Usa ReLU para identificar patróns

modelo.add(Dense(1, activation='sigmoid')) # Usa Sigmoid para clasificación binaria, 6é ou non é >=5

Adam é un optimizador que mellora a aprendizaxe do modelo axustando os cálculos para obter mellores resultados.

Exemplo : Pensa en aprender a lanzar unha pelota a unha canastra. Primeiro tiras e ves onde fallaches. Logo, axustas a forza e a dirección.

Adam fai algo parecido: axusta os pesos das conexións da rede para reducir os erros.

Código básico:

modelo.compile(optimizer='adam')

Loss (Perda): É unha medida de que tan mal está a facer o modelo as súas predicións.

Pensa na perda como un marcador de erro: se o modelo comete erros grandes, o valor de loss será alto, pero se está a predicir correctamente, o valor será baixo.

Exemplo : Imaxínate que un amigo tenta adiviñar a temperatura de hoxe. Se di que fai 30°C pero realmente fai 10°C, a diferenza é grande, así que a perda será alta. Se adiviña que fai 12°C, a perda será menor porque estivo máis preto da verdade.

Accuracy (Precisión): É a porcentaxe de predicións correctas que fai o modelo.

En termos simples, mide que tan frecuentemente o modelo acerta.

A precisión debería incrementarse con cada época, comezando probablemente cun valor baixo (50%) e mellorando cara ao 100% (ou preto)

Exemplo : Se en 100 intentos o teu amigo acerta a temperatura 80 veces, entón a súa accuracy (precisión) é do 80%.

Código Python: Graficando loss e accuraty en modelo básico con Keras para predicir se un número é >=5

# Importamos as bibliotecas necesarias.
print("Estou chamando ás bibliotecas de TensorFlow e NumPy...")  # Impresión explicativa
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
import numpy as np
import matplotlib.pyplot as plt

# Xerar datos de entrada para adestrar o modelo.
print("Xerando datos de entrada...")  # Impresión explicativa
datos_x = np.random.randint(0, 10, size=1000)  # Números aleatorios entre 0 e 9.
datos_y = (datos_x > 5).astype(int)  # Etiquetas: 1 se o número é maior que 5, 0 en caso contrario.

# Crear o modelo usando Keras.
print("Creando o modelo da rede neuronal...")  # Impresión explicativa
modelo = Sequential([
    Dense(10, input_shape=(1,), activation='relu'),  # Primeira capa: 10 neuronas, activación 'relu'.
    Dense(1, activation='sigmoid')                  # Segunda capa: 1 neurona, activación 'sigmoid'.
])

# Imprimir as características da rede neuronal.
print("Características da rede neuronal:")
print(f"Modelo: {modelo.name}")
print(f"Número de capas: {len(modelo.layers)}")
print("Detalles das capas:")
for i, layer in enumerate(modelo.layers):
    print(f"  Capa {i+1}:")
    print(f"    Tipo: {type(layer).__name__}")
    print(f"    Número de neuronas: {layer.units}")
    print(f"    Activación: {layer.activation.__name__}")

# Compilamos o modelo.
print("Compilando o modelo para o adestramento...")  # Impresión explicativa
modelo.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# Adestramos o modelo.
print("Adestrando o modelo... Isto pode levar algún tempo.")  # Impresión explicativa
historia = modelo.fit(
    datos_x,
    datos_y,
    epochs=10,
    batch_size=32,
    verbose=1  # Verbose=1 amosa información detallada do progreso.
)

print("¡Adestramento finalizado!")  # Impresión explicativa

# Graficar loss e accuracy.
print("Xerando gráficos do erro (loss) e precisión (accuracy)...")  # Impresión explicativa
plt.figure(figsize=(12, 6))
plt.ion()

# Gráfico da función de perda (loss).
plt.subplot(1, 2, 1)
plt.plot(historia.history['loss'], label='Loss', color='red')
plt.title('Erro (Loss) durante o adestramento')
plt.xlabel('Épocas')
plt.ylabel('Erro')
plt.legend()
plt.grid(True)

# Gráfico da precisión (accuracy).
plt.subplot(1, 2, 2)
plt.plot(historia.history['accuracy'], label='Accuracy', color='blue')
plt.title('Precisión (Accuracy) durante o adestramento')
plt.xlabel('Épocas')
plt.ylabel('Precisión')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.pause(0.1)

# Engadir un bucle para pedir números continuamente
print("Introduce números para predicir se son maiores que 5. Escribe 'sair' para rematar.")

while True:
    entrada_usuario = input("Introduce un número ou escribe 'sair': ").strip().lower()
    if entrada_usuario == "sair":
        print("Programa finalizado. Ata logo!")
        break
    try:
        numero_novo = int(entrada_usuario)  # Convertir a número enteiro
        resultado = modelo.predict(np.array([[numero_novo]]))  # Predicir co modelo
        print(f"A probabilidade de que o número {numero_novo} sexa maior que 5 é: {resultado[0][0]:.2f}")
    except ValueError:
        print("Por favor, introduce un número válido.")    

Código Python: Predicir se un número é >=50

'''Neste caso, o modelo aprenderá a clasificar números en función do novo límite (50 en lugar de 5). 
Poderás observar se a perda (loss) ou a precisión (accuracy) cambian drasticamente e analizar 
como responde o modelo a esta nova configuración de datos.'''

# Cambiamos o rango de números xenerados, por exemplo, entre 0 e 99
datos_x = np.random.randint(0, 100, size=1000)

# Actualizamos as etiquetas para reflexar este novo rango
# Agora un número será etiquetado como 1 si é maior que 50, en lugar de 5
datos_y = (datos_x > 50).astype(int)

# Entrenamos novamente o modelo con estes novos datos
modelo.fit(
    datos_x,
    datos_y,
    epochs=10,
    batch_size=32
)
import numpy as np

# Predecimos o 97
predicion = modelo.predict(np.array([97]))
print(predicion)

#Esto daría unha probabilidad cercana a 1, xa que 97 é maior que 50.
#Se probas con un número como 30, obterás un valor cercano a 0.    

EXPLICACIÓN EPOCHS E batch_size e como afecta á precisión da predición

Épocas (epochs): As épocas indican cantas veces o modelo verá todos os datos de adestramento de principio a fin durante o adestramento. É como repetir unha tarefa varias veces para asegurarte de aprendela ben.

Exemplo : Supoñamos que tes un libro de 100 páxinas (os datos de adestramento). Se les todas as páxinas unha vez, iso é 1 época. Se les o libro completo 10 veces, completaches 10 épocas.

No contexto do modelo: Se epochs=5, o modelo procesa os mesmos datos completos 5 veces para axustar os seus parámetros e mellorar o seu rendemento.

Más épocas permiten que o modelo aprenda máis, pero demasiadas poderían facer que "memorice" os datos e non funcione ben con datos novos (sobreaxuste).

Tamaño de Lote (batch_size): O tamaño de lote indica cantos datos procesa o modelo á vez durante o adestramento. Dividir os datos en pequenos grupos ou "lotes" axuda ao modelo para aprender de maneira máis eficiente.

Exemplo : Supoñamos que tes 1000 datos (por exemplo, números entre 0 e 9). Se o batch_size=100, o modelo procesará os datos en grupos de 100: Primeiro analiza os primeiros 100 números, axusta os seus parámetros e logo pasa ao seguinte grupo de 100, e así sucesivamente.

No contexto do modelo: Se batch_size=32, o modelo verá 32 datos á vez, actualizará os seus parámetros e logo pasará aos seguintes 32.

Un tamaño de lote máis pequeno pode facer que a aprendizaxe sexa máis detallado pero máis lento, mentres que un lote máis grande é máis rápido, pero o modelo pode ser menos preciso.

Relación entre Épocas e Tamaño de Lote:

Se tes 1000 datos,

Se batch_size=100 e epochs=5, o modelo procesará os datos en grupos de 100. Como hai 10 lotes (1000 / 100), fará 10 axustes por época.

Ao final de 5 épocas, realizaría 50 axustes en total.

epochs: Cantas veces repasamos todos os datos.

batch_size: Cantos datos procesamos á vez.

A taxa de aprendizaxe (learning rate) é un dos parámetros máis importantes nun modelo de aprendizaxe automática.

Define canto modifican as neuronas os seus pesos en cada iteración para mellorar as súas predicións.

Exemplo : Imaxina que estás aprendendo a lanzar unha pelota a unha canastra. Se cada intento axustas moito a forza e a dirección, podes acabar fallando moito. Se axustas demasiado pouco, tardarás moito en mellorar.

A taxa de aprendizaxe funciona do mesmo xeito: se é demasiado alta, pode saltar os valores correctos, e se é demasiado baixa, pode aprender moi lentamente.

Como afecta ao modelo?

  • Taxa de aprendizaxe alta (learning rate = 0.1 ou máis)
    • O modelo cambia moito os pesos en cada iteración.
    • Aprende rápido, pero pode ser inestable e non atopar a mellor solución.
    • Posible problema: O erro oscila e non baixa correctamente.
  • Taxa de aprendizaxe baixa (learning rate = 0.001 ou menos)
    • O modelo axusta os pesos moi lentamente.
    • Aprende con máis estabilidade pero pode tardar moito en mellorar.
    • Posible problema: Precísanse moitas épocas para obter bos resultados.

Como elixir a taxa correcta?

Probas típicas: Comezar cun valor medio, como 0.01, e probar con valores maiores ou menores.

  • Se o modelo aprende demasiado lento, probar 0.05 ou 0.1.
  • Se o erro flutúa moito, probar valores menores, como 0.001.
  • Na práctica: Moitos modelos usan taxa de aprendizaxe adaptativa, como o optimizador Adam, que axusta automaticamente a taxa durante o adestramento.

Nos programas anteriores, a taxa de aprendizaxe está presente dentro da función modelo.compile(), que define como o modelo aprenderá durante o adestramento.

Exemplo dos programas anteriores:

modelo.compile(
optimizer='adam', # Optimizador 'adam' que axusta automaticamente a taxa de aprendizaxe.
loss='binary_crossentropy', # Función de perda para clasificación binaria.
metrics=['accuracy'] # Métrica para monitorizar a precisión do modelo.
)

Onde aparece exactamente?O optimizador Adam axusta automaticamente a taxa de aprendizaxe, polo que non aparece como un número explícito no código.

Se quixésemos especificar a taxa de aprendizaxe manualmente, poderíamos facelo así:

from tensorflow.keras.optimizers import Adam

modelo.compile(
optimizer=Adam(learning_rate=0.01), # Aquí establecemos a taxa de aprendizaxe en 0.01
loss='binary_crossentropy',
metrics=['accuracy']
)

Se queres experimenta con diferentes valores de learning_rate para ver o impacto na aprendizaxe do modelo...

Código Python: Perda e precisión durante o adestramento e a validación

# Importamos as bibliotecas necesarias.
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
import numpy as np
import matplotlib.pyplot as plt

# Xeración dos datos:
# Creamos datos de entrada e etiquetas para adestrar o modelo.
# Xeramos 1000 números aleatorios entre 0 e 9 como datos de entrada.
datos_x = np.random.randint(0, 10, size=1000)

# Creamos as etiquetas (valores esperados) en base aos datos.
# Os números maiores que 5 etiquétanse como 1, e os demais como 0.
datos_y = (datos_x > 5).astype(int)

# Creación do modelo de rede neuronal:
# Utilizamos Keras para definir un modelo secuencial cunha estrutura simple.
modelo = Sequential([
    # Primeira capa: Especificamos explícitamente a entrada con Input(shape=(1,))
    Input(shape=(1,)),
    Dense(10, activation='relu'),  # Capa oculta con 10 neuronas e activación 'relu'.
    Dense(1, activation='sigmoid')  # Capa de saída con 1 neurona e activación 'sigmoid'.
])

# Compilación do modelo:
# Configuramos como aprenderá o modelo durante o adestramento.
modelo.compile(
    optimizer='adam',                 # Optimizador 'adam' para axustar parámetros automaticamente.
    loss='binary_crossentropy',       # Función de perda para clasificación binaria.
    metrics=['accuracy']              # Métrica para monitorizar a precisión do modelo.
)

# Adestramento do modelo:
# Adestramos o modelo cos datos xerados. Aquí tamén se inclúe unha división
# para validación, de maneira que poidamos observar o progreso en datos novos.
historial = modelo.fit(
    datos_x,
    datos_y,
    epochs=10,                        # Número de veces que o modelo verá os datos.
    batch_size=32,                    # Dividimos os datos en bloques pequenos para eficiencia.
    validation_split=0.2              # Usamos o 20% dos datos para validación.
)

# **Visualización do progreso:**
# Debuxamos o progreso da perda e da precisión durante o adestramento.
plt.figure(figsize=(12, 4))

# Gráfico da perda
plt.subplot(1, 2, 1)
plt.plot(historial.history['loss'], label='Perda no Adestramento')
plt.plot(historial.history['val_loss'], label='Perda na Validación')
plt.xlabel('Época')
plt.ylabel('Perda')
plt.title('Progreso da Perda')
plt.legend()

# Gráfico da precisión
plt.subplot(1, 2, 2)
plt.plot(historial.history['accuracy'], label='Precisión no Adestramento')
plt.plot(historial.history['val_accuracy'], label='Precisión na Validación')
plt.xlabel('Época')
plt.ylabel('Precisión')
plt.title('Progreso da Precisión')
plt.legend()

# Amosar as gráficas
plt.tight_layout()
plt.show()

# **Notas finais:**
# - O modelo agora pode predicir se un número aleatorio entre 0 e 9 é maior ou menor que 5.
# - Coas gráficas, podes observar como diminúe a perda e mellora a precisión ao longo das épocas.    

Actividade 2: Aumentando capas

Obxectivo: Entender como pode afectar o aumento de capas a unha rede neuronal
Instrución: Usar Keras para realizar un modelo de 3 capas que prediga se un número é >=5.

Código Python: Modelo con 3 capas

# Importamos as bibliotecas necesarias.
# TensorFlow proporciónanos as ferramentas para crear e adestrar redes neuronais.
print("Importando TensorFlow e Keras para crear e adestrar redes neuronais...")
import tensorflow as tf

# Sequential e Dense son partes de Keras (que está integrado en TensorFlow).
# Sequential permítenos crear un modelo de rede neuronal agregando capas en orde.
# Dense engade capas densas ao noso modelo, que son capas onde cada neurona está conectada con todas as da capa anterior.
print("Importando Sequential e Dense de Keras para construír a rede neuronal...")
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense

# Importamos NumPy, que é unha ferramenta para traballar con números e xerar datos de proba.
print("Importando NumPy para xerar datos de entrada...")
import numpy as np

# Importamos Matplotlib para visualizar os resultados do adestramento do modelo.
print("Importando Matplotlib para xerar gráficas...")
import matplotlib.pyplot as plt

# Xeración dos datos.
# Creamos datos de entrada e etiquetas para adestrar o modelo.
# Xeramos 1000 números aleatorios entre 0 e 9 como datos de entrada.
print("Xerando 1000 números aleatorios entre 0 e 9 para adestrar o modelo...")
datos_x = np.random.randint(0, 10, size=1000)

# Creamos as etiquetas (valores esperados) en base aos datos.
# Os números maiores que 5 etiquétanse como 1, e os demais como 0.
print("Asignando etiquetas aos datos (1 se é maior que 5, 0 se non)...")
datos_y = (datos_x > 5).astype(int)

# Creación do modelo de rede neuronal.
# Utilizamos Keras para definir un modelo secuencial cunha estrutura simple.
print("Creando o modelo da rede neuronal...")
modelo = Sequential()

# Primeira capa: Especificamos explícitamente a entrada con Input(shape=(1,))
print("Engadindo a capa de entrada...")
modelo.add(Input(shape=(1,)))

# Primeira capa oculta con 10 neuronas e activación 'relu'.
print("Engadindo a primeira capa oculta con 10 neuronas e activación 'relu'...")
modelo.add(Dense(10, activation='relu'))

# Segunda capa oculta con 5 neuronas e activación 'relu'.
print("Engadindo unha segunda capa oculta con 5 neuronas e activación 'relu'...")
modelo.add(Dense(5, activation='relu'))

# Capa de saída con 1 neurona e activación 'sigmoid'.
print("Engadindo a capa de saída con activación 'sigmoid'...")
modelo.add(Dense(1, activation='sigmoid'))

# Compilación do modelo.
# Configuramos como aprenderá o modelo durante o adestramento.
print("Compilando o modelo con optimizador 'adam' e función de perda 'binary_crossentropy'...")
modelo.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# Adestramento do modelo.
# Adestramos o modelo cos datos xerados. Aquí tamén se inclúe unha división
# para validación, de maneira que poidamos observar o progreso en datos novos.
print("Adestrando o modelo con 10 épocas e validación do 20% dos datos...")
historial = modelo.fit(
    datos_x,
    datos_y,
    epochs=10,
    batch_size=32,
    validation_split=0.2
)

# Visualización do progreso.
# Debuxamos o progreso da perda e da precisión durante o adestramento.
print("Xerando os gráficos de perda e precisión...")
plt.figure(figsize=(12, 4))

# Gráfico da perda
plt.subplot(1, 2, 1)
plt.plot(historial.history['loss'], label='Perda no Adestramento')
plt.plot(historial.history['val_loss'], label='Perda na Validación')
plt.xlabel('Época')
plt.ylabel('Perda')
plt.title('Progreso da Perda')
plt.legend()

# Gráfico da precisión
plt.subplot(1, 2, 2)
plt.plot(historial.history['accuracy'], label='Precisión no Adestramento')
plt.plot(historial.history['val_accuracy'], label='Precisión na Validación')
plt.xlabel('Época')
plt.ylabel('Precisión')
plt.title('Progreso da Precisión')
plt.legend()

# Amosar as gráficas
plt.tight_layout()
plt.show()

# Notas finais.
# O modelo agora pode predicir se un número aleatorio entre 0 e 9 é maior ou menor que 5.
print("O modelo está listo para predicir se un número entre 0 e 9 é maior ou menor que 5!")
# Coas gráficas, podes observar como diminúe a perda e mellora a precisión ao longo das épocas.
print("Observa como evoluciona o modelo con cada época: perda diminuíndo e precisión aumentando!")
    

Unha capa oculta axuda a o modelo para identificar patróns en os datos. 

Pensa en cada neurona como un novo traballador.Cada neurona analiza unha parte do problema e atopa pequenas pistas (patróns).Ao combinar as neuronas, o modelo pode identificar patróns máis complexos.

Cando engadimos unha nova capa oculta, estamos a dicir: "Hei, imos deixar que outro grupo de traballadores (neuronas) analice as pistas que atopou a primeira capa para que podamos descubrir algo máis  profundo. 

Exemplo : Supoñamos que o modelo tenta determinar se un número é maior que 5. Isto é o que ocorre:

  • Cunha soa capa oculta (modelo orixinal):As neuronas desa capa traballan xuntas para aprender unha regra simple, como "Se o número é maior a 5, etiqueta como 1." Funciona, pero ten menos flexibilidade para aprender regras máis complexas.
  • Con dúas capas ocultas (modelo modificado):
    • Primeira capa: Atopa patróns básicos, como: "O número está máis preto de 0." "O número está máis preto de 10."
    • Segunda capa: Usa eses patróns para facer unha análise máis detallada, como: "Se o número é claramente maior a 5 segundo a capa anterior, etiqueta como 1." Esta segunda capa permite axustar máis finamente como decide o modelo. 

Más capas e neuronas permiten ao modelo captar relacións máis complicadas entre os datos.

Actividade 3: Aumentar neuronas

Obxectivo: Entender como pode afectar o aumento de neuronas nas capas de unha rede neuronal
Instrución: Usar Keras para realizar un modelo de 2 capas con 50 neuronas na capa oculta que prediga se un número é >=5.

Código Python: Red neuronal de 2 capas con 50 neuronas na 2ª capa

# Importamos as librarías necesarias
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
import numpy as np
import matplotlib.pyplot as plt

# Xeración dos datos
datos_x = np.random.randint(0, 10, size=1000)
datos_y = (datos_x > 5).astype(int)

# Modelo básico con 2 capas
modelo = Sequential([
    Input(shape=(1,)),             # Capa de entrada
    Dense(50, activation='relu'),  # Capa oculta con 50 neuronas
    Dense(1, activation='sigmoid') # Capa de saída
])

# Compilación do modelo
modelo.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# Adestramento do modelo
historial = modelo.fit(
    datos_x,
    datos_y,
    epochs=10,                        # 10 épocas para observar o progreso
    batch_size=32,
    validation_split=0.2              # Usamos un 20% para validación
)

# Gráfica do progreso
plt.figure(figsize=(12, 4))

# Gráfica da perda
plt.subplot(1, 2, 1)
plt.plot(historial.history['loss'], label='Perda no Adestramento')
plt.plot(historial.history['val_loss'], label='Perda na Validación')
plt.xlabel('Época')
plt.ylabel('Perda')
plt.title('Progreso da Perda')
plt.legend()

# Gráfica da precisión
plt.subplot(1, 2, 2)
plt.plot(historial.history['accuracy'], label='Precisión no Adestramento')
plt.plot(historial.history['val_accuracy'], label='Precisión na Validación')
plt.xlabel('Época')
plt.ylabel('Precisión')
plt.title('Progreso da Precisión')
plt.legend()

# Amosar as gráficas
plt.tight_layout()
plt.show()