Miscellany / Time series and sequences
Time Series Modeling Challenges and Solutions
Contents
- Introduction to Time Series Modeling with RAG
- Hierarchical Multi-Agent Architecture
- Specialized Language Models (SLMs)
- Prompt Repository
- Retrieval-Augmented Generation (RAG)
- Handling Complex Spatio-Temporal Dependencies
- Addressing Distribution Shifts
- Real-Life Example: Weather Forecasting
- Real-Life Example: Energy Consumption Forecasting
- Improving Predictions with RAG
- Handling Complex Spatio-Temporal Dependencies in Energy Forecasting
- Addressing Distribution Shifts in Energy Forecasting
- Evaluation and Benchmarking
- Conclusion and Future Work
- Additional Resources
Introduction to Time Series Modeling with RAG
Time series modeling is a critical component in various applications, from weather forecasting to stock market analysis. This presentation introduces a novel approach using a Retrieval-Augmented Generation (RAG) framework for time series analysis. We'll explore how this method addresses challenges like complex spatio-temporal dependencies and distribution shifts, leveraging a hierarchical, multi-agent architecture to improve predictions on new data.
import numpy as np
import matplotlib.pyplot as plt
# Generate a sample time series
np.random.seed(42)
time = np.arange(100)
series = np.cumsum(np.random.randn(100))
# Plot the time series
plt.figure(figsize=(10, 6))
plt.plot(time, series)
plt.title('Sample Time Series')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
Hierarchical Multi-Agent Architecture
The proposed framework utilizes a hierarchical, multi-agent architecture. A master agent orchestrates specialized sub-agents, each designed for specific time series tasks. This modular approach allows for flexibility and improved performance across various time series challenges.
class MasterAgent:
def __init__(self):
self.sub_agents = {
'forecasting': ForecastingAgent(),
'anomaly_detection': AnomalyDetectionAgent(),
'classification': ClassificationAgent()
}
def delegate_task(self, task_type, data):
if task_type in self.sub_agents:
return self.sub_agents[task_type].process(data)
else:
raise ValueError(f"Unknown task type: {task_type}")
class SubAgent:
def process(self, data):
raise NotImplementedError("Subclasses must implement this method")
class ForecastingAgent(SubAgent):
def process(self, data):
# Implement forecasting logic
pass
class AnomalyDetectionAgent(SubAgent):
def process(self, data):
# Implement anomaly detection logic
pass
class ClassificationAgent(SubAgent):
def process(self, data):
# Implement classification logic
pass
# Usage
master_agent = MasterAgent()
result = master_agent.delegate_task('forecasting', time_series_data)
Specialized Language Models (SLMs)
Each sub-agent in our framework utilizes smaller, pre-trained language models (SLMs) customized for specific time series tasks. These models are fine-tuned using instruction tuning and direct preference optimization, allowing them to specialize in particular aspects of time series analysis.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
class SLM:
def __init__(self, model_name):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(model_name)
def fine_tune(self, dataset, task_instructions):
# Implement fine-tuning logic
pass
def generate(self, prompt):
inputs = self.tokenizer(prompt, return_tensors="pt")
outputs = self.model.generate(**inputs)
return self.tokenizer.decode(outputs[0])
# Initialize and fine-tune an SLM for forecasting
forecasting_slm = SLM("gpt2-small")
forecasting_slm.fine_tune(forecasting_dataset, "Forecast the next 7 days of this time series")
# Generate a forecast
forecast = forecasting_slm.generate("Time series data: [1, 2, 3, 4, 5]. Forecast:")
print(forecast)
Prompt Repository
Our framework leverages a shared repository of prompt pools containing distilled knowledge about historical patterns and trends. This repository helps improve predictions on new data by providing relevant context and examples for the SLMs to work with.
import sqlite3
class PromptRepository:
def __init__(self, db_path):
self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor()
self.create_table()
def create_table(self):
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS prompts
(id INTEGER PRIMARY KEY, task TEXT, prompt TEXT)
''')
self.conn.commit()
def add_prompt(self, task, prompt):
self.cursor.execute('INSERT INTO prompts (task, prompt) VALUES (?, ?)', (task, prompt))
self.conn.commit()
def get_prompts(self, task):
self.cursor.execute('SELECT prompt FROM prompts WHERE task = ?', (task,))
return [row[0] for row in self.cursor.fetchall()]
# Usage
repo = PromptRepository('prompts.db')
repo.add_prompt('forecasting', 'Given the historical data [1, 2, 3, 4, 5], predict the next value.')
relevant_prompts = repo.get_prompts('forecasting')
print(relevant_prompts)
Retrieval-Augmented Generation (RAG)
The RAG framework combines the power of retrieval-based and generation-based approaches. It retrieves relevant information from the prompt repository and uses it to augment the input to the SLMs, resulting in more informed and contextually relevant predictions.
class RAG:
def __init__(self, slm, prompt_repo):
self.slm = slm
self.prompt_repo = prompt_repo
def generate(self, task, input_data):
relevant_prompts = self.prompt_repo.get_prompts(task)
augmented_input = f"Task: {task}\nInput: {input_data}\nRelevant examples:\n"
augmented_input += "\n".join(relevant_prompts[:3]) # Use top 3 relevant prompts
return self.slm.generate(augmented_input)
# Usage
rag = RAG(forecasting_slm, repo)
forecast = rag.generate('forecasting', '[1, 2, 3, 4, 5]')
print(forecast)
Handling Complex Spatio-Temporal Dependencies
One of the key challenges in time series modeling is handling complex spatio-temporal dependencies. Our framework addresses this by incorporating attention mechanisms and temporal convolutions in the SLMs, allowing them to capture both long-range and local dependencies effectively.
import torch.nn as nn
class TemporalConvNet(nn.Module):
def __init__(self, num_inputs, num_channels, kernel_size=2, dropout=0.2):
super(TemporalConvNet, self).__init__()
layers = []
num_levels = len(num_channels)
for i in range(num_levels):
dilation_size = 2 ** i
in_channels = num_inputs if i == 0 else num_channels[i-1]
out_channels = num_channels[i]
layers += [TemporalBlock(in_channels, out_channels, kernel_size,
stride=1, dilation=dilation_size,
padding=(kernel_size-1) * dilation_size,
dropout=dropout)]
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
class TemporalBlock(nn.Module):
def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
super(TemporalBlock, self).__init__()
self.conv1 = nn.utils.weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
stride=stride, padding=padding,
dilation=dilation))
self.relu1 = nn.ReLU()
self.dropout1 = nn.Dropout(dropout)
self.conv2 = nn.utils.weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
stride=stride, padding=padding,
dilation=dilation))
self.relu2 = nn.ReLU()
self.dropout2 = nn.Dropout(dropout)
self.net = nn.Sequential(self.conv1, self.relu1, self.dropout1,
self.conv2, self.relu2, self.dropout2)
self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
self.relu = nn.ReLU()
self.init_weights()
def init_weights(self):
self.conv1.weight.data.normal_(0, 0.01)
self.conv2.weight.data.normal_(0, 0.01)
if self.downsample is not None:
self.downsample.weight.data.normal_(0, 0.01)
def forward(self, x):
out = self.net(x)
res = x if self.downsample is None else self.downsample(x)
return self.relu(out + res)
# Usage
model = TemporalConvNet(num_inputs=1, num_channels=[32, 64, 128])
input_data = torch.randn(32, 1, 100) # (batch_size, num_inputs, sequence_length)
output = model(input_data)
print(output.shape) # Should be torch.Size([32, 128, 100])
Addressing Distribution Shifts
Distribution shifts pose a significant challenge in time series modeling, especially when learning from historical context to predict task-specific outcomes. Our framework tackles this issue by incorporating adaptive normalization techniques and continual learning strategies.
import torch
import torch.nn as nn
class AdaptiveInstanceNorm1d(nn.Module):
def __init__(self, num_features, eps=1e-5, momentum=0.1):
super(AdaptiveInstanceNorm1d, self).__init__()
self.num_features = num_features
self.eps = eps
self.momentum = momentum
self.weight = nn.Parameter(torch.ones(1, num_features, 1))
self.bias = nn.Parameter(torch.zeros(1, num_features, 1))
self.register_buffer('running_mean', torch.zeros(1, num_features, 1))
self.register_buffer('running_var', torch.ones(1, num_features, 1))
def forward(self, x):
b, c, l = x.size()
running_mean = self.running_mean.repeat(b, 1, l)
running_var = self.running_var.repeat(b, 1, l)
# Use instance stats during training, running stats during evaluation
if self.training:
instance_mean = x.mean(dim=2, keepdim=True)
instance_var = x.var(dim=2, keepdim=True)
x = (x - instance_mean) / (torch.sqrt(instance_var + self.eps))
# Update running stats
self.running_mean = self.momentum * instance_mean.mean(0) + (1 - self.momentum) * self.running_mean
self.running_var = self.momentum * instance_var.mean(0) + (1 - self.momentum) * self.running_var
else:
x = (x - running_mean) / (torch.sqrt(running_var + self.eps))
return x * self.weight + self.bias
# Usage in a model
class TimeSeriesModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(TimeSeriesModel, self).__init__()
self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
self.adaptive_norm = AdaptiveInstanceNorm1d(hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
x, _ = self.lstm(x)
x = x.transpose(1, 2) # (batch, hidden_size, seq_len)
x = self.adaptive_norm(x)
x = x.transpose(1, 2) # (batch, seq_len, hidden_size)
return self.fc(x[:, -1, :]) # Use last time step for prediction
# Test the model
model = TimeSeriesModel(input_size=1, hidden_size=64, output_size=1)
input_data = torch.randn(32, 100, 1) # (batch_size, seq_len, input_size)
output = model(input_data)
print(output.shape) # Should be torch.Size([32, 1])
Real-Life Example: Weather Forecasting
Let's apply our RAG framework to a weather forecasting task. We'll use historical temperature data to predict future temperatures, demonstrating how our approach can handle the complex patterns and seasonality inherent in weather data.
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
# Generate synthetic weather data
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', end='2023-12-31', freq='D')
temperatures = 20 + 10 * np.sin(np.arange(len(dates)) * 2 * np.pi / 365) + np.random.normal(0, 2, len(dates))
df = pd.DataFrame({'date': dates, 'temperature': temperatures})
# Prepare data for LSTM
scaler = MinMaxScaler()
scaled_temp = scaler.fit_transform(df['temperature'].values.reshape(-1, 1))
def create_sequences(data, seq_length):
sequences = []
targets = []
for i in range(len(data) - seq_length):
seq = data[i:i+seq_length]
target = data[i+seq_length]
sequences.append(seq)
targets.append(target)
return np.array(sequences), np.array(targets)
seq_length = 30
X, y = create_sequences(scaled_temp, seq_length)
# Build and train LSTM model
model = Sequential([
LSTM(50, activation='relu', input_shape=(seq_length, 1)),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X, y, epochs=50, batch_size=32, validation_split=0.2, verbose=0)
# Make predictions
last_sequence = scaled_temp[-seq_length:]
next_day_scaled = model.predict(last_sequence.reshape(1, seq_length, 1))
next_day_temp = scaler.inverse_transform(next_day_scaled)[0][0]
print(f"Predicted temperature for next day: {next_day_temp:.2f}°C")
Real-Life Example: Energy Consumption Forecasting
Energy consumption forecasting is a crucial application of our RAG framework. By analyzing historical energy usage patterns, we can predict future consumption, enabling efficient energy management and grid optimization. This example demonstrates how to process and model hourly energy consumption data.
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
# Generate synthetic energy consumption data
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', end='2023-12-31', freq='H')
base_consumption = 100 + 50 * np.sin(np.arange(len(dates)) * 2 * np.pi / (24*7))
yearly_pattern = 20 * np.sin(np.arange(len(dates)) * 2 * np.pi / (24*365))
noise = np.random.normal(0, 10, len(dates))
consumption = base_consumption + yearly_pattern + noise
df = pd.DataFrame({'date': dates, 'consumption': consumption})
# Prepare data for LSTM
scaler = MinMaxScaler()
scaled_consumption = scaler.fit_transform(df['consumption'].values.reshape(-1, 1))
def create_sequences(data, seq_length):
sequences, targets = [], []
for i in range(len(data) - seq_length):
sequences.append(data[i:i+seq_length])
targets.append(data[i+seq_length])
return np.array(sequences), np.array(targets)
seq_length = 168 # One week of hourly data
X, y = create_sequences(scaled_consumption, seq_length)
# Build and train LSTM model
model = Sequential([
LSTM(50, activation='relu', input_shape=(seq_length, 1)),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X, y, epochs=50, batch_size=32, validation_split=0.2, verbose=0)
# Make predictions
last_sequence = scaled_consumption[-seq_length:]
next_hour_scaled = model.predict(last_sequence.reshape(1, seq_length, 1))
next_hour_consumption = scaler.inverse_transform(next_hour_scaled)[0][0]
print(f"Predicted energy consumption for next hour: {next_hour_consumption:.2f} kWh")
Improving Predictions with RAG
Our RAG framework enhances time series predictions by leveraging historical patterns and expert knowledge. Here's how we can integrate RAG into our energy consumption forecasting model:
class EnergyConsumptionRAG:
def __init__(self, base_model, prompt_repository):
self.base_model = base_model
self.prompt_repository = prompt_repository
def get_relevant_prompts(self, current_date, consumption_pattern):
# Retrieve relevant prompts based on current context
prompts = self.prompt_repository.get_prompts('energy_consumption')
return [p for p in prompts if self.is_relevant(p, current_date, consumption_pattern)]
def is_relevant(self, prompt, current_date, consumption_pattern):
# Implement relevance criteria (e.g., similar date, pattern)
return True # Simplified for this example
def generate_forecast(self, input_sequence, current_date):
relevant_prompts = self.get_relevant_prompts(current_date, input_sequence)
augmented_input = self.augment_input(input_sequence, relevant_prompts)
return self.base_model.predict(augmented_input)
def augment_input(self, input_sequence, prompts):
# Combine input sequence with relevant prompts
# This is a simplified version; in practice, you'd use more sophisticated methods
augmented = np.concatenate([input_sequence] + [np.array(p) for p in prompts])
return augmented.reshape(1, -1, 1)
# Usage
rag_model = EnergyConsumptionRAG(model, prompt_repository)
next_hour_consumption = rag_model.generate_forecast(last_sequence, pd.Timestamp.now())
print(f"RAG-enhanced prediction: {next_hour_consumption[0][0]:.2f} kWh")
Handling Complex Spatio-Temporal Dependencies in Energy Forecasting
Energy consumption patterns often exhibit complex spatio-temporal dependencies. Our framework addresses this by incorporating attention mechanisms and temporal convolutions. Here's an example of how we can enhance our model:
import torch
import torch.nn as nn
class TemporalAttention(nn.Module):
def __init__(self, hidden_dim):
super(TemporalAttention, self).__init__()
self.attn = nn.Linear(hidden_dim, 1)
def forward(self, x):
attn_weights = torch.softmax(self.attn(x), dim=1)
return torch.sum(x * attn_weights, dim=1)
class EnhancedEnergyModel(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(EnhancedEnergyModel, self).__init__()
self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)
self.temporal_attention = TemporalAttention(hidden_dim)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
lstm_out, _ = self.lstm(x)
attn_out = self.temporal_attention(lstm_out)
return self.fc(attn_out)
# Initialize and train the model
input_dim = 1 # Single feature (energy consumption)
hidden_dim = 64
output_dim = 1 # Predict next hour's consumption
model = EnhancedEnergyModel(input_dim, hidden_dim, output_dim)
# Training loop (pseudo-code)
# for epoch in range(num_epochs):
# for batch in dataloader:
# optimizer.zero_grad()
# inputs, targets = batch
# outputs = model(inputs)
# loss = criterion(outputs, targets)
# loss.backward()
# optimizer.step()
# Make predictions
# test_input = torch.tensor(last_sequence).unsqueeze(0) # Add batch dimension
# prediction = model(test_input)
# print(f"Predicted energy consumption: {prediction.item():.2f} kWh")
Addressing Distribution Shifts in Energy Forecasting
Distribution shifts in energy consumption patterns can occur due to various factors such as seasonal changes, economic fluctuations, or global events. Our framework tackles this challenge using adaptive normalization techniques:
import torch
import torch.nn as nn
class AdaptiveNormalization(nn.Module):
def __init__(self, num_features, eps=1e-5, momentum=0.1):
super(AdaptiveNormalization, self).__init__()
self.num_features = num_features
self.eps = eps
self.momentum = momentum
self.weight = nn.Parameter(torch.ones(num_features))
self.bias = nn.Parameter(torch.zeros(num_features))
self.register_buffer('running_mean', torch.zeros(num_features))
self.register_buffer('running_var', torch.ones(num_features))
def forward(self, x):
if self.training:
mean = x.mean(dim=0)
var = x.var(dim=0, unbiased=False)
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
else:
mean = self.running_mean
var = self.running_var
x_normalized = (x - mean) / torch.sqrt(var + self.eps)
return self.weight * x_normalized + self.bias
class AdaptiveEnergyModel(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(AdaptiveEnergyModel, self).__init__()
self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)
self.adaptive_norm = AdaptiveNormalization(hidden_dim)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
lstm_out, _ = self.lstm(x)
normalized = self.adaptive_norm(lstm_out[:, -1, :]) # Normalize last time step
return self.fc(normalized)
# Initialize and use the model
model = AdaptiveEnergyModel(input_dim=1, hidden_dim=64, output_dim=1)
# Training and prediction (pseudo-code)
# model.train()
# for epoch in range(num_epochs):
# for batch in dataloader:
# # ... training loop ...
# model.eval()
# with torch.no_grad():
# test_input = torch.tensor(last_sequence).unsqueeze(0)
# prediction = model(test_input)
# print(f"Adaptive prediction: {prediction.item():.2f} kWh")
Evaluation and Benchmarking
To assess the performance of our RAG-based time series model, we compare it against traditional forecasting methods and state-of-the-art deep learning models. Here's an example of how we can evaluate our model:
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error
from statsmodels.tsa.arima.model import ARIMA
from prophet import Prophet
def evaluate_models(true_values, predictions_dict):
results = {}
for model_name, predictions in predictions_dict.items():
mse = mean_squared_error(true_values, predictions)
mae = mean_absolute_error(true_values, predictions)
results[model_name] = {'MSE': mse, 'MAE': mae}
return results
# Prepare data
train_data = df['consumption'][:len(df)-168] # Use last week for testing
test_data = df['consumption'][-168:]
# RAG model predictions (assuming we have these from previous steps)
rag_predictions = np.array([/* RAG model predictions */])
# ARIMA model
arima_model = ARIMA(train_data, order=(1, 1, 1))
arima_results = arima_model.fit()
arima_predictions = arima_results.forecast(steps=168)
# Prophet model
prophet_data = df.rename(columns={'date': 'ds', 'consumption': 'y'})
prophet_model = Prophet()
prophet_model.fit(prophet_data[:-168])
future_dates = prophet_model.make_future_dataframe(periods=168, freq='H')
prophet_forecast = prophet_model.predict(future_dates)
prophet_predictions = prophet_forecast['yhat'][-168:].values
# Evaluate all models
all_predictions = {
'RAG': rag_predictions,
'ARIMA': arima_predictions,
'Prophet': prophet_predictions
}
evaluation_results = evaluate_models(test_data.values, all_predictions)
for model, metrics in evaluation_results.items():
print(f"{model} - MSE: {metrics['MSE']:.2f}, MAE: {metrics['MAE']:.2f}")
Conclusion and Future Work
Our novel RAG-based approach for time series modeling demonstrates promising results in addressing complex challenges such as spatio-temporal dependencies and distribution shifts. By leveraging a hierarchical multi-agent architecture and specialized language models, we achieve state-of-the-art performance across various time series tasks.
Future work may include:
- Exploring more advanced attention mechanisms for capturing long-range dependencies
- Investigating transfer learning techniques to improve performance on limited data
- Developing interpretability methods to explain model predictions
- Extending the framework to handle multivariate time series and cross-series interactions
As we continue to refine and expand this approach, we anticipate significant advancements in the field of time series analysis, with wide-ranging applications across industries.
Additional Resources
For those interested in diving deeper into the concepts and techniques discussed in this presentation, here are some valuable resources:
- "Attention Is All You Need" by Vaswani et al. (2017) ArXiv: https://arxiv.org/abs/1706.03762
- "Temporal Fusion Transformers for Interpretable Multi-horizon Time Series Forecasting" by Lim et al. (2020) ArXiv: https://arxiv.org/abs/1912.09363
- "Modeling Long- and Short-Term Temporal Patterns with Deep Neural Networks" by Lai et al. (2018) ArXiv: https://arxiv.org/abs/1703.07015
- "N-BEATS: Neural basis expansion analysis for interpretable time series forecasting" by Oreshkin et al. (2019) ArXiv: https://arxiv.org/abs/1905.10437
These papers provide in-depth insights into advanced time series modeling techniques and form the foundation for many of the ideas presented in our RAG-based approach.