Guía de Trabajo Práctico 2#

Materia: Aprendizaje Profundo Basado en la Física (optativa del Instituto Balseiro)

Docente: José I. Robledo

Edición: abril-mayo 2026

Open in Colab


Ejercicio 1 — Autograd sobre energía potencial#

La idea de este ejercicio es explorar la capacidad de diferenciación automática de PyTorch. Analizaremos la energía potencial del sistema físico simple compuesto por un resorte sometido a gravedad. Consideremos una partícula de masa \(m\) unida a un resorte de constante elástica \(k\), en presencia de un campo gravitatorio \(g\). La energía potencial del sistema está dada por la elástica \(\frac{1}{2}k x^2\) y la gravitatoria \(mgx\). Sabemos que el negativo del gradiente de esta energía respecto a \(x\) representa la fuerza.

Consignas#

  1. Definir un tensor unidimensional x con requires_grad=True. Inicializar x en algún valor.

  2. Definir una variable para la energía potencial U = 0.5 k x^2 + m g x en función de \(x\). Puede utilizar una constante elástica de \(2 \frac{N}{m}\), masa de \(1.5kg\) y aceleración de la gravedad de de \(9.8\frac{m}{s^2}\).

  3. Calcular dU/dx con el método backward() de PyTorch y extraer el gradiente mediante x.grad.

  4. Comparar este valor con la derivada analítica.

  5. Explorar varios valores de x y graficar energía y su derivada. Identifique el punto de equilibrio.

  6. Implementar descenso por gradiente para encontrar el mínimo de la energía.

  7. (Opcional): Utilizar PyTorch para el descenso (pasar x al optimizador) y comparar con descenso por gradiente estocástico (SGD)

  8. (Opcional): extender el problema a 2 o 3 dimensiones.

# TODO: definir x, k, m, g
# TODO: calcular U(x) y su gradiente con autograd
# TODO: comparar con derivada analítica
# TODO: implementar descenso por el gradiente.

Ejercicio 2 — MLP de regresión para la deflexión de una viga#

Desarrollar un modelo de regresión basado en una red neuronal (MLP) que estime la deflexión máxima de una viga simplemente apoyada sometida a una carga puntual.

Según la teoría de vigas de Euler-Bernoulli 1, la deflexión máxima \(\delta\) de una viga simplemente apoyada con una carga puntual \(P\) aplicada en el centro está dada por:

\[ \delta = \frac{P L^3}{48 EI} \]

donde:

  • ( P ): carga aplicada (N)

  • ( L ): longitud de la viga (m)

  • ( E ): módulo de elasticidad del material (Pa)

  • ( I ): momento de inercia de la sección (m\(^4\))

  • ( EI ): rigidez flexional

Para este ejercicio, se utilizará una versión simplificada y con ruido de esta relación. La base de datos se encuentra en ./data/beam_dataset.csv, en donde se mide la deflexión máxima para distintos valores de \(P\), \(L\), \(EI\).

  1. Cargar los datos a partir del archivo csv beam_dataset.csv.

  2. Entrenar un modelo de regresión basado en un MLP (Multi-Layer Perceptron) que prediga \(\delta\) a partir de ( \(P\), \(L\), \(EI\) ).

  3. Calcular el error medio absoluto (MAE) sobre el conjunto de test. Graficar \( y_{\text{true}} \) vs \( y_{\text{pred}} \) y el error residual.

# TODO: cargar dataset de deflexión
# TODO: entrenar MLP de regresión
# TODO: calcular MAE y graficar y_true vs y_pred

Ejercicio 3 — Clasificación de fases en Modelo Ising 2D#

En este ejercicio se trabajará con un conjunto de datos generado a partir de simulaciones del modelo de Ising bidimensional (2D) utilizando métodos de Monte Carlo (algoritmo de Metropolis).

El modelo de Ising describe un sistema de espines en una red cuadrada, donde cada sitio puede tomar valores:

  • (+1) (espín “arriba”)

  • (-1) (espín “abajo”)

Los espines vecinos tienden a alinearse, pero la temperatura introduce fluctuaciones térmicas que pueden desordenar el sistema.

El comportamiento del sistema depende de la temperatura:

  • Temperatura baja \((T < T_c)\): el sistema tiende a ordenarse, formando dominios grandes de espines alineados.

  • Temperatura alta \((T > T_c)\): el sistema permanece desordenado debido a las fluctuaciones térmicas.

Existe una temperatura crítica conocida:

\[ T_c \approx 2.269 \]

en la cual ocurre una transición de fase entre el estado ordenado y desordenado.

Se provee un dataset compuesto por configuraciones del modelo de Ising. Los datos se encuentran en .data/ising_dataset.pt. Cada muestra corresponde a un vector de 625 elementos, que corresponden al aplanamiento de imagen (grilla 2D) \(25\times 25\) de espines. Cada imagen fue generada a una cierta temperatura y tiene una etiqueta asociada a si se encuentra por encima o por debajo de \(T_C\):

  • \(y = 0\): temperatura por debajo de \(T_c\)

  • \(y = 1\): temperatura por encima de \(T_c\)

Entrenar un modelo de clasificación supervisada que, dada una configuración del sistema, sea capaz de predecir si corresponde a:

  • fase ordenada ((T < T_c))

  • fase desordenada ((T > T_c))

utilizando un perceptrón multicapa (MLP) utilizando el vector de datos provisto y una red neuronal convolucional (CNN) transformando el vector en un arreglo de 2 dimensiones (imagen). Para esto, utilizar una función de pérdida adecuada para clasificación binaria. Comparar los resultados entre ambas arquitecturas comparando la accuracy en cada caso.

# cargar datos de ising_dataset.pt 
data = torch.load("data/ising_dataset.pt")

# TODO: definir y entrenar clasificadores
# TODO: calcular accuracy 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 # cargar datos de ising_dataset.pt
----> 2 data = torch.load("data/ising_dataset.pt")
      3 
      4 # TODO: definir y entrenar clasificadores
      5 # TODO: calcular accuracy

NameError: name 'torch' is not defined

Ejercicio 4 — Gradiente por diferencias finitas vs backprop#

Antes de usar restricciones físicas conviene verificar que entendemos cómo se propagan los gradientes. En este ejercicio vas a comparar dos formas de estimar la derivada de una función de costo respecto de un parámetro.

  • Definir una red pequeña y una función de pérdida simple.

  • Calcular el gradiente con backward() e imprimir el gradiente de un parámetro a elección del modelo. Para acceder a los gradientes de los pesos de la capa \(i\) del modelo se puede usar model[i].weight.grad.

  • Aproximar ese mismo gradiente mediante diferencias finitas. Para esto, vamos a aproximar \(\partial f_w(x) / \partial w_i \approx (f_{w_i+\epsilon}(x)-f_{w_i-\epsilon}(x))/(2\epsilon)\). consejo: utilizar with torch.no_grad para no trackear los cambios manuales en los parámetros en el grafo.

  • Comparar ambos valores y comentar si la diferencia es pequeña.

# TODO: definir una red pequeña y una loss
# TODO: calcular gradiente por backward
# TODO: aproximar el gradiente por diferencias finitas
# TODO: comparar ambos valores

Ejercicio 5 — Positividad de un coeficiente físico#

Muchos parámetros físicos no pueden tomar cualquier valor. Por ejemplo, un coeficiente de difusión o un amortiguamiento deben ser positivos. Si el modelo predice valores negativos, el ajuste puede ser numéricamente correcto pero físicamente inválido.

Generaremos un dataset sintético donde el coeficiente objetivo deba ser siempre positivo.

  • Entrenar un modelo con salida libre.

  • Entrenar otro modelo que garantice positividad usando softplus en la salida.

  • Comparar predicciones y detectar si el modelo libre produce valores sin sentido físico.

T = torch.linspace(300, 900, 300).view(-1, 1)
def generador_de_datos(T):
    return 0.05 + 0.0002 * (T - 300) + 0.005 * torch.rand_like(T)


# TODO: entrenar un modelo libre y otro con salida softplus
# TODO: comparar si aparecen valores no físicos

Ejercicio 6 — Penalización física por constraint#

En muchos sistemas físicos no solo importa el valor predicho, sino también cómo cambia respecto a las variables de entrada. Por ejemplo, en flujo en tuberías, la caída de presión debe ser una función creciente del caudal.

  • Verificar que el código presentado para la generación de datos genere datos de la forma \(\Delta P \propto \frac{Q^2}{D^5}\)

  • Definir una red cuyo objetivo sea predecir el logaritmo de la caída de presión (\(\log \Delta P\)) en función del logaritmo del caudal \(\log Q\) y el logaritmo del diámetro \(\log D\).

  • Utilizar autograd para calcular la derivada de la predicción respecto de las entradas.

  • Agregar una penalización que castigue regiones donde dP/dQ < 0 (para esto tener en cuenta la desnormalización y la transformada inversa del logaritmo).

  • Entrenar con pérdida total = pérdida de datos + cte * penalización física (inclusión de sesgo inductivo).

Q = torch.rand(400, 1) * 5.0 # Caudal
D = 0.05 + 0.15 * torch.rand(400, 1) # Diámetro
X = torch.cat([torch.log(Q + 1e-8), torch.log(D)], dim=1)
y = 10.0 * Q**2 / D**5 
y = torch.log(y+ 1e-8) + 0.01 * torch.randn_like(Q)

X_mean = X.mean(0, keepdim=True) 
X_std = X.std(0, keepdim=True) 

y_mean = y.mean() 
y_std = y.std() 

Xn = (X - X_mean) / X_std
yn = (y - y_mean) / y_std


# TODO: definir una penalización de monotonicidad dP/dQ >= 0
# TODO: entrenar con loss de datos + penalidad