
Docente: Ing. Angela Santos
Fecha: 15 de Agosto de 2025
En este documento se presenta una simulación del modelo
Lotka–Volterra (depredador-presa) con ruido, utilizando
una red neuronal artificial entrenada con el paquete
nnet en R para predecir la evolución poblacional de un paso
hacia adelante.
library(dplyr)
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(ggplot2)
library(tidyr)
library(nnet)
set.seed(42)
simulate_lv <- function(T = 300,
r = 0.5, a = 0.02, b = 0.01, m = 0.3,
N0 = 40, P0 = 9, noiseN = 1.0, noiseP = 0.8) {
N <- numeric(T); P <- numeric(T)
N[1] <- N0; P[1] <- P0
for (t in 2:T) {
dN <- r * N[t-1] - a * N[t-1] * P[t-1]
dP <- b * N[t-1] * P[t-1] - m * P[t-1]
N[t] <- pmax(N[t-1] + dN + rnorm(1, 0, noiseN), 0)
P[t] <- pmax(P[t-1] + dP + rnorm(1, 0, noiseP), 0)
}
tibble(t = 1:T, N = N, P = P)
}
df_ts <- simulate_lv()
df <- df_ts |>
transmute(
N_t = N,
P_t = P,
N_tp1 = lead(N),
P_tp1 = lead(P)
) |>
drop_na()
set.seed(123)
n <- nrow(df)
idx <- sample.int(n, size = floor(0.7*n))
train <- df[idx, ]
test <- df[-idx, ]
minmax_fit <- function(x) list(min = min(x), max = max(x))
minmax_tr <- function(x, s) (x - s$min) / (s$max - s$min + 1e-8)
minmax_inv <- function(z, s) z * (s$max - s$min + 1e-8) + s$min
cols <- c("N_t","P_t","N_tp1","P_tp1")
sc <- lapply(train[cols], minmax_fit)
train_s <- train; test_s <- test
for (nm in cols) {
train_s[[nm]] <- minmax_tr(train[[nm]], sc[[nm]])
test_s[[nm]] <- minmax_tr(test[[nm]], sc[[nm]])
}
x_train <- as.matrix(train_s[, c("N_t","P_t")])
y_train <- as.matrix(train_s[, c("N_tp1","P_tp1")])
x_test <- as.matrix(test_s[, c("N_t","P_t")])
set.seed(999)
model <- nnet(
x = x_train,
y = y_train,
size = 8, # 8 neuronas ocultas
linout = TRUE, # salida lineal (regresión)
rang = 0.1,
decay = 1e-4,
maxit = 500,
trace = FALSE
)
pred_s <- predict(model, x_test)
pred <- tibble(
N_hat = minmax_inv(pred_s[,1], sc$N_tp1),
P_hat = minmax_inv(pred_s[,2], sc$P_tp1)
)
res <- bind_cols(test, pred)
mae <- function(y, yhat) mean(abs(y - yhat))
rmse <- function(y, yhat) sqrt(mean((y - yhat)^2))
metrics <- tibble(
Variable = c("Presas (N)","Depredadores (P)"),
MAE = c(mae(res$N_tp1, res$N_hat), mae(res$P_tp1, res$P_hat)),
RMSE = c(rmse(res$N_tp1, res$N_hat), rmse(res$P_tp1, res$P_hat))
)
metrics
## # A tibble: 2 × 3
## Variable MAE RMSE
## <chr> <dbl> <dbl>
## 1 Presas (N) 11.5 19.3
## 2 Depredadores (P) 1.89 3.15
plot_df <- res |>
mutate(idx = row_number()) |>
select(idx, N_real = N_tp1, N_pred = N_hat,
P_real = P_tp1, P_pred = P_hat) |>
pivot_longer(-idx, names_to = "serie", values_to = "valor")
ggplot(plot_df, aes(x = idx, y = valor, color = serie)) +
geom_line(linewidth = 0.9) +
scale_color_manual(
values = c("N_real" = "#0000FF","N_pred" = "#87CEFA",
"P_real" = "#FFC0CB","P_pred" = "#FF0000"),
labels = c("N_real"="Presas (real)","N_pred"="Presas (pred.)",
"P_real"="Depredadores (real)","P_pred"="Depredadores (pred.)")
) +
labs(
title = "Predicción de un paso con red neuronal (nnet)",
subtitle = "Sólido = real | Semitransparente = predicción",
x = "Índice en conjunto de prueba", y = "Población"
) +
theme_minimal(base_size = 12) +
theme(legend.title = element_blank())