入口类

  1. Facade
  2. 文档最完善的入口类: smac.facade.smac_ac_facade.SMAC4AC

程序入口

  1. Facade.optimize() –> SMBO.run() (Facade.solver().run())
  2. SMBO.run() 迭代循环

    1. Intensifier.get_next_run() (SMBO.run()–> SMBO.intensifier.get_next_run()) –> AbstractRacer._next_challenger()

      • AbstractRacer 是 Intensifier 的基类
      • 选择下一个 challenger (候选配置config)
      • 方法:

        • 使用 EPM(代理模型) EPMChooser 选择
      • 参数:

        • SMBO.epm_chooser
        • runhistory
        • num_workers
        • challengers: 如果手动提供 challengers 使用他们,否则使用 epm_chooser 生成
    2. AbstractRacer._next_challenger() –> EPMChooser.choose_next() (SMBO.epm_chooser.choose_next())

      • 内部:

        • 使用 acquisition_function 采集函数,排序参数 config
        • EPMChooser.choose_next() —> :

          1. 初次迭代: 随机搜索 –> EPMChooser._random_search.maximize() (AcquisitionFunctionMaximizer.maximize())
          2. 后续迭代: –> 自定义的 Acquisition_function_Maximizer.maximize() (AcquisitionFunctionMaximizer.maximize())
      • EPMChooser.choose_next() 内部

        1. 获取历史数据:通过 EPMChooser.runhistory 获取 (X, Y, X_configs) 迭代历史数据, EPMChooser._collect_data_to_train_model()

          • 使用 EPMChooser.rh2EPM.transform(runhistory) 把RunHistory 变成 (X, Y) 代理模型友好的数据格式

            • EPMChooser.rh2EPM: AbstractRunHistory2EPM 抽象基类

              • 内部处理 多目标 multit-object 数据的Cost 转换 cost 数组 –> 单个 float cost 数值
          • epm_chooser.choose_next() 中的参数

            • X_configurations: 所有的 configuration 以 np.array 形式存储的数据,包括失败的和成功的参数, 和 X 类似
            • X, Y: 通过最大 budget 筛选出来的成功 和 timeout 的 Config
            • best_observation: 是 cost
            • x_best_array: incumbent_value 对应的 X 取值
            • _get_x_best() –> self.model.predict_marginalized_over_instances(X) -> mean, var
        2. 训练代理模型: EPMChooser.model.train(X, Y)
        3. 更新 incumbent (最好参数), 存储在 EPMChooser.acquisition_function 实例内部

          1. 如果传入和 incumbent_value, 使用这个 incumbent_value
          2. 否则,通过 (EPMChooser) self._get_x_best(predict, X), 计算出最佳值, incumbent_value

            • self._get_x_best(predict, X) 使用X + 代理模型,计算出Y_predict
            • Y_predict –> mean, var 均值和方差
            • 使用 Y_predict 的 mean 作为 costs –> 计算 x_best, incumbent_value
          3. _get_x_best() –> self.model.predict_marginalized_over_instances(X) -> mean, var

            • 对于不需要 instance_features 情况:

              • 直接返回 proxymodel.predict(X) –> mean, var

                1
                2
                3
                4
                5
                6
                7
                8
                
                if self.instance_features is None or \
                        len(self.instance_features) == 0:
                    mean_, var = self.predict(X)
                    assert var is not None  # please mypy
                
                    var[var < self.var_threshold] = self.var_threshold
                    var[np.isnan(var)] = self.var_threshold
                    return mean_, var
              • 使用 mean_ –> 作为 cost 对 X 排序获得 x_best 和 best_observation(incumbent_value)
        4. 采集函数 最好值更新 self.acquisition_function()
        5. 获取新生成X, 作为下一轮的 chanllengers

          • self.acq_optimizer.maximize(runhistory, stats, num_points, random_config_chooser)

            • 基于(1)采集函数(2)历史数据(3)最佳样本点(4)随机选择器(5)maximizer 生成新的 configs(参数 Xs)
        6. 去重: 去掉以前已经运行过的参数 config
        7. maximizer.maximize() –> Iterator[Configuration]
    3. 通过生成 (RunInfoIntent, config) 返回值判断是否需要运行
    4. RunInfoIntent.RUN: Runner.submit_run() 负责运行,存储结果到 Runner.results 字段中

      1. SMBO.run() –> Runner.submit_run() (SMBO.tae_runner.submit_run(run_info))
      2. Runner.submit_run() –> Runner.run_wrapper(run_info)

        1
        
        def run_wrapper( self, run_info: RunInfo, ) -> Tuple[RunInfo, RunValue]:
        • 内部负责调用 Runner.run()
        • 负责确定运行结果状态 status

          • StatusType.STOP: 退出整个 SMBO 循环(实际源码中没有发现主动赋值 StatusType.STOP 的情况)
      3. Runner.submit_run() –> Runner.run()

        • Runner: 所有 runnner 的基类
        • smac.tae.execute_func.AbstractTAFunc: 用来执行算法的 Runner 抽象类
        1
        2
        
        def run(self, config: Configuration, instance: str, cutoff: Optional[float] = None, seed: int = 12345, budget: Optional[float] = None, instance_specific: str = "0") -> Tuple[StatusType, float, float, Dict]:
           # Tuple[status: int, cost: np.ndarrray, runtime: float, additional_info: dict]
      4. 运行结果存储:

        • Runner.results: Dict[RunInfo, RunValue]
    5. 结果处理: –> SMBO._incorporate_run_results

      • 把结果存储到 SMBO.runhistory (smac.runhistory.Runhistory) 中
      • intensifier: 处理 racer 和 incumbent 竞争问题

        • 替换和裁定最新的 incumbent (最好结果)
        • SMBO.intensifier.process_results(run_info: RnInfo, incumbent, run_history, result: RunValue)

          • 功能: 添加当前点(RunInfo, RunValue)
          • AbstractRacer. _compare_configs (previous_incumbent, challenger, runhistory)

            • 功能: 更新最好点 ,比较以前的最好点 previous_incumbent,和新添加的点 challenger
            • cost 获得方法

              • 通过 RunHistory.average_cost(config, …) 方法获得
              • 先正规化,再求平均值
              • 多目标 multi-objectives 处理: 通过直接平均,把多个 ojectives 转换成单个 cost
            • chall_cost < previous_incumbent_cost —> 挑战成功
      • callback 回调函数调用
    6. 结束SMBO循环

      • 资源耗尽 budget 或者 result.status == StatusType.STOP,

最好点 best_point / incumbent / incumbent_perf 和 multi-objectives

内部涉及到了两种场景:

  1. 添加样本点时: SMBO._incorporate_run_results() –> SMBO.intensifier.process_results(RunInfo, RunValue, …) –> SMBO.intensifier._compare_configs(previous_incumbent, challenger)

    • 内部真正执行比较的是: SMBO.intensifier._compare_configs(previous_incumbent, challenger)

      • 通过正规化 + 简单平均把 multi-objectives 转换成单个cost, 评估最好点
    • 存储到 SMBO.incumbent 中
  2. 生成下一个样本点时:在 SMBO.run() –> SMBO.get_next_run() –> SMBO.intensifier.EPMChooser.choose_next() 为了获得下一个 challengers 时

    • 在更新代理模型之前,把 runhistory 转换成 X, Y, 通过 multi_objective_algorithm 把 multi-objectives 转换成单个 cost

      • 调用位置: 在 SMBO.epmchooser._collect_data_to_train_model() 内部
      • x 是 configs array –> list[x] –> X
      • y 是 转换后的 单个 cost 列表 –> list[y] –> Y
    • 更新代理模型

      • 代理模型的单目标 Vs 多目标

        • 默认情况下,内部代理函数实现是 X –> y 模型,预测单个 cost (单目标)
        • 如果需要实现多目标预测,需要手动改写 代理模型的实现,重载 AbstractEPM.train(), AbstractRacer._train(), AbstractEPM.predict(), AbstractEPM._predict()

          • 在保持内部 外部接口的 X –> y 基础上,内部能够实现 X –> Y –> y 的两种预测模式
    • 获取最好点

      • 可以通过传参传入 到 choose_next() 中
      • 如果没有,通过 EPMChooser._get_x_best(X) 通过代理模型 + X(历史 configs) 筛选出来

        • 代理模型的单目标 Vs 多目标

          • 这里默认使用 X –> y 单个cost 的代理模型
          • 如果是预测多目标的代理模型,需要手动实现代理函数内部的多目标 —> 单目标(X –> Y –> y)
    • 使用 (代理模型, best_point, random_chooser) + EPMChooser.acq_optimizer 生成下一个 challengers

概率代理模型

  1. 基础类型: AbstractEPM
  2. 存储位置

    • facade.solver (SBMO) –> emp_chooser (EPMChooser) –> model (AbstractEPM)
    • 相关类

      1. Facade: eg: SMAC4HPO, SMAC4AC

        • 入口类
      2. SMBO:

        • 管理贝叶斯循环
      3. EPMChooser

        • 训练概率代理模型 EPM, 生成下一个超参数 config (epm_chooser.choose_next())
        • choose_next() 中(1)先会完成训练,再(2)生成下一个 config

          • 利用runhistory 获取历史数据
  3. epm mode 创建流程:

    1. 在 Facade 类中创建
    2. 在 SMBO 中作为 EPMChooser 的参数用来创建 EPMChooser

      • 创建后只有和 EPMChooser 发生了实际的关系
  4. 替换model(代理模型) 方法:

    • 直接修改 facade.solver.emp_chooser.model 即可
  5. 替换 epm_chooser 方法:

    • 直接替换 facade.solver.epm_chooser 即可
    • 注意里面引用的 scenario, stats, runhistory, model 等对象的引用,不要使用新建的,要是在 facade, smbo 中引用的

多目标优化 multi-objective

涉及的部件

multi_objective_algorithm 多目标优化的多 cost 转换算法

使用流程:

  1. Facade(multi_objective_algorithm)
  2. Facade.rh2epm 工具 AbstractRunHistory2EPM 的初始化参数

    1. AbstractRunHistory2EPM 抽象基类

      • 用途不同,使用不同的子类:

        • scenario.run_obj –> "runtime": RunHistory2EPM4LogCost
        • scenario.run_obj –> "quality": 根据 y 的转换方式而定

          • scenario.transform_y == "NONE": RunHistory2EPM4Cost
          • scenario.transform_y == "LOG": RunHistory2EPM4LogCost
          • scenario.transform_y == "LOGS": RunHistory2EPM4ScaledCost
          • scenario.transform_y == "INV": RunHistory2EPM4InvScaledCost
    2. 入口方法: AbstractRunHistory2EPM.transform(runhistory) --> AbstractRunHistory2EPM._build_matrix()
    3. 实际调用的重载入口函数 AbstractRunHistory2EPM._build_matrix(run_dict, runhistory) -> tuple[X_ndarray, Y_ndarray]

      • 把 runhistory 中的数据转换成 代理模型 优化的数组格式
      • 在 X, Y 中添加上 feature_dict/feature_instances 数据
  3. AbstractRunHistory2EPM 作为 EPMChooser.runhistory2epm --> EPMChooser.rh2EPM 参数用来初始化

    • AbstractRunHistory2EPM 在 EPMChooser._collect_data_to_train_model() 中处理训练数据
    • AbstractRunHistory2EPM 在 EPMChooser._get_x_best() 中 最好值 best_observation 的转换
  4. 传输流程:

    • Facade.__init__.multi_objective_algorithm
    • Facade.__init__.r2e_def_kwargs
    • Facade.__init__.RunHistory2EPM4Cost.multi_objective_algorithm –> Facade.__init__.rh2epm.multi_objective_algorithm
    • Facade.__init__.RunHistory2EPM4Cost –> Facade.__init__.rh2epm
    • Facade.__init__.smbo_args.runhistory2epm
    • SMBO.__init__.runhistory2epm
    • SMBO.epm_chooser.__init__.runhistory2epm (EPMChooser) –> EPMChooser.rh2EPM
    • 使用: SMBO.epm_chooser.rh2EPM.transform(runhistory, budget_subset) –> X,Y

      • SMBO.epm_chooser.rh2EPM.RunHistory2EPM4Cost._build_matrix()
      • SMBO.epm_chooser.rh2EPM.RunHistory2EPM4Cost.multi_objective_algorithm(y_)
      • 这里的Y 就是使用 multi_objective_algorithm(multi_object) 运行后生成的
    • 综上,只需要在 Facade 中传入一个 multi_objective_algorithm 算法实现即可
  5. 实现一个 AggregationStrategy 的子类,传入 Facade 的 multi_objective_algorithm 参数,即可

转换多目标到单 cost 的工具

  • AggregationStrategy 抽象类
  • ParEGO 子类
  • MeanAggregationStrategy 子类

在SMBO 中 multi_objective_algorithm 被调用的过程

SMBO.run() 中的调用(优化)流程:

  1. SMBO.internsifier.get_next_run(challengers, incubent) 获取新的 chanllengers (configs)

    1. AbstractRacer._next_chanllenger()
    2. SMBO.intensifier. _next_challenger (challengers, chooser)
    3. EPMChooser. choose_next (), 这个是从 chooser 是从 SMBO 传递过来的

      1. 收集和转换 runhistory 格式:

        • 功能:

          1. 收集到 X, Y
          2. 如果是多目标优化,使用 multi_objective_algorithm, 把多个值转换成单个值
        • X, Y, X_configs = EPMChooser._collect_data_to_train_model()

          • 说明:这里的 train model 是训练代理模
          • X 被添加 instance_features 相关数据后的 config array
          • X_configs 是没有添加 instance_features 相关数据的原生 config array
        • X, Y = EPMChooser.rh2EPM.transform(runhistory, budget_subset)

          • EPMChooser.rh2EPM._build_matrix()

            • 把 runhistory 转换成矩阵
            • 参考: runhistory2epm.py: RunHistory2EPM4Cost._build_maxtrix() 实现
            • 作用: 把 [n_row, n_target] 形状的 multi_objectives [[y00, y01], [y10, y11]] 转换成 –> [[c0], [c1]]
            • 内部实现:

              1
              2
              3
              4
              5
              6
              7
              
              # 下面这个是伪代码
              Ys = []
              for run in runs:
                  y_ = normalize_costs([run.cost], runhistory.objective_bounds)
                  y_ = self.multi_objective_algorithm(y_)
                  Ys.append(y_)
              return Xs, Ys
            • 注: 这里实际调用了 multi_objective_algorithm 算法实现
      2. 代理模型训练: EPMChooser.model.train(X, Y)

        • 注意: 代理模型内部接收到的永远是 单目标数据,因为 在 _collect_data_to_train_model 阶段已经把多目标转换成了单目标数据(单个cost)
      3. 选择下一个,最好 x,y: 使用体力模型生成

        • 用户代理模型内部选取新的样本点的生成机制
        • EPMChooser.acquisition_function.update(model=proxy_model, best_observation, best_x_arry, …)
  2. SMBO._incorporate_run_results

    1. 处理加入的样本点

      1. SMBO.internsifier.process_results

        • eg: simple_intensifier.py: SimpleIntensifier.process_results()
      2. SMBO.intensifier._compare_configs(challegners, previous_incumbent)

相关依赖

  1. ConfigSpace: automl 系列工具

    • Quickstart - ConfigSpace
    • 用途:

      • 限制取值范围: ConfigurationSpace
      • 参数之间的相互依赖关系: EqualsCondition
      • 记录多次迭代参数变更历史

        • 类似实例化包含一个 ConfigurationSpace
      • 调用不同的模型

概念

  1. 参数: config

    • 在 smac 中,parameter 参数或者 HyperParamter 超参数被称作 configuration | config
  2. challenger

    • smac 选出的候选参数
    • 相关工具: ChallengerList
  3. trial

    • 一次黑盒函数的运行
    • 包括整合相关要素:config, seed, budget, instance
  4. incumbent, current incumbent

    • 当前效果最好的参数 best_config
    • 注意: 如果是 multi-objective 多目标优化,可能有多组最好参数 multiple current incumbent
    • incumbent 存储在 Intensifier
    • incumbent 值存储在 AquisitionFunction.eta 中 (best_observation), 参数在 AquisitionFunction.incumbent_array=x_best_array
  5. optimizer: SMBO

    • 贝叶斯优化循环工具类实例: SMBO 对象
    • Facade.optimizer() –> SMBO
  6. instance: 数据集

    • smac 允许被优化模型使用多个数据集,一个数据集或数据子集,被称为一个 instance
    • 需要在 Scenario 类中声明 instance 列表
  7. hyperband:

    • 一种超参数调优,资源分配策略,可以用来筛选出性能较好的参数
    • Hyperband的核心思想是结合了随机搜索和贝叶斯优化的优点,通过动态地分配资源来快速识别出性能较好的超参数配置
  8. imputator

    • 数据缺失填充器: RFRImputator
  9. epm_chooser

    • 负责 epm(代理模型)、采集函数、acquisition function maximizer 、run_history 、 scenario 之间的互相调用
    • 作用:

      1. 使用 run_history 中的数据,训练代理模型
      2. 使用代理模型 + 采集函数 + maximizer –> 生成新的参数 config
  10. run_obj:

    • 实际使用到的地方

      • Scenario: 只是存储这个参数
      • Runner:
  11. censored: 截断数据

    • Censored 数据是指在超参数优化过程中,由于某些超参数组合的评估成本过高或耗时过长,导致其评估结果被提前终止或截断。这些被截断的评估结果通常只包含部分信息,如部分迭代次数的训练结果。

组件

参考:

smac.epm (Surrogat Model 代理模型) AbstractEPM

  • epm (Empirical Performance Model)
  • smac 中的代理模型包括:

    • 高斯过程 Gaussian Processes
    • 随机森林 Random Forest

属性解释

self.instance_features

参考资料:

  • smace v 1.2: <project-root>/test/test_epm/test_base_emp.py: TestRFWithInstances.test_apply_pca

详解:

  • 创建来源: scenario.feature_array <— scenario.feature_dict

    • 数据创建流程:

      1. 创建 Scenario 时,实际传入的是 feature_dict: List[Dict[str, np.array(一维数组)]]
      2. scenario 内部生成 scenario.feature_array: np.ndarray, 二维数组
      3. 在 Facade 中调用 scenario.feature_array 用来生成–> AbstractEPM.feature_instances: np.ndarray, 二维数组
    • feautre_dict: dict[str, np.ndarray] 例子:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      self.rng = np.random.RandomState(seed=42)
      self.scen_fn = 'test/test_files/validation/scenario.txt'
      self.train_insts = ['0', '1', '2']
      self.test_insts = ['3', '4', '5']
      # 下面这个其实是 scenario.instance_specific
      self.inst_specs = {'0': 'null', '1': 'one', '2': 'two',
                         '3': 'three', '4': 'four', '5': 'five'}
      self.feature_dict = {'0': np.array((1, 2, 3)),
                           '1': np.array((1, 2, 3)),
                           '2': np.array((1, 2, 3)),
                           '3': np.array((1, 2, 3)),
                           '4': np.array((1, 2, 3)),
                           '5': np.array((1, 2, 3))}
      self.output_rh = 'test/test_files/validation/'
      scen = Scenario(self.scen_fn, cmd_options={'run_obj': 'quality'})
      # config space
      # x1 [-5,10] [0]
      # x2 [0,15]  [0]
    • instance_features: np.ndarray, 一维数组, 例子:

      1
      
      instance_features = np.array([np.random.rand(10) for _ in range(5)])
      • 关联工具:

        • smac.epm.util_funcs.get_types –> Tuple[types, bounds]

          • 生成数据类型,和类型的取值范围
          • types: (len(hyperparameters) + len(instance_features.shape[1]))*[0]
    • 单个 instance_features

      1
      
      np.array([1,2,3])
    • 包含多个 instance 的 instance_features (AbstractEPM 中的实际情况)

      1
      2
      3
      4
      
      np.array(
          [1,2,3]
          [4,5,6]
      )
      • 表示有2个 instance
      • 每个 instance 有三个 feature (特征)

        • instance1: feature –> [1,2,3]
        • instance2: feature –> [4,5,6]
    • AbstractEPM 中 predict_marginalized_over_instances() 方法中 关于 instance_features 的代码解读

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      
      # 没有定义 instance_features 的情况,直接使用 超参数数据集 X
      if self.instance_features is None or \
               len(self.instance_features) == 0:
           mean, var = self.predict(X)
           assert var is not None  # please mypy
      
           var[var < self.var_threshold] = self.var_threshold
           var[np.isnan(var)] = self.var_threshold
           return mean, var
       else:
           n_instances = len(self.instance_features)
      
       mean = np.zeros(X.shape[0])
       var = np.zeros(X.shape[0])
       for i, x in enumerate(X):
           # (1) 把 X 中的 一行 row, 按 n_instance 先复制成n_instance(实例个数) 行(n_instance, n_instance_features)
           # (2) 把 self.instance_features 水平拼接到上面扩展的 x 的新数据上, (x_rows, self.instance_features)
           # In [27]: np.hstack((np.tile([1,2,3], (3, 1)), [[1,2,3], [4,5,5], [6,6,6]]))
           # Out[27]:
           # array([[1, 2, 3, 1, 2, 3],
           #        [1, 2, 3, 4, 5, 5],
           #        [1, 2, 3, 6, 6, 6]])
           # 上面的例子数据: x --> [1, 2, 3], instance_features --> [[1,2,3], [4,5,5], [6,6,6]]
           X_ = np.hstack(
               (np.tile(x, (n_instances, 1)), self.instance_features))
           means, vars = self.predict(X_)
           assert vars is not None  # please mypy
           # VAR[1/n (X_1 + ... + X_n)] =
           # 1/n^2 * ( VAR(X_1) + ... + VAR(X_n))
           # for independent X_1 ... X_n
           var_x = np.sum(vars) / (len(vars) ** 2)
           if var_xs < self.var_threshold:
               var_x = self.var_threshold
      
           var[i] = var_x
           mean[i] = np.mean(means)

Acquisition Function 采集函数

数据:

1
2
3
4
5
6
7
self.acquisition_func.update(
    model=self.model,
    eta=best_observation,
    incumbent_array=x_best_array,
    num_data=len(self._get_evaluated_configs()),
    X=X_configurations,
)

Acquisition Maximizer

功能:使用采集函数获取能够最大化 objective 的参数 (config | parameter)

特点:

  1. 使用搜索算法,使用采集函数来获取可能的最好参数(即下一次迭代使用的参数,config | challenger)

    1. local search: 局部近邻搜索
    2. (sorted) random search: 定义域空间config space 中随机搜索,使用采集函数排序
    3. local (sorted) random search
    4. Differential Search: 差分进化,类似遗传算法

Initial Design 初始化

功能:第一次迭代参数的生成

特点:

  • 包含了多种数据生成方法

    • 随机设计(random):这是一种简单的方法,通过随机选择参数值来生成数据点。
    • 拉丁超立方抽样(latin hypercube):这是一种统计方法,用于从多维分布中生成近似随机的参数值样本。
    • 索伯序列(sobol):这是一种准随机的低差异序列,用于生成数据点,以减少随机抽样可能带来的偏差。
    • 因子设计(factorial):这种方法生成配置空间的角点点,即在每个维度的极端值处生成数据点。
    • 默认设计(default):使用配置空间的默认配置来生成数据点。
  • config selector 结合使用,可以避免生成重复的参数

配置流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
SMAC4AC(

# 传入创建参数
initial_design_kwargs={
            "ta_run_limit": scenario.ta_run_limit,  # type: ignore[attr-defined] # noqa F821
            "configs": initial_configurations,
            "n_configs_x_params": 0, # 初始化使用的参数组数
            "max_config_fracs": 0.0,
        }
)

# 手动创建 InitialDesign 类的实例
inital = InitialDesign()
SMAC4AC(
initial_configurations=initial,
...
)

参考: smac v1.2 smac/facade/smac_ac_facade.py: 541~570 行

Intensifier

功能:

  1. 用来确定那个参数 config, 值得使用花费更多的资源 (budget, 迭代运算) 计算
  2. Intensifier.get_next_challenger() 生成下一个需要运行的参数 config

    1. 通过 self.configs_to_run 字段存储用来采样的 configs

      • 生成方法: Intensifier._generate_challengers()

        • 真正的config 来源: 传入的 challengers 或者 Interface.chooser.choose_next()
    2. self.to_run 字段存储的是已经安排要运行的 config
    3. 等待运行的configs, 消耗完后, Intensifier._next_iteration() –> 重置状态,清空 self.configs_to_run 等
    4. 调用

特点:

  • 直接调用 Configuration Selector

configuration Selector

功能:生成参数

步骤:

  1. 生成参数 config
  2. 训练代理模型 Surrogat Model, 使用历史数据 RunHistory
  3. 生成下一次的参数 config, 使用 Acquisition Function/ Maximizer

smac.runhistory RunHistory

参考:

dict[Trial_Info, Trial_Value]:

  • Trial_Info:

    • 一次迭代的 初始化数据
    • config: 参数
    • seed
    • budget
    • instance 等等
  • Trial_Value:

    • 一次迭代的运行 结果信息
    • cost: 损失函数的值
    • time
    • status
    • starttime
    • endtime
    • ….

RunHistory Encoder

把 RunHistory 中存储的数据处理成 代理模型 可以使用的格式

CallBack

一次迭代的不同时间段调用的回调函数

ConfigSpace

类型

OridinalHyperparameter

特点:离散有序数据类型(优先的取值)

例子:

  • [1, 2, 3, 5]
  • [0.1, 0.6, 0.8, 1.0]
  • ['small', 'medium', 'large']

eg:

OrdinalHyperparameter('hp', sequence=['small', 'medium', 'large'], default_value='medium')

CategoricalHyperparameter

类别类型数据,相比于 OrdinalHyperparameter, 不需要有序

UniformFloatHyperparameter

均匀分别 float

UniformIntegerHyperparameter

均匀分布 int

NormalIntegerHyperparameter

正态分布 int

如果生成 config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cs = ConfigurationSpace()
cs.add_hyperparameter(UniformFloatHyperparameter("x0", 0, 1, default_value=0.5))
cs.add_hyperparameter(UniformFloatHyperparameter("x1", 0, 1, default_value=0.5))
cs.add_hyperparameter(UniformFloatHyperparameter("x2", 0, 1, default_value=0.5))
cs.add_hyperparameter(UniformFloatHyperparameter("x3", 0, 1, default_value=0.5))


# * 生成单个config
config = cs.sample_configuration()
# Configuration(values={
#   'x0': 0.1757723375360455,
#   'x1': 0.07792241128456479,
#   'x2': 0.02019465139013299,
#   'x3': 0.8004214815452955,
# })

# * 转换成 np.ndarray
config.get_arrary()
#array([0.17577234, 0.07792241, 0.02019465, 0.80042148])

# * 生成多个 config
cs.sample_configuration(2)
# [Configuration(values={
#    'x0': 0.1166258005176769,
#    'x1': 0.4105461212878182,
#    'x2': 0.6637621500592897,
#    'x3': 0.14026134045196892,
#  }),
#  Configuration(values={
#    'x0': 0.18761772118035736,
#    'x1': 0.4693413816546851,
#    'x2': 0.20484388410274912,
#    'x3': 0.25734296874097395,
#  })]


# * 多个 config 转换成 array
np.array([config.get_array() for config in configs], dtype=np.float64)

主要类

SMBO

功能:

  • Sequential Model-Based Bayesian Optimization
  • BayesOpt 优化迭代循环的实现

使用的组件:

class smac.main.smbo.SMBO(scenario, runner, runhistory, intensifier, overwrite=False)[source]
(scenario, runner, runhistory, intensifier)

运行结果的处理

  • SMBO._incorporate_run_results(run_info: RunInfo, result: RunValue, time_left: float)

    • RunInfo: config, instance, seed, …
    • RunValue: cost, time, status, ….
    • 功能:

      1. 添加运行结果到 SMBO.runhistory
      2. 保存状态

        • SMBO.stats
        • SMBO.runhistory
      3. 更新运行状态和 通过 intensifier.processe_results() 计算得到最好内 incumbent (最好结果)

        • 可以参考: simple_intensifierprocesse_results 实现方法

          • 内部调用 AbstractRacer._compare_configs()
        • 存储在 SMBO.incumbent
        • AbstractRacer._compare_configs(): 真正确定哪一个 config 是最优点的方法

          • 内部通过 cost = RunHistory.average_cost(config) 类比较哪一个更小,选择下一个 incument

            • 注意: 这个只适合单目标,如果需要做多目标,需要在生成的RunValue 结果就要把多目标转换成单目标形式
          • 评价机制:

            • 执行的参数相等,或者更多
            • 使用相同 (instance, seed) 的情况下,执行的结果, cost 更小
    • RunHistory 内部的

      • self._cost(config) –> costs –> self.average_cost(config) –> normalize_costs(costs) –> mean(costs) –> 多次运行的平均值

        • 获取 costs 的方法:

          1. 通过获得一个config 对应的多个 InstSeedBudgetKey –> tuple[intances, seed, budget]

            • self._configid_to_inst_seed_budget Dict[Tuple[instance, seed]] = List[budget]
            • 内部只取 budget 最大的那个
          2. 获得多个相关(实际上是多次运行的相同的 config)的数据, 获取 RunKey, 进而获取到 多次运行的 RunValue.cost
        • tuple[instance, seed]表示的是一种运行初始化数据, budget 是实际消耗的费用或者时间, RunValue.cost 是黑盒函数的效果的评估(比如模型的召回率)
      • costs 的 normalize_costs 涉及到 objective 的取值范围, self.objective_bounds

Facade (AbstractFacade) || smac.facade 模块

功能:

  • SMBO 贝叶斯优化循环的包裹类,用来整合 SMACv3 中的各个组件

Facade 类别

算法优化(Algorithm): SMAC4AC, smac.facade.smac_ac_facade.SMAC4AC

算法、模型的优化

要点: SMAC4AC 类的init 方法文档中包含了各种类型参数的说明

黑盒函数优化(blackbox function): SMAC4BB, smac.facade.smac_ac_facade.SMAC4BB

特点:

  • 使用高斯过程
超参数优化(hyperparameter): SMAC4HPO, smac.facade.smac_ac_facade.SMAC4HPO

特点:

  • 很大程度上类似 SMAC4AC(algorithm configuration)

TrajLogger 优化记录保留

  • 在 Facade 中初始化

Scenario

作用:

  • smac 各个工具初始化使用的参数储存容器,它自己并不会直接使用这些参数

存储的参数:

  • Runner 相关

    • 参考位置: BaseRunner
    • ta :: target algorithm

Runner

作用:

  • 负责实际算法(黑盒函数)或者实际目标训练模型的调用
  • 把超参数转换成 –> 运行结果 (损失函数,运行状态数据)

参数:

  • run_obj:

    • 取值:

      quality
      优化 cost_function 损失函数,提升算法的质量,如精度
      runtime
      优化算法的运行时间,提高算法的执行时间(速度)

特点:

  • 调用顺序: self.submit_run() –> self.run_wrapper() –> self.run()
  • 结果存储: self.results (一个列表)
  • 自定义一个Runner需要实现 self.run() 方法
  • self.run()

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    @abstractmethod
    def run(
            self,
            config: Configuration,
            instance: str,
            cutoff: Optional[float] = None,
            seed: int = 12345,
            budget: Optional[float] = None,
            instance_specific: str = "0",
    ) -> Tuple[StatusType, float, float, Dict]:
        """Runs target algorithm <self.ta> with configuration <config> on """
    
        pass
    
    # 调用例子
    status, cost, runtime, additional_info = self.run(
        config=run_info.config,
        instance=run_info.instance,
        cutoff=cutoff,
        seed=run_info.seed,
        budget=run_info.budget,
        instance_specific=run_info.instance_specific,
    )

类型:

  • 黑盒函数: ExecuteTAFuncDict, smac.tae.execute_func.ExecuteTAFuncDict

Runhistory

数据:

  • Runhistory.data:

    1
    2
    3
    4
    5
    6
    7
    8
    
    OrderedDict([
        (RunKey(config_id=1, instance_id=None, seed=0, budget=0.0),
         RunValue(cost=-2.2500963017546054, time=3.719329833984375e-05, status=<StatusType.SUCCESS: 1>, starttime=1736151483.725089, endtime=1736151483.7251318, additional_info={})),
        (RunKey(config_id=2, instance_id=None, seed=0, budget=0.0),
         RunValue(cost=-2.0912465314361626, time=3.1948089599609375e-05, status=<StatusType.SUCCESS: 1>, starttime=1736151483.8881495, endtime=1736151483.8881872, additional_info={})),
        (RunKey(config_id=3, instance_id=None, seed=0, budget=0.0),
         RunValue(cost=-2.8037686916529245, time=3.4332275390625e-05, status=<StatusType.SUCCESS: 1>, starttime=1736151484.017562, endtime=1736151484.017602, additional_info={}))
    ])
  • Runhistory.ids_config:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    {1: Configuration(values={
      'x0': 0.764471385627985,
      'x1': 0.03850575536489487,
      'x2': 0.400804003700614,
      'x3': 0.22312777861952782,
    })
    , 2: Configuration(values={
      'x0': 0.7365197642826136,
      'x1': 0.5236543953848458,
      'x2': 0.6667633501189131,
      'x3': 0.10099340067826357,
    })
    , 3: Configuration(values={
      'x0': 0.8637879149190518,
      'x1': 0.08961023240372623,
      'x2': 0.20420678034113104,
      'x3': 0.2707453344500017,
    })
    }

数据遍历方法:

  1. history.data.key –> config_id –> history.id_configs[config_id] –> config

    • 损失函数: history.data.value.cost –> cost
    • 超参数: history.id_configs[history.key.config_id]

结果保存

保存文件夹中的 runhistory.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
    "data": [
        [
            [1,                 # 迭代序号
             null, 0, 0.0],
            [
                -2.2500963017546054,  # cost 损失函数
                5.14984130859375e-05,
                {"__enum__": "StatusType.SUCCESS"},
                1736150375.9613395, # 开始时间戳
                1736150375.9613993, # 结束时间戳
                {},
            ],
        ],
        [
            [2, null, 0, 0.0],
            [
                -2.3674935445507694,
                3.504753112792969e-05,
                {"__enum__": "StatusType.SUCCESS"},
                1736150375.9627178,
                1736150375.96276,
                {},
            ],
        ],
    ],
    "config_origins": {  # 每一次迭代参数的生成方式,
        "1": "Sobol",
        "2": "Sobol",
        "4": "Random Search (sorted)",
        "5": "Random Search",
    },
    "configs": {  # 每一次迭代生成的超参数,对应的 cost 见上文
        "1": {
            "x0": 0.764471385627985,
            "x1": 0.03850575536489487,
            "x2": 0.400804003700614,
            "x3": 0.22312777861952782,
        },
        "2": {
            "x0": 0.32599365059286356,
            "x1": 0.7236258173361421,
            "x2": 0.8391415951773524,
            "x3": 0.879259736277163,
        },
    },
}

结果保存数据

  • runhistory.json : config 和 cost 记录
  • stats : 运行的总体状况数据汇总,用时、迭代次数
  • configspace: 参数取值范围
  • traj.json: 每一次黑盒函数运行的具体运行数据

deepseek 对话 prompt:

  • smac epm中的featture 是什么,感觉不是 超参数(config)
  • 举一个同时包含config和figure的epm 训练数据的例子

FAQ

命名方式中的 def 是什么意思

  • default
  • eg: runhistory_def_kwargs Vs runhistory_kwargs

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    runhistory_def_kwargs = {}
    if runhistory_kwargs is not None:
        runhistory_def_kwargs.update(runhistory_kwargs)
    if runhistory is None:
        runhistory = RunHistory(**runhistory_def_kwargs)
    elif inspect.isclass(runhistory):
        runhistory = runhistory(**runhistory_def_kwargs)  # type: ignore[operator] # noqa F821
    elif isinstance(runhistory, RunHistory):
        pass
    else:
        raise ValueError("runhistory has to be a class or an object of RunHistory")

scenario.rand_prob | rand_conf_chooser_kwargs

可能的含意: 参数探测的探测 Vs 挖掘倾向性比率

关联类: ChooserProbe

输出目录: output_dir

Scenario({"output_dir": …})

1
2
3
4
5
6
7
scenario = Scenario({
    "run_obj": "quality",
    "runcount_limit": 3,
    "cs": cs,
    "limit_resources": False,
    "output_dir": "../Output"
})

实际输出目录

create_output_directory(output_dir, run_id) 生成

即使 output_dir 和 run_id 一致,也不会使用原来的那个,而是重命名旧的,创建新的

smac 如何把 Configuration 转换成 np.ndarray 的?

参考:

  • smac v1.2: smac/runhistory/runhistory2epm.py

方法:

  • 使用 Configuration.get_array():

    np.array([config.get_array() for config in configs], dtype=np.float64)
    
    • 注意:这种方法,会让数据防缩到0~1之间

源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def get_configurations(
    self,
    runhistory: RunHistory,
    budget_subset: typing.Optional[typing.List] = None,
) -> np.ndarray:
    """Returns vector representation of only the configurations.

    Instance features are not appended and cost values are not taken into account.

    Parameters
    ----------
    runhistory : smac.runhistory.runhistory.RunHistory
        Runhistory containing all evaluated configurations/instances
    budget_subset : list of budgets to consider

    Returns
    -------
    numpy.ndarray
    """
    s_runs = self._get_s_run_dict(runhistory, budget_subset)
    s_config_ids = set(s_run.config_id for s_run in s_runs)
    t_runs = self._get_t_run_dict(runhistory, budget_subset)
    t_config_ids = set(t_run.config_id for t_run in t_runs)
    config_ids = s_config_ids | t_config_ids
    configurations = [runhistory.ids_config[config_id] for config_id in config_ids]
    configs_array = convert_configurations_to_array(configurations)
    return configs_array

依赖兼容性

smac 1.2

1
2
numpy<=1.26
scikit-learn<1.2