refactor: 清理测试文件和2019相关代码
- 删除测试脚本: test_2026_classification.py, test_lagllama.py, test_timesfm.py, test_ttm.py - 删除2019相关文件: analyze_2019_*, apply_2019_*, dlinear_2019_*, three_models_2019_* - 添加集成模型相关代码和分析脚本 - 添加N-BEATS模型和CatBoost分类模型
This commit is contained in:
1 parent
c74f4f6949
commit
1bd9d25f60
33 files changed
+6557
-233
No files matched your search
@@ -0,0 +1,265 @@
|
||||
"""N-BEATS模型实现:预测未来1小时BTC价格"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
|
||||
class NBeatsBlock(nn.Module):
|
||||
"""N-BEATS基础块"""
|
||||
|
||||
def __init__(self, input_size, theta_size, basis_function, layers, layer_size):
|
||||
super().__init__()
|
||||
self.input_size = input_size
|
||||
self.theta_size = theta_size
|
||||
self.basis_function = basis_function
|
||||
|
||||
# 全连接层栈
|
||||
self.fc_stack = nn.ModuleList([
|
||||
nn.Linear(input_size if i == 0 else layer_size, layer_size)
|
||||
for i in range(layers)
|
||||
])
|
||||
|
||||
# Theta层(用于生成基函数参数)
|
||||
self.theta_b_fc = nn.Linear(layer_size, theta_size, bias=False) # backcast
|
||||
self.theta_f_fc = nn.Linear(layer_size, theta_size, bias=False) # forecast
|
||||
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Args:
|
||||
x: (batch_size, input_size)
|
||||
Returns:
|
||||
backcast: (batch_size, input_size)
|
||||
forecast: (batch_size, forecast_size)
|
||||
"""
|
||||
# 通过FC层栈
|
||||
for fc in self.fc_stack:
|
||||
x = self.relu(fc(x))
|
||||
|
||||
# 生成theta参数
|
||||
theta_b = self.theta_b_fc(x) # backcast coefficients
|
||||
theta_f = self.theta_f_fc(x) # forecast coefficients
|
||||
|
||||
# 应用基函数
|
||||
backcast = self.basis_function(theta_b, is_forecast=False)
|
||||
forecast = self.basis_function(theta_f, is_forecast=True)
|
||||
|
||||
return backcast, forecast
|
||||
|
||||
|
||||
class GenericBasis(nn.Module):
|
||||
"""通用基函数"""
|
||||
|
||||
def __init__(self, backcast_size, forecast_size):
|
||||
super().__init__()
|
||||
self.backcast_size = backcast_size
|
||||
self.forecast_size = forecast_size
|
||||
|
||||
def forward(self, theta, is_forecast):
|
||||
if is_forecast:
|
||||
return theta[:, :self.forecast_size]
|
||||
else:
|
||||
return theta[:, :self.backcast_size]
|
||||
|
||||
|
||||
class TrendBasis(nn.Module):
|
||||
"""趋势基函数(多项式)"""
|
||||
|
||||
def __init__(self, degree, backcast_size, forecast_size):
|
||||
super().__init__()
|
||||
self.backcast_size = backcast_size
|
||||
self.forecast_size = forecast_size
|
||||
|
||||
# 多项式时间点
|
||||
backcast_time = torch.arange(backcast_size, dtype=torch.float32) / backcast_size
|
||||
forecast_time = torch.arange(forecast_size, dtype=torch.float32) / forecast_size
|
||||
|
||||
# 构建多项式基
|
||||
self.backcast_basis = torch.stack([backcast_time ** i for i in range(degree)], dim=1)
|
||||
self.forecast_basis = torch.stack([forecast_time ** i for i in range(degree)], dim=1)
|
||||
|
||||
def forward(self, theta, is_forecast):
|
||||
basis = self.forecast_basis if is_forecast else self.backcast_basis
|
||||
basis = basis.to(theta.device)
|
||||
return torch.matmul(theta, basis.T)
|
||||
|
||||
|
||||
class SeasonalityBasis(nn.Module):
|
||||
"""季节性基函数(傅里叶级数)"""
|
||||
|
||||
def __init__(self, harmonics, backcast_size, forecast_size):
|
||||
super().__init__()
|
||||
self.backcast_size = backcast_size
|
||||
self.forecast_size = forecast_size
|
||||
|
||||
backcast_time = 2 * np.pi * torch.arange(backcast_size, dtype=torch.float32) / backcast_size
|
||||
forecast_time = 2 * np.pi * torch.arange(forecast_size, dtype=torch.float32) / forecast_size
|
||||
|
||||
# 构建傅里叶基(sin和cos)
|
||||
backcast_basis = []
|
||||
forecast_basis = []
|
||||
for i in range(1, harmonics + 1):
|
||||
backcast_basis.extend([torch.cos(i * backcast_time), torch.sin(i * backcast_time)])
|
||||
forecast_basis.extend([torch.cos(i * forecast_time), torch.sin(i * forecast_time)])
|
||||
|
||||
self.backcast_basis = torch.stack(backcast_basis, dim=1)
|
||||
self.forecast_basis = torch.stack(forecast_basis, dim=1)
|
||||
|
||||
def forward(self, theta, is_forecast):
|
||||
basis = self.forecast_basis if is_forecast else self.backcast_basis
|
||||
basis = basis.to(theta.device)
|
||||
return torch.matmul(theta, basis.T)
|
||||
|
||||
|
||||
class NBeatsStack(nn.Module):
|
||||
"""N-BEATS栈(多个块的组合)"""
|
||||
|
||||
def __init__(self, input_size, forecast_size, num_blocks, block_layers,
|
||||
layer_size, theta_size, basis_function):
|
||||
super().__init__()
|
||||
self.blocks = nn.ModuleList([
|
||||
NBeatsBlock(
|
||||
input_size=input_size,
|
||||
theta_size=theta_size,
|
||||
basis_function=basis_function,
|
||||
layers=block_layers,
|
||||
layer_size=layer_size
|
||||
)
|
||||
for _ in range(num_blocks)
|
||||
])
|
||||
self.forecast_size = forecast_size
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Args:
|
||||
x: (batch_size, input_size)
|
||||
Returns:
|
||||
forecast: (batch_size, forecast_size)
|
||||
"""
|
||||
residual = x
|
||||
forecast = torch.zeros(x.size(0), self.forecast_size, device=x.device)
|
||||
|
||||
for block in self.blocks:
|
||||
backcast, block_forecast = block(residual)
|
||||
residual = residual - backcast
|
||||
forecast = forecast + block_forecast
|
||||
|
||||
return forecast
|
||||
|
||||
|
||||
class NBeatsConfig:
|
||||
"""N-BEATS模型配置"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_size=168, # 输入序列长度
|
||||
forecast_size=1, # 预测长度
|
||||
stacks=['trend', 'seasonality', 'generic'], # 使用的栈类型
|
||||
num_blocks_per_stack=3, # 每个栈的块数
|
||||
block_layers=4, # 每个块的FC层数
|
||||
layer_size=256, # FC层大小
|
||||
trend_degree=2, # 趋势多项式阶数
|
||||
seasonality_harmonics=4, # 季节性谐波数
|
||||
):
|
||||
self.input_size = input_size
|
||||
self.forecast_size = forecast_size
|
||||
self.stacks = stacks
|
||||
self.num_blocks_per_stack = num_blocks_per_stack
|
||||
self.block_layers = block_layers
|
||||
self.layer_size = layer_size
|
||||
self.trend_degree = trend_degree
|
||||
self.seasonality_harmonics = seasonality_harmonics
|
||||
|
||||
|
||||
class NBeats(nn.Module):
|
||||
"""N-BEATS模型"""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
|
||||
# 创建多个栈
|
||||
self.stacks = nn.ModuleList()
|
||||
|
||||
for stack_type in config.stacks:
|
||||
if stack_type == 'trend':
|
||||
basis_function = TrendBasis(
|
||||
degree=config.trend_degree,
|
||||
backcast_size=config.input_size,
|
||||
forecast_size=config.forecast_size
|
||||
)
|
||||
theta_size = config.trend_degree
|
||||
|
||||
elif stack_type == 'seasonality':
|
||||
basis_function = SeasonalityBasis(
|
||||
harmonics=config.seasonality_harmonics,
|
||||
backcast_size=config.input_size,
|
||||
forecast_size=config.forecast_size
|
||||
)
|
||||
theta_size = 2 * config.seasonality_harmonics
|
||||
|
||||
else: # generic
|
||||
basis_function = GenericBasis(
|
||||
backcast_size=config.input_size,
|
||||
forecast_size=config.forecast_size
|
||||
)
|
||||
theta_size = max(config.input_size, config.forecast_size)
|
||||
|
||||
stack = NBeatsStack(
|
||||
input_size=config.input_size,
|
||||
forecast_size=config.forecast_size,
|
||||
num_blocks=config.num_blocks_per_stack,
|
||||
block_layers=config.block_layers,
|
||||
layer_size=config.layer_size,
|
||||
theta_size=theta_size,
|
||||
basis_function=basis_function
|
||||
)
|
||||
self.stacks.append(stack)
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Args:
|
||||
x: (batch_size, lookback, features) 或 (batch_size, lookback)
|
||||
Returns:
|
||||
forecast: (batch_size, forecast_size)
|
||||
"""
|
||||
# 如果输入是多特征,取最后一个特征(close price)
|
||||
if len(x.shape) == 3:
|
||||
x = x[:, :, -1] # (batch_size, lookback)
|
||||
|
||||
# 确保是2D
|
||||
if len(x.shape) == 1:
|
||||
x = x.unsqueeze(0)
|
||||
|
||||
# 通过所有栈
|
||||
forecast = torch.zeros(x.size(0), self.config.forecast_size, device=x.device)
|
||||
residual = x
|
||||
|
||||
for stack in self.stacks:
|
||||
stack_forecast = stack(residual)
|
||||
forecast = forecast + stack_forecast
|
||||
# 更新残差(用于下一个栈)
|
||||
# 注意:这里简化了,标准N-BEATS会从每个栈获取backcast
|
||||
|
||||
return forecast.squeeze(-1) # (batch_size,)
|
||||
|
||||
def count_parameters(self):
|
||||
"""统计模型参数数量"""
|
||||
return sum(p.numel() for p in self.parameters() if p.requires_grad)
|
||||
|
||||
def save(self, path):
|
||||
"""保存模型"""
|
||||
torch.save({
|
||||
'model_state_dict': self.state_dict(),
|
||||
'config': self.config
|
||||
}, path)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, device='cpu'):
|
||||
"""加载模型"""
|
||||
checkpoint = torch.load(path, map_location=device)
|
||||
model = cls(checkpoint['config'])
|
||||
model.load_state_dict(checkpoint['model_state_dict'])
|
||||
return model
|
||||
Reference in new issue
Block a user