Con nuestros datos listos, es hora de entrenar nuestra primera red neuronal. Usaremos una arquitectura similar a la que hicimos en la última publicación de blog de la serie, usando una versión lineal de nuestra red neuronal con la capacidad de manejar patrones lineales:
from torch import nnclass LinearModel(nn.Module):
def __init__(self):
super().__init__()
self.layer_1 = nn.Linear(in_features=12, out_features=5)
self.layer_2 = nn.Linear(in_features=5, out_features=1)
def forward(self, x):
return self.layer_2(self.layer_1(x))
Esta red neuronal utiliza el nn.Linearmódulo de pytorch para crear una red neuronal con 1 capa profunda (una capa de entrada, una capa profunda y una capa de salida).
Aunque podemos crear nuestra propia clase heredando de nn.Module también podemos usar (más elegantemente) el nn.Sequential constructor para hacer lo mismo:
model_0 = nn.Sequential(
nn.Linear(in_features=12, out_features=5),
nn.Linear(in_features=5, out_features=1)
)
¡Fresco! Entonces nuestra red neuronal contiene una única capa interna con 5 neuronas (esto se puede ver en la out_features=5 en la primera capa).
Esta capa interna recibe la misma cantidad de conexiones de cada neurona de entrada. el 12 en in_features en la primera capa refleja el número de entidades y el 1 en out_features de la segunda capa refleja la salida (un valor único que varía de 0 a 1).
Para entrenar nuestra red neuronal, definiremos una función de pérdida y un optimizador. definiremos BCEWithLogitsLoss (Documentación de PyTorch 2.1) como esta función de pérdida (implementación de antorcha de entropía cruzada binaria, apropiada para problemas de clasificación) y Descenso de gradiente estocástico como optimizador (usando torch.optim.SGD ).
# Binary Cross entropy
loss_fn = nn.BCEWithLogitsLoss()# Stochastic Gradient Descent for Optimizer
optimizer = torch.optim.SGD(params=model_0.parameters(),
lr=0.01)
Finalmente, como también querré calcular la precisión de cada época del proceso de entrenamiento, diseñaremos una función para calcular eso:
def compute_accuracy(y_true, y_pred):
tp_tn = torch.eq(y_true, y_pred).sum().item()
acc = (tp_tn / len(y_pred)) * 100
return acc
¡Es hora de entrenar a nuestro modelo! Entrenemos nuestro modelo durante 1000 épocas y veamos cómo una red lineal simple puede manejar estos datos:
torch.manual_seed(42)epochs = 1000
train_acc_ev = []
test_acc_ev = []
# Build training and evaluation loop
for epoch in range(epochs):
model_0.train()
y_logits = model_0(X_train).squeeze()
loss = loss_fn(y_logits,
y_train)
# Calculating accuracy using predicted logists
acc = compute_accuracy(y_true=y_train,
y_pred=torch.round(torch.sigmoid(y_logits)))
train_acc_ev.append(acc)
# Training steps
optimizer.zero_grad()
loss.backward()
optimizer.step()
model_0.eval()
# Inference mode for prediction on the test data
with torch.inference_mode():
test_logits = model_0(X_test).squeeze()
test_loss = loss_fn(test_logits,
y_test)
test_acc = compute_accuracy(y_true=y_test,
y_pred=torch.round(torch.sigmoid(test_logits)))
test_acc_ev.append(test_acc)
# Print out accuracy and loss every 100 epochs
if epoch % 100 == 0:
print(f"Epoch: {epoch}, Loss: {loss:.5f}, Accuracy: {acc:.2f}% | Test loss: {test_loss:.5f}, Test acc: {test_acc:.2f}%")
Lamentablemente, la red neuronal que acabamos de construir no es lo suficientemente buena para resolver este problema. Veamos la evolución del entrenamiento y la precisión de las pruebas:
(Estoy trazando la precisión en lugar de la pérdida, ya que es más fácil de interpretar en este problema)
Curiosamente, nuestra red neuronal no puede mejorar gran parte de la precisión del conjunto de pruebas.
Con el conocimiento que tenemos de publicaciones de blog anteriores, podemos intentar agregar más capas y neuronas a nuestra red neuronal. Intentemos hacer ambas cosas y ver el resultado:
deeper_model = nn.Sequential(
nn.Linear(in_features=12, out_features=20),
nn.Linear(in_features=20, out_features=20),
nn.Linear(in_features=20, out_features=1)
)
Aunque nuestro modelo más profundo es un poco más complejo con una capa extra y más neuronas, eso no se traduce en más rendimiento en la red:
Aunque nuestro modelo es más complejo, eso realmente no aporta más precisión a nuestro problema de clasificación.
Para poder lograr un mayor rendimiento, necesitamos desbloquear una nueva característica de Neural Networks: ¡las funciones de activación!
Si hacer nuestro modelo cada vez más grande no trajo muchas mejoras, debe haber algo más que podamos hacer con las redes neuronales que pueda mejorar su rendimiento, ¿verdad?
¡Ahí es donde se pueden utilizar las funciones de activación! En nuestro ejemplo, volveremos a nuestro modelo más simple, pero esta vez con un cambio:
model_non_linear = nn.Sequential(
nn.Linear(in_features=12, out_features=5),
nn.ReLU(),
nn.Linear(in_features=5, out_features=1)
)
¿Cuál es la diferencia entre este modelo y el primero? La diferencia es que agregamos un nuevo bloque a nuestra red neuronal: nn.ReLU . El unidad lineal rectificada es una función de activación que cambiará el cálculo en cada uno de los pesos de la Red Neural:
Cada valor que pase por nuestros pesos en la red neuronal se calculará con esta función. Si el valor de la característica multiplicado por el peso es negativo, el valor se establece en 0; de lo contrario, se asume el valor calculado. Sólo este pequeño cambio añade mucho poder a la arquitectura de una red neuronal. torch Tenemos diferentes funciones de activación que podemos usar, como nn.ReLU , nn.Tanh o nn.ELU . Para obtener una descripción general de todas las funciones de activación, consulte esto enlace.
Nuestra arquitectura de red neuronal contiene un pequeño giro, por el momento:
Con este pequeño giro en la red neuronal, cada valor proveniente de la primera capa (representado por nn.Linear(in_features=12, out_features=5) ) tendrá que pasar por la prueba “ReLU”.
Veamos el impacto de adaptar esta arquitectura a nuestros datos:
¡Fresco! Aunque vemos que parte del rendimiento se degrada después de 800 épocas, este modelo no muestra un sobreajuste como los anteriores. Tenga en cuenta que nuestro conjunto de datos es muy pequeño, por lo que existe la posibilidad de que nuestros resultados sean mejores simplemente por aleatoriedad. Sin embargo, agregar funciones de activación a su torch Los modelos definitivamente tienen un gran impacto en términos de rendimiento, entrenamiento y generalización, particularmente cuando tienes muchos datos para entrenar.
Ahora que conoces el poder de las funciones de activación no lineales, también es relevante saber: