484.1 La reproducción es una relación que hay que definir
Una simulación reproduce un fenómeno respecto de determinadas propiedades observadas y no de otras. Puede acertar la distribución de tamaños y fallar en el orden temporal de los eventos. Puede reconstruir una curva promedio y perder las diferencias entre unidades, o generar una imagen convincente mediante reglas que ya contenían, de manera más o menos explícita, la imagen buscada. Antes de evaluar su aporte conviene precisar qué se comparó, cómo se midió la discrepancia y qué información se utilizó para construir el programa. Sin esas precisiones, la palabra reproducción termina agrupando logros científicos de alcance muy desigual.
El problema central aparece cuando varios mecanismos producen resultados semejantes. Una simulación satisfactoria demuestra que las reglas implementadas pueden generar el patrón en las condiciones examinadas. Para sostener que esas mismas reglas explican el sistema observado hace falta examinar alternativas y derivar consecuencias adicionales que permitan contrastarlas. Mostrar que un mecanismo es suficiente, descubrir que una intuición era incorrecta y diseñar una observación que separe hipótesis rivales son aportes distintos, y cada uno puede verificarse por separado.
Wilson y Collins (2019) recomiendan usar los modelos para simular conducta antes de ajustarlos a datos reales, y observar cómo cambia esa conducta al variar el modelo, sus parámetros o el diseño experimental. En su guía tratan como pasos diferentes la recuperación de parámetros, la capacidad de arbitrar entre modelos y la validación del modelo ganador mediante sus propias simulaciones. Un procedimiento puede recuperar bien los parámetros cuando el propio modelo generó los datos y, aun así, describir mal la conducta real; el éxito dentro del mundo simulado no indica que ese mundo sea el adecuado.
Este capítulo examina esa diferencia con ejemplos construidos para la ocasión. Los datos son sintéticos y los cálculos pueden reconstruirse ejecutando el código en el orden en que aparece, ya que los bloques posteriores reutilizan funciones definidas en los anteriores. Nada de lo que ocurre en el programa se atribuye a un experimento biológico o a un estudio clínico.
484.2 Igual distribución, distinta organización temporal
Considérese una serie de observaciones independientes con distribución normal de media cero y varianza unitaria. Compárese con un proceso autorregresivo estacionario de primer orden cuyo coeficiente de persistencia vale 0,8 y cuya innovación tiene varianza 0,36. Si el segundo proceso parte de su distribución estacionaria, ambos tienen exactamente la misma distribución marginal en cada instante. Un criterio de reproducción limitado al histograma no puede preferir uno sobre otro, salvo por diferencias de precisión muestral: en una muestra finita, el histograma del proceso persistente fluctúa más, porque sus observaciones aportan menos información independiente. Las secuencias, en cambio, no son equivalentes. En el proceso autorregresivo el valor presente informa sobre el valor siguiente.
La varianza estacionaria del proceso autorregresivo se obtiene dividiendo la varianza de la innovación por uno menos el cuadrado del coeficiente. Con los valores elegidos el resultado es uno, y la autocovarianza de primer rezago coincide con la autocorrelación, 0,8. En el proceso independiente esa autocovarianza es nula. Una simulación que solo muestra histogramas oculta precisamente la propiedad que distingue ambos mecanismos.
El código siguiente genera ambos procesos a partir de una semilla fija. El primer valor del proceso dependiente se extrae de su distribución estacionaria para evitar un transitorio artificial de varianza. Las comprobaciones verifican que las varianzas muestrales están próximas a uno y que las covarianzas a un paso separan con claridad los modelos.
import numpy as np
rng = np.random.default_rng(484)
n = 100000
independent = rng.normal(size=n)
dependent = np.empty(n)
dependent[0] = rng.normal()
for t in range(1,n):
dependent[t] = .8*dependent[t-1] + .6*rng.normal()
def lag_cov(x):
return np.mean((x[1:]-x[1:].mean())*(x[:-1]-x[:-1].mean()))
assert abs(independent.var()-1) < .03
assert abs(dependent.var()-1) < .05
assert abs(lag_cov(independent)) < .03
assert abs(lag_cov(dependent)-.8) < .05
print("Varianzas:", independent.var(), dependent.var())
print("Covarianzas a un paso:", lag_cov(independent), lag_cov(dependent))
En la ejecución de referencia las varianzas resultan 0,997 y 1,024, y las covarianzas a un paso, −0,003 y 0,822. Una autocovarianza cercana a cero no demuestra independencia, porque existen dependencias no lineales que esa medida no registra. La comparación se restringe aquí a dos mecanismos gaussianos completamente especificados; una estadística discrimina respecto de rivales concretos y no adquiere capacidad general de identificación por funcionar en un caso controlado.
484.3 Una curva puede estar impuesta o generada por realimentación
Supóngase una cantidad que parte de diez unidades y en cada paso se reduce al ochenta por ciento de su valor anterior. Un modelo autónomo calcula el estado siguiente a partir del actual. Otro programa consulta un reloj interno y devuelve directamente el valor de la curva exponencial correspondiente al tiempo transcurrido. Mientras nada altere el estado, ambos generan exactamente la misma trayectoria, y esa coincidencia no permite decidir si el sistema responde a su estado presente o sigue una entrada externa programada.
La segunda expresión es la solución analítica del primer modelo, de modo que también puede leerse como otra forma de evaluar la misma ecuación. En ese caso los dos programas no son rivales causales. La comparación tiene sentido cuando la segunda expresión se implementa como un mecanismo distinto, en el que una fuente externa fija el valor en cada instante con independencia de lo que haya ocurrido antes. Para comparar mecanismos hay que declarar cuál de las dos lecturas se adopta. El contraste aparece cuando uno de los programas reacciona a los cambios de estado y el otro restituye la trayectoria marcada por el reloj.
Añádanse cinco unidades en el quinto paso. En el modelo autónomo la diferencia respecto de la trayectoria basal se multiplica por 0,8 en cada paso posterior, de modo que tres pasos después vale 5 · 0,8³ = 2,56. En el modelo de imposición externa el incremento existe solo en el instante perturbado y desaparece en cuanto la fuente fija el valor siguiente. La observación bajo perturbación distingue reglas que el ajuste a la trayectoria basal era incapaz de separar.
t = np.arange(13)
baseline = 10*.8**t
feedback = baseline.copy()
forced = baseline.copy()
feedback[5] += 5
forced[5] += 5
for k in range(6, len(t)):
feedback[k] = .8*feedback[k-1]
assert np.allclose(feedback[5:]-baseline[5:], 5*.8**np.arange(8))
assert np.allclose(forced[6:], baseline[6:])
assert np.isclose(feedback[8]-forced[8], 2.56)
print("Diferencia tres pasos después del pulso:", feedback[8]-forced[8])
El ejemplo no implica que reproducir un fenómeno mediante una función del tiempo carezca de valor científico. Una trayectoria programada puede servir para controlar un instrumento o como referencia descriptiva; lo que no establece es cómo responde el sistema por sí mismo cuando se lo perturba. Para interpolar dentro de condiciones habituales puede bastar, mientras que la elección de una intervención exige evidencia adicional sobre cómo depende el sistema de su estado y de sus entradas.
484.4 Elegir una observación que los modelos no compartan
Una prueba discriminante parte de las diferencias específicas entre las predicciones de los candidatos. En el primer ejemplo esa diferencia está en la dependencia temporal; en el segundo, en la respuesta posterior a un pulso. Acumular más medidas del mismo histograma o repetir la trayectoria sin perturbar mejora la precisión dentro de una equivalencia que persiste, pero no la rompe. El diseño debe localizar el punto donde las explicaciones divergen de forma observable y concentrar allí el esfuerzo de medición.
Que dos modelos se separen en teoría no garantiza que puedan separarse en la práctica. Las respuestas pueden diferir menos que el error de medición o durante un intervalo demasiado breve para el instrumento, y a veces la perturbación no puede aplicarse sin alterar otras propiedades del sistema. Una simulación del procedimiento completo, con generación de estados, medición y análisis, permite anticipar estas dificultades antes de recoger datos. Simular solo el sistema ideal lleva con facilidad a sobrestimar la capacidad del experimento para distinguir mecanismos.
Un diseño puede elegirse para maximizar la distancia esperada entre predicciones, pero también cuentan el costo, la factibilidad y las consecuencias de la observación o de la intervención. En un contexto clínico, que una perturbación sea informativa desde el punto de vista matemático no justifica aplicarla. Puede ser preferible una medición menos discriminante pero aceptable, o reconocer que con los recursos disponibles ciertos mecanismos no pueden separarse, limitación que debe acompañar la interpretación.
Conviene anticipar, además, resultados que contradigan a todos los candidatos. Si el análisis obliga a escoger siempre uno, el elegido puede ganar solo por ser el menos inadecuado. Una prueba de adecuación absoluta sobre los patrones relevantes complementa la comparación relativa: ambos candidatos podrían reproducir la media y fallar en la duración de los eventos extremos, y mantener visible ese fracaso compartido puede orientar la construcción de un tercer mecanismo.
484.5 Recuperar modelos en un mundo donde se conoce la respuesta
La recuperación de modelos consiste en generar datos con cada candidato y comprobar si el procedimiento de análisis identifica su origen. En el ejemplo gaussiano puede usarse una regla sencilla, que elige el modelo dependiente cuando la covarianza muestral a un paso supera 0,4. La regla no pretende ser óptima. Basta para mostrar que la longitud de la serie modifica la capacidad de discriminación y que la evaluación debe repetirse sobre muchas realizaciones, porque una sola simulación favorable da una impresión inestable del desempeño del método.
La matriz que resume este ejercicio registra, para cada generador, cuántas veces se elige cada candidato. Wilson y Collins (2019) la llaman matriz de confusión: sus filas estiman probabilidades de elección condicionadas al origen conocido. Esas filas no deben leerse como probabilidades del origen dado el modelo elegido. Para responder esta segunda pregunta los autores proponen lo que denominan matriz de inversión, que se obtiene de la de confusión mediante la regla de Bayes y que en su ejemplo calculan con una distribución previa uniforme sobre los modelos. Advierten también que la matriz de confusión puede depender mucho de los valores de parámetros usados para simular, de modo que dos modelos pueden ser distinguibles en un régimen e indistinguibles en otro.
def draw_series(rng, n, a):
x = np.empty(n)
x[0] = rng.normal()
for t in range(1,n):
x[t] = a*x[t-1] + np.sqrt(1-a*a)*rng.normal()
return x
rng = np.random.default_rng(2484)
for n in [30, 300]:
counts = np.zeros((2,2), dtype=int)
for source, a in enumerate([0., .8]):
for repetition in range(500):
x = draw_series(rng,n,a)
chosen = int(lag_cov(x) > .4)
counts[source,chosen] += 1
assert np.all(counts.sum(axis=1) == 500)
print("Longitud", n, "matriz de recuperación", counts.tolist())
El resultado se interpreta dentro del experimento simulado. Las medias, la gaussianidad y la familia de dependencia son conocidas, y no se han introducido cambios de régimen ni datos faltantes. Una buena recuperación en estas condiciones no indica que la regla sirva para registros reales con otras propiedades; para acercarse a esa aplicación habría que incorporar las dificultades relevantes y medir cuánto deterioran la discriminación.
484.5.1 Interpretación de la recuperación obtenida
Con la semilla indicada, la regla asigna correctamente al proceso independiente 499 de las 500 series de treinta observaciones, pero reconoce solo 272 de las 500 series dependientes de igual longitud. La asimetría importa, porque un procedimiento puede descartar bien una alternativa y reconocer mal la otra. Con series de trescientas observaciones, los aciertos suben a 500 y 499, respectivamente. Estos recuentos pertenecen a una ejecución concreta y estiman, con error, las probabilidades del procedimiento.
| Observaciones por serie | Origen independiente, elección independiente | Origen independiente, elección dependiente | Origen dependiente, elección independiente | Origen dependiente, elección dependiente |
|---|---|---|---|---|
| 30 | 499 | 1 | 228 | 272 |
| 300 | 500 | 0 | 1 | 499 |
La dificultad con las series cortas tiene una explicación que el propio ejemplo permite examinar. La covarianza muestral varía de una realización a otra y se calcula después de centrar cada segmento con su propia media. En un proceso persistente, una ventana breve recorre solo una parte de la distribución estacionaria, y el centrado con la media local absorbe parte de la variación lenta. El resultado es un estimador sesgado hacia abajo. El bloque siguiente lo cuantifica con cien mil series por condición: con treinta observaciones, la covarianza media bajo el modelo dependiente queda en torno a 0,54 en lugar de 0,8, de modo que una fracción considerable de las series cae por debajo del umbral. El fenómeno no indica ausencia de persistencia, sino una sensibilidad limitada de la regla en esas condiciones. Una alternativa sería estimar directamente un modelo autorregresivo, que a su vez necesitaría su propia evaluación de recuperación.
def lag_cov_rows(x):
a = x[:, 1:] - x[:, 1:].mean(axis=1, keepdims=True)
b = x[:, :-1] - x[:, :-1].mean(axis=1, keepdims=True)
return (a*b).mean(axis=1)
def draw_many(rng, reps, n, a):
x = np.empty((reps, n))
x[:, 0] = rng.normal(size=reps)
for t in range(1, n):
x[:, t] = a*x[:, t-1] + np.sqrt(1-a*a)*rng.normal(size=reps)
return x
rng_long = np.random.default_rng(4840)
reps = 100000
long_run = {}
for n in [30, 300]:
c0 = lag_cov_rows(draw_many(rng_long, reps, n, 0.))
c1 = lag_cov_rows(draw_many(rng_long, reps, n, .8))
long_run[n] = (np.mean(c1 > .4), np.mean(c0 > .4))
print("Longitud", n, "sensibilidad", long_run[n][0],
"falso positivo", long_run[n][1], "covarianza media bajo M1", c1.mean())
assert 0.005 < long_run[30][1] < 0.015
assert 0.52 < long_run[30][0] < 0.62
La inversión de probabilidades puede ilustrarse tomando provisionalmente las frecuencias de la tabla para series cortas. Si solo una de cada cien series proviniera del mecanismo dependiente, la probabilidad de ese origen tras una elección positiva sería de 0,733 aproximadamente; con una proporción previa de una en mil bajaría a 0,214. Las tasas de clasificación son las mismas en ambos casos; lo que cambia es la población a la que se aplica la regla. Esta dependencia de la proporción previa es la razón por la cual una matriz de recuperación no puede interpretarse sin considerar el contexto de selección.
sensitivity, false_positive = 272/500, 1/500
for prior in [.01, .001]:
posterior = sensitivity*prior/(sensitivity*prior+false_positive*(1-prior))
print("Proporción previa", prior, "probabilidad invertida", posterior)
Esos valores dependen de una estimación frágil. El falso positivo descansa en un único caso entre quinientos, y el intervalo de Wilson al 95 % para esa proporción va aproximadamente de 0,0004 a 0,011. La simulación prolongada sitúa la tasa en torno a 0,010, unas cinco veces el valor de la tabla, y la sensibilidad en torno a 0,57. Con esas tasas, las probabilidades invertidas bajan a cerca de 0,36 y 0,053 para las mismas proporciones previas. La identidad de Bayes es exacta, pero los números que se introducen en ella no lo son; cuando el mecanismo buscado es raro, el resultado queda dominado por la tasa de falsos positivos, que es justamente la peor estimada. Tampoco sería legítimo concluir que esa tasa es cero si en quinientas repeticiones no hubiera aparecido ningún caso. Cuando una decisión depende de eventos raros conviene aumentar las repeticiones y declarar la incertidumbre de las tasas que entran en el cálculo.
def wilson_interval(k, n, z=1.96):
p = k/n
center = (p + z*z/(2*n))/(1 + z*z/n)
half = z*np.sqrt(p*(1-p)/n + z*z/(4*n*n))/(1 + z*z/n)
return center-half, center+half
lo, hi = wilson_interval(1, 500)
print("Intervalo de Wilson para 1/500:", lo, hi)
sens_lr, fp_lr = long_run[30]
assert lo < fp_lr < hi
for prior in [.01, .001]:
posterior_lr = sens_lr*prior/(sens_lr*prior+fp_lr*(1-prior))
print("Proporción previa", prior, "probabilidad invertida con tasas prolongadas", posterior_lr)
484.6 Una propiedad demostrada dentro de una red idealizada
El trabajo de Hopfield (1982) ofrece un ejemplo de aporte que va más allá de la semejanza visual. En su modelo, cada unidad toma los valores cero o uno según su entrada ponderada supere o no un umbral, y las unidades revisan su estado de manera asíncrona, en instantes aleatorios con una tasa media de intentos. Para el caso de pesos simétricos, con autoconexiones nulas, Hopfield define una función de energía y muestra que cada cambio individual de estado la hace disminuir, de modo que los cambios continúan hasta alcanzar un mínimo local. La propiedad vincula una regla local con un comportamiento global. Las simulaciones del artículo exploran la memoria asociativa que de ahí resulta; no demuestran que el cerebro implemente literalmente esas reglas ni que la memoria tenga una capacidad ilimitada.
Para ilustrar el argumento se usará una variante bipolar, en la que las unidades valen +1 o −1 y los umbrales son nulos. Se trata de una formulación didáctica del principio de energía, no de una reproducción del código ni de la codificación del artículo original. Los pesos son simétricos, la diagonal es nula y en cada paso se revisa una sola unidad. Si el campo local de esa unidad es cero, el estado se conserva, convención que elimina cambios arbitrarios en situaciones de empate y que el artículo original no necesitó especificar.
Cuando cambia solo la unidad i, la variación de energía es igual al cambio de esa unidad multiplicado por su campo local, con signo negativo. La simetría de los pesos y la diagonal nula son necesarias para llegar a esa expresión. Como la actualización alinea la unidad con su campo, la energía nunca aumenta, y cada cambio efectivo, en el que la unidad pasa de −1 a +1 o al revés, la reduce exactamente en el doble del valor absoluto del campo.
De aquí no se sigue todavía que el proceso termine en un estado fijo de la red. Como la energía baja estrictamente en cada cambio efectivo y el número de estados es finito, ningún estado puede repetirse a lo largo de los cambios efectivos y su número total es finito. Después del último cambio el estado queda inmóvil, pero eso no lo convierte en un punto fijo. Un estado es fijo cuando ninguna unidad modificaría su valor si fuera revisada, y la monotonía de la energía es compatible con un calendario que omita indefinidamente a una unidad cuyo campo se opone a su estado. En ese caso la energía permanece constante, el estado no cambia y la red no está en equilibrio. La convergencia a un estado fijo global requiere, por tanto, un calendario justo, en el que cada unidad sea revisada una y otra vez. Los barridos cíclicos cumplen esa condición de forma determinista: tras el último cambio efectivo, un barrido completo sin cambios certifica que se alcanzó un estado fijo. La selección aleatoria independiente con probabilidades fijas positivas para cada unidad la cumple con probabilidad uno, aunque el número de pasos necesarios sea aleatorio. Las revisiones en instantes aleatorios del modelo de Hopfield pertenecen a este segundo tipo.
Se almacena un patrón de cuatro unidades mediante el producto de sus componentes y se anula la diagonal. El estado inicial contiene un error en la primera unidad. La versión del código que se presenta ahora, a diferencia de un bucle con un número fijo de barridos, se detiene cuando un barrido completo no produce cambios, de modo que la parada misma certifica el estado fijo. La actualización corrige el error y lleva la energía de cero a −1,5. El ejemplo demuestra la recuperación en esta configuración concreta; no estima la capacidad de una red grande ni garantiza recuperar cualquier patrón desde cualquier punto de partida.
pattern = np.array([1,1,-1,-1])
W = np.outer(pattern,pattern)/4
np.fill_diagonal(W,0)
def energy(s):
return -.5*s@W@s
def is_fixed(s):
h = W@s
return bool(np.all((h == 0) | (np.sign(h) == s)))
def run_cyclic(state, max_sweeps=100):
state = state.copy()
energies = [energy(state)]
for sweep in range(max_sweeps):
changed = False
for i in range(len(state)):
field = W[i]@state
if field != 0 and np.sign(field) != state[i]:
state[i] = int(np.sign(field))
changed = True
energies.append(energy(state))
if not changed:
return state, energies, sweep+1
raise RuntimeError("Sin estado fijo tras el máximo de barridos")
start = pattern.copy()
start[0] *= -1
state, energies, sweeps = run_cyclic(start)
assert np.all(np.diff(energies) <= 1e-12)
assert np.array_equal(state,pattern) and is_fixed(state)
assert np.isclose(energies[0],0) and np.isclose(energies[-1],-1.5)
print("Energía inicial y final:", energies[0], energies[-1], "barridos:", sweeps)
El bloque siguiente recorre los dieciséis estados iniciales con barridos cíclicos y registra los estados fijos alcanzados. Construye luego un calendario injusto que revisa solo las unidades segunda a cuarta a partir del estado con el error en la primera: la energía no aumenta en ningún paso y la red nunca llega a un estado fijo. Por último, elige cada vez una unidad al azar y verifica que se alcanza el estado fijo. En este caso la parada usa una comprobación global de estabilidad que pertenece al analista y no al mecanismo simulado.
import itertools
finals = set()
for s0 in itertools.product([-1, 1], repeat=4):
s, e, _ = run_cyclic(np.array(s0))
assert np.all(np.diff(e) <= 1e-12) and is_fixed(s)
finals.add(tuple(int(v) for v in s))
print("Estados fijos alcanzados:", sorted(finals))
unfair = start.copy()
unfair_energies = [energy(unfair)]
for k in range(300):
i = 1 + k % 3
field = W[i]@unfair
if field != 0:
unfair[i] = int(np.sign(field))
unfair_energies.append(energy(unfair))
assert np.all(np.diff(unfair_energies) <= 1e-12)
assert not is_fixed(unfair)
print("Calendario injusto:", unfair.tolist(), "energía", unfair_energies[-1],
"campo de la unidad omitida", W[0]@unfair)
rng_h = np.random.default_rng(4841)
s = start.copy()
steps = 0
while not is_fixed(s):
i = rng_h.integers(4)
field = W[i]@s
if field != 0:
s[i] = int(np.sign(field))
steps += 1
assert np.array_equal(s, pattern)
print("Selección aleatoria: estado fijo tras", steps, "pasos")
La exploración exhaustiva encuentra dos estados fijos, el patrón almacenado y su inverso, ambos con energía −1,5. El segundo no fue almacenado de manera explícita; aparece porque la energía es simétrica ante un cambio de signo de todas las unidades. Es el caso más simple de un atractor no deseado, y muestra que la demostración de monotonía no dice por sí sola qué estados fijos sirven como memorias. Una simulación puede revisar muchos estados iniciales, pero no sustituye la demostración cuando esta existe; la demostración, a su vez, deja abiertas preguntas que solo la exploración responde. El análisis matemático establece una propiedad general bajo supuestos explícitos, y la exploración computacional examina consecuencias particulares, distribuciones de resultados o regiones difíciles de caracterizar analíticamente.
484.7 Un detalle de implementación puede cambiar la teoría
Si todas las unidades se actualizan a la vez sobre el mismo estado anterior, el argumento de la sección previa deja de aplicarse. Con dos unidades unidas por pesos positivos y estados opuestos, una actualización simultánea intercambia los signos y la siguiente los vuelve a intercambiar. Se obtiene un ciclo de período dos aunque los pesos sean simétricos. No hay contradicción con la demostración asíncrona, porque se ha cambiado una de sus premisas. Bruck (1990) enuncia los resultados de convergencia para ambos modos de operación: con pesos simétricos y diagonal no negativa, la operación en serie converge a un estado estable, mientras que la operación completamente paralela converge a un estado estable o a un ciclo de longitud no mayor que dos.
W2 = np.array([[0.,1.],[1.,0.]])
s0 = np.array([1,-1])
s1 = np.sign(W2@s0).astype(int)
s2 = np.sign(W2@s1).astype(int)
assert not np.array_equal(s1,s0)
assert np.array_equal(s2,s0)
print("Ciclo síncrono:", s0.tolist(), s1.tolist(), s2.tolist())
El ejemplo ilustra una fuente frecuente de discrepancia entre la descripción de un modelo y su programa. Un texto puede afirmar que los agentes reaccionan uno tras otro mientras el código calcula todas las respuestas a partir del mismo estado anterior, y ambas implementaciones definen sistemas dinámicos distintos. Cuando el esquema de actualización importa, quien intente reproducir el trabajo necesita saber qué información ve cada unidad al decidir y en qué momento se modifica el estado compartido.
Grimm y colaboradores (2020) presentan la segunda actualización del protocolo ODD, un formato estandarizado para describir modelos de simulación. Sus elementos incluyen el propósito del modelo y los patrones con que se evaluará, las entidades con sus variables de estado y escalas, los procesos y su calendario de ejecución, la inicialización, los datos de entrada y los submodelos. Esta versión incorpora los patrones al primer elemento, como criterios para decidir si el modelo es lo bastante realista para su propósito, y abre espacio para justificar las decisiones de diseño. Completar la descripción no demuestra que el mecanismo sea verdadero, pero permite saber qué se implementó y qué pruebas serían pertinentes.
La documentación debe corresponder al programa que efectivamente se ejecutó. Si una regla cambia durante el desarrollo y la descripción anterior se conserva, los resultados pueden explicarse de manera equivocada aunque el código funcione sin errores. Ayuda vincular cada componente conceptual con la función o el fragmento que lo implementa y registrar las condiciones de cada experimento. Así puede separarse una discrepancia teórica de un error de implementación, y se evita atribuir una propiedad a reglas que el programa ya no utiliza.
484.8 Lo que se impone y lo que aparece como consecuencia
Un patrón introducido directamente como condición de entrada no constituye una predicción independiente. Si el programa recibe la secuencia empírica de actividad y luego se evalúa su capacidad para reproducir esa misma secuencia, el resultado puede ser correcto desde el punto de vista computacional y, a la vez, circular como evidencia. La simulación podría aportar información sobre otra variable derivada de esa entrada, siempre que se declare así. Usar datos empíricos como entradas es legítimo; lo que no lo es es presentar como emergente aquello que fue suministrado.
La distinción se vuelve menos visible cuando se ajustan muchos parámetros. El patrón no está impuesto línea por línea, pero quizá fue el objetivo explícito de la calibración, y reproducirlo solo confirma que el ajuste cumplió su propósito. Para evaluar si el modelo aporta conocimiento adicional hay que examinar propiedades que no intervinieron en la calibración o condiciones nuevas, teniendo presente que dos estadísticas pueden contener casi la misma información y que no todo acierto cuenta como una prueba separada.
La eliminación de componentes ayuda a localizar qué reglas son necesarias dentro del modelo. Si al retirar una interacción desaparece el patrón, esa interacción es necesaria para producirlo, manteniendo fijas las demás condiciones del programa. Con ello no se demuestra que sea necesaria en el sistema real ni que ningún mecanismo alternativo pueda reemplazarla. Estas pruebas son contrastes internos útiles; su interpretación causal fuera del modelo exige conectar las modificaciones del programa con operaciones realizables y con observaciones pertinentes del sistema estudiado.
También es informativo que un patrón persista al modificar detalles que se suponían esenciales. La persistencia puede indicar que la explicación es robusta o revelar que el resultado depende de una condición más simple que la propuesta inicial; un comportamiento colectivo atribuido a reglas de interacción elaboradas podría sobrevivir al reemplazarlas por respuestas aleatorias sometidas a una entrada común.
484.9 Sensibilidad, incertidumbre y error numérico
El análisis de sensibilidad pregunta cuánto cambian los resultados al modificar supuestos o parámetros, y debe distinguirse de la incertidumbre sobre los valores de esos parámetros. Un parámetro puede tener un gran efecto y estar medido con precisión; otro puede ser muy incierto y tener poco efecto dentro del rango plausible. Una exploración arbitraria alrededor del valor ajustado puede ocultar regiones donde el comportamiento cambia de forma cualitativa, o exagerar variaciones que corresponden a valores implausibles.
En las simulaciones estocásticas se añade la variabilidad entre realizaciones con los mismos parámetros. Una trayectoria llamativa no representa necesariamente el comportamiento típico, y cuando una conclusión depende de distribuciones o frecuencias hay que examinarlas junto con su error de Monte Carlo. Una semilla fija permite reproducir una realización, pero no muestra que el resultado sea estable frente al azar. En la sección 484.5.1, la ejecución con quinientas repeticiones era perfectamente reproducible y su estimación del falso positivo se alejaba de la tasa del generador por un factor cercano a cinco.
El error numérico es una fuente distinta de las anteriores. Una ecuación diferencial puede tener una solución que decae sin oscilar mientras una discretización demasiado gruesa produce oscilaciones artificiales. Reducir el paso y comparar resultados ayuda a detectar el problema. Cuando existe una solución analítica, como en la relajación exponencial, esta proporciona una referencia independiente. Si un comportamiento desaparece al refinar la integración, debe tratarse como posible artefacto antes de interpretarlo como una propiedad del mecanismo.
Las comprobaciones deben guardar relación con la afirmación principal. Si se sostiene que una red converge siempre bajo ciertas reglas, hay que verificar los supuestos de la demostración, incluido el calendario de actualización, y buscar casos límite en la implementación. Si se sostiene que una política reduce en promedio un resultado, hay que evaluar la variación entre realizaciones y la sensibilidad a los parámetros relevantes.
484.9.1 Una oscilación creada por el integrador
La relajación continua de una cantidad positiva puede describirse con una ecuación cuya tasa de cambio es proporcional al negativo de su valor. La solución exacta permanece positiva y se acerca a cero sin oscilar. El método de Euler explícito reemplaza esa evolución por multiplicaciones sucesivas por el factor uno menos el paso temporal. Si ese factor es negativo, la aproximación alterna de signo, cosa que la solución original nunca hace, y la oscilación es entonces un producto del método numérico.
Con un paso de media unidad el factor vale 0,5 y la aproximación decrece sin cambiar de signo. Con un paso de una unidad y media el factor vale −0,5, de modo que la aproximación alterna de signo mientras su magnitud disminuye. Con un paso de 2,1 el factor vale −1,1 y la magnitud crece. Para esta ecuación, el método conserva la positividad solo si el paso es menor que uno y conserva el decaimiento de la magnitud solo si es menor que dos. Los tres programas ejecutan correctamente la misma fórmula, pero solo algunos pasos respetan las propiedades cualitativas de la solución, de modo que un programa que corre sin errores puede ser numéricamente inadecuado para la pregunta planteada.
for h in [.5, 1.5, 2.1]:
numerical = (1-h)**np.arange(11)
exact = np.exp(-h*np.arange(11))
assert np.all(exact > 0)
print("Paso", h, "valor numérico final", numerical[-1],
"valor exacto final", exact[-1])
assert abs((1-2.1)**10) > 1
Este control resulta especialmente útil cuando lo que llama la atención es una transición inesperada. Antes de explicarla mediante realimentaciones del modelo, conviene comprobar si persiste al refinar la integración y al usar un método adecuado, y revisar las tolerancias del integrador y la conservación de las cantidades que el modelo debería preservar. No todas las discrepancias numéricas desaparecen al reducir el paso, ya que los sistemas rígidos o con discontinuidades pueden requerir métodos específicos.
484.10 Validar fuera del ajuste sin perder el vínculo con la pregunta
Reservar datos permite evaluar si el modelo conserva capacidad predictiva más allá de las observaciones con que se ajustó, siempre que la partición respete la estructura del problema. En series temporales, mezclar al azar puntos vecinos puede introducir en el ajuste información muy próxima al período de prueba. Si el uso previsto es anticipar el futuro, la evaluación debe ajustar con el pasado y evaluar con lo que viene después.
Un cambio de condiciones ofrece una prueba más exigente que una nueva muestra del mismo régimen. El fracaso ante un cambio no previsto no demuestra que el modelo carezca de utilidad; puede limitarse a delimitar su dominio. Un modelo descriptivo puede ser adecuado dentro de un régimen y necesitar otra estructura bajo intervención, y esa frontera debe quedar a la vista antes de usarlo para decidir.
Cuando un modelo supera a otros en predicción, queda por examinar qué parte de su estructura está identificada. Parámetros muy distintos pueden producir resultados casi iguales, y atribuir a cada parámetro un significado psicológico o biológico requiere pruebas de recuperación y evidencia externa. Wilson y Collins (2019) recomiendan validar al menos el modelo ganador simulándolo con los parámetros ajustados y analizando esos datos simulados del mismo modo que los empíricos, para comprobar que reproduce los efectos conductuales importantes. Una buena puntuación de ajuste no garantiza que el modelo, cuando genera sus propias trayectorias, produzca el comportamiento que se pretende explicar.
Una comparación honesta conserva los resultados negativos. Si el informe incluye solo los patrones que la simulación reproduce, el lector no puede juzgar su alcance. Grimm y colaboradores (2020) advierten, a propósito del modelado orientado por patrones, que también deben comunicarse los patrones observados que se consideraban esenciales antes de construir el modelo y que este no logró reproducir; omitirlos, señalan, se parecería a formular hipótesis después de conocer los resultados. Esa información permite decidir si la discrepancia afecta a una característica secundaria o invalida el uso propuesto, y orienta modificaciones dirigidas a un fallo específico.
La evaluación debe distinguir también la incertidumbre dentro de un modelo de la incertidumbre sobre su estructura. Repetir millones de veces un único mecanismo permite estimar con precisión sus probabilidades condicionales y deja intacta la duda sobre si ese mecanismo corresponde al sistema. Un intervalo muy estrecho alrededor de una predicción simulada puede reflejar únicamente error de Monte Carlo. Si no incorpora la incertidumbre de parámetros, de medición y de estructura, no debe presentarse como incertidumbre total sobre el fenómeno real, y cada uno de esos componentes requiere una justificación propia.
Una estrategia útil consiste en comparar varias familias plausibles bajo el mismo procedimiento de observación y presentar las predicciones en que coinciden y aquellas en que se separan. El acuerdo entre familias puede respaldar una decisión aunque los mecanismos sigan indeterminados, y el desacuerdo indica dónde la elección depende de supuestos difíciles de comprobar. Promediar modelos no resuelve por sí solo esta dificultad, porque los pesos asignados requieren interpretación y pueden variar con el contexto.
El registro debe permitir reconstruir también los intentos descartados cuando formaron parte de la selección. Explorar muchas variantes y quedarse con la más favorable produce optimismo incluso sin errores deliberados. Una evaluación posterior, definida antes de conocer sus resultados, ayuda a estimar cuánto se generaliza el patrón. La simulación conserva su valor exploratorio durante el desarrollo; lo que disminuye es la fuerza de la evidencia atribuible a un acierto elegido después de muchas pruebas.
484.11 Qué conocimiento queda después de la comparación
Una simulación puede establecer una posibilidad, mostrando que determinadas reglas bastan para producir un patrón. Puede establecer una relación interna, según la cual el patrón varía de forma calculable al cambiar una condición. Dentro de un dominio validado puede aportar además una herramienta predictiva. Para sostener una explicación del sistema observado debe mostrar, en cambio, por qué sus mecanismos son preferibles a alternativas relevantes y cómo se conectan con mediciones o intervenciones posibles. La fuerza de la conclusión depende del conjunto de pruebas efectivamente realizadas.
En los ejemplos de este capítulo, la dependencia temporal separó la independencia de la persistencia cuando el histograma no podía hacerlo, y una perturbación distinguió la realimentación de la imposición externa. En la red recurrente, la monotonía de la energía se demostró bajo actualización asíncrona, la llegada a un estado fijo exigió además un calendario justo y la garantía de punto fijo se perdió con la actualización simultánea. La matriz de recuperación mostró que una tasa estimada con pocas repeticiones puede desplazar mucho una probabilidad invertida. Cada resultado vincula supuestos concretos con observaciones concretas, sin apoyarse en la semejanza global del producto final.
Una evaluación puede concluir que la simulación es útil aunque el mecanismo siga abierto. Puede servir para calcular qué mediciones harían falta, para explorar las consecuencias de una hipótesis o para mostrar que cierta evidencia resulta insuficiente. También puede justificar el abandono de una estructura cuando fallan sus predicciones distintivas. El significado científico de una imagen convincente o de un ajuste excelente depende de las alternativas consideradas y de las comprobaciones que los acompañan.
Referencias
Bruck, J. (1990). On the convergence properties of the Hopfield model. Proceedings of the IEEE, 78(10), 1579–1585. https://doi.org/10.1109/5.58341
Grimm, V., Railsback, S. F., Vincenot, C. E., Berger, U., Gallagher, C., DeAngelis, D. L., Edmonds, B., Ge, J., Giske, J., Groeneveld, J., Johnston, A. S. A., Milles, A., Nabe-Nielsen, J., Polhill, J. G., Radchuk, V., Rohwäder, M.-S., Stillman, R. A., Thiele, J. C., y Ayllón, D. (2020). The ODD protocol for describing agent-based and other simulation models: A second update to improve clarity, replication, and structural realism. Journal of Artificial Societies and Social Simulation, 23(2), 7. https://doi.org/10.18564/jasss.4259
Hopfield, J. J. (1982). Neural networks and physical systems with emergent collective computational abilities. Proceedings of the National Academy of Sciences of the United States of America, 79(8), 2554–2558. https://doi.org/10.1073/pnas.79.8.2554
Wilson, R. C., y Collins, A. G. E. (2019). Ten simple rules for the computational modeling of behavioral data. eLife, 8, e49547. https://doi.org/10.7554/eLife.49547