为了回测基于机器学习模型的交易策略,我希望能够并行计算模型的重新训练过程。
现在我的问题是:
有没有机会提高我的算法速度?
它是使用scikit-learn在Python中实现的。回测过程定义如下:
- 在前900个数据点(前三年)上训练模型
- 对下一天的t+1进行预测
- 在下一个数据点t+1上重新训练模型
- 再次用模型对t+2天进行预测
- 重新训练模型….
- 进行预测…
简单来说,就是对下一个数据点进行预测,然后在这个数据点上重新训练模型。一直这样做,直到当前天(最后一个数据点)。对于某些股票预测,这可能是多达5000个数据点,这意味着通过从最初的900个数据点开始训练模型,我需要重新训练和预测模型4100次。
为了并行化这个过程,我使用了多处理。我有机会使用一台提供40个CPU内核的服务器。所以我所做的是:
- 将4100个数据点分成40份
- 在每个内核上启动一个进程,运行一部分的过程
- 完成过程后,将结果写入磁盘
- 收集每个结果并汇总
Python代码:
pool = Pool()results = []for i in range(len(parts)): try: result = pool.apply_async(classify_parts,[parts[i], parts[i+1]]) results.append(result) except IndexError: continuefor result in results: result.get()
方法classify_parts启动给定范围的过程。例如,如果我有500个数据点,并且通过在100个数据点上训练模型开始整个回测,那么剩下的400个数据点(天)用于回测,这意味着:
- 将400个数据点分为40份 [10,20,30,…,380,390,400]
- 在每个内核上启动一个进程:
classify_parts( 10, 20 )
, … ,classify_parts( 390, 400 )
- 从磁盘收集结果并汇总
希望我能清晰地说明我的概念。
所以我最大的问题是,是否有更有效的方法来进行基于机器学习模型的回测,该模型在每个下一个数据点(天)上重新训练?因为用这种方法,对5000个数据点的回测过程需要超过10分钟。
也许增量/在线学习是这里的出路?
回答:
首先,这并不是并行化进程调度。
如果有疑问,阅读进一步内容前,可以重新审视一下关于串行、并行和仅并发进程调度的理论细节。
用简单英语表达
事情可能基于原则(意图)或某种资源限制而进入串行操作模式。如果一个人要在键盘上输入单词”parallel
“,字母p
、接下来a
、接下来r
…等必须按顺序(串行)触碰,否则输入过程无法提供保证正确的结果——即屏幕上的单词”parallel
“(为了这个例子的清晰度,我们假设忽略了使用Shift
键的可能性)。
任何人都可以测试,如果尝试同时按下所有键,或成对按下,或在键盘上有猫站着时会产生什么…欢迎任何额外的想法进行实践实验。
串行进程无法加速
在任何其他情况下,除了通过获取更快的处理资源和/或降低进程间传输延迟(或在存在已知的新、更智能的算法,且该算法对善良的程序员来说可行并能在可用资源上实现的情况下)。
什么是真正的并行进程?
另一方面,有些事情必须组织起来,以便在光谱的另一端发生——如果所有事情必须同时开始、发生和完成,否则进程的结果被认为是错误的。能举个例子吗?
-
想象几个视觉上简单易懂的例子——红箭飞行表演队——一组9架喷气式飞机,展示高难度的飞行表演和机动
或者Frecce Tricolori
所有飞机必须在同一时间以相同的方式移动,否则精彩的表演就会失败。
-
想象一场音乐会必须进行。这正是最终的
PARALLEL
进程,人们会立即认识到,如果不是这样会多么具有破坏性 -
想象一个多自由度机器人控制系统,其中所有运动必须在闭环[执行器 – 检测器 – 驱动器]子系统的同步、协调控制下进行,适用于所有几个编程控制的轴。系统必须作为一个并行进程一起控制所有数控轴——否则机器人手臂将永远无法跟随黑线——定义的、曲线的、时空轨迹在+4-D空间中(最好实时观看)。
机器人控制的例子显示,当我们从特技飞行或交响乐的例子中走得越远,对于我们有一些直接经验(或我们的想象力可以提供一些理解)的例子,并行进程调度可能看起来更难感知或实现,但交响乐似乎是最好的提醒,当我们质疑一个问题是否确实是真正的并行时。
只需问问自己,如果只有一半的乐队准时到达19:00开始演奏,或者如果一些小提琴以加速的速度演奏…或者如果演奏者只演奏一个音符,然后顺序地让他的/她的左侧邻居演奏他的/她的音符,等待演奏顺序回到同一个演奏者以演奏他的/她的下一个音符在音乐会上。没错,那将是纯粹的串行调度。)
那么,40个CPU核心能否加速我的问题?
问题仍然取决于处理过程。
如果学习方法(在未公开的MCVE中)内置了依赖性,例如如果classify_parts( 20, 30 )
需要从classify_parts( 10, 20 )
的“前一步”中获取一些输入或信息,那么答案是否定的。
如果学习方法在上述定义的意义上是独立的,那么就有机会将进程调度为“仅并发”,并从高速公路上有更多“自由”车道的事实中受益,这不仅允许车辆在一个车道上行驶,还允许一辆车超越另一辆较慢的车,在从起点到终点的路上。橄榄球队可以从城市A移动到城市B“更快”,如果在城市A的体育场有2辆或3辆或更多4座车可用,而不是只有一辆,它必须来回往返以成功地将所有球员移动到城市B。
接下来,即使有足够的车辆将所有球员移动到城市B,持续时间也会不舒服地高,如果从A到B的道路只有一条车道,并且从B到A的方向有高流量(不允许超车),而且有一辆拖拉机也从A到B行驶,但速度为1英里/小时。
警告:(如果在寻找真正快速的处理,更是为了最快可能的处理)
即便没有上述概述的“外部”障碍,人们也必须非常小心,不要失去从“更大”的机器+“更宽”的高速公路(更多可用资源)中获得的“仅并发”进程调度的优势。
是的,这里问题与编程工具有关。
Python在快速原型制作方面帮助很大,但要注意全局解释器锁(GIL)对代码执行的影响。
在你的情况下,使用40个线程的Pool()
可能看起来很有吸引力,但仍然会等待GIL的获取/释放,所有工作将再次变成SERIAL
,仅仅是因为忽略了在Python线程的Pool()
上已知的GIL机制。
是的,还有其他更有前景的替代方案,如ThreadPool()
等,但不仅是与代码相关的GIL步骤在破坏你加速进程的努力,而且共享数据结构也是需要注意和避免的问题。此外,Pool()
的简单复制环境可能会超出系统RAM容量,交换机制将使加速进程的尝试完全不可用。不共享任何东西,否则你加速处理的梦想将因共享机制而失去,你将回到起点1号方格。
最好的方法是将代码设计为独立进程,具有零共享、非阻塞(MEM, IO)操作。