如何用Multiprocessing或MPI并行化加速非线性回归代码?
两种并行化改造方案:Multiprocessing(单机)& MPI(分布式)
你的串行代码瓶颈在于每个epoch里的求和计算,当数据量较大时,可通过并行拆分数据分片、分散计算求和任务再汇总的方式加速。以下是两种可行的改造方案:
方案一:Multiprocessing 单机多核心加速
适合单机多CPU核心场景,利用Python内置的multiprocessing模块实现进程级并行,无需额外依赖。
改造后代码
import pandas as pd import multiprocessing as mp # 定义子进程计算函数:负责单分片的求和项计算 def compute_partial_sums(args): X_chunk, Y_chunk, a, b, c = args Y_pred = a * X_chunk * X_chunk + b * X_chunk + c diff = Y_chunk - Y_pred sum_a = sum(X_chunk * X_chunk * diff) sum_b = sum(X_chunk * diff) sum_c = sum(diff) return (sum_a, sum_b, sum_c) if __name__ == "__main__": # 加载数据并转为numpy数组(比pandas Series计算更高效) data = pd.read_csv('....csv') X = data.iloc[:, 0].values Y = data.iloc[:, 1].values # 初始化模型参数 a, b, c = 0.0, 0.0, 0.0 L = 0.0001 epochs = 10000 n = float(len(X)) num_processes = mp.cpu_count() # 使用全部可用CPU核心 # 拆分数据为多个分片,对应进程数 chunk_size = len(X) // num_processes data_chunks = [] for i in range(num_processes): start = i * chunk_size end = start + chunk_size if i != num_processes-1 else len(X) data_chunks.append((X[start:end], Y[start:end])) # 创建进程池,避免频繁创建销毁进程的开销 pool = mp.Pool(num_processes) # 梯度下降主循环 for _ in range(epochs): # 给每个进程分配当前参数和数据分片 tasks = [(chunk[0], chunk[1], a, b, c) for chunk in data_chunks] # 并行计算各分片的求和结果 partial_results = pool.map(compute_partial_sums, tasks) # 汇总所有分片的结果,计算全局梯度 total_sum_a = sum(res[0] for res in partial_results) total_sum_b = sum(res[1] for res in partial_results) total_sum_c = sum(res[2] for res in partial_results) # 更新模型参数 D_a = (-2/n) * total_sum_a D_b = (-2/n) * total_sum_b D_c = (-2/n) * total_sum_c a -= L * D_a b -= L * D_b c -= L * D_c # 输出最终参数 print(a, b, c) pool.close() pool.join() # 生成预测结果 Y_pred = a * X * X + b * X + c
关键说明
- 必须在
if __name__ == "__main__":代码块内执行主逻辑,避免multiprocessing的导入冲突问题 - 将pandas Series转为numpy数组,大幅提升数值计算效率
- 进程池复用进程,避免频繁创建销毁进程的额外开销
方案二:MPI 分布式多机器加速
适合多机器集群场景,需先安装mpi4py库(pip install mpi4py),通过MPI协议实现跨机器进程通信。
改造后代码
import pandas as pd from mpi4py import MPI # 初始化MPI通信环境 comm = MPI.COMM_WORLD rank = comm.Get_rank() # 当前进程编号(主进程为0) size = comm.Get_size() # 总进程数 # 主进程加载数据,从进程等待接收 if rank == 0: data = pd.read_csv('....csv') X = data.iloc[:, 0].values Y = data.iloc[:, 1].values n = len(X) else: X = None Y = None n = None # 广播全局参数:数据长度、初始参数、学习率、迭代次数 n = comm.bcast(n, root=0) a = comm.bcast(0.0, root=0) b = comm.bcast(0.0, root=0) c = comm.bcast(0.0, root=0) L = comm.bcast(0.0001, root=0) epochs = comm.bcast(10000, root=0) # 拆分数据到各个进程 chunk_size = n // size start = rank * chunk_size end = start + chunk_size if rank != size-1 else n X_chunk = comm.scatter(X, root=0) if rank != 0 else X[start:end] Y_chunk = comm.scatter(Y, root=0) if rank != 0 else Y[start:end] # 梯度下降主循环 for _ in range(epochs): # 广播当前最新的模型参数到所有进程 a = comm.bcast(a, root=0) b = comm.bcast(b, root=0) c = comm.bcast(c, root=0) # 计算当前分片的求和项 Y_pred = a * X_chunk * X_chunk + b * X_chunk + c diff = Y_chunk - Y_pred sum_a = sum(X_chunk * X_chunk * diff) sum_b = sum(X_chunk * diff) sum_c = sum(diff) # 收集所有进程的部分求和结果到主进程 partial_sums = (sum_a, sum_b, sum_c) all_sums = comm.gather(partial_sums, root=0) # 主进程汇总结果并更新参数 if rank == 0: total_sum_a = sum(s[0] for s in all_sums) total_sum_b = sum(s[1] for s in all_sums) total_sum_c = sum(s[2] for s in all_sums) D_a = (-2.0 / n) * total_sum_a D_b = (-2.0 / n) * total_sum_b D_c = (-2.0 / n) * total_sum_c a -= L * D_a b -= L * D_b c -= L * D_c # 主进程输出最终参数并生成预测 if rank == 0: print(a, b, c) Y_pred = a * X * X + b * X + c
运行方式
通过MPI命令启动多进程(示例为4个进程):
mpiexec -n 4 python your_script.py
关键说明
- 主进程(rank=0)负责数据加载、参数更新和结果汇总,从进程仅负责分片计算
- 用
bcast广播全局参数,scatter分发数据分片,gather收集计算结果
额外优化建议
- 改用numpy向量化求和:将代码中的
sum()替换为np.sum(),即使串行也能大幅提升计算速度 - 数据规模判断:如果数据量小于百万级,并行化的进程通信开销可能超过收益,建议保持串行
- 小批量梯度下降:若数据量极大,可改为小批量梯度下降(Mini-batch GD),天然适配并行计算逻辑
内容的提问来源于stack exchange,提问作者Telecaster
相关产品推荐
相关产品推荐

