1 黑盒优化的概念
上一篇《新手一步步学习黑盒优化算法,借助达摩院MindOpt的RABBO榜单【系列1/4】》中给大家介绍了黑盒优化的概念、和怎么借助阿里达摩院的RABBO“动手”。这篇将着重学习黑盒优化的算法。
先复习一下上篇关于雨黑盒优化的内容:
什么是黑盒优化?
「黑盒优化问题」泛指目标函数难以从数学上解析表达,缺少可直接利用的梯度信息,仅可利用目标函数输入和对应输出函数值进行最优解搜索的优化问题。
这类问题要怎么优化求解?
可以 通过获取不同控制参数(输入变量)对应的系统表现,来推断和搜寻优化解。架构就像下面的图这样,我们假设我们要求解的问题不好描述,就把这个问题做成一个仿真系统,这个系统可以根据输入的变量值(“候选解”)来给出对应的评价(“观测值”),然后黑盒优化算法,就是接上这个仿真系统,通过不断地去提供候选解和得到观测值,来搜索可能的最优表现的候选解。
图注: 云栖大会上王孟昌博士 https://yunqi.aliyun.com/2021/agenda/session170 直播回放大约20分钟的时候截图
2 RABBO中的算法:CMA方法、randomSearch方法。
根据网页介绍:https://tianchi.aliyun.com/specials/promotion/BlackBox 的指引,下载了RABBO仓库。可以参看上一篇文档《【系列1/4】》指导来下载。RABBO的algorithms文件夹里面有两个内置的算法,在目录 blackbox_starter_kit
-> algorithms
文件夹下面。
2.1 CMA(CMA-ES, 协方差矩阵自适应进化策略)
2.1.1 cma 背景知识
点开RABBO文件夹下面的blackbox_starter_kit/algorithms/cmaes/cma_algorithm.py
文件,可以看到第13行写着:
import cma.purecma as purecma
看来这个代码示例是利用cma工具包来操作的。在安装的requirements.txt
文件中需要cma
。
可在这儿查看cma的一些介绍:https://www.cnpython.com/pypi/cma (英文的在这儿:https://pypi.org/project/cma/)。
一种求解困难(非凸、病态、多模、粗糙、带噪声)连续优化问题的随机数值优化算法,使用Python实现。( 参考 )
其中pycma模块提供了独立的两个CMA-ES算法实现,即cma.CMAEvolutionStrategy类和cma.purecma.CMAES类。
再百度搜索一下CMA-ES(协方差矩阵自适应进化策略)会有很多的理论的教程。
2.1.2 读代码
来看看RABBO中的示例代码。首先看blackbox_starter_kit/algorithms/submission/submission_algorithm.py
,与cma_algorithm.py
相似。submission_algorithm.py
中注释掉的代码部分,是4种功能的参考:
- 提取问题相关接口和参数。
- 实现一个循环采样的逻辑直到完成指定数量的采样并且消耗完指定的优化时长。
- 在循环采样过程中使用相应api评估函数值。
- 在循环采样过程中使用相应api评估约束违背情况。
图注:这个截图的文件里,我添加了cma算法代码进去,所以行号和长度不一样。
然后再看cma_algorithm.py
。
- 后面有几个自定义函数
def convert_to_real_config(self):
、def get_bounds(self, real_config):
、def convert_to_origin_param_dict(self, suggest_x):
、def update_best(self, param, fval, directs):
,做参数数据等的计算和更新工作。 - 算法迭代的内容在
def optimize(self, prob_info, query_budget, time_budget):
中。
这个示例代码里,用CMA库去做RABBO库中的问题求解的主要步骤如下:
(1) 首先,初始化参数,参数用处后面会体现。
def __init__(self, sigma=0.3, penalty_coeff=10e8, penalty_thresh=10e-3, **kwargs):
"""Build wrapper class to use an optimizer in benchmark.
Parameters
----------
"""
AbstractAlgorithm.__init__(self)
self._sigma = sigma
self.name = "CMA"
self.penalty_coeff = penalty_coeff
self.penalty_thresh = penalty_thresh # 0
(2) 然后对输入的一番处理。
如_query_budget
和_time_budget
(次数和时间预算)用于后面迭代次数的控制,在examples/experiment_example.py
中会用到。time_budget时间单位在optimize()
中注释的是秒。
(3) 创建一个CMAES的实例
x0 = np.asarray(lb+(ub-lb)/2) # initial point for CMA
sigma = np.asarray(self._sigma) # standard deviation (step size)
es = purecma.CMAES(x0, sigma) # create CMAES instance
(4)开始迭代优化
最外层while循环是用
_query_budget
和_time_budget
(次数和时间预算)控制的。每次通过
es.ask()
来获取一组推荐解X
;es.tell(X, fitlist)来
更新ES。 这里要注意CMA默认的算法direction是minimization的,因此fitlist赋值时候需要转换一下。X循环内,
- 采用
funcval = self._problem(suggest_x_ori)
来获得待优化问题的评价evaluate, - 采用
violations, fg = self._constr_eval(suggest_x_ori)
来评估约束的违反情况,并根据惩罚penalty调整评价。
- 采用
- 如果没有违反约束(
fg
),则把def update_best(self, param, fval, directs)
结果更新。
(5)返回结果:return self.best_param, self.best_fval
核心部分的代码copy如下:
ct = 0
while self._query_budget - ct > 0 or self._time_budget - tm >0: # loop until both query budget and time budget are used
X = es.ask() # deliver candidate solutions
fitlist=[]
for x in X:
fg = True
suggest_x_ori = self.convert_to_origin_param_dict(x)
self.para_list.append(suggest_x_ori)
funcval = self._problem(suggest_x_ori)
self.func_list.append(funcval)
aug_funcval = copy.deepcopy(funcval)
if self._constraint_types != []:
penalty = 0
violations, fg = self._constr_eval(suggest_x_ori)
self.constraint_viol.append(violations)
for j in range(len(violations)):
if self._constraint_types[j] == 'lt':
penalty = max(violations[j], 0) * self.penalty_coeff
elif self._constraint_types[j] == 'eq':
penalty = abs(violations) * self.penalty_coeff
for k in range(len(funcval)):
if self._directions[k] == "minimize":
aug_funcval[k] += penalty
elif self._directions[k] == "maximize":
aug_funcval[k] -= penalty
self.aug_func_list.append(aug_funcval)
if self._directions[0] == "maximize": # for now only consider single objective
fitlist.append(-aug_funcval[0])
elif self._directions[0] == "minimize":
fitlist.append(aug_funcval[0])
# if penalty > self.penalty_thresh * abs(funcval[k]): # use customized judgement
if fg: # fg = True means feasible
self.update_best(suggest_x_ori, funcval, self._directions)
else:
# print("The current point is infeasible.")
print(".") # yoyo add
ct += len(funcval)
es.tell(X, fitlist) # all the work is done here
tm = time.time() - st
if self.best_param == []:
print("No feasible points have been found.")
self.best_fval = []
else:
print("Best value found:\n\tf(x) = {}\nObserved at:\n\tx = {}".format(self.best_fval, self.best_param))
return self.best_param, self.best_fval
2.2 Random Search(随机搜索)
2.2.1 Random Search 背景知识
RandomSearch的代码在RABBO的blackbox_starter_kit/algorithms/random_search/random_search_algorithm.py
文件。
继续百度一下Random Search,又会有一堆材料。随机搜索,如字面意思,就是随机撒点来搜索。
2.2.2 读代码
继续读RABBO的random_search_algorithm.py
文件。挺多代码内容是一样的。类似前面的内容分解代码:
(1) 首先,初始化参数,用处后面会体现。random search与前面的CMA不同的地方是sigma换成了n_suggestion
,意思是采样点的个数,在后面的代码里可以看出用途。
def __init__(self, n_suggestion=1, penalty_coeff=10e8, penalty_thresh=10e-3, **kwargs):
"""Build wrapper class to use an optimizer in benchmark.
Parameters
----------
"""
AbstractAlgorithm.__init__(self)
self._n_suggestions = n_suggestion
self.name = "Random search"
self.penalty_coeff = penalty_coeff
self.penalty_thresh = penalty_thresh # 0
(2)然后对输入的一番处理。类似之前CMA的代码。
(3)开始迭代优化,大部分内容与前面CMA代码解析相似,除了红色标记部分。
最外层while循环是用
_query_budget
和_time_budget
(次数和时间预算)控制的。_n_suggestions
(多个个采样点)的循环内,默认是1个- 采用numpy生成随机推荐值
cand_x = np.random.uniform(lb, ub, size=(1, len(lb)))
- 采用
funcval = self._problem(suggest_x_ori)
来获得待优化问题的评价evaluate, - 采用
violations, fg = self._constr_eval(suggest_x_ori)
来评估约束的违反情况,并根据惩罚penalty调整评价。 - 如果没有违反约束(
fg
),则把def update_best(self, param, fval, directs)
结果更新。
- 采用numpy生成随机推荐值
(4)返回结果:return self.best_param, self.best_fval
核心部分的代码copy如下:
while self._query_budget - ct > 0 or self._time_budget - tm >0:
ct += self._n_suggestions
for i in range(self._n_suggestions):
fg = True
cand_x = np.random.uniform(lb, ub, size=(1, len(lb)))
suggest_x_ori = self.convert_to_origin_param_dict(cand_x.tolist()[0])
self.para_list.append(suggest_x_ori)
funcval = self._problem(suggest_x_ori)
self.func_list.append(funcval)
if self._constraint_types != []:
aug_funcval = copy.deepcopy(funcval)
penalty = 0
violations, fg = self._constr_eval(suggest_x_ori)
print('fg','feasible' if fg==True else 'infeasible')
self.cosntraint_viol.append(violations)
for j in range(len(violations)):
if self._constraint_types[j] == 'lt':
penalty = max(violations[j], 0) * self.penalty_coeff
elif self._constraint_types[j] == 'eq':
penalty = abs(violations) * self.penalty_coeff
for k in range(len(funcval)):
if self._directions[k] == "minimize":
aug_funcval[k] += penalty
elif self._directions[k] == "maximize":
aug_funcval[k] -= penalty
self.aug_func_list.append(aug_funcval)
# print(self.cosntraint_viol)
if fg:
self.update_best(suggest_x_ori, funcval, self._directions)
else:
print("The point is infeasible.")
tm = time.time() - st
if self.best_param == []:
print("No feasible points have been found.")
self.best_fval = []
else:
print("Best value found:\n\tf(x) = {}\nObserved at:\n\tx = {}".format(self.best_fval, self.best_param))
return self.best_param, self.best_fval
2.3 两种方法结果对比
2.3.1 肉眼对比
怎么评判算法的好坏呢?RABBO里面提供了个评测思路:对于单一问题,从采样次数和优化时间两个维度考察算法表现,分别记录:
- 算法在给定采样次数内得到的最优解,
- 算法在给定优化时间内得到的最优解。
也就是根据最后的目标值f(x) 和运算耗时来评估。
这里我们把RABBO自带的两种方法的输出结果整理一下,得到如下的结果对比表格。问题的f(x)值不是都是越大越好的,需要根据优化方向来定的,可以去对应问题的Python文件看到directions
。
跑实验给定的参数可在examples/experiment_example.py
中修改,我用的是默认的(根据后面RABBO线上测评的结果看,是与线上测评的次数设置不一样的,大家根据需要修改):
exp.run(n_exps=2, query_budget=200, time_budget=1.5)
表注:CMA 和 RandomSearch算法效果对比
Problem | CMA | Random Search |
---|---|---|
Rover(D60) ["maximize"] | f(x) = [0.6208424134973445] | f(x) = [-7.616628618190374] |
Time_cost:4.069791 | Time_cost:1.501341 | |
f(x) = [-2.208531434656983] | f(x) = [-5.440533957191118] | |
Time_cost:4.035468 | Time_cost:1.504337, | |
Ackley20 ["minimize"] | f(x) = [0.15557523403160234] | f(x) = [19.210505137025393] |
Time_cost:1.526049, | Time_cost:1.502744, | |
f(x) = [0.09328943244080223] | f(x) = [19.65136222860868] | |
Time_cost:1.529486, | Time_cost:1.521297, | |
Rastrigin20D["minimize"] | f(x) = [4.561892672012249] | f(x) = [182.66992495064426] |
Time_cost:1.516006, | Time_cost:1.522427, | |
f(x) = [3.468608199278009] | f(x) = [173.0407459158929] | |
Time_cost:1.539840, | Time_cost:1.511754, | |
Windfarm ["maximize"] | No feasible points | No feasible points |
Time_cost:21.864100, | Time_cost:14.613770, | |
No feasible points | No feasible points | |
Time_cost:21.935938, | Time_cost:15.937529, |
从上表可以看出,目标值f(x)结果是CMA的结果更优秀呢,虽然在有些问题上耗时久一些。不过风场的这个Windfarm问题两个算法都解不出来,可以感觉到这两种方法都有些弱呢。
2.3.2 提交到RABBO线上测评
上一篇《【系列1/4】》中给大家介绍了怎么打包镜像评测,
最近RABBO的线上评测题有修改,测评的时间非常的长,要好几个小时。
根据评分规则,totalScore
是越高越好,据说是不知道上限有多高的,因为有些问题没有人知道最优解是多少。windfarm
那个问题没有解出来,拉低了平均分很多呀。smelting
的问题只有在线上测评时候有,看起来也很难解呢…………
RABBO线上测评结果截图如下(结果里面的日志是有测评平台输出和我们自己编代码里的print输出):
RABBO线上评测给出的成绩score整理成表格如下:
problem | testcma | testRandomSearch | 对比分析 |
---|---|---|---|
synthetic_score | 1.0622723286900113 | 0.00036699951336779913 | 随机搜索可能目标值太差了,溃败 |
smelting_score | 0.047487121954627516 | 0.000045399929762484854 | 随机搜索这个评分应该是没有求解出来 |
windfarm_score | 0.000045399929762484854 | 0.000045399929762484854 | 随机搜索这个评分应该是没有求解出来 |
rover_score | 1.0220139967289352 | 0.951324606382894 | 差异不大 |
totalScore | 0.532954711826 | 0.237945601439 | 线上评测也是CMA算法更好 |
3 其他的算法
从前面的测评结果看,CMA(CMA-ES, 协方差矩阵自适应进化策略)要比random Search(随机搜索)要优秀,不过对于RABBO中给出的smelting
和windfarm
问题都不能好的求解,根据云栖大会MindOpt团队的视频分享( https://yunqi.aliyun.com/2021/agenda/session170 直播回放),他们的求解器性能应该很不错的。还有更好的算法值得去发掘、学习和研究。
从前面的搜索中,大家应该能看到很多其他的优化算法。有一篇写的挺不错的:《贝叶斯优化: 一种更好的超参数调优方式》https://zhuanlan.zhihu.com/p/29779000
自动调参算法:说到自动调参算法,大家可能已经知道了Grid search(网格搜索)、Random search(随机搜索),还有Genetic algorithm(遗传算法)、Paticle Swarm Optimization(粒子群优化)、Bayesian Optimization(贝叶斯优化)、TPE、SMAC等。像遗传算法和PSO这些经典黑盒优化算法,我归类为群体优化算法,也不是特别适合模型超参数调优场景,因为需要有足够多的初始样本点,并且优化效率不是特别高,本文也不再详细叙述。
目前业界用得比较多的分别是Grid search、Random search和Bayesian Optimization。
大家也可以多利用互联网搜索,多学习一下。
后面我也会找一个经典的方法改造一下参与测评,欢迎交流。
4 其他文
后面,立个flag,将补充更多文章,学会黑盒优化。
借助阿里达摩院的MindOpt的RABBO榜单系列:
1.《新手一步步学习黑盒优化算法,借助达摩院MindOpt的RABBO榜单【系列1/4】》
2.(本篇)《学习黑盒优化榜单RABBO提供的两个参考算法CMA和RandomSearch》
- todo:《改编一个自己的黑盒优化算法或开源算法》
- todo:《将我要解决的业务问题建模成RABBO的问题格式,然后去求解》