[原创] 结合大模型(LLM)的函数搜索器 FunctionEvolve 简单实测

系统:MacOS

Python版本:3.12

测试时间:2026-06-19

不知道大家有没有做过"从数据里猜测其符合什么样的y=f(x)数学公式"这样的事情?在很多年前没有AI的时代,这个领域基本是欧美软件的天下,后来出现了一个石破天惊的国产软件1stOpt( http://www.7d-soft.com/ )打破了它们的垄断,在当年算是国产软件在这个领域取得的重大成就。 巧了,这两天我刚好看到科技媒体报道了一个新的国产开源软件 FunctionEvolve ,正是和解决此类问题有关,于是我去了解了一下,并且拿它做了一些简单的测试,写成此文。
本站的关联文章链接:最优化/Optimization文章合集


在正文开始之前我想先闲聊一下,文中测试使用的大模型是DeepSeek V4 Flash,测试过程中总共用了30多万token,花费0.19元,由于任务的性质缓存命中率很低,但DS定价太便宜所以几乎没花钱。

下面开始正文。

FunctionEvolvehttps://github.com/Phoinikas03/FunctionEvolve )是一个符号回归(Symbolic Regression)搜索框架,核心目标是从数值数据中自动发现数学公式。它采用 LLM + 传统数值优化 的混合架构:LLM 负责分析领域知识、生成种子公式、选择父本和定向变异建议;传统优化器(DE / CMA-ES / L-BFGS-B / TRF)负责拟合公式中的参数。
文章来源:https://www.codelast.com/

同类软件一览

符号回归和自动公式发现领域已有不少成熟工具,按原理大致可分为两类:

类别 代表软件 国别 核心技术
传统全局优化 1stOpt 🇨🇳 国产 UGO(通用全局优化),无需初值
  MATLAB Curve Fitting 🇺🇸 Trust-Region / Levenberg-Marquardt
  OriginPro 🇺🇸 Levenberg-Marquardt / 全局优化
  DataFit 🇺🇸 多算法自动搜索
遗传编程 (GP) Eureqa 🇺🇸 遗传编程符号回归(最早商业化的 GP 工具)
  PySR 🌍 开源 遗传编程(Python 库)
  GPTIPS 🇬🇧 多基因遗传编程(MATLAB)
  gplearn 🌍 开源 遗传编程(Python 库)
LLM + 优化 FunctionEvolve 🌟 🌍 开源 LLM 指导搜索方向 + 数值优化器拟合参数

重点说明:1stOpt(七维高科)

我很多年以前用过几次 1stOpt,当时对它留下了深刻印象(不知道现在是否还在维护),所以这里单独介绍一下。

1stOpt 是国内 7D-Soft 开发的数值优化分析软件,在工程和科研领域有大量用户。它的核心特点是:

  • 无需人工初值 — 传统工具(如 MATLAB)做曲线拟合时,参数初值给不对就会发散或收敛到局部最优。1stOpt 内置 UGO(通用全局优化) 算法族,号称"万用公式,任意初值"。
  • 公式模板驱动 — 用户提供公式骨架(如 y = a*x^b + c*e^(d*x)),1stOpt 负责搜索最优参数。和 FunctionEvolve 相比,1stOpt 的公式结构需要人先指定,FunctionEvolve 则连结构也可以自动发现
  • 适用场景 — 曲线/曲面拟合、非线性回归、参数估计、微分方程求解、工程反演。
  • 局限 — 商业软件、闭源、Windows 独占。公式结构依赖用户经验,无法自动探索未知结构。

在 FunctionEvolve 中,大模型起什么作用

LLM 起的是"搜索方向指导"作用,数学计算由传统优化器完成。

FunctionEvolve 是 LLM + 传统数值优化 的混合架构,LLM 不做数学计算。


完整工作流

阶段 组件 是否用 LLM 作用
① 领域分析 Generator ✅ LLM 分析问题属于什么领域(物理/化学/生物...),给出该领域常见公式模板
② 种子生成 Generator ✅ LLM 基于领域知识,生成 20 个初始候选公式(带参数占位符 c0, c1...)
③ 参数拟合 StructureOptimizer 纯数值 VARPRO + DE + CMA-ES + L-BFGS-B,拟合 c0, c1... 的最优值
④ 评估 Evaluator 纯数值 计算 NMSE(归一化均方误差)
⑤ 选父本 Selector ✅ LLM 看当前进化树所有公式的 NMSE 和结构,决定下一轮"从哪些公式出发"
⑥ 结构变异 ASTMutator 规则引擎 程序化地删子树、加项、拆函数包装,生成候选变体
⑦ 结构建议 LLMMutator ✅ LLM "试试加个 sin 项"、"试试把 x² 换成 exp(x)"
⑧ 回到 ③ 循环 拟合 → 评估 → 选父本 → 变异 → 再拟合...

优化算法详情

所有参数拟合(阶段③)由 StructureOptimizer 执行,它是一个多策略流水线。

第三方库提供的基础算法(4 个)

算法 第三方来源 独立包装文件 StructureOptimizer 中的用途
L-BFGS-B scipy.optimize.minimize optimizer/lbfgs.py 局部精修(全局搜索后的细化);Pow 指数对齐后的重拟合
DE (差分进化) scipy.optimize.differential_evolution optimizer/de.py 全局搜索主路径之一(_run_de
CMA-ES cmaes optimizer/cma.py 全局搜索主路径之一(_run_cma);DE 的并行兜底
TRF (Trust Region Reflective) scipy.optimize.least_squares optimizer/least_squares.py 全局搜索路径之一(_run_trf

项目自身实现的策略(3 个)

策略 所在方法 (structure.py) 说明
① VARPRO (变量投影分解) _try_varpro 将参数分解为线性 + 非线性两组:线性组用 np.linalg.lstsq 直接 OLS 求解,非线性组用基础算法搜索。本质是降维策略
② Compound-Pow 预搜索 _try_pow_presearch 检测公式中 x^(c) 且底数含其他参数的情况,枚举候选有理指数(1, 2, 1/2, 1/3...),固定指数后用 VARPRO 拟合其余参数
③ Pow 指数对齐 _snap_pow_and_refit + _pow_rational_grid 将拟合出的浮点指数对齐到最接近的有理数/整数,再用 L-BFGS-B 重新拟合其他参数

StructureOptimizer 执行流水线

输入: 公式骨架 + 数据
  │
  ├─ ① Pow 预搜索 (Compound-Pow pre-search)
  │   枚举候选指数,固定后用 VARPRO 拟合其余参数
  │
  ├─ ② VARPRO 分解 (主路径)
  │   线性参数 → np.linalg.lstsq OLS 求解
  │   非线性参数 → DE / CMA / TRF 并行搜索
  │
  ├─ ③ 并行兜底 (DE / CMA / TRF 同时跑, 取最优)
  │
  ├─ ④ L-BFGS-B 局部精修
  │
  ├─ ⑤ Pow 指数对齐 & 重拟合
  │
  └─ 输出: 最优参数 + NMSE

所有路径均有超时保护多次重启机制。
文章来源:https://www.codelast.com/

关键源码位置

组件 文件
主入口 & 装配 main.py
搜索循环 src/search.pyTreeSearch.run()
LLM 生成器 src/generator.py
LLM 选择器 src/selector.py
LLM 变异器 src/mutator.py
AST 规则变异器 src/mutator.pyASTMutator 类)
参数优化器总管 src/optimizer/structure.pyStructureOptimizer
L-BFGS-B 优化器 src/optimizer/lbfgs.py
DE 优化器 src/optimizer/de.py
CMA-ES 优化器 src/optimizer/cma.py
Least-Squares 优化器 src/optimizer/least_squares.py
优化器基类 & 工具 src/optimizer/base.py
评估器 src/evaluator.py
进化树 src/evolution_tree.py
LLM 配置 llm_config.yaml

一句话总结

LLM 是"策略师"——告诉你该往哪个方向找公式;数学优化器是"计算器"——实际算出参数值。

LLM 不碰任何数值计算。所有参数拟合由 StructureOptimizerVARPRO → DE/CMA/TRF(并行全局搜索)→ L-BFGS-B(局部精修)→ Pow 指数对齐 的多层流水线完成。


完整测试流程

以下是从零开始创建测试数据、搭建环境、运行测试、清理环境的完整步骤记录。 

测试目标

验证 FunctionEvolve 能否从数值数据中自动发现复杂公式:

y = \frac{\sin(x_1 x_2)}{\cos(x_3) + 1.5} + e^{-x_2} \cdot \ln(|x_4| + 1) + \frac{x_1 x_3}{1 + x_5^2}

步骤一览

Step 1: 生成测试数据集
Step 2: 创建临时 Python 环境并安装依赖
Step 3: 编写测试脚本
Step 4: 运行测试
Step 5: 查看结果
Step 6: 删除临时环境

Step 1:生成测试数据集

cd /path/to/FunctionEvolve
python3 datasets/z/generate_complex_dataset.py --noise-std 0.02

生成程序完整源码

保存为 datasets/z/generate_complex_dataset.py

#!/usr/bin/env python3
"""
生成一个复杂公式的符号回归数据集,用于验证 FunctionEvolve 的效果。

公式(5 个输入变量,混合三角函数、指数、对数、有理分式):

    sin(x1 * x2)                              x1 * x3
y = ──────────── + exp(-x2) * ln(|x4| + 1) + ─────────
    cos(x3) + 1.5                             1 + x5²


输出:
  datasets/z/
    dataset.npz        — NumPy 压缩格式(直接用 from_arrays 加载)
    dataset.csv        — CSV 文本格式(肉眼查看用)
    ground_truth.txt   — 真实公式说明
    preview.txt        — 数据统计摘要
"""

from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np

GROUND_TRUTH_EXPRESSION = (
    "sin(x1 * x2) / (cos(x3) + 1.5) + exp(-x2) * log(abs(x4) + 1) + x1 * x3 / (1 + x5**2)"
)

GROUND_TRUTH_SYMBOLS = ["x1", "x2", "x3", "x4", "x5"]
RNG = np.random.RandomState(42)


def _safe_log_abs(x):
    return np.log(np.abs(x) + 1.0)


def ground_truth_y(X):
    x1, x2, x3, x4, x5 = X[:, 0], X[:, 1], X[:, 2], X[:, 3], X[:, 4]
    term1 = np.sin(x1 * x2) / (np.cos(x3) + 1.5)
    term2 = np.exp(-x2) * _safe_log_abs(x4)
    term3 = x1 * x3 / (1.0 + x5**2)
    return term1 + term2 + term3


def main():
    parser = argparse.ArgumentParser(description="生成复杂符号回归数据集")
    parser.add_argument("--n-train", type=int, default=2000)
    parser.add_argument("--n-test", type=int, default=500)
    parser.add_argument("--noise-std", type=float, default=0.0)
    parser.add_argument("--output-dir", type=str, default=None)
    args = parser.parse_args()

    out_dir = Path(args.output_dir) if args.output_dir else Path(__file__).resolve().parent
    out_dir.mkdir(parents=True, exist_ok=True)

    # 生成训练集
    X_train = RNG.uniform(low=-3.0, high=3.0, size=(args.n_train, 5))
    y_train = ground_truth_y(X_train)
    if args.noise_std > 0:
        y_train += RNG.normal(0, args.noise_std, size=args.n_train)

    # 生成测试集
    rng_test = np.random.RandomState(2024)
    X_test = rng_test.uniform(low=-3.0, high=3.0, size=(args.n_test, 5))
    y_test = ground_truth_y(X_test)
    if args.noise_std > 0:
        y_test += rng_test.normal(0, args.noise_std, size=args.n_test)

    # 保存 NumPy 格式
    np.savez(out_dir / "dataset.npz",
             X_train=X_train, y_train=y_train,
             X_test=X_test, y_test=y_test,
             expression=GROUND_TRUTH_EXPRESSION)

    # 保存 CSV
    header = ",".join(GROUND_TRUTH_SYMBOLS) + ",y"
    rows = np.column_stack([np.vstack([X_train, X_test]),
                            np.hstack([y_train, y_test])])
    with open(out_dir / "dataset.csv", "w") as f:
        f.write(f"# 真实公式: {GROUND_TRUTH_EXPRESSION}\n")
        f.write(f"# 噪声标准差: {args.noise_std}\n")
        f.write(header + "\n")
        for row in rows:
            f.write(",".join(f"{v:.8f}" for v in row) + "\n")

    print(f"数据集已保存到 {out_dir}")


if __name__ == "__main__":
    main()

生成的数据样例

x1,x2,x3,x4,x5,y
-0.75275929,2.70428584,1.39196365,0.59195091,-2.06388816,-0.73060157
-2.06403288,-2.65149833,2.19705687,0.60669007,1.24843547,4.13384154
-2.87649303,2.81945911,1.99465584,-1.72596534,-1.90905020,-2.05631804
-1.89957294,-1.17454654,0.14853859,-0.40832989,-1.25262516,1.29225609

每行 6 列:x₁ ~ x₅ 为输入,y 为输出。

产出文件:

文件 说明
datasets/z/dataset.npz NumPy 压缩格式,供 from_arrays() 加载
datasets/z/dataset.csv CSV 文本格式,肉眼查看用
datasets/z/ground_truth.txt 真实公式说明
datasets/z/preview.txt 数据统计摘要

Step 2:创建临时环境

使用 micromamba 创建独立的 Python 3.12 环境,避免污染已有环境:

# 创建
micromamba create -n py312 python=3.12 -y

# 安装依赖
micromamba run -n py312 pip3 install scipy sympy scikit-learn h5py \
    tiktoken json-repair cmaes threadpoolctl

Step 3:编写测试脚本

保存为 datasets/z/run_test.py

#!/usr/bin/env python3
"""
在 FunctionEvolve 上运行自定义复杂数据集(datasets/z/dataset.npz)的测试脚本。

用法:
  cd /path/to/FunctionEvolve

  # 退化模式(无 LLM,仅验证程序化变异 + 数值优化)
  python datasets/z/run_test.py --degenerated

  # 完整模式(使用 LLM,需要 llm_config.yaml)
  python datasets/z/run_test.py
"""

import argparse
import os
import sys
from pathlib import Path

import numpy as np

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from src.dataset import SRDataset
from src.evaluator import Evaluator
from src.evolution_tree import EvolutionTree
from src.search import TreeSearch
from src.generator import MockGenerator, create_generator
from src.selector import MockSelector, create_selector
from src.mutator import MockMutator, LLMMutator
from src.llm_client import build_openai_client, LLMUsageLogger


def _load_dataset(data_dir):
    npz_path = data_dir / "dataset.npz"
    data = np.load(npz_path)
    expr = str(data["expression"].item())
    return SRDataset.from_arrays(
        X_train=data["X_train"], y_train=data["y_train"],
        X_test=data["X_test"], y_test=data["y_test"],
        symbols=["x1", "x2", "x3", "x4", "x5"],
        symbol_descs=["v1", "v2", "v3", "v4", "v5"],
        expression=expr, equation_name="complex_z",
    )


def _evaluate_ground_truth(ds, evaluator):
    """对真实公式做参数拟合,给出精度天花板。"""
    import sympy as sp
    sympy_expr = sp.sympify(ds.expression)
    param_names = sorted(
        {str(s) for s in sympy_expr.free_symbols} - set(ds.feature_names))
    result = evaluator.evaluate_skeleton(sympy_expr, param_names)
    print(f"[Ground Truth] Train NMSE = {result.train_nmse:.2e}")
    print(f"[Ground Truth] Test  NMSE = {result.test_nmse:.2e}")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--degenerated", action="store_true")
    parser.add_argument("--max-steps", type=int, default=30)
    parser.add_argument("--n-seeds", type=int, default=20)
    parser.add_argument("--candidate-num", type=int, default=5)
    parser.add_argument("--timeout", type=float, default=120.0)
    parser.add_argument("--llm-config", type=str, default=None)
    parser.add_argument("--run-tag", type=str, default="z_test")
    args = parser.parse_args()

    data_dir = Path(__file__).resolve().parent
    ds = _load_dataset(data_dir)
    print(f"训练样本: {ds.X_train.shape[0]}, 测试样本: {ds.X_test.shape[0]}")

    evaluator = Evaluator(
        feature_names=ds.feature_names, X_train=ds.X_train,
        y_train=ds.y_train, X_test=ds.X_test, y_test=ds.y_test,
        timeout=args.timeout,
    )
    _evaluate_ground_truth(ds, evaluator)

    # 构造 LLM / Mock Agent
    llm_config = None
    if not args.degenerated:
        cfg_path = args.llm_config or str(PROJECT_ROOT / "llm_config.yaml")
        if os.path.exists(cfg_path):
            import yaml
            with open(cfg_path) as f:
                llm_config = yaml.safe_load(f) or {}

    def _resolve(comp, field, fallback=None):
        return llm_config.get(comp, {}).get(field, fallback) if llm_config else fallback

    if args.degenerated:
        generator = MockGenerator(variables=ds.feature_names)
        selector = MockSelector(variables=ds.feature_names)
        llm_mutator = MockMutator()
    else:
        gen = _resolve("generator", "model"); gen_url = _resolve("generator", "base_url")
        gen_key = _resolve("generator", "api_key"); gen_mode = _resolve("generator", "mode", "openai")
        gen_temp = _resolve("generator", "temperature", 0.8)
        gen_tok = _resolve("generator", "max_tokens", 128000)
        logger = LLMUsageLogger(str(Path("logs") / "z_custom" / "llm_usage.csv"))
        generator = create_generator(gen, gen_url, gen_key, gen_temp, gen_tok,
                                     usage_logger=logger, llm_mode=gen_mode)

        sm = _resolve("selector", "model", gen); su = _resolve("selector", "base_url", gen_url)
        sk = _resolve("selector", "api_key", gen_key); sd = _resolve("selector", "mode", gen_mode)
        st = _resolve("selector", "temperature", 0.3)
        selector = create_selector(sm, su, sk, st, usage_logger=logger, llm_mode=sd)

        mm = _resolve("mutator", "model", gen); mu = _resolve("mutator", "base_url", gen_url)
        mk = _resolve("mutator", "api_key", gen_key); md = _resolve("mutator", "mode", gen_mode)
        mt = _resolve("mutator", "temperature", 0.7)
        m_tok = _resolve("mutator", "max_tokens", 128000)
        client = build_openai_client(mm, mu, mode=md, api_key=mk)
        llm_mutator = LLMMutator(client, mm, mt, m_tok, usage_logger=logger)

    tree = EvolutionTree()
    searcher = TreeSearch(
        dataset=ds, evaluator=evaluator, tree=tree,
        selector=selector, generator=generator, llm_mutator=llm_mutator,
        max_steps=args.max_steps, n_seeds=args.n_seeds,
        candidate_num=args.candidate_num, timeout=args.timeout, verbose=True,
    )
    searcher.initialize_seeds()
    searcher.run()

    # 输出最佳结果
    evaluated = [n for n in tree.all_nodes if n.is_evaluated and n.train_nmse < float("inf")]
    evaluated.sort(key=lambda n: n.train_nmse)
    if evaluated:
        best = evaluated[0]
        print(f"\n最佳公式: {best.skeleton_str}")
        print(f"训练集 NMSE: {best.train_nmse:.4e}")
        print(f"测试集 NMSE: {best.test_nmse:.4e}")


if __name__ == "__main__":
    main()

Step 4:运行测试

共运行三个实验:

① 退化模式(1 步):

cd /path/to/FunctionEvolve
python3 datasets/z/run_test.py --degenerated --max-steps 1 --n-seeds 3 --candidate-num 2 --timeout 120

② LLM 模式(1 步):

cd /path/to/FunctionEvolve
python3 datasets/z/run_test.py --max-steps 1 --n-seeds 3 --candidate-num 2 --timeout 120

③ LLM 模式(3 步):

cd /path/to/FunctionEvolve
python3 datasets/z/run_test.py --max-steps 3 --n-seeds 3 --candidate-num 2 --timeout 120

为加快运行速度,上述实验使用了 200 训练样本 + 100 测试样本的子集(完整数据集为 2000 + 500)。

Step 5:测试输出及分析

[Ground Truth] 训练集 NMSE = 1.77e-05
[Ground Truth] 测试集 NMSE = 2.14e-05

真实公式本身不需要拟合任何参数,NMSE ≈ 1.8e-5 来自 0.02 标准差的高斯噪声,是本次测试的精度天花板。
文章来源:https://www.codelast.com/

模式 A:退化模式结果(200 样本)

初始种子(退化模式仅基于 x1 生成简易多项式/指数公式):

种子公式 训练集 NMSE 测试集 NMSE 说明
y = c_0 x_1 + c_1 0.998 0.992 线性
y = c_0 x_1^2 + c_1 x_1 + c_2 0.998 0.991 二次多项式

最优变异后(1 步,2 父本,111 个候选):

公式 训练集 NMSE 测试集 NMSE 说明
y = \frac{c_0 x_1 + c_1}{c_2 e^{c_3 x_2} + 1} 0.270 0.319 分式 + 指数,引入 x₂
y = (c_0 + c_1 x_1) e^{c_2 x_2} 0.271 0.303 线性 × 指数
y = c_0 x_1 + c_1 + c_2 e^{c_3 x_2} 0.283 0.344 线性 + 指数

退化模式瓶颈:种子只使用 x1,NMSE approx 1.0;一轮变异后最优 NMSE 0.270(解释 ~73% 方差)。但缺乏 LLM 指引方向,变异盲目枚举(111 个候选中仅少数引入 x2,无法发现 sin 等函数)。

模式 B:完整模式结果(使用 DeepSeek,200 样本)

1. LLM Generator 领域分析

LLM 成功分析出数据集属于"通用符号回归 / 数学建模"领域,输出了 20+ 个经典公式模式供种子生成参考。

2. LLM 种子生成

种子公式 训练集 NMSE 测试集 NMSE 说明
y = c_0 + c_1 x_2 + c_2 x_3 + c_3 x_4 + c_4 x_5 ~0.89 ~0.89 LLM 尝试全部 5 个变量
y = c_0 x_2^{c_1} x_3^{c_2} ~0.84 ~0.84 LLM 聚焦 x₂, x₃
y = c_0 e^{c_1 x_2} + c_2 \sin(c_3 x_3 + c_4) ~0.35 ~0.34 LLM 直接猜中 sin + exp 结构!
y = c_0 x_2^2 x_3^{c_1} (退化兜底) ~0.84 ~0.84

LLM 的第三个种子 c0exp(c1x2) + c2sin(c3x3 + c4) 已经包含了 exp + sin,NMSE 仅 ~0.35,远超退化种子的 ~0.998。

3. LLM Mutator 变异建议

LLM 为选中的父本生成了 20 条定向变异建议(退化模式中 ASTMutator 靠穷举生成了约 111 条),例如:"尝试多项式展开"、"添加指数衰减项 exp"、"尝试分式结构"、"添加正弦/余弦项"。

4. 各步最优公式

LLM 1 步后:

公式 训练集 NMSE 测试集 NMSE 说明
y = c_0 e^{c_1 x_2} + c_2 \sin(c_3 x_3 + c_4) + c_5 \sin(c_6 x_4 + c_7) 0.224 0.300 双 sin + exp,引入 x₂~x₄
y = c_0 e^{c_1 x_2} + c_2 \sin(c_3 x_3 + c_4) + c_5 x_4^{c_6} 0.228 0.301 幂次替代 sin
y = c_0 e^{c_1 x_2} + c_2 x_3 (c_3 + x_1)^{c_4} + c_5 \sin(c_6 x_3 + c_7) 0.241 0.282 引入 x₁

LLM 3 步后:

公式 训练集 NMSE 测试集 NMSE 说明
\frac{c_0 x_1 + c_1 + (c_2 x_3 + c_3)(c_4 + c_5 x_3 + c_6 \sin(c_7 x_4 + c_8)) e^{c_9 x_2}}{c_2 x_3 + c_3} 0.081 0.129 复合分式 + sin + exp
c_0 x_3 (c_1 + x_1)^{c_2} + c_3 e^{c_4 x_2} + c_5 x_3 e^{c_4 x_2} + c_6 e^{c_4 x_2} \sin(c_7 x_4 + c_8) 0.088 0.110 分项式
\frac{c_0 x_2 + c_1 + (c_2 x_3 + c_3)(c_4 + c_5 x_3 + c_6 \sin(c_7 x_4 + c_8)) e^{c_9 x_2}}{c_2 x_3 + c_3} 0.099 0.157 类似结构

两种模式找到的最优函数:

实验 最优公式 训练集 NMSE 测试集 NMSE
真实公式 y = \frac{\sin(x_1 x_2)}{\cos(x_3) + 1.5} + e^{-x_2} \ln(\lvert x_4 \rvert + 1) + \frac{x_1 x_3}{1 + x_5^2} 1.77e-5 2.14e-5
退化 1 步 y = \frac{c_0 x_1 + c_1}{c_2 e^{c_3 x_2} + 1} 0.270 0.319
LLM 1 步 y = c_0 e^{c_1 x_2} + c_2 \sin(c_3 x_3 + c_4) + c_5 \sin(c_6 x_4 + c_7) 0.224 0.300
LLM 3 步 y = \frac{c_0 x_1 + c_1 + (c_2 x_3 + c_3)(c_4 + c_5 x_3 + c_6 \sin(c_7 x_4 + c_8)) e^{c_9 x_2}}{c_2 x_3 + c_3} 0.081 0.129

NMSE 对比(越低越好):

模式 步数 训练集 NMSE 测试集 NMSE 相比退化改善 关键发现
真实公式 -- 1.77e-5 2.14e-5 -- 精度天花板
退化 1 步 0.270 0.319 基准 分式 + 指数(x₁, x₂)
LLM 1 步 0.224 0.300 ↓ 17% 发现 sin(),引入 x₃, x₄
LLM 3 步 0.081 0.129 ↓ 70% 复合分式 + sin + exp(x₁~x₄)

LLM 1 步内就发现了退化模式无法找到的 sin()。3 步后 NMSE 降到 0.081(解释 ~92% 方差),但 x₁×x₂ 的 sin 交互和 x₅ 尚未被完整识别。预计 LLM 模式 5~10 步可收敛到 NMSE < 1e-3。

当前进度与收敛预估

维度
变量数 5(x₁~x₅)
函数种类 sin, cos, exp, log, 除法(5 种)
交互关系 x₁×x₂, x₁×x₃, x₂(指数项), x₄(对数项), x₅(分式)
噪声 0.02 标准差

当前实验进度(基于 200 训练样本 + 100 测试样本):

实验 训练集 NMSE 测试集 NMSE 关键发现
退化 1 步 0.270 0.319 分式 + 指数,仅 x₁, x₂
LLM 1 步 0.224 0.300 发现 sin(),引入 x₃, x₄
LLM 3 步 0.081 0.129 复合分式 + sin + exp,引入 x₁~x₄

LLM 3 步后 NMSE 0.081(解释 ~92% 方差),已成功发现 sin() 函数并引入了 x₁~x₄ 四个变量。但真实公式中 x₁×x₂ 的 sin 交互、x₁×x₃ 的分式交互以及 x₅ 的分式尚未被完整识别。预计:

  • LLM 模式:5~10 步可收敛到 NMSE < 1e-3
  • 退化模式:相同步数下 NMSE 下降仅 ~18%/步,效率远低于 LLM

这正是 FunctionEvolve 采用 LLM + 数值优化混合架构的原因——LLM 提供方向,数值优化负责精确拟合
文章来源:https://www.codelast.com/

Step 6:清理临时环境

# 测试完成后删除临时环境
micromamba remove -n py312 --all -y

总结

项目 状态
测试数据集生成 完成
测试脚本 完成(datasets/z/run_test.py
退化模式(1 步) 完成(NMSE 0.270 / 0.319,无 LLM)
LLM 模式(1 步) 完成(NMSE 0.224 / 0.300,发现 sin())
LLM 模式(3 步) 完成(NMSE 0.081 / 0.129,解释 ~92% 方差)
搜索正确性验证 全流程无错误,所有实验数据完整记录
临时环境清理 已删除

结论:FunctionEvolve 的搜索管道可以正常运行。LLM 模式显著优于退化模式——1 步即可发现退化模式无法找到的 sin() 函数,3 步后 NMSE 降至 0.081。其实上面的测试还没有找到最终的正确公式,由于我电脑性能不强,仅仅运行上面的测试就已经花了很长时间+风扇狂转,因此测试只能止步于此。但可以看到,FunctionEvolve相较于传统优化方法是有较大优势的。

文章来源:https://www.codelast.com/
➤➤ 版权声明 ➤➤ 
转载需注明出处:codelast.com 
感谢关注我的微信公众号(微信扫一扫):
wechat qrcode of codelast
以及我的微信视频号:

发表评论