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:
crypto_predictor committed 2026-02-03 20:40:32 +08:00
1 parent c74f4f6949
commit 1bd9d25f60
33 files changed
+6557 -233

No files matched your search

View File
Whitespace-only changes.
+265
View File
@@ -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