Supongamos que deseamos aproximar una función arbitrariamente $f(x)$ haciendo uso de un perceptrón multicapa. ¿En qué modo afecta el número de neuronas en la capa intermedia a la calidad de la aproximación?
Propongamos como ejemplo la función de una dimensión: \begin{align} f(x) = \frac{x}{2} \left(\text{sen}(20x) - \text{cos}\left(\frac{20}{3}x\right)\right) \end{align}
import numpy as np
def f(x):
"""
Función objetivo.
Intentaremos que un perceptrón multicapa la aproxime.
"""
return x/2 * (np.sin(20*x) - np.cos(20*x/3))
# Rango de valores a estudiar
x = np.arange(-1, 1, 0.02)
y = f(x)
import matplotlib.pyplot as plt
plt.style.use('ggplot')
# Visualización de la función objetivo
plt.plot(x, y)
plt.title('$f(x) = \\frac{x}{2} \\left( sin(20x) - cos(\\frac{20}{3}x) \\right)$')
plt.xlabel('$x$')
plt.ylabel('$y$')
Text(0, 0.5, '$y$')
# Instalar pyTorch según:
# https://pytorch.org/get-started/locally/
# Elegir versión.
# Ej: Para ejecutar desde aquí versión para cpu descomentar
# CUDA:
#!pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu116
# SIN CUDA:
#!pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cpu
import torch
from torch import nn
# Vector 1D a matriz columna
x_vec = np.reshape(x, (len(x),1)) # Arreglo de numpy
x_vec.dtype
dtype('float64')
# Matriz de numpy a tensor de pyTorch
X = torch.tensor(x_vec.astype(np.float32)) # Tensor 1D
X.dtype
torch.float32
# Vector de salidas esperadas
y_vec = np.reshape(y, (len(y),1))
Y = torch.tensor(y_vec.astype(np.float32)) # Tensor 1D
class NeuralNetwork(nn.Module):
"""
Red neuronal de tres capas.
Las capas intermedias utilizan una sigmoide como función de activación.
La capa final sólo calcula la combinación lineal, pues la sigmoide no
puede salir del rango [0,1], para este problema de regresión no es
adecuada.
"""
def __init__(self, s1):
"""
param s1: Número de neuronas en la capa oculta
"""
super(NeuralNetwork, self).__init__()
self.layers_stack = nn.Sequential(
nn.Linear(1, s1, bias=True),
nn.Sigmoid(),
nn.Linear(s1, 1, bias=True),
)
def forward(self, x):
"""
Evaluación de la red sobre la entrada x
usando alimentación hacia adelante para los pesos
actuales.
"""
logits = self.layers_stack(x)
return logits
import math
def train(X, Y, model, learning_rate, num_steps):
"""
Función que realiza el entrenamiento:
Intentará reducir la distancia entre los valores que salen
de la red y lo que deseamos modificando los pesos de la red.
"""
size = len(X)
loss_fn = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
#optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
step_report = 100
num_reports = int(math.ceil(num_steps/step_report))
e = np.zeros(num_reports)
for i in range(num_steps):
# Predicción y error
Y_hat = model(X)
loss = loss_fn(Y_hat, Y)
# Reduciendo el error
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Cómo vamos
if i % step_report == 0:
num_report = int(i/step_report)
e[num_report] = loss.item()
#print(f"{num_report} loss: {loss:>7f}")
#plt.plot(e)
return Y_hat, e
# Versión interactiva
from ipywidgets import interact_manual
import ipywidgets as widgets
def grafica_entrenamiento(x, e, y_pred):
"""
Grafica la evolución de la función de error
y la función que evalúa actualmente la red.
"""
fig, axes = plt.subplots(1, 2, figsize=(12,6))
axes[0].plot(np.arange(len(e))*100,e)
axes[0].set_xlabel('pasos')
axes[0].set_ylabel('error')
axes[1].plot(x, y_pred)
axes[1].set_xlabel('$x$')
axes[1].set_ylabel('$y$')
learning_rates = {2: 0.025, 5: 0.0015, 50:0.001, 500:0.0005, 5000:0.0001}
num_steps = {2: 4000, 5: 17000, 50: 10000, 500: 10000, 5000: 10000}
model = None
@interact_manual(
s1 = widgets.SelectionSlider(
options = [2,5,50,500,5000]
)
)
def aproxima(s1):
learning_rate = learning_rates[s1]
n_steps = num_steps[s1]
global model
model = NeuralNetwork(s1)
y_pred, e = train(X, Y, model, learning_rate, n_steps)
y_pred = y_pred.detach().numpy()
grafica_entrenamiento(x, e, y_pred)
interactive(children=(SelectionSlider(description='s1', options=(2, 5, 50, 500, 5000), value=2), Button(descri…
@interact_manual(
learning_rate = widgets.FloatLogSlider(value=0.001,
base=10,
min=-7, # min exponent of base
max=3, # min exponent of base
step=0.2), # exponent step,
num_steps = widgets.FloatLogSlider(value=1000,
base=10,
min=3, # min exponent of base
max=5, # min exponent of base
step=0.2), # exponent step,
)
def entrena_mas(learning_rate, num_steps):
n_steps = int(num_steps)
global model
y_pred, e = train(X, Y, model, learning_rate, n_steps)
y_pred = y_pred.detach().numpy()
grafica_entrenamiento(x, e, y_pred)
interactive(children=(FloatLogSlider(value=0.001, description='learning_rate', max=3.0, min=-7.0, step=0.2), F…