(Acest articol a fost publicat pentru prima dată pe DataGeeekși cu amabilitate a contribuit la R-bloggeri). (Puteți raporta problema legată de conținutul acestei pagini aici)
Doriți să vă distribuiți conținutul pe R-bloggeri? dați clic aici dacă aveți un blog, sau aici dacă nu aveți.
Învățarea profundă pentru prognoza seriilor temporale în R s-a confruntat istoric cu un obstacol arhitectural major: Python deasupra capului. Cadre ca modeltime.gluonts oferă pachete de interfață în jurul AWS GluonTS, dar se bazează pe un lanț de execuție complex care trece prin reticulatemedii virtuale, serializare Python și backend-uri MXNet/PyTorch.
Pentru a depăși blocajele de performanță și frecarea dependențelor legate de legătura între limbi, am conceput un motor de prognoză DeepAR pur R-native. Alimentat de C++ LibTorch backend prin R torch pachet, această arhitectură oferă o execuție ușoară, în memorie, fără nici un Python sau reticulate dependențe.
Comparație arhitecturală: Modeltime/GluonTS vs Native R Torch
Diferența arhitecturală dintre wrapper-urile tradiționale și legarea noastră nativă C++ LibTorch constă în distribuția datelor și adâncimea de execuție:


Aprofundare în arhitectura codului
Implementarea noastră R oglindește profunzimea probabilistică a DeepAR, menținând în același timp stabilitatea computațională și interactivitatea vizuală curată.
1. Delimitat Student-t Șef de distribuție
Serii temporale financiare, cum ar fi SOXX ETFprezintă distribuții de rentabilitate grele („cozi grase”) și șocuri bruște de volatilitate. Modelele gaussiene subestimează adesea riscurile extreme sau produc benzi de predicție prea reactive.
Implementăm a Arhitectura cu 3 capete dezactivați starea ascunsă LSTM:
- Parametru de locație (μ): Strat de ieșire liniar neconstrâns.
- Parametrul de scară (σ): Strat de activare Softplus cu compensare a stabilității numerice.
- Parametrul grade de libertate (ν): Delimitat dinamic între 4,0 și 30,0 folosind un sigmoid scalat:
Limitarea ν ≥ 4.0 garantează varianța finită din punct de vedere matematic, prevenind explozia de varianță Monte Carlo pe orizonturi autoregresive cu mai multe etape.
2. Eșantionare Monte Carlo stocastică controlată de varianță
Pe parcursul orizontului de prognoză de 10 zile, generăm 100 de căi de simulare autoregresive. Pentru a alinia varianța Monte Carlo cu σ prezis, scalam eșantioanele Student-t prin factorul de varianță teoretic:
Acest lucru asigură că limitele traiectoriei rămân stabile pe parcursul lansărilor autoregresive în mai multe etape.
3. Vizualizare interactivă granulară a intrigilor
Stratul de front-end folosește ggplot2, ggtextși plotly pentru a oferi interactivitate UI/UX curată:
- Titluri HTML încorporate: Elimină casetele de legendă redundante prin codificarea culorilor numelor de serie direct în interiorul titlului Markdown folosind
ggtext::element_markdown. - Ancore invizibile de delimitare: Puncte de hover invizibile (
alpha = 0) sunt plasate de-a lungul limitelor de încredere de 95% (conf_hişiconf_lo). Utilizatorii pot inspecta dinamic prețurile exacte la limita superioară/inferioară, fără a aglomera parcela cu linii suplimentare.
Scriptul R complet
# ==============================================================================
# TITLE: Pure R-Native Torch DeepAR - Bounded Student-t Distribution Engine
# PATH: tool_nodes/forecasting/engine/evaluate_torch_deepar_student_t_bounded.R
# DEPLOYMENT TARGET: Native R Pipeline (Zero Python / Zero Reticulate Dependency)
# All code descriptions and labels are systematically maintained in English.
# ==============================================================================
if (!require("pacman")) install.packages("pacman")
pacman::p_load(tidyquant, tidyverse, timetk, torch, plotly, yardstick)
# 1. Fetch & Prepare Data from Yahoo Finance
df_dl <- tq_get("SOXX") %>%
select(date, close) %>%
filter(date >= last(date) - months(12)) %>%
drop_na()
# Data Normalization Parameters
mean_close <- mean(df_dl$close)
sd_close <- sd(df_dl$close)
df_dl <- df_dl %>% mutate(close_scaled = (close - mean_close) / sd_close)
# Configuration Parameters
lookback_length <- 20
prediction_length <- 10
num_paths <- 100
train_data <- head(df_dl, nrow(df_dl) - prediction_length)
test_data <- tail(df_dl, prediction_length)
# 2. Sequence Generator
create_sequences <- function(data_vector, lookback) {
num_samples <- length(data_vector) - lookback
x_mat <- matrix(0, nrow = num_samples, ncol = lookback)
y_mat <- matrix(0, nrow = num_samples, ncol = 1)
for (i in 1:num_samples) {
x_mat(i, ) <- data_vector(i:(i + lookback - 1))
y_mat(i, 1) <- data_vector(i + lookback)
}
list(
x = torch_tensor(x_mat, dtype = torch_float())$unsqueeze(3),
y = torch_tensor(y_mat, dtype = torch_float())
)
}
seqs <- create_sequences(train_data$close_scaled, lookback_length)
# 3. Native Torch DeepAR Architecture with Bounded Student-t Head
deepar_student_net <- nn_module(
"DeepARStudentNetBounded",
initialize = function(input_size = 1, hidden_size = 32, num_layers = 2) {
self$lstm <- nn_lstm(input_size = input_size, hidden_size = hidden_size,
num_layers = num_layers, batch_first = TRUE)
self$fc_mu <- nn_linear(hidden_size, 1)
self$fc_sigma <- nn_linear(hidden_size, 1)
self$fc_v <- nn_linear(hidden_size, 1)
},
forward = function(x) {
out <- self$lstm(x)
last_hidden <- out((1))(, dim(out((1)))(2), )
mu <- self$fc_mu(last_hidden)
sigma <- nnf_softplus(self$fc_sigma(last_hidden)) + 1e-4
# Bound degrees of freedom v between 4.0 and 30.0 to prevent explosive tails
v <- 4.0 + 26.0 * torch_sigmoid(self$fc_v(last_hidden))
list(mu = mu, sigma = sigma, v = v)
}
)
model <- deepar_student_net()
optimizer <- optim_adam(model$parameters, lr = 0.003)
# Stable Student-t Negative Log-Likelihood Loss
student_t_nll_loss <- function(mu, sigma, v, y) {
term1 <- torch_lgamma((v + 1) / 2)
term2 <- torch_lgamma(v / 2)
term3 <- 0.5 * torch_log(v * pi)
term4 <- torch_log(sigma)
residual <- (y - mu) / sigma
term5 <- ((v + 1) / 2) * torch_log(1 + (residual$pow(2) / v))
- (term1 - term2 - term3 - term4 - term5)
}
# 4. Training Loop
model$train()
for (epoch in 1:40) {
optimizer$zero_grad()
preds <- model(seqs$x)
loss <- student_t_nll_loss(preds$mu, preds$sigma, preds$v, seqs$y)$mean()
loss$backward()
# Gradient clipping for numerical stability
nn_utils_clip_grad_norm_(model$parameters, max_norm = 1.0)
optimizer$step()
}
# 5. Stochastic Monte Carlo Trajectory Sampling (Variance Variance-Controlled)
model$eval()
price_paths <- matrix(0, nrow = num_paths, ncol = prediction_length)
initial_input_seq <- tail(train_data$close_scaled, lookback_length)
with_no_grad({
for (s in 1:num_paths) {
curr_seq <- initial_input_seq
for (t in 1:prediction_length) {
curr_tensor <- torch_tensor(matrix(curr_seq, nrow = 1), dtype = torch_float())$unsqueeze(3)
pred <- model(curr_tensor)
mu <- as.numeric(pred$mu)
sigma <- as.numeric(pred$sigma)
v_val <- as.numeric(pred$v)
# Scaled Student-t sampling to strictly align variance with sigma
scale_factor <- sqrt((v_val - 2) / v_val)
sampled_scaled <- mu + sigma * scale_factor * rt(1, df = v_val)
price_paths(s, t) <- sampled_scaled * sd_close + mean_close
# Autoregressive slide
curr_seq <- c(curr_seq(-1), sampled_scaled)
}
}
})
# 6. Extract Quantiles & Prepare Tidy Evaluation Data Frame
predicted_prices <- colMeans(price_paths)
lower_bound <- apply(price_paths, 2, quantile, probs = 0.025)
upper_bound <- apply(price_paths, 2, quantile, probs = 0.975)
df_eval <- tibble(
date = test_data$date,
actual = test_data$close,
pred = predicted_prices,
conf_lo = lower_bound,
conf_hi = upper_bound
)
# 7. Tidymodels / Yardstick Metric Engine
eval_metrics <- metric_set(mape, rmse, rsq)
metrics_summary <- df_eval %>%
eval_metrics(truth = actual, estimate = pred) %>%
select(.metric, .estimate) %>%
rename(Metric = .metric, Value = .estimate)
print(metrics_summary)
mape_val <- metrics_summary %>%
filter(Metric == "mape") %>%
pull(Value)
# 8. Modern Interactive Plotly Visualization (Clean Lines & Clear Ribbon)
if (!require("pacman")) install.packages("pacman")
pacman::p_load(tidyquant, tidyverse, plotly, scales, glue, ggtext)
# 1. Prepare Dedicated Hover Text Layers
df_plot_actual <- df_eval %>%
select(date, actual) %>%
mutate(text_actual = glue::glue("Actual Price: ${round(actual, 2)}\nDate: {format(date, '%b %d, %Y')}"))
df_plot_pred <- df_eval %>%
select(date, pred) %>%
mutate(text_pred = glue::glue("DeepAR Pred: ${round(pred, 2)}\nDate: {format(date, '%b %d, %Y')}"))
df_plot_hi <- df_eval %>%
select(date, conf_hi) %>%
mutate(text_hi = glue::glue("95% Upper Bound: ${round(conf_hi, 2)}\nDate: {format(date, '%b %d, %Y')}"))
df_plot_lo <- df_eval %>%
select(date, conf_lo) %>%
mutate(text_lo = glue::glue("95% Lower Bound: ${round(conf_lo, 2)}\nDate: {format(date, '%b %d, %Y')}"))
# 2. Build GGPlot Spec with Invisible Boundary Anchors
p <- ggplot() +
# Clean Background Ribbon
geom_ribbon(
data = df_eval,
aes(x = date, ymin = conf_lo, ymax = conf_hi),
fill = "#808080",
alpha = 0.20
) +
# Invisible Upper Bound Hover Points (No Lines, Pure Hover)
geom_point(
data = df_plot_hi,
aes(x = date, y = conf_hi, text = text_hi),
color = "transparent",
alpha = 0,
size = 3
) +
# Invisible Lower Bound Hover Points (No Lines, Pure Hover)
geom_point(
data = df_plot_lo,
aes(x = date, y = conf_lo, text = text_lo),
color = "transparent",
alpha = 0,
size = 3
) +
# Actual Price: Solid Dark Line & Hover Points
geom_line(
data = df_plot_actual,
aes(x = date, y = actual),
color = "#2c3e50",
linewidth = 1.2
) +
geom_point(
data = df_plot_actual,
aes(x = date, y = actual, text = text_actual),
color = "#2c3e50",
size = 2
) +
# DeepAR Forecast: Dashed Red Line & Clean Hover Points
geom_line(
data = df_plot_pred,
aes(x = date, y = pred),
color = "#e74c3c",
linetype = "dashed",
linewidth = 1.2
) +
geom_point(
data = df_plot_pred,
aes(x = date, y = pred, text = text_pred),
color = "#e74c3c",
size = 2
) +
# Formatting & Theme
scale_y_continuous(labels = dollar_format(accuracy = 1)) +
labs(
x = "",
y = "",
title = paste0(
"SOXX ETF Actual Prices vs ",
"Torch DeepAR Forecast
",
"10-Day Horizon | MAPE: ", round(mape_val, 2), "%"
)
) +
theme_minimal() +
theme(
plot.title = element_markdown(
hjust = 0.5,
face = "bold"
),
plot.background = element_rect(fill = "#ffffff", color = NA),
panel.background = element_rect(fill = "#ffffff", color = NA),
panel.grid.minor = element_blank()
)
# 3. Render Interactive Plotly Spec
font_family <- list(family = "Roboto Slab, Sans-Serif", size = 16)
label_font <- list(font = list(family = "Roboto Slab, Sans-Serif", size = 13))
ggplotly(p, tooltip = "text") %>%
style(hoverlabel = label_font) %>%
layout(font = font_family) %>%
config(displayModeBar = FALSE)


Concluzie
Prin implementarea DeepAR direct în R prin torch (LibTorch), obținem o arhitectură Python cu latență scăzută, zero care se încadrează în mod firesc în existentă tidymodels fluxurilor de lucru. Conducta rezultată oferă predicții probabilistice de înaltă precizie (realizarea unui MAPE de ~2,17% pe un orizont de prognoză SOXX de 10 zile) cu performanțe rapide, în memorie, potrivite pentru implementarea în producție.
