Source code for phenonn.models.linear_baseline

# Copyright 2026 IPSL / CNRS / Sorbonne University
# Authors:
#
# This work is licensed under the Creative Commons
# Attribution-NonCommercial-ShareAlike 4.0 International License.
# To view a copy of this license, visit
# http://creativecommons.org/licenses/by-nc-sa/4.0/

"""
Linear regression baselines for LAI prediction.

This module provides simple linear models that serve as reference baselines
for evaluating more complex neural network architectures. Both models map
meteorological feature sequences to a single LAI prediction while maintaining
the same input/output interface as the project's single-day prediction models.

Implemented Models
------------------
LinearBaseline
    A full-window linear regression model that flattens the entire input
    sequence and applies a single fully connected layer. Each feature at
    each timestep receives an independent weight, allowing the model to
    learn temporally specific linear relationships.

    Mathematically:

        y = w^T x + b

    where ``x`` is the flattened ``(C × L)`` feature window.

    This model is equivalent to ordinary least squares regression over all
    feature–time combinations and provides a strong linear benchmark.

PerDayLinearBaseline
    A per-timestep linear model that ignores temporal history. The model
    applies a shared linear mapping from features to LAI and returns the
    prediction associated with the final timestep of the input sequence.

    This baseline evaluates how much predictive information is contained
    in the current day's meteorological conditions alone. Strong performance
    indicates that temporal context contributes little beyond instantaneous
    feature values.

Input and Output Conventions
----------------------------
Both models follow the project's single-day prediction interface:

Input:
    Tensor of shape ``(batch_size, feature_channels, sequence_length)``.

Output:
    Tensor of shape ``(batch_size, 1)``.

Notes
-----
* Neither model contains hidden layers, nonlinear activations, recurrence,
  attention mechanisms, or convolutional operations.
* The models are intentionally simple and interpretable, making them useful
  as lower-bound performance baselines.
* Performance gains achieved by more sophisticated architectures can be
  interpreted relative to these linear reference models.
"""

import torch
import torch.nn as nn


[docs] class LinearBaseline(nn.Module): """ Full-window linear regression baseline. Flattens the entire (feature_channel × seq_length) input into one vector, applies a single Linear layer to produce a scalar prediction. This is mathematically equivalent to: gcc_pred = w @ flatten(input) + b where w has C×L weights. It can learn things like "temperature on day 300 matters more than temperature on day 50" because each (channel, timestep) pair gets its own weight. Parameters ---------- feature_channel : int Number of input feature channels. seq_length : int Window length in days (365). Examples -------- >>> model = LinearBaseline(feature_channel=16, seq_length=365) >>> x = torch.randn(32, 16, 365) >>> y = model(x) >>> y.shape torch.Size([32, 1]) """
[docs] def __init__(self, feature_channel: int, seq_length: int = 365): super().__init__() self.feature_channel = feature_channel self.seq_length = seq_length self.linear = nn.Linear(feature_channel * seq_length, 1)
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor: """ Parameters ---------- x : (batch, feature_channel, seq_length) Returns ------- (batch, 1) """ # Flatten: (B, C, L) → (B, C*L) x = x.reshape(x.size(0), -1) return self.linear(x)
[docs] class PerDayLinearBaseline(nn.Module): """ Per-day linear regression baseline (no temporal context). Applies the same Linear(C, 1) to every timestep independently, then returns the prediction for the last day. This tests whether today's feature values alone (without any history) can predict today's LAI. If this baseline scores well, it means the temporal context from the 365-day window isn't adding much — the model is mostly using the current day's meteorology. Parameters ---------- feature_channel : int Number of input feature channels. Examples -------- >>> model = PerDayLinearBaseline(feature_channel=16) >>> x = torch.randn(32, 16, 365) >>> y = model(x) >>> y.shape torch.Size([32, 1]) """
[docs] def __init__(self, feature_channel: int): super().__init__() self.linear = nn.Linear(feature_channel, 1)
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor: """ Parameters ---------- x : (batch, feature_channel, seq_length) Returns ------- (batch, 1) """ # Permute to (B, L, C), take last day, apply linear x = x.permute(0, 2, 1) # (B, L, C) last_day = x[:, -1, :] # (B, C) return self.linear(last_day)