Depășirea barierei Python: construirea unui motor DeepAR pur R-Native cu LibTorch

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(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:
ν=4.0+26.0sigmoid(z)\nu = 4,0 + 26,0 \cdot \text{sigmoid}(z)

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:

factor_de_scalare=ν2ν\text{scale\_factor} = \sqrt{\frac{\nu – 2}{\nu}}

yt=μ+σfactor_de_scalaretνy_{t} = \mu + \sigma \cdot \text{scale\_factor} \cdot t_{\nu}

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 şi conf_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.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.