TensorFlow: Redes neuronales

Domina la creación de redes neuronales con la API Keras de TensorFlow. Descubre cómo diseñar, implementar y entrenar modelos neuronales desde cero, entendiendo cada componente esencial y aplicando buenas prácticas para problemas reales de machine learning

Aprende TensorFlow GRATIS y certifícate

TensorFlow ofrece varias formas de construir redes neuronales, pero Keras se ha convertido en la API estándar y recomendada para el desarrollo de estos modelos debido a su simplicidad y flexibilidad. Keras está integrado directamente en TensorFlow desde la versión 2.0, proporcionando una interfaz de alto nivel que facilita la creación, entrenamiento y evaluación de modelos de aprendizaje profundo.

Keras sigue una filosofía de diseño centrada en la experiencia del desarrollador, siendo intuitivo, modular y extensible. Esto permite construir redes neuronales complejas con pocas líneas de código, manteniendo al mismo tiempo la potencia y flexibilidad de TensorFlow como motor de cómputo subyacente.

La estructura fundamental de cualquier red neuronal en Keras se compone de capas que procesan tensores de entrada y producen tensores de salida. Estas capas encapsulan tanto los parámetros entrenables (pesos y sesgos) como las operaciones que transforman los datos.

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# Verificamos la versión de Keras que estamos utilizando
print(f"Versión de TensorFlow: {tf.__version__}")
print(f"Versión de Keras: {keras.__version__}")

En Keras existen dos formas principales de construir modelos: la API Secuencial y la API Funcional. La primera es más sencilla y adecuada para redes con una sola entrada y una sola salida, donde las capas se conectan secuencialmente. La segunda ofrece mayor flexibilidad para crear arquitecturas complejas con múltiples entradas, salidas o conexiones no lineales.

Veamos primero cómo construir un modelo secuencial básico:

# Modelo secuencial para clasificación de imágenes
modelo = keras.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

Este ejemplo ilustra una arquitectura convolucional típica para clasificación de imágenes, donde las capas convolutivas extraen características, las capas de pooling reducen la dimensionalidad, y las capas densas realizan la clasificación final.

Para modelos con estructuras más complejas, la API Funcional proporciona mayor flexibilidad:

# Modelo funcional con múltiples entradas
entrada_texto = keras.Input(shape=(100,))
entrada_imagen = keras.Input(shape=(28, 28, 3))

# Procesar texto
x1 = layers.Embedding(20000, 128)(entrada_texto)
x1 = layers.LSTM(32)(x1)

# Procesar imagen
x2 = layers.Conv2D(32, (3, 3))(entrada_imagen)
x2 = layers.MaxPooling2D((2, 2))(x2)
x2 = layers.Flatten()(x2)

# Combinar ambas entradas
combinado = layers.concatenate([x1, x2])
salida = layers.Dense(1, activation='sigmoid')(combinado)

# Crear el modelo especificando entradas y salidas
modelo = keras.Model(inputs=[entrada_texto, entrada_imagen], outputs=salida)

Las capas son los bloques fundamentales de cualquier red neuronal en Keras. TensorFlow ofrece una amplia variedad de capas predefinidas:

  • Capas básicas: Dense (completamente conectada), Dropout, Activation
  • Capas convolucionales: Conv1D, Conv2D, Conv3D, SeparableConv2D
  • Capas de pooling: MaxPooling2D, AveragePooling2D, GlobalMaxPooling2D
  • Capas recurrentes: SimpleRNN, LSTM, GRU, Bidirectional
  • Capas de normalización: BatchNormalization, LayerNormalization
  • Capas de regularización: Dropout, ActivityRegularization, SpatialDropout
  • Capas de atención: Attention, MultiHeadAttention
  • Capas para procesamiento de secuencias: Embedding, TimeDistributed
  • Capas para operaciones de fusión: Add, Concatenate, Multiply, Average

La compilación del modelo es un paso crucial antes de iniciar el entrenamiento. Aquí definimos el optimizador, la función de pérdida y las métricas que evaluarán el rendimiento:

modelo.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.001),
    loss=keras.losses.SparseCategoricalCrossentropy(),
    metrics=['accuracy']
)

TensorFlow ofrece varios optimizadores como SGD, Adam, RMSprop o Adagrad, cada uno con sus propias características y casos de uso recomendados. Igualmente, podemos elegir entre diversas funciones de pérdida según el tipo de problema, como BinaryCrossentropy para clasificación binaria, MeanSquaredError para regresión o KLDivergence para distribuciones probabilísticas.

Una vez compilado el modelo, podemos entrenarlo con el método fit():

# Supongamos que x_train e y_train son nuestros datos de entrenamiento
history = modelo.fit(
    x_train, y_train,
    epochs=10,
    batch_size=64,
    validation_split=0.2,
    callbacks=[
        keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True),
        keras.callbacks.ModelCheckpoint('mejor_modelo.keras', save_best_only=True),
        keras.callbacks.TensorBoard(log_dir='./logs')
    ]
)

Los callbacks son funciones que se ejecutan en determinados momentos durante el entrenamiento y permiten automatizar tareas como detener el entrenamiento cuando no hay mejora (EarlyStopping), guardar el mejor modelo (ModelCheckpoint) o registrar métricas para su visualización posterior (TensorBoard).

El objeto history devuelto por fit() contiene información valiosa sobre el proceso de entrenamiento:

import matplotlib.pyplot as plt

# Visualizar la evolución del entrenamiento
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Pérdida entrenamiento')
plt.plot(history.history['val_loss'], label='Pérdida validación')
plt.title('Evolución de la pérdida')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='Precisión entrenamiento')
plt.plot(history.history['val_accuracy'], label='Precisión validación')
plt.title('Evolución de la precisión')
plt.legend()

plt.tight_layout()
plt.show()

Una vez entrenado, podemos evaluar el rendimiento del modelo en un conjunto de datos de prueba:

resultados = modelo.evaluate(x_test, y_test, verbose=1)
print(f"Pérdida en test: {resultados[0]:.4f}")
print(f"Precisión en test: {resultados[1]:.4f}")

Y realizar predicciones con nuevos datos:

predicciones = modelo.predict(nuevos_datos)

Las arquitecturas de redes neuronales son muy diversas y se adaptan a diferentes tipos de problemas. Algunas arquitecturas fundamentales incluyen:

  1. Redes fully-connected (MLP): Para datos tabulares y problemas sencillos
  2. Redes convolucionales (CNN): Para procesar datos con estructura de rejilla como imágenes
  3. Redes recurrentes (RNN, LSTM, GRU): Para secuencias y datos temporales
  4. Redes con atención: Para capturar dependencias a larga distancia en secuencias
  5. Autoencoders: Para reducción de dimensionalidad y aprendizaje no supervisado
  6. Redes generativas adversarias (GAN): Para generar nuevos datos similares a los de entrenamiento

Keras facilita la implementación de estas arquitecturas mediante sus capas predefinidas. Por ejemplo, una red neuronal recurrente para procesamiento de texto:

modelo = keras.Sequential([
    layers.Embedding(input_dim=10000, output_dim=128, input_length=100),
    layers.Bidirectional(layers.LSTM(64, return_sequences=True)),
    layers.Bidirectional(layers.LSTM(32)),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(1, activation='sigmoid')
])

Una característica poderosa de Keras es la capacidad de crear capas personalizadas para implementar funcionalidades específicas:

class CapaPersonalizada(layers.Layer):
    def __init__(self, unidades=32, **kwargs):
        super(CapaPersonalizada, self).__init__(**kwargs)
        self.unidades = unidades
    
    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.unidades),
            initializer='random_normal',
            trainable=True,
            name='pesos'
        )
        self.b = self.add_weight(
            shape=(self.unidades,),
            initializer='zeros',
            trainable=True,
            name='sesgo'
        )
    
    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b
    
    def get_config(self):
        config = super(CapaPersonalizada, self).get_config()
        config.update({'unidades': self.unidades})
        return config

Además de las capas personalizadas, podemos crear funciones de pérdida y métricas personalizadas para problemas específicos:

# Función de pérdida personalizada
def focal_loss(gamma=2.0, alpha=0.25):
    def focal_loss_fn(y_true, y_pred):
        pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))
        pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))
        
        pt_1 = tf.clip_by_value(pt_1, 1e-9, 1.0)
        pt_0 = tf.clip_by_value(pt_0, 1e-9, 1.0)
        
        return -tf.reduce_sum(alpha * tf.pow(1. - pt_1, gamma) * tf.math.log(pt_1)) - \
               tf.reduce_sum((1-alpha) * tf.pow(pt_0, gamma) * tf.math.log(1. - pt_0))
    
    return focal_loss_fn

# Métrica personalizada
class F1Score(keras.metrics.Metric):
    def __init__(self, name='f1_score', **kwargs):
        super(F1Score, self).__init__(name=name, **kwargs)
        self.precision = keras.metrics.Precision()
        self.recall = keras.metrics.Recall()
    
    def update_state(self, y_true, y_pred, sample_weight=None):
        self.precision.update_state(y_true, y_pred, sample_weight)
        self.recall.update_state(y_true, y_pred, sample_weight)
    
    def result(self):
        p = self.precision.result()
        r = self.recall.result()
        return (2 * p * r) / (p + r + keras.backend.epsilon())
    
    def reset_state(self):
        self.precision.reset_st
Empezar curso de TensorFlow

Lecciones de este módulo de TensorFlow

Lecciones de programación del módulo Redes neuronales del curso de TensorFlow.