Python环境中在线训练机器学习模型所遇到的问题及解决方案
我最近开发个智能控制系统,包括实时数据采集、预测、策略优化等功能,最近增加在线学习功能,也就是在线进行模型训练,在线进行模型训练时出现了问题,现象为:
控制台报:
- cmdstanpy - INFO - Chain [1] start processing
- 所有任务、线程停止,Web服务登录无法访问
- 后台的python进行仍在,CPU资源降低到10%以下(训练时都在70%到90%)
环境信息:
- CentOS操作系统
- Python 3.8
- apscheduler 任务管理
- python 线程
- python 进程 multiprocessing
- Flask提供Web服务
- 机器学习使用Scikit-learn,时序预测使用Prophet
1. 原因分析
1.1. 我的初步分析
直接原因是我在线学习功能设计忽略了资源争用的情景,只考虑到在线训练是偶尔发生的事。
1.2. 根本原因分析
再看看DeepSeek给出分析原因:
(1) CPU 资源耗尽
- Scikit-learn 和 Prophet 默认会占用所有可用的 CPU 核心(尤其是 n_jobs=-1 时)。
- 多线程任务(如 Flask 请求、APScheduler)因 CPU 争抢被饿死。
(2) 内存不足(OOM)
- 大规模数据训练时,内存被占满,触发系统 OOM Killer 杀死进程或频繁换页(Swapping),导致卡顿。
(3) Python 的 GIL(全局解释器锁)
- Prophet 和 Scikit-learn 的部分计算是 C++ 并行的,但 Python 线程仍受 GIL 限制,导致线程调度阻塞。
(4) 磁盘/文件 I/O 阻塞
- Prophet 会生成临时文件(如 Stan 模型的编译缓存),高负载时磁盘 I/O 成为瓶颈。
(5) 线程死锁
- 如果任务之间有共享资源的锁竞争(如数据库连接、文件读写),可能导致死锁。
2. 解决方案
2.1. 启动子进程进行训练
我在进行在线机器学习训练时,使用“multiprocessing.Process”,启动进程进行训练。
在使用 multiprocessing.Process 启动子进程进行机器学习训练时,确实可以避免 GIL 锁竞争 和 主线程阻塞 的问题。
2.1.1. 解决方案(兼容 Windows + Flask)
方法 1:将多进程代码封装到单独模块
(1) 创建独立模块 train_worker.py
# train_worker.py
from multiprocessing import Process, Queue
# 启动多进程训练
def run_train(model_type, model_param):"""子进程实际执行的函数"""if model_type == '24h':return LoadMLPModelTrain(model_param)elif model_type == 'realtime':return LoadRealTimeMLPModelTrain(model_param)def _worker_proc(q: Queue, model_type, model_param):"""替代 lambda 的全局函数"""result = run_train(model_type, model_param) # 调用实际训练函数q.put(result)def async_train(model_type, model_param):"""Flask 调用的接口"""result_queue = Queue()p = Process(target=_worker_proc, # 使用全局函数而非 lambdaargs=(result_queue, model_type, model_param))p.start()ret = result_queue.get() # 阻塞等待结果p.join()return ret