如何加速或Python化5层嵌套for循环实现的行星光度参数空间搜索
(注:我已查阅本站其他相关问题,认为其不适用于我的具体场景,若有疏漏深表歉意。)
问题背景
我需要推导行星天体特定位置的光度参数,该问题为强非线性问题,各光度参数间存在非线性相互作用。我已将问题简化为5个核心参数,经咨询领域专家确认,该方程组无解析解,业内均采用参数空间遍历的方法求解。整体方案为先进行大范围参数空间搜索,待参数收敛后再缩小搜索范围(该逻辑暂未写入下方代码片段)。
由于需要遍历5维参数空间,我编写了5层嵌套for()循环实现该逻辑。每次最内层循环迭代时,会将计算结果与实测数据对比,判断本次计算的RMS(均方根)是否小于此前的最优值,若是则保存当前对应的参数。
问询内容
是否有方法可以为该代码提速?当前代码运行速度极慢,甚至慢于我此前使用的Igor Pro语言,但由于我的其余代码均为Python编写且Igor Pro无法被Python调用,因此需要基于Python实现优化。我暂时没有想到将代码“Python化”的思路,因为每组唯一参数值都对应大量数学计算,且每次迭代都需要执行校验逻辑。我能想到的唯一其他优化思路是在每次迭代时粗化参数空间,例如减少示例中10^5量级的迭代次数,但这样做存在遗漏敏感局部极小值的风险。
现有实现代码
import math # 第一轮迭代:先使用大范围参数区间进行搜索 parameter_w = 0.0 parameter_b0 = 0.0 parameter_h = 1E-10 parameter_b = 0.0 parameter_c = 0.0 iterator_w = 0.10 iterator_b0 = 0.25 iterator_h = 0.10 iterator_b = 0.10 iterator_c = 0.10 maxits_w = 10+1 maxits_b0 = 10+1 maxits_h = 10+1 maxits_b = 10+1 maxits_c = 10+1 lastbest = 9999 # 执行参数空间搜索 for counter_c in range(0,maxits_c): parameter_c_test = parameter_c + iterator_c*counter_c for counter_b in range(0,maxits_b): parameter_b_test = parameter_b + iterator_b*counter_b for counter_h in range(0,maxits_h): parameter_h_test = parameter_h + iterator_h*counter_h for counter_b0 in range(0,maxits_b0): parameter_b0_test = parameter_b0 + iterator_b0*counter_b0 for counter_w in range(0,maxits_w): parameter_w_test = parameter_w + iterator_w*counter_w Vgamma = math.sqrt(1-parameter_w_test) SurfaceRoughnessFunction = 1 # 当粗糙度参数theta为0时该值取1 # 修正后的入射角和出射角(参考Hapke 2002年论文公式2上方定义) mu0 = [math.cos(Incidence[i]*3.1415926/180.) for i in range(len(Incidence))] mu = [math.cos(Emission[i] *3.1415926/180.) for i in range(len(Emission ))] # 系数...归一化因子? part1 = [parameter_w_test / 4. * mu0[i] / (mu0[i] + mu[i]) for i in range(len(DN))] # 阴影隐藏冲日效应(SHOE)——参考Hapke 2002年论文公式28和29 part2 = [1 + parameter_b0_test / (1 + math.tan(Phase[i]/2.*3.1415926/180.)/parameter_h_test) for i in range(len(DN))] # 双Henyey-Greenstein函数 part3 = [(1+parameter_c_test)*(1-parameter_b_test**2) / (2* (1+2*parameter_b_test*math.cos(Phase[i]*3.1415926/180.)+parameter_b_test**2)**(1.5) ) + (1-parameter_c_test)*(1-parameter_b_test**2) / (2* (1-2*parameter_b_test*math.cos(Phase[i]*3.1415926/180.)+parameter_b_test**2)**(1.5) ) for i in range(len(DN))] # H函数——参考Hapke 2002年论文公式2 part4 = [(1+2*mu0[i]) / (1+2*Vgamma*mu0[i]) * (1+2*mu[i]) / (1+2*Vgamma*mu[i]) for i in range(len(DN))] # 参考Hapke 2002年论文公式38 DN_model = [DN[i] - part1[i] * ( part2[i]*part3[i] + part4[i] - 1) * SurfaceRoughnessFunction for i in range(len(DN))] MS = 0 for i in range(len(DN)): MS += DN_model[i]**2 RMS = math.sqrt(MS/len(DN)) if RMS < lastbest: parameter_w_best = parameter_w_test parameter_b0_best= parameter_b0_test parameter_h_best = parameter_h_test parameter_b_best = parameter_b_test parameter_c_best = parameter_c_test lastbest = RMS print(RMS, parameter_w_best, parameter_b0_best, parameter_h_best, parameter_b_best, parameter_c_best)
优化方案
以下优化方案完全保留你原有的参数搜索逻辑,不需要粗化参数空间,不会有遗漏局部极小值的风险,可实现数倍到上百倍的提速:
- 优先提取所有不变量到循环外:你现有代码中每次最内层循环都会重复计算
mu0、mu、相位角的三角函数值等和迭代参数完全无关的量,这部分属于无效重复开销,直接提前计算即可:import numpy as np # 提前把所有输入转成numpy数组,一次性计算所有常量 Incidence_rad = np.deg2rad(Incidence) Emission_rad = np.deg2rad(Emission) Phase_rad = np.deg2rad(Phase) mu0_const = np.cos(Incidence_rad) mu_const = np.cos(Emission_rad) tan_half_phase_const = np.tan(Phase_rad / 2) cos_phase_const = np.cos(Phase_rad) DN_arr = np.array(DN) n = len(DN_arr) - 替换内层Python循环为numpy向量化运算:原有代码中每个
part的列表推导、RMS计算都是Python级别的循环,换成numpy的向量化运算后,底层会用C语言执行计算,速度可以提升10~100倍,取决于你的数据量:# 内层循环的计算逻辑替换为向量化版本 Vgamma = np.sqrt(1 - parameter_w_test) SurfaceRoughnessFunction = 1 part1 = parameter_w_test / 4. * mu0_const / (mu0_const + mu_const) part2 = 1 + parameter_b0_test / (1 + tan_half_phase_const / parameter_h_test) term1 = (1 + parameter_c_test) * (1 - parameter_b_test**2) / (2 * (1 + 2 * parameter_b_test * cos_phase_const + parameter_b_test**2)**1.5) term2 = (1 - parameter_c_test) * (1 - parameter_b_test**2) / (2 * (1 - 2 * parameter_b_test * cos_phase_const + parameter_b_test**2)**1.5) part3 = term1 + term2 part4 = (1 + 2 * mu0_const) / (1 + 2 * Vgamma * mu0_const) * (1 + 2 * mu_const) / (1 + 2 * Vgamma * mu_const) DN_model = DN_arr - part1 * (part2 * part3 + part4 - 1) RMS = np.sqrt(np.mean(DN_model ** 2)) - 用Numba编译整个循环逻辑:如果后续缩小参数范围后迭代次数大幅提升,可以给整个参数搜索函数加上
numba.njit装饰器,把Python代码直接编译成机器码,还能再提升几倍速度,避免Python嵌套循环的开销。 - 多核并行计算:由于每组参数的计算完全独立,没有依赖关系,你可以先生成所有待测试的参数组合,用多进程并行计算每组的RMS,直接拉满CPU多核性能,N核CPU就能获得接近N倍的提速。
内容的提问来源于stack exchange,提问作者Stuart Robbins
相关产品推荐
相关产品推荐

