Ctrl K
ComunidadGuías…

flood_warning/model.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Two-branch 1D CNN for hourly streamflow forecasting.

Architecture:
  past branch:   3 channels × 24 hours -> 1D convs -> features
  future branch: 1 channel × 12 hours -> 1D convs -> features
  concat -> FC -> 12-step flow forecast (normalized space)

Small enough to train per-gauge in minutes on CPU. Large enough to capture
storm response patterns when given hourly precip.
"""
from __future__ import annotations

import torch
import torch.nn as nn
import torch.nn.functional as F


class FloodCNN(nn.Module):
    def __init__(self, n_past_features: int = 4, n_future_features: int = 1,
                 past_steps: int = 24, future_steps: int = 12,
                 hidden: int = 32, kernel: int = 3, dropout: float = 0.1):
        super().__init__()
        pad = kernel // 2

        # Past stream: (B, 3, 24) -> (B, hidden, 24) -> pool -> (B, hidden, 12)
        self.past_cnn = nn.Sequential(
            nn.Conv1d(n_past_features, hidden, kernel_size=kernel, padding=pad),
            nn.ReLU(),
            nn.Conv1d(hidden, hidden, kernel_size=kernel, padding=pad),
            nn.ReLU(),
            nn.Conv1d(hidden, hidden, kernel_size=kernel, padding=pad),
            nn.ReLU(),
        )

        # Future precip stream: (B, 1, 12) -> (B, hidden, 12)
        self.future_cnn = nn.Sequential(
            nn.Conv1d(n_future_features, hidden, kernel_size=kernel, padding=pad),
            nn.ReLU(),
            nn.Conv1d(hidden, hidden, kernel_size=kernel, padding=pad),
            nn.ReLU(),
        )

        # Heads
        feat_size = hidden * past_steps + hidden * future_steps
        self.head = nn.Sequential(
            nn.Linear(feat_size, hidden * 4),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden * 4, future_steps),
        )

    def forward(self, past: torch.Tensor, future: torch.Tensor) -> torch.Tensor:
        h_past = self.past_cnn(past).flatten(1)
        h_fut = self.future_cnn(future).flatten(1)
        z = torch.cat([h_past, h_fut], dim=1)
        return self.head(z)


def nse_loss(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
    """Negative Nash-Sutcliffe efficiency, averaged across the batch.
    Operates in the model's normalized space — works fine as a minimization
    target since translation/scale of the standardized series is consistent.
    """
    mean_t = target.mean(dim=1, keepdim=True)
    num = ((pred - target) ** 2).sum(dim=1)
    den = ((target - mean_t) ** 2).sum(dim=1) + 1e-6
    return (num / den).mean()