
Qué son las redes neuronales y sus aplicaciones
La IA está en boca de todos y se presenta como una tecnología que lo cambiará todo. En este artículo tratamos sobre...

¿Te gustaría aprender a construir una red neuronal funcional con PyTorch, pero no sabes por dónde empezar? Esta guía paso a paso te acompaña desde los fundamentos hasta un ejemplo práctico para que entiendas cada componente del proceso y pongas en marcha tus primeros modelos de IA.
¿No sabes qué framework de Deep Learning utilizar en tu próximo proyecto? ¿Tienes experiencia utilizando Tensorflow y quieres aprender a utilizar PyTorch? ¿Quieres aprender a crear redes neuronales?
En OpenWebinars te ayudamos a resolver todas estas preguntas y a comenzar ese proyecto de redes neuronales que llevas meses rumiando.
En el mundo de la inteligencia artificial existen dos frameworks principales para convertir en realidad nuestros proyectos de inteligencia artificial: Tensorflow y PyTorch.
Todos los desarrolladores, investigadores y adictos a la tecnología que queremos desarrollar un proyecto con redes neuronales nos vemos obligados a utilizar uno de estos dos frameworks, a continuación veremos cuando elegir PyTorch y qué pasos seguir para crear nuestras primeras redes neuronales.
¡Vamos a PyTorchear!
PyTorch es un framework open-source creado para abordar problemas de Deep Learning. Meta AI (cuando todavía se llamaba Facebook AI) comenzó a desarrollar este framework en 2016. Y actualmente se ha convertido en una de las herramientas fundamentales en el desarrollo de la inteligencia artificial.
PyTorch se caracteriza por ofrecer facilidades para crear prototipos de manera rápida y eficaz. Al mismo tiempo, ofrece una gran flexibilidad. Estas dos características hacen que sea el framework más adecuado para llevar a cabo proyectos educativos, de investigación o para crear prototipos tempranos en proyectos que tienen como objetivo validar la viabilidad de un proyecto.
En el artículo PyTorch 2.0: Innovaciones en el marco de trabajo de Machine Learning comentamos en mayor detalle las características claves de PyTorch, así que aprovecha y échale un ojo.
Regresión y clasificación son los dos problemas clásicos del aprendizaje automático supervisado. Estos algoritmos tienen como objetivo buscar patrones en los datos de entrenamiento para aprender a predecir correctamente el resultado de un problema. El aspecto clave que diferencia estos dos tipos de problemas es la naturaleza del dato que se desea predecir. Los problemas de clasificación tienen como objetivo asignar una categoría (de entre un conjunto de categorías conocidas previamente) a cada dato de entrada.
Mientras tanto el objetivo de los problemas de regresión es encontrar como se relacionan los datos de entrada con la salida deseada. Esta aproximación hace que en los problemas de regresión podamos predecir un valor continuo, es decir, un número en lugar de una categoría conocida previamente.
A la hora de desarrollar redes neuronales podemos utilizar muchos entornos. Podríamos crear nuestro propio entorno local, pero si queremos aprovechar la capacidad que ofrece PyTorch para utilizar GPUs yo personalmente recomiendo utilizar Google Collab.
Instalar PyTorch en un entorno cualquier que disponga de Python es extremadamente sencillo. PyTorch se distribuye como una librería de Python, de manera que se instalar mediante pip, el gestor de librerías estándar de Python.
A la hora de instalar PyTorch o cualquier otra de las librerías recomendadas que aparecen en este artículo simplemente debemos abrir una consola de comandos y ejecutar el comando:
pip install paqueteDeseado
Siguiendo este patrón, si queremos instalar PyTorch ejecutaremos la siguiente orden en nuestra consola de comandos:
pip install torch
De todas formas, si utilizamos Google Collab, ya tendremos instalado PyTorch y todas librerías que vamos a utilizar en los ejemplos, ya que estos entornos vienen con las librerías más comunes preinstaladas.
Si nos gusta elegir el camino complicado para entrenar redes neuronales podemos utilizar únicamente PyTorch. Pero si somos más prácticos nos apoyaremos en otras librerías open-source que nos facilitarán el proceso. Algunas de estas librerías son:
Antes de ponernos manos a la obra para crear redes neuronales debemos conocer los componentes que vamos a utilizar.
Los modelos de PyTorch están formados por capas y funciones de activación, la combinación de estas forma la estructura de cualquier modelo.
Las capas son un conjunto de neuronas que reciben un input de datos y aplican una transformación matemática sobre estos, generando una nueva salida. Todas las capas neuronales (incluyo aquellas que no se llaman “Linear”) introducen transformaciones lineales sobre los datos. Para remediar esto existen las funciones de activación. Una función de activación es una función matemática que introduce no linealidades en el modelo, haciendo que el modelo sea capaz de aprender relaciones complejas.
Es importante destacar que en la mayoría de casos siempre se ven las capas y las funciones de activación juntas, es decir, cada capa tiene su función de activación.
Las capas densas (también conocidas como “Linear”) son la capa básica. Es la primera capa que se creó para entrenar redes neuronales, y es la más utilizada. Concretamente una capa densa aplica la siguiente operación matemática:
y=Wx+b
Donde:
Las funciones de activación permiten que las capas introduzcan no linealidades, es decir, que puedan representar funciones matemáticas no lineales. Las capas más utilizadas son:
Si quieres conocer mejor las diversas capas y funciones de activación que existen te recomendamos nuestro Curso de introducción a Deep Learning con PyTorch.
Los dos componentes claves para que nuestras redes neuronales puedan aprender son las funciones de pérdidas y los algoritmos de optimización.
Las funciones de pérdida son funciones matemáticas que cuantifican como de equivocada ha sido la predicción que ha hecho nuestra red neuronal.
Los algoritmos de optimización son aquellos algoritmos que permiten ajustar las capas de nuestra red neuronal, es decir, deciden cuánto hay que modificar cada capa para que la próxima predicción sea mejor que la anterior.
A continuación, vamos a resolver un problema de regresión. Concretamente utilizaremos el dataset diabetes para predecir la progresión de la enfermedad en cada paciente un año después de la muestra de los datos.
En primer lugar, importaremos las librerías necesarias. Para resolver este problema utilizaremos la librería scikit-learn para dividir los datos y evaluar los resultados obtenidos. A su vez usaremos PyTorch para crear la red neuronal.
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
Una vez tengamos acceso a las librerías que vamos a utilizar procederemos a preprocesar los datos. Para este experimento utilizaremos el 80% de los datos disponibles para entrenar la red neuronal y el 20% para evaluar el rendimiento final.
data = load_diabetes()
x = torch.tensor(data.data, dtype=torch.float32)
y = torch.tensor(data.target.reshape(-1, 1), dtype=torch.float32)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
train_dataset = TensorDataset(x_train, y_train)
test_dataset = TensorDataset(x_test, y_test)
train_data_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_data_loader = DataLoader(test_dataset, batch_size=32, shuffle=True)
A continuación, crearemos nuestra red neuronal. Esta red está formada por 4 capas densas, todas ellas activadas por la función de activación ReLU.
class NeuralRegression(nn.Module):
def __init__(self, input_size):
super().__init__()
self.input = nn.Linear(input_size, 20)
self.act_1 = nn.ReLU()
self.hidden_1 = nn.Linear(20, 10)
self.act_2 = nn.ReLU()
self.hidden_2 = nn.Linear(10, 5)
self.act_3 = nn.ReLU()
self.out = nn.Linear(5, 1)
def forward(self, x):
x = self.input(x)
x = self.act_1(x)
x = self.hidden_1(x)
x = self.act_2(x)
x = self.hidden_2(x)
x = self.act_3(x)
return self.out(x)
El siguiente paso será entrenar el modelo. Para ellos utilizaremos la métrica Mean Squared Error Loss, también conocida como pérdida L2. Esta métrica calcula el promedio de los errores al cuadrado y es una de las métricas más utilizadas para resolver problemas de regresión. Respecto al optimizador utilizaremos AdamW, una función de optimización que permite entrenar la red de manera más rápida que otros algoritmos más tradicionales como pueden ser el descenso estocástico por gradiente.
model = NeuralRegression(x.shape[1])
loss_fn = nn.MSELoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
epochs = 5000
for epoch in range(epochs):
for batch, (x, y) in enumerate(train_data_loader):
optimizer.zero_grad()
pred = model(x)
loss = loss_fn(pred, y)
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f"Epoch {epoch+1 }/{epochs}, Loss: {loss.item():.4f}")
Finalmente evaluaremos el modelo ayudándonos de las funciones auxiliares disponibles en scikit-learn. Concretamente utilizaremos las métricas R2 y MSE (Mean Squared Error). Gracias a R2 podremos analizar la correlación entre los resultados predichos y los valores correctos. A su vez calculamos el MSE con el objetivo de comprobar si la red al utilizar datos no vistos en entrenamiento la red se comporta mejor o peor que en la fase de entrenamiento.
model.eval()
y_true, y_pred = [], []
with torch.no_grad():
for X_batch, y_batch in test_data_loader:
predictions = model(X_batch).squeeze()
y_true.extend(y_batch.numpy().flatten())
y_pred.extend(predictions.numpy().flatten())
mse = mean_squared_error(y_true, y_pred)
r2 = r2_score(y_true, y_pred)
print(f"Model Evaluation:")
print(f"MSE: {mse:.4f}")
print(f"R² Score: {r2:.4f}")
Además de las métricas vamos a comprobar manualmente las 5 primeras predicciones.
for x, y in zip(y_pred[:5], y_true[:5]):
print(f"Predicción: {x} \t Valor real: {y}")
Y el resultado que obtenemos es el siguiente.
Predicción: 118.00285339355469 Valor real: 69.0
Predicción: 143.7850341796875 Valor real: 182.0
Predicción: 189.33412170410156 Valor real: 190.0
Predicción: 104.28594207763672 Valor real: 104.0
Predicción: 71.55996704101562 Valor real: 72.0
Seguidamente, vamos a resolver un problema de clasificación. Concretamente utilizaremos el conjunto de datos MNIST con el objetivo de reconocer los dígitos entre el 0 y el 9.
En primer lugar, importaremos las librerías necesarias. Al igual que en el ejemplo anterior únicamente utilizaremos PyTorch y scikit-learn. Sin embargo, cabe destacar que esta vez también utilizaremos la librería scikit-learn para preprocesar los datos.
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report, confusion_matrix
from sklearn.preprocessing import StandardScaler
A continuación, descargaremos el dataset, lo preprocesaremos y finalmente los divideremos en datos de entrenamiento y evaluación. A la hora de preprocesar los datos simplemente los normalizaremos, haciendo que presenten una media de 0 y una desviación típica de 1. A la hora de dividirlos en conjuntos de entrenamiento y evaluación seguiremos la misma estrategia que antes, utilizaremos el 20% de los datos para evaluar la red y el resto para entrenarla.
data = load_digits()
x = torch.tensor(data.data, dtype=torch.float32)
y = torch.tensor(data.target, dtype=torch.long)
scaler = StandardScaler()
x = torch.tensor(scaler.fit_transform(x), dtype=torch.float32)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
x_train = x_train.view(-1, 1, 8, 8)
x_test = x_test.view(-1, 1, 8, 8)
train_dataset = TensorDataset(x_train, y_train)
test_dataset = TensorDataset(x_test, y_test)
train_data_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_data_loader = DataLoader(test_dataset, batch_size=32, shuffle=True)
A continuació,n definiremos el modelo que vamos a utilizar. Debido a que el problema de clasificación de dígitos es un problema más complejo utilizaremos una mayor variedad de capas. Cabe destacar el uso de capas convoluciones (Conv2d) y de pooling (MaxPool2d) ya que son capas especiales para realizar tratamiento de imágenes. También se ha decidido añadir una capa de dropout con el fin de mejorar el rendimiento de la red neuronal cuando vea datos que no ha visto durante el proceso de entrenamiento.
class NeuralClassification(nn.Module):
def __init__(self, input_size):
super().__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=2)
self.relu = nn.ReLU()
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(10, 20, kernel_size=2)
self.fc1 = nn.Linear(20, 50)
self.flatten = nn.Flatten()
self.max_pool2d = nn.MaxPool2d(2, 2)
self.dropout1 = nn.Dropout(0.25)
self.log_softmax = nn.LogSoftmax()
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.max_pool2d(x)
x = self.conv2(x)
x = self.relu(x)
x = self.max_pool2d(x)
x = self.dropout1(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.relu(x)
output = self.fc2(x)
return self.log_softmax(output)
El siguiente paso consistirá en el entrenamiento del modelo. A la hora de elegir optimizador hemos decido volver a utilizar AdamW, sin embargo, ya que ahora nos enfrentamos a un problema diferente hemos cambiado la función de pérdida. Para este reto hemos decidido utilizar como métrica la pérdida de log-verosimilitud negativa (negative log likelihood loss), ya que es una métrica creada para resolver problemas de clasificación con número arbitrario de clases objetivo.
model = NeuralClassification(x.shape[1])
loss_fn = nn.NLLLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
epochs = 10
for epoch in range(epochs):
for batch, (x, y) in enumerate(train_data_loader):
optimizer.zero_grad()
pred = model(x)
loss = loss_fn(pred, y)
loss.backward()
optimizer.step()
if (epoch + 1) % 2 == 0:
print(f"Epoch {epoch+1 }/{epochs}, Loss: {loss.item():.4f}")
Finalmente evaluaremos el modelo utilizando todo el potencial de las funciones auxiliares de scikit-learn. En primer lugar ,calcularemos las 4 métricas básicas de los problemas de clasificación: precisión, exactitud, recall y f1. Después exploraremos las funciones más completas de scikit-learn para obtener un informe de los resultados a nivel de clase y la matriz de confusión de los datos de evaluación.
model.eval()
y_true, y_pred = [], []
with torch.no_grad():
for X_batch, y_batch in test_data_loader:
predictions = model(X_batch)
_, predicted = torch.max(predictions, 1)
y_true.extend(y_batch.numpy().flatten())
y_pred.extend(predicted.numpy().flatten())
accuracy = accuracy_score(y_true, y_pred)
precision = precision_score(y_true, y_pred, average='macro')
recall = recall_score(y_true, y_pred, average='macro')
f1 = f1_score(y_true, y_pred, average='macro')
print(f"Model Evaluation:")
print(f"Accuracy: {accuracy:.4f}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")
print(f"Classification Report:\n{classification_report(y_true, y_pred)}")
print(f"Confusion Matrix:\n{confusion_matrix(y_true, y_pred)}")
Al evaluar el modelo obtendremos las siguientes métricas de evaluación. Si analizamos los resultados cuidadosamente veremos que obtenemos unas métricas generales correctas, sin embargo, nuestra red neuronal presenta grandes dificultades a la hora de clasificar el dígito 8. A la hora de paliar este efecto podríamos realizar diversas acciones, entre las cuales están aumentar el número de muestras del dígito 8 o buscar técnicas de preprocesamiento de datos que aumentan las características únicas del dígito 8.
Model Evaluation:
Accuracy: 0.8028
Precision: 0.8065
Recall: 0.8218
F1 Score: 0.8026
Classification Report:
precision recall f1-score support
0 0.97 0.94 0.96 36
1 0.82 0.91 0.86 35
2 0.82 0.97 0.89 33
3 0.76 0.76 0.76 38
4 0.82 0.82 0.82 34
5 0.71 0.90 0.79 30
6 0.75 0.90 0.82 30
7 0.80 0.75 0.78 44
8 0.86 0.40 0.55 47
9 0.74 0.85 0.79 33
accuracy 0.80 360
macro avg 0.81 0.82 0.80 360
weighted avg 0.81 0.80 0.79 360
Confusion Matrix:
[[34 1 0 0 0 0 0 0 1 0]
[ 0 32 0 0 0 1 2 0 0 0]
[ 0 0 32 0 0 0 1 0 0 0]
[ 0 0 3 29 0 0 0 3 0 3]
[ 0 0 0 0 28 0 5 1 0 0]
[ 0 0 1 0 0 27 0 0 0 2]
[ 0 0 0 0 3 0 27 0 0 0]
[ 1 0 0 6 0 2 0 33 1 1]
[ 0 5 3 3 3 5 1 4 19 4]
[ 0 1 0 0 0 3 0 0 1 28]]
A lo largo del tiempo se han desarrollado muchas técnicas que permiten mejorar los modelos. A continuación, vamos a analizar algunas de las más populares.
El punto más crucial para obtener buenos resultados utilizando modelos de aprendizaje profundo son los datos. Por ello el preprocesamiento que se aplica a los datos es muy importante. Las dos técnicas que vamos a analizar a continuación hacen justamente eso, preprocesar los datos.
Normalización hace referencia a preprocesar los datos de manera que los datos finales tengan un valor medio “0” y una desviación típica de 1. Mientras tanto batch normalization es una capa neuronal que hace aplica la función de normalización entre dos capas cualquiera de una red neuronal.
Uno de los mayores riesgos al entrenar una red neuronal es el “overfitting”. El “overfitting” sucede cuando una red memoriza todos los datos de entrenamiento y no es capaz de predecir correctamente datos nuevos. Dropout y la regularización L2 son dos técnicas que ayudan a evitar el “overfitting”.
¿Alguna vez te has planteado si puedes partir de un modelo que has hecho para una tarea y modificarlo para resolver otro problema? Si la respuesta es sí has pensado en transfer learning. Transfer learning engloba todas las técnicas que permiten adaptar un modelo ya entrenado.
PyTorch ofrece facilidades para utilizar redes previamente entrenadas e incluso ofrece un conjunto de redes ya entrenadas para algunas de las arquitecturas neuronales más comunes como son ResNet, VGG o Video SwinTransformer.
PyTorch es uno de los frameworks más importantes para trabajar con redes neuronales. Inicialmente solamente los investigadores utilizaban PyTorch y la mayoría de los desarrolladores utilizaban Tensorflow, sin embargo, en los últimos 4 años este hecho se ha difuminado. Cada vez más empresas utilizan PyTorch, y si bien PyTorch no va a reemplazar el resto de frameworks del mercado los complementa, permitiendo una mayor flexibilidad y un desarrollo más rápido de prototipos.
Para todos aquellos que nos dedicamos al campo de la inteligencia artificial PyTorch es una herramienta fundamental que debemos conocer. Cuando queramos informarnos sobre los últimos modelos de IA open-source deberemos leer código escrito mediante PyTorch, cuando queramos prototipar modelos de aprendizaje supervisado PyTorch será la opción más adecuada. Hoy más que ayer, pero menos que mañana, es crucial conocer las bases de PyTorch.
También te puede interesar
La IA está en boca de todos y se presenta como una tecnología que lo cambiará todo. En este artículo tratamos sobre...
Esta formación ofrece una introducción concentrada y profunda al Deep Learning, con un enfoque específico en la visión...
Esta formación ofrece una introducción exhaustiva al Deep Learning, cubriendo desde los fundamentos teóricos hasta la implementación práctica...