上篇基于《AI系列研究之二:多模型集成量价Alpha策略》理论分析
本篇文章会对其中各部分进行代码研究
项目需求分析与技术架构设计
业务需求梳理
项目的核心目标是构建一个多模型股票预测系统,具体需求包括:
数据层面的要求:
- 股票池:全A股票市场,但需要剔除ST、*ST股票、退市股票以及上市不满三个月的新股
- 数据源:使用数据库中的日线量价数据,包含高开低收价格、成交量以及市值信息
- 预测目标:T+1日至T+11日的复权日内VWAP价格收益率
- 数据预处理:采用3倍MAD截断、zscore标准化、缺失值填充为0的处理策略
模型设计要求:
- MLP模型:学习率1e-3,3层隐藏层,每层128个神经元,dropout率0.05,最大训练轮数1000轮
- GBDT模型:支持XGBoost和LightGBM两种实现,学习率1e-2,最大树深64,最大叶子数512
- GRU&AGRU模型:学习率1e-3,2层隐藏层,序列长度30,dropout率0.1,最大训练轮数200轮
系统功能要求:
- 滚动训练模式,训练数据范围2011年10月1日至2023年8月1日
- 支持多种损失函数:MSE、IC损失、CCC损失
- 实现完整的回测系统,对比沪深300、中证500、中证1000基准
- 支持模型保存、加载和继续训练功能
技术架构设计思路
面对如此复杂的需求,我采用了模块化的设计思路:
数据层设计:建立统一的数据库接口,封装股票数据获取、特征工程、数据预处理等功能。这样做的好处是可以确保不同模型使用相同的数据处理逻辑,避免因数据不一致导致的问题。
模型层设计:为每种算法创建独立的类,但保持统一的接口设计。所有模型类都实现相同的训练、预测、评估方法,便于后续的模型比较和集成。
优化层设计:考虑到训练数据量庞大(全市场5000多只股票,多年历史数据),必须从一开始就考虑性能优化问题。设计了缓存机制、多线程处理、GPU加速等优化策略。
实现过程中的问题
1、中文显示问题
项目开始时遇到的第一个问题看似简单,但却反映了开发环境配置的重要性。图表无法显示中文字符,这个问题虽然不影响模型核心功能,但会严重影响结果的可读性。
问题分析:
matplotlib默认字体不支持中文字符显示,需要设置中文字体。
解决方案:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
思考过程:这个问题提醒我,在开发机器学习系统时,用户体验同样重要。一个功能完善但结果难以理解的系统,其实用价值会大打折扣。
2、数据质量控制
在模型开发过程中,数据质量问题逐渐显现。发现预测结果中包含了退市股票,这引发了对数据筛选逻辑的重新审视。
问题分析:
- 数据库中包含了已退市的股票数据
- 股票筛选逻辑不够完善,没有有效识别和排除问题股票
- 缺乏对数据完整性的验证机制
解决方案:
def get_all_stocks(self, exclude_st=True, exclude_delisted=True):
"""获取所有股票代码,排除ST和退市股票"""
query = """
SELECT DISTINCT stock_id
FROM stock_info
WHERE 1=1
"""
if exclude_st:
query += " AND stock_name NOT LIKE '%ST%'"
query += " AND stock_name NOT LIKE '%*ST%'"
if exclude_delisted:
# 排除退市股票的逻辑
query += " AND stock_id NOT IN (SELECT stock_id FROM delisted_stocks)"
return self.execute_query(query)
深入思考:这个问题让我意识到,在量化投资领域,数据的准确性和完整性比算法的复杂性更为重要。一个基于错误数据训练的模型,无论算法多么先进,都无法产生有价值的结果。
3、模型训练不收敛问题
最令人困惑的问题出现在模型训练阶段。发现经过26个epoch的训练,损失函数值几乎没有变化,始终在1.0左右徘徊。
现象描述:
- 训练损失:1.002050 → 1.001862 → 0.999455
- 验证损失:1.003484 → 1.002537 → 1.000537
- 损失值变化极小,模型似乎无法学习到有效的模式
问题分析思路:
- 学习率问题:学习率过小可能导致收敛缓慢,过大可能导致无法收敛
- 模型容量问题:模型可能过于简单,无法捕捉数据中的复杂模式
- 数据预处理问题:特征标准化可能存在问题
- 损失函数设计问题:MSE损失可能不适合当前的预测任务
解决尝试:
首先调整了学习率策略:
# 使用可变学习率
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10)
然后尝试修改模型架构:
# 增加模型复杂度
self.gru = nn.GRU(input_size=6, hidden_size=128, num_layers=2,
dropout=0.1, batch_first=True)
self.attention = nn.MultiheadAttention(embed_dim=128, num_heads=8)
反思:这个问题的持续存在促使我重新审视整个建模流程。模型不收敛往往不是算法问题,而是数据问题的表现。
4、GPU内存管理
随着模型复杂度的增加和数据量的扩大,GPU内存不足问题频繁出现。
错误信息:
RuntimeError: CUDA out of memory. Tried to allocate 132.17 GiB
(GPU 0; 23.99 GiB total capacity; 13.18 GiB already allocated)
问题分析:
- 批次大小设置过大,超出GPU内存限制
- 没有及时清理GPU缓存
- 模型在评估时没有采用分批处理策略
解决方案:
实现动态批次大小调整:
def get_optimal_batch_size(self, total_samples, max_memory_gb=8):
"""根据GPU内存动态调整批次大小"""
if torch.cuda.is_available():
gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1e9
max_batch_size = min(total_samples, int(gpu_memory * 0.8 / max_memory_gb * 1000))
else:
max_batch_size = min(total_samples, 1000)
return max_batch_size
添加内存清理机制:
def clear_gpu_cache(self):
"""清理GPU缓存"""
if torch.cuda.is_available():
torch.cuda.empty_cache()
gc.collect()
思考总结:GPU内存管理是深度学习项目中的常见问题。通过动态调整批次大小和及时清理缓存,可以在有限的硬件资源下训练更大规模的模型。
5、数据处理性能优化
批次数据准备过程需要10多个小时,这严重影响了开发效率。
性能瓶颈分析:
- 对每个交易日都重复查询数据库
- 特征计算没有充分利用向量化操作
- 缺乏有效的缓存机制
- 串行处理导致CPU利用率不足
优化策略:
数据预加载:
def preload_all_data(self):
"""预先加载所有需要的股票数据"""
print("预加载股票数据...")
query = """
SELECT stock_id, trade_date, open, high, low, close, volume, market_cap
FROM stock_daily_data
WHERE trade_date BETWEEN ? AND ?
ORDER BY stock_id, trade_date
"""
self.all_data = pd.read_sql(query, self.conn,
params=[self.start_date, self.end_date])
self.all_data = self.all_data.set_index(['stock_id', 'trade_date'])
向量化特征计算:
def calculate_features_vectorized(self, data):
"""使用pandas向量化操作计算特征"""
features = pd.DataFrame()
# 价格特征
features['return_1d'] = data.groupby('stock_id')['close'].pct_change()
features['return_5d'] = data.groupby('stock_id')['close'].pct_change(5)
# 技术指标
features['rsi'] = data.groupby('stock_id').apply(
lambda x: self.calculate_rsi(x['close'])
)
return features
多线程处理:
def prepare_batches_parallel(self, dates, num_workers=4):
"""使用多线程并行处理批次数据"""
with ThreadPoolExecutor(max_workers=num_workers) as executor:
futures = [executor.submit(self.prepare_single_batch, date)
for date in dates]
batches = []
for future in tqdm(concurrent.futures.as_completed(futures),
total=len(futures)):
batch = future.result()
if batch is not None:
batches.append(batch)
return batches
效果评估:通过这些优化,数据处理时间从10多个小时缩短到2-3个小时,性能提升显著。
6、模型保存与继续训练
为了支持长时间的训练任务,需要实现模型检查点保存和继续训练功能。
需求分析:
- 支持训练中断后从断点继续
- 保存最佳模型和定期检查点
- 兼容不同的模型类型(TensorFlow和PyTorch)
实现方案:
def save_checkpoint(self, model, optimizer, epoch, loss, filepath):
"""保存训练检查点"""
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
'timestamp': datetime.now().isoformat()
}
torch.save(checkpoint, filepath)
def load_checkpoint(self, filepath, model, optimizer=None):
"""加载训练检查点"""
checkpoint = torch.load(filepath)
model.load_state_dict(checkpoint['model_state_dict'])
if optimizer:
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
return checkpoint['epoch'], checkpoint['loss']
核心问题的最终诊断
经过多轮优化和调试,模型训练和系统性能问题都得到了解决,但最终的回测结果却令人震惊:
异常结果:
- 总收益率:1,983,622.875%(近200万倍收益)
- 夏普比率:极高的异常值
- 最大回撤:负值,这在逻辑上不合理
问题追踪过程:
初始怀疑是回测代码的计算错误,但深入分析后发现问题的根源在数据层面。
数据诊断结果:
实际收益率统计:
- 最小值: -5.509762
- 最大值: 11.567825
- 均值: 0.000123
- 标准差: 0.234567
问题根源分析:
-
收益率计算错误:正常的股票日收益率应在-10%到10%之间(考虑涨跌停限制),而数据中出现了-550%到1156%的极端值,这明显不合理。
-
数据预处理问题:
- 可能在计算收益率时使用了错误的公式
- 标准化处理可能放大了异常值的影响
- 缺失值处理策略可能引入了错误数据
-
特征工程缺陷:
- 没有对计算出的特征进行合理性检查
- 缺乏异常值检测和处理机制
- 特征标准化的参数可能不合适
解决思路:
打算重构项目代码,做一些单元检测,从数据层开始实现完整的日志系统,只有地基牢固了,后续才会更轻松
经验总结与反思
技术层面的收获
-
数据质量的重要性:这个项目最大的教训是,再精妙的算法也无法弥补数据质量的缺陷。在项目初期投入足够的时间进行数据清洗和验证,比后期的算法优化更为重要。
-
性能优化的系统性思考:性能优化不应该是事后的补救措施,而应该在系统设计之初就予以考虑。向量化计算、缓存机制、并行处理等优化策略需要有机结合。
-
错误处理的重要性:在复杂的机器学习流水线中,错误处理和异常检测机制至关重要。及时发现和报告异常,可以避免错误的累积和传播。
工程实践的思考
-
模块化设计的价值:通过模块化设计,我们可以独立地测试和优化系统的各个组件,这大大提高了问题定位和解决的效率。
-
版本控制和实验管理:在模型开发过程中,保持良好的版本控制习惯,记录每次实验的参数和结果,对于追踪问题和复现结果非常重要。
-
渐进式开发策略:与其一开始就构建复杂的系统,不如采用渐进式的开发策略。先实现基本功能,确保其正确性,再逐步增加复杂特性。
量化投资领域的特殊考虑
-
金融数据的特殊性:金融市场数据具有噪声大、非平稳、存在结构性变化等特点。传统的机器学习方法可能需要针对这些特点进行调整。
-
风险控制的重要性:在量化投资中,风险控制往往比收益优化更为重要。模型的稳定性和可解释性应该得到足够的重视。
-
回测的陷阱:回测结果可能受到数据泄露、生存偏差、过拟合等因素的影响。需要建立严格的回测框架来确保结果的可靠性。
结论
该次实战结果与文章数据不匹配,不确定是哪里问题,但意识到了初版代码设计的不合理,后续会从头重构一版更加模块化的工程代码。