Sympy修改随机种子导致固定种子下运行结果不可复现如何解决
问题根因
你定位的方向是对的,问题出在sympy.parsing.sympy_parser.parse_expr的实现逻辑上:1.12版本之前的SymPy,解析字符串表达式时如果遇到未提前声明的符号,会动态生成临时符号对象,这个过程中会直接调用Python标准库random模块的接口,且没有做随机状态隔离,会直接打乱你提前设置的全局随机种子状态。而DEAP的选择、交叉、变异等核心算子全依赖全局random模块的随机序列,自然会出现固定种子也无法复现结果的问题。
修复方案
按改造成本从低到高排序:
- 升级SymPy版本:直接升级到1.12及以上正式版即可,官方在这个版本修复了解析器污染全局随机状态的问题,解析过程的随机逻辑全部切换到SymPy内部独立的随机数生成器,不会再修改全局random的状态,你原有设置固定种子的代码不需要任何改动就能实现结果复现。
- 手动隔离随机状态:如果因为项目依赖限制不能升级SymPy,可以在每次调用
parse_expr的前后手动保存、恢复全局随机状态,对现有业务逻辑侵入性极低,示例代码如下:
import random from sympy.parsing.sympy_parser import parse_expr # 初始化时设置固定种子 random.seed(42) # 调用解析函数前保存当前随机状态 saved_rand_state = random.getstate() target_expr = parse_expr("a*b + c**2 - 3") # 解析完成后立刻恢复随机状态 random.setstate(saved_rand_state) # 后续执行DEAP相关的所有进化逻辑,随机序列会保持完全一致
- 提前声明所有符号:如果不想每次调用解析函数都写状态保存恢复的逻辑,可以提前把表达式里会用到的所有SymPy符号定义好,调用
parse_expr时通过local_dict参数传入,解析器不需要动态生成临时符号,就不会触发调用随机数的逻辑,自然不会改动全局种子:
import random from sympy import symbols from sympy.parsing.sympy_parser import parse_expr random.seed(42) a, b, c = symbols("a b c") # 传入预定义的符号映射,解析过程无随机调用 target_expr = parse_expr("a*b + c**2 - 3", local_dict={"a": a, "b": b, "c": c})
验证方式
修复后可以写个极简测试用例:设置固定种子后调用一次parse_expr,连续生成5个random.random()的结果,连续运行两次脚本,如果两次输出的随机数完全一致,就说明随机状态没有被SymPy污染,后续DEAP的运行结果就可以稳定复现。
补充提示:如果你的DEAP代码里同时用到了numpy相关的随机算子,记得同步给numpy也设置相同的固定种子,避免其他随机源导致结果波动。
内容的提问来源于stack exchange,提问作者AnassBou
相关产品推荐
相关产品推荐

