Files
crpto_predictor/diagnose_simple.py
T
crypto_predictorandClaude Sonnet 4.5 9bbdc8d42b Initial commit: BTC 1h prediction model (53.10% accuracy)
🎯 项目成果:
- 准确率: 53.10% (2026年测试集)
- 任务类型: 二分类 (预测涨/跌)
- 特征: OHLCV + RSI + MACD (9个特征)
- 训练数据: 2020-2025年BTC小时数据 (52,351序列)
- 测试数据: 2026年1-29日 (484序列)

📊 性能指标:
- 精确率: 51.79%
- 召回率: 49.36%
- F1分数: 0.5054
- 预测分布: 非常平衡 (偏差仅-11个)

🔬 实验历史:
- 尝试了5种方案 (回归/分类, 不同数据划分)
- 分类任务优于回归任务 (53.10% vs 49.5%)
- 全量训练避免了过拟合问题

📦 主要模型文件:
- models/checkpoints/dlinear_classification_2026/
  - best_model.pt (最佳模型)
  - preprocessor.pkl (预处理器)
  - test_2026_results.json (测试结果)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-29 21:27:15 +08:00

135 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""简化版模型诊断(无需matplotlib)"""
import torch
import numpy as np
from models.dlinear.model import DLinear, DLinearConfig
from data.fetcher import BinanceDataFetcher
from data.preprocessor import CryptoPreprocessor, split_data
from data.dataset import create_dataloaders
def diagnose():
print("="*80)
print("🔍 模型预测偏差诊断报告")
print("="*80)
# 加载模型
print("\n[1/4] 加载已训练模型...")
model_path = './models/checkpoints/dlinear_btc_1h_balanced/best_model.pt'
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
checkpoint = torch.load(model_path, map_location=device, weights_only=False)
config = checkpoint['config']
model = DLinear(config)
model.load_state_dict(checkpoint['model_state_dict'])
model = model.to(device)
model.eval()
print(f"✓ 模型配置: task={config.task}, hidden_dim={config.hidden_dim}")
# 准备数据
print("\n[2/4] 加载测试数据...")
fetcher = BinanceDataFetcher()
df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True)
preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24, price_change_threshold=0.0)
X, y, _ = preprocessor.preprocess_pipeline(df, task='classification', fit=True)
_, _, test_data = split_data(X, y, train_split=0.7, val_split=0.15)
_, _, test_loader = create_dataloaders(test_data, test_data, test_data, batch_size=64)
# 收集输出
print("\n[3/4] 分析模型输出...")
all_outputs = []
all_labels = []
with torch.no_grad():
for batch_X, batch_y in test_loader:
batch_X = batch_X.to(device)
outputs = model(batch_X).squeeze()
all_outputs.extend(outputs.cpu().numpy().flatten())
all_labels.extend(batch_y.numpy().flatten())
all_outputs = np.array(all_outputs)
all_labels = np.array(all_labels)
# 统计分析
print("\n" + "="*80)
print("📊 关键发现")
print("="*80)
print(f"\n1️⃣ 模型输出统计(已经过Sigmoid):")
print(f" 最小值: {all_outputs.min():.6f}")
print(f" 最大值: {all_outputs.max():.6f}")
print(f" 均值: {all_outputs.mean():.6f} ← 应该接近0.5")
print(f" 中位数: {np.median(all_outputs):.6f}")
print(f" 标准差: {all_outputs.std():.6f}")
print(f"\n2️⃣ 预测阈值分析:")
for threshold in [0.5, 0.4, 0.3, 0.2, 0.1]:
count = (all_outputs > threshold).sum()
pct = (all_outputs > threshold).mean() * 100
print(f" 输出 > {threshold}: {count:4d} 个 ({pct:5.2f}%)")
print(f"\n3️⃣ 真实标签分布:")
n_down = (all_labels == 0).sum()
n_up = (all_labels == 1).sum()
print(f" 下跌(0): {n_down} ({n_down/len(all_labels)*100:.1f}%)")
print(f" 上涨(1): {n_up} ({n_up/len(all_labels)*100:.1f}%)")
# 验证双重sigmoid问题
print("\n" + "="*80)
print("🔴 问题诊断:双重Sigmoid Bug")
print("="*80)
print("\n📋 当前架构:")
print(" 模型输出层: self.output_activation = nn.Sigmoid() (models/dlinear/model.py:128)")
print(" 损失函数: BCEWithLogitsLoss() (models/base.py:74)")
print("\n⚠️ 问题:")
print(" BCEWithLogitsLoss 内部包含 Sigmoid,期望输入是 logits(未激活)")
print(" 但模型已经输出了 sigmoid(logits)")
print(" → 损失函数再次应用: sigmoid(sigmoid(logits)) ← 双重激活!")
print("\n🧪 双重Sigmoid效果演示:")
test_logits = np.array([-3, -1, 0, 1, 3])
single = 1 / (1 + np.exp(-test_logits))
double = 1 / (1 + np.exp(-single))
print(f" 原始logits: {test_logits}")
print(f" 单次sigmoid: {np.round(single, 4)}")
print(f" 双重sigmoid: {np.round(double, 4)} ← 全部压缩到~0.5")
print("\n💥 后果:")
print(f" - 模型输出均值仅 {all_outputs.mean():.1%}(期望~50%)")
print(f" - {(all_outputs>0.5).sum()} 个样本输出>0.5(共{len(all_outputs)}个)")
print(f" - 模型永远预测类别0(下跌),因为没有输出超过阈值0.5")
print(f" - 梯度消失,模型无法有效学习")
# 解决方案
print("\n" + "="*80)
print("✅ 修复方案")
print("="*80)
print("\n方案1(推荐):移除模型的Sigmoid输出")
print(" 文件: models/dlinear/model.py")
print(" 位置: 第127-130行")
print(" 修改前: if config.task == 'classification':")
print(" self.output_activation = nn.Sigmoid()")
print(" 修改后: if config.task == 'classification':")
print(" self.output_activation = nn.Identity() # 输出logits")
print(" 说明: 保持损失函数为BCEWithLogitsLoss")
print("\n方案2:更换损失函数(不推荐)")
print(" 文件: models/base.py")
print(" 位置: 第70-76行")
print(" 修改: 将 BCEWithLogitsLoss 改为 BCELoss")
print(" 说明: 保持模型输出Sigmoid,但BCELoss数值稳定性较差")
print("\n" + "="*80)
print("📌 结论")
print("="*80)
print("✓ 预测偏差的根本原因是【双重Sigmoid Bug】")
print("✓ 与数据处理、类别平衡、特征工程无关")
print("✓ 修复后需要重新训练模型")
print("="*80)
if __name__ == "__main__":
diagnose()