
Récupération de séries chronologiques : comment une rétrospective améliore les prévisions
Aide à la prévision des séries chronologiques
Nous savons tous comment cela se passe : les données de séries chronologiques sont délicates.
Les modèles de prévision traditionnels ne sont pas préparés à des incidents tels que des krachs soudains du marché, des événements de type cygne noir ou des conditions météorologiques rares.
Même les grands modèles sophistiqués comme Chronos ont parfois du mal parce qu’ils n’ont jamais eu affaire à ce genre de modèle auparavant.
Nous pouvons atténuer cela avec récupération. Avec la récupération, nous pouvons demander Est-ce que quelque chose comme ça s’est déjà produit ? puis en utilisant cet exemple passé pour guider les prévisions.
Comme nous le savons tous maintenant, dans le traitement du langage naturel (NLP), cette idée s’appelle Génération augmentée par récupération (RAG). Il devient également populaire dans le monde de la prévision de séries chronologiques.
Le modèle considère alors situations passées qui ressemble à l’actuel, et à partir de là, cela peut faire plus fiable prédictions.
En quoi ce RAF est-il différent des séries chronologiques traditionnelles ? La prévision de récupération ajoute un étape d’accès mémoire explicite.
Au lieu de:
Passé -> paramètres -> prévisions
Avec récupération nous avons:
Situation actuelle -> recherche de similarité -> épisodes passés concrets-> prévision

Au lieu de simplement utiliser ce que le modèle a appris au cours entraînementle idée est de lui donner accès à une gamme de similaire situations.
C’est comme laisser un modèle météorologique vérifier : « À quoi ressemblaient les hivers passés comme celui-ci ? avant? »
Salut, je suis Sara Nobregaun ingénieur en IA. Si vous travaillez sur des problèmes similaires ou souhaitez obtenir des commentaires sur l’application de ces idées, je rassemble mes écrits, mes ressources et mes liens de mentorat. ici.
Dans cet article, j’explore récupération–prévision augmentée à partir des premiers principes et montrez, avec des exemples concrets et des exemples de code, comment la récupération peut être utilisée dans de véritables pipelines de prévision.
Qu’est-ce que la prévision augmentée par récupération (RAF) ?
Qu’est-ce que le RAF ? D’un point de vue de très haut niveau, au lieu de s’appuyer uniquement sur ce qu’un modèle a appris lors de la formation, RAF permet au modèle de rechercher activement situations passées concrètes similaire à celui actuel et utiliser leurs résultats pour guide sa prédiction.
Voyons cela plus en détail :
- Vous convertissez la situation actuelle (par exemple, les dernières semaines d’un ensemble de données boursières de séries chronologiques) en requête.
- Ce requête est alors utilisé pour recherche un base de données de segments de séries chronologiques historiques pour trouver le plus similaire motifs.
- Ces correspondances ne doivent pas nécessairement provenir du même stock ; le système devrait également faire apparaître des mouvements similaires provenant d’autres actions ou produits financiers.
Il récupère ceux motifs et que s’est-il passé après.
Ensuite, ces informations sont ingéré au prévision modèle pour l’aider à faire de meilleures prédictions.
Cette technique est puissante dans :
- Scénarios sans tir: Quand le modèle fait face à quelque chose sur lequel il n’a pas été formé.
- Événements rares ou anormaux: Comme le COVID, les krachs financiers soudains, etc.
- Tendances saisonnières en évolution: Où les données passées contiennent des modèles utiles, mais elles évoluent avec le temps.
RAF ne remplace pas votre prévision modèle, mais à la place augmente en le donnant supplémentaire conseils et en le fondant sur des exemples historiques pertinents.
Autre exemple : disons que vous souhaitez prévoir la consommation d’énergie pendant une semaine inhabituellement chaude.
Au lieu d’espérer que votre modèle rappelle comment les vagues de chaleur affectent l’utilisation, la récupération révèle similaire les vagues de chaleur passées et permet au modèle de considérer ce qui s’est passé pendant cette période.
Que récupèrent réellement ces modèles ?
Les « connaissances » récupérées ne sont pas seulement des données brutes. C’est le contexte qui donne des indices au modèle.
Voici quelques exemples courants :

Comme vous pouvez le constater, la récupération se concentre sur des situations historiques significatives, telles que des chocs rares, des effets saisonniers et des modèles présentant des structures similaires. Ceux-ci donnent un contexte exploitable pour les prévisions actuelles.
Comment ces modèles sont-ils récupérés ?
Pour trouver pertinent motifs du passé, ces modèles utilisent des mécanismes structurés qui représentent la situation actuelle de manière à la rendre facile pour rechercher de grandes bases de données et trouver les correspondances les plus proches.
Les extraits de code de cette section sont une illustration simplifiée destinée à développer l’intuition, ils ne représentent pas le code de production.

Certaines de ces méthodes sont :
Similitude basée sur l’intégration
Celui-ci convertit les séries temporelles (ou les patchs/fenêtres d’une série) en formats compacts. vecteurspuis comparez-les avec des métriques de distance telles que la similarité euclidienne ou cosinus.
Dans simple mots : Le modèle transforme des morceaux de données de séries chronologiques en courtes résumés puis vérifie lequel passé les résumés ressemblent le plus à ce qui se passe actuellement.
Certains prévisionnistes utilisant la récupération augmentée (par exemple, RADEAU) récupérer l’historique le plus similaire correctifs à partir des données d’entraînement / de la série entière, puis agrégat valeurs récupérées avec des poids de type attention.
En termes simples : il trouve similaire situations du passé et moyennes eux, payant plus attention au meilleur matchs.
import numpy as np
# Example: embedding-based retrieval for time-series patches
# This is a toy example to show the *idea* behind retrieval.
# In practice:
# - embeddings are learned by neural networks
# - similarity search runs over millions of vectors
# - this logic lives inside a larger forecasting pipeline
def embed_patch(patch: np.ndarray) -> np.ndarray:
"""
Convert a short time-series window ("patch") into a compact vector.
Here we use simple statistics (mean, std, min, max) purely for illustration.
Real-world systems might use:
- a trained encoder network
- shape-based representations
- frequency-domain features
- latent vectors from a forecasting backbone
"""
return np.array([
patch.mean(), # average level
patch.std(), # volatility
patch.min(), # lowest point
patch.max() # highest point
])
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
"""
Measure how similar two vectors are.
Cosine similarity focuses on *direction* rather than magnitude,
which is often useful for comparing patterns or shapes.
"""
return float(a @ b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
# Step 1: Represent the current situation
# A short window representing the current time-series behavior
query_patch = np.array([10, 12, 18, 25, 14, 11])
# Turn it into an embedding
query_embedding = embed_patch(query_patch)
# Step 2: Represent historical situations
# Past windows extracted from historical data
historical_patches = [
np.array([9, 11, 17, 24, 13, 10]), # looks similar
np.array([2, 2, 2, 2, 2, 2]), # flat, unrelated
np.array([10, 13, 19, 26, 15, 12]) # very similar
]
# Convert all historical patches into embeddings
historical_embeddings = [
embed_patch(patch) for patch in historical_patches
]
# Step 3: Compare and retrieve the most similar past cases
# Compute similarity scores between the current situation
# and each historical example
similarities = [
cosine_similarity(query_embedding, hist_emb)
for hist_emb in historical_embeddings
]
# Rank historical patches by similarity
top_k_indices = np.argsort(similarities)[::-1][:2]
print("Most similar historical patches:", top_k_indices)
# Step 4 (conceptual):
# In a retrieval-augmented forecaster, the model would now:
# - retrieve the *future outcomes* of these similar patches
# - weight them by similarity (attention-like weighting)
# - use them to guide the final forecast
# This integration step is model-specific and not shown here.
Outils et bibliothèques de récupération
1. FAISS
FAISS est un super rapide et GPU-bibliothèque conviviale pour la recherche de similarité sur des vecteurs denses. Le meilleur les ensembles de données de cette bibliothèque sont ceux qui sont grand et dans-mémoirebien que sa structure rende les mises à jour en temps réel plus difficiles à mettre en œuvre.
import faiss
import numpy as np
# Suppose we already have embeddings for historical windows
d = 128 # embedding dimension
xb = np.random.randn(100_000, d).astype("float32") # historical embeddings
xq = np.random.randn(1, d).astype("float32") # query embedding
index = faiss.IndexFlatIP(d) # inner product (often used with normalized vectors for cosine-like behavior)
index.add(xb)
k = 5
scores, ids = index.search(xq, k)
print("Nearest neighbors (ids):", ids)
print("Similarity scores:", scores)
# Some FAISS indexes/algorithms can run on GPU.
Recherche du voisin le plus proche (Ennuyer)
La bibliothèque Annoy est relativement léger et facile à travailler.
Les meilleurs ensembles de données pour cette bibliothèque sont les ensembles de données historiques qui restent pour la plupart statiques, puisque toute modification de l’ensemble de données nécessite la reconstruction de l’index.
from annoy import AnnoyIndex
import numpy as np
# Number of values in each embedding vector.
# The "length" of each fingerprint.
f = 64
# Create an Annoy index.
# This object will store many past embeddings and help us quickly find the most similar ones.
ann = AnnoyIndex(f, "angular")
# "angular" distance is commonly used to compare patterns
# and behaves similarly to cosine similarity.
# Add historical embeddings (past situations).
# Each item represents a compressed version of a past time-series window.
# Here we use random numbers just as an example.
for i in range(10000):
ann.add_item(i, np.random.randn(f).tolist())
# Build the search structure.
# This step organizes the data so similarity searches are fast.
# After this, the index becomes read-only.
ann.build(10)
# Save the index to disk.
# This allows us to load it later without rebuilding everything.
ann.save("hist.ann")
# Create a query embedding.
# This represents the current situation we want to compare
# against past situations.
q = np.random.randn(f).tolist()
# Find the 5 most similar past embeddings.
# Annoy returns the IDs of the closest matches.
neighbors = ann.get_nns_by_vector(q, 5)
print("Nearest neighbors:", neighbors)
# Important note:
# Once the index is built, you cannot add new items.
# If new historical data appears, the index must be rebuilt.
Qdrant / Pomme de Pin
Qdrant et Pinecone sont comme Google pour les intégrations.
Vous stockez de nombreuses « empreintes digitales » vectorielles (plus des balises supplémentaires comme ville/saison), et lorsque vous avez une nouvelle empreinte digitale, vous demandez :
Montrez-moi les plus similaires, mais uniquement pour ce type de ville/saison/magasin.
C’est ce qui les rend plus faciles que de lancer votre propre récupération : ils fonctionnent rapidement. recherche et filtration!
Qdrant appelle les métadonnées charge utileet vous pouvez filtrer les résultats de recherche à l’aide de conditions.
# Example only (for intuition). Real code needs a running Qdrant instance + real embeddings.
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
collection = "time_series_windows"
# Pretend this is the embedding of the *current* time-series window
query_vector = [0.12, -0.03, 0.98, 0.44] # shortened for readability
# Filter = "only consider past windows from New York in summer"
# Qdrant documentation shows filters built from FieldCondition + MatchValue. :contentReference[oaicite:3]{index=3}
query_filter = models.Filter(
must=[
models.FieldCondition(
key="city",
match=models.MatchValue(value="New York"),
),
models.FieldCondition(
key="season",
match=models.MatchValue(value="summer"),
),
]
)
# In real usage, you’d call search/query and get back the nearest matches
# plus their payload (metadata) if you request it.
results = client.search(
collection_name=collection,
query_vector=query_vector,
query_filter=query_filter,
limit=5,
with_payload=True, # return metadata so you can inspect what you retrieved
)
print(results)
# What you'd do next (conceptually):
# - take the matched IDs
# - load the actual historical windows behind them
# - feed those windows (or their outcomes) into your forecasting model
Magasins de pomme de pin paires clé-valeur de métadonnées aux côtés des vecteurs et vous permet de filtrer au moment de la requête (y compris $eq) et renvoie les métadonnées.
# Example only (for intuition). Real code needs an API key + an index host.
from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_API_KEY")
index = pc.Index(host="INDEX_HOST")
# Pretend this is the embedding of the current time-series window
query_vector = [0.12, -0.03, 0.98, 0.44] # shortened for readability
# Ask for the most similar past windows, but only where:
# city == "New York" AND season == "summer"
# Pinecone docs show query-time filtering and `$eq`. :contentReference[oaicite:5]{index=5}
res = index.query(
namespace="windows",
vector=query_vector,
top_k=5,
filter={
"city": {"$eq": "New York"},
"season": {"$eq": "summer"},
},
include_metadata=True, # return tags so you can sanity-check matches
include_values=False
)
print(res)
# Conceptually next:
# - use the returned IDs to fetch the underlying historical windows/outcomes
# - condition your forecast on those retrieved examples
Pourquoi les bases de données vectorielles sont-elles utiles ? Ils vous laissent faire recherche de similarité + « filtres WHERE de type SQL » en une seule étape, ce qui est difficile à faire proprement avec une configuration DIY (le filtrage de la charge utile Qdrant et le filtrage des métadonnées Pinecone sont des fonctionnalités de première classe dans leurs documents.)
Chaque outil a ses compromis. Par exemple, FAISS est excellent pour la performance mais ne convient pas pour fréquent mises à jour. Qdrant donne flexibilité et filtrage en temps réel. Pinecone est facile à configurer mais uniquement en SaaS.
Récupération + prévision : comment les combiner
Après avoir su quoi récupérer, l’étape suivante consiste à combiner ces informations avec l’entrée actuelle.
Cela peut varier en fonction de l’architecture et de la tâche. Il existe plusieurs stratégies pour ce faire (voir l’image ci-dessous).

UN. Enchaînement
Idée: traiter le contexte récupéré comme « plus d’entrée » en l’ajoutant à la séquence existante (très courant dans les configurations de génération augmentée par récupération).
Fonctionne bien avec les modèles basés sur des transformateurs comme Chronos et ne nécessite pas de modifications d’architecture.
import torch
# x_current: the model's usual input sequence (e.g., last N timesteps or tokens)
# shape: [batch, time, d_model] (or [batch, time] if you think in tokens)
x_current = torch.randn(8, 128, 256)
# x_retrieved: retrieved context encoded in the SAME representation space
# e.g., embeddings for similar past windows (or their summaries)
# shape: [batch, retrieved_time, d_model]
x_retrieved = torch.randn(8, 32, 256)
# Simple fusion: just append retrieved context to the end of the input sequence
# Now the model sees: [current history ... + retrieved context ...]
x_fused = torch.cat([x_current, x_retrieved], dim=1)
# In practice, you'd also add:
# - an attention mask (so the model knows what’s real vs padded)
# - segment/type embeddings (so the model knows which part is retrieved context)
# Then feed x_fused to your transformer.
B. Fusion d’attention croisée
Idée: gardez «l’entrée actuelle» et le «contexte récupéré» séparés, et laissez le modèle prêter attention au contexte récupéré quand il en a besoin. Il s’agit du modèle principal de « fusion dans le décodeur via une attention croisée » utilisé par les architectures de récupération augmentée comme FiD.
import torch
# current_repr: representation of the current time-series window
# shape: [batch, time, d_model]
current_repr = torch.randn(8, 128, 256)
# retrieved_repr: representation of retrieved windows (could be concatenated)
# shape: [batch, retrieved_time, d_model]
retrieved_repr = torch.randn(8, 64, 256)
# Think of cross-attention like:
# - Query (Q) comes from the current sequence
# - Keys/Values (K/V) come from retrieved context
Q = current_repr
K = retrieved_repr
V = retrieved_repr
# Attention scores: "How much should each current timestep look at each retrieved timestep?"
scores = torch.matmul(Q, K.transpose(-1, -2)) / (Q.size(-1) ** 0.5)
# Turn scores into weights (so they sum to 1 across retrieved positions)
weights = torch.softmax(scores, dim=-1)
# Weighted sum of retrieved information (this is the “fused” retrieved signal)
retrieval_signal = torch.matmul(weights, V)
# Final fused representation: current info + retrieved info
# (Some models add, some concatenate, some use a learned projection)
fused = current_repr + retrieval_signal
# Then the forecasting head reads from `fused` to predict the future.
C. Mélange d’experts (MoE)
Idée: combiner deux « experts » :
- le prévisionniste basé sur la récupération (non paramétrique, basé sur des cas)
- le prévisionniste de base (connaissance paramétrique)
Une « porte » décide à laquelle faire le plus confiance à chaque pas de temps.
import torch
# base_pred: forecast from the main model (what it "learned in weights")
# shape: [batch, horizon]
base_pred = torch.randn(8, 24)
# retrieval_pred: forecast suggested by retrieved similar cases
# shape: [batch, horizon]
retrieval_pred = torch.randn(8, 24)
# context_for_gate: summary of the current situation (could be last hidden state)
# shape: [batch, d_model]
context_for_gate = torch.randn(8, 256)
# gate: a number between 0 and 1 saying "how much to trust retrieval"
# (In real models, this is a tiny neural net.)
gate = torch.sigmoid(torch.randn(8, 1))
# Mixture: convex combination
# - if gate ~ 1 -> trust retrieval more
# - if gate ~ 0 -> trust the base model more
final_pred = gate * retrieval_pred + (1 - gate) * base_pred
# In practice:
# - gate might be timestep-dependent: shape [batch, horizon, 1]
# - you might also add training losses to stabilize routing/usage (common in MoE)
D. Invite de canal
Idée: traiter les séries récupérées comme canaux d’entrée/fonctionnalités supplémentaires (particulièrement naturel dans les séries chronologiques multivariées, où chaque variable est un « canal »).
import torch
# x: multivariate time series input
# shape: [batch, time, channels]
# Example: channels could be [sales, price, promo_flag, temperature, ...]
x = torch.randn(8, 128, 5)
# retrieved_series_aligned: retrieved signal aligned to the same time grid
# Example: average of the top-k similar past windows (or one representative neighbor)
# shape: [batch, time, retrieved_channels]
retrieved_series_aligned = torch.randn(8, 128, 2)
# Channel prompting = append retrieved channels as extra features
# Now the model gets "normal channels + retrieved channels"
x_prompted = torch.cat([x, retrieved_series_aligned], dim=-1)
# In practice you’d likely also include:
# - a mask or confidence score for retrieved channels
# - normalization so retrieved signals are on a comparable scale
# Then feed x_prompted into the forecaster.
Certains modèles combinent même multiple méthodes.
Une approche courante consiste à récupérer multiple séries similaires, fusionnez-les en utilisant votre attention afin que le modèle puisse se concentrer sur les parties les plus pertinentes, puis les transmettre à un expert.
Conclure
Prévisions augmentées par récupération (RAF) permet à votre modèle d’apprendre du passé d’une manière que la modélisation de séries chronologiques traditionnelle ne permet pas.
Il agit comme un mémoire externe cela aide le modèle à naviguer dans des situations inconnues avec plus de confiance.
Il est simple à expérimenter et apporte des améliorations significatives dans les tâches de prévision.
La récupération n’est plus un battage médiatique académique, elle donne déjà des résultats dans des systèmes du monde réel.
Merci d’avoir lu!
Mon nom est Sara Nobrega. Je suis un ingénieur en IA axé sur le MLOps et sur le déploiement de systèmes d’apprentissage automatique en production.
Références
[1] J. Liu, Y. Zhang, Z. Wang et coll., Prévisions de séries chronologiques augmentées par récupération (2025), préimpression arXiv
Source: https://arxiv.org/html/2505.04163v1
[2] UConn DSIS, TS-RAG : génération augmentée par récupération de séries chronologiques (sd), Dépôt GitHub
Source: https://github.com/UConn-DSIS/TS-RAG
[3] Y. Zhang, H. Xu, X. Chen et coll., Prévisions à mémoire augmentée pour les séries chronologiques avec des événements rares (2024), préimpression arXiv
Source: https://arxiv.org/abs/2412.20810



