因为目前来看,最常用的贝叶斯优化方法是基于高斯过程的,所以本篇blog主要记录基于高斯过程的贝叶斯优化方法的使用(不讲理论,不讲理论~)

一、贴两个比较关键的基于高斯过程的贝叶斯优化包:

1. BayesianOptimization --- https://github.com/fmfn/BayesianOptimization

2. Botorch --- https://github.com/pytorch/botorch

我个人更加推荐第一个,实现和修改起来更加的方便。

二、关于贝叶斯优化方法,注意最大的一个特点就是对参数的优化,其实它也可以理解为一个搜索问题。所以注意BayesianOptimization的包中的block_xxx function里面的x, y, ...都是值,而不是vector。这就是说明,我们在想要利用贝叶斯优化的时候,需要先确定自己需要优化哪一些参数。如果原本想优化的是vector,那么则需要考虑如何将vector拆成单个的变量。比如我们想优化一个one-hot向量,那么很简单的我们就可以将其转换为优化一个值,比如有4种商品,我们想评估哪个商品最好吃。如果是one-hot编码,(0,0,0,1)表示第四种类型。那么直接定义一个变量,itemType,值域是{1,2,3,4}。我们通过BO去优化这么一个变量就可以了,black_xxx function就是描述商品好吃程度的打分。优化参数 参数 参数,这一点很重要,因为我自己看论文的时候总以为可以直接优化集合或者向量啥的(可能是本人愚钝)。比如哈参考下面一个随机设置的复杂函数(想象一下,太复杂的也不太好说清楚):

def black_box_function(x, y):
    return -x ** 2 - (y - 1) ** 2 + 1

#可以看到这里面一个x,一个y。然而这里的y并不是目标值的含义。而只是单纯的只两个参数,一个是x一个是#y。也就是优化了两个参数,如果你的参数更多,那么black_box_function()的参数就越多。

三、贝叶斯优化方法中最重要的一个部分就是Kernel的定义,因为基于高斯过程的贝叶斯优化方法,概率代理模型已经定义清楚了,不同的建模思路核心区别就在于Kernel,又由于采集函数是根据概率代理模型定义的(通常有ucb、pi,ei三种方法),所以直接调用1方法的包就可以了,不需要额外的考虑。因此说,利用基于高斯过程的贝叶斯优化的核心在于设计一个良好的Kernel。

四、在1包中,他定义了一些常见的Kernel,但是这些Kernel如果用于优化神经网络的参数或许能够直接适配,但是如果用于特定的一些场景,或许效果就不太好。因为Kernel function的核心目的就是计算不同的参数之间的相似性。举个例子,利用高斯过程找到的第一个参数值是v1,它的black function给出的得分是s1,因为得分不好所以我们要寻找下一个值。那么此时很显然,我们人在搜索的时候就不希望再去搜索和v1的值得分相似的值了,但是由于black function通常代价比较大,我们就需要一个快捷的衡量参数的不同取值之间的相似性的方法,这个时候Kernel就产生了。也就是说我们只要定义好kernel,我们选取的第二个值就不可能和第一个值是太过相似的,也就是说black function更可能给出我们期望的效果。后面也是依次类推。

五、经过三和四的解释,我们知道了Kernel是整个基于高斯过程的BO方法的核心。那么第1个包封装的很好的情况下,我们如何自定义Kernel function呢?注意第1个包是基于scikit-learn(注意一定要是0.24.2或以上的版本,0.24.2最好)实现的,那么我们只需要找到所有的Kernel function的base class就可以了,我贴出一下定义的链接和例子: 

        a、base class: https://scikit-learn.org/stable/modules/generated/sklearn.gaussian_process.kernels.Kernel.html#sklearn.gaussian_process.kernels.Kernel

        b、例子:https://scikit-learn.org/stable/auto_examples/gaussian_process/plot_gpr_on_structured_data.html#sphx-glr-auto-examples-gaussian-process-plot-gpr-on-structured-data-py

注意只需要实现 def _f(self, s1, s2) 这个方法就可以了。其中s1,s2是两个向量(list),分别代表了第一步取出的参数和第二步取出的参数。如果我们优化了2个参数a,b。那么s1=[a1,b1] s2=[a2,b2],如果是多个参数依次类推。然后我们计算这些参数在不同的步(step)下,他们的相似性就可以了。这个地方具体情况具体分析。我的建议是相似性的取值(0,1)即可。小tips: 有的时候计算kernel的不同参数下的相似性可能还需要依赖一些额外的信息,这些都可以直接定义在Kernel function里面,然后初始化的时候赋值就可以了,并不影响kernel的计算,比如

class YourKernel(Kernel):
    def __init__(self,your_extra_information,
                 baseline_similarity_bounds=(1e-5,1),
                 rootcause_type=2):
        self.your_extra_information=your_extra_information
        self.baseline_similarity_bounds=baseline_similarity_bounds
    @property
    def hyperparameters_baseline_similarity(self):
        return Hyperparameter("baseline_similarity", self.baseline_similarity_bounds)

    def _f(self,s1,s2):
        #s1,s2分别是两个list,里面放了不同step下的参数
        #计算s1,s2的相似性,还需要利用到your_extra_information。
        #最终只要return 的是相似性的值就可以了
        if (s1 and s2 satisfy your_extra_information):
            return 1 #满足条件 则 s1 和 s2相似
        else:
            return 0 #不满足条件则为0

    def __call__(self, X, Y=None, eval_gradient=False):
        if Y is None:
            Y = X

        if eval_gradient:
            return (np.array([[self._f(x, y) for y in Y] for x in X]),
                    np.array([[[self._g(x, y)] for y in Y] for x in X]))
        else:
            return np.array([[self._f(x, y) for y in Y] for x in X])

    def diag(self, X):
        return np.array([self._f(x, x) for x in X])

    def is_stationary(self):
        return False

    def clone_with_theta(self, theta):
        cloned = clone(self)
        cloned.theta = theta
        return cloned

可以看到,你只需要实现def __init__()和 def _f(self, s1,s2)就可以了。相似性是在_f()函数中定义的

六、定义好自己的kernel之后,接下来就是如何将第1个包中的BayesianOptimization类种的高斯过程的kernel function替换

low=0;
high=100;
optimizer = BayesianOptimization(
    f=function_to_be_optimized, #Your score function/black function which is high expensive to run.
    pbounds={'your parameter': (low, high) }, #参数的取值范围从low-high,比如从10到20区间内的实数
    verbose=2,
    random_state=1,
)

#Then, you can see the original code file named "bayesian_optimization.py" and find the #internal variable "_gp" when initializing the Gaussian process. So you can do the following #operations in your code.

optimizer._gp=GaussianProcessRegressor(
    kernel=your_kernel_function,
    alpha=1, #you should tune the value according to your need, such as iteration number of your optimization
    normalize_y=True,
    n_restarts_optimizer=0,
    random_state=optimizer._random_state,
)

七、关于自定义Kernel而言,需要考虑一个问题,高斯过程的设计是需要满足一定的要求的,也就是说kernel function的设计要满足一定的要求,有个比较合适的解决方法就是我们先自己根据需求定义出来Kernel function,然后再通过一个已有的Kernel进行包装,来满足需要。通常情况下,Exponentiation Kernel是一个理想的包装方式

your_kernel = Exponentiation(YourKernel(), exponent=2)

八、当完成上述的内容之后,就完成了对第1个包的一个改善工作,达到自定义Kernel并进行优化的目的。具体的优化代码可以参考里面给的例子:

https://github.com/fmfn/BayesianOptimization/blob/master/examples/advanced-tour.ipynb

九、关于前面所说的如果优化的参数取值只能是整数怎么办,这其实也很简单,我们按照连续值的方式进行定义,比如有1,2,3,4可以取值,那么pbounds里面的元组就定义为(1,4),虽然取出来的是实数,但是只要我们转为int类型其实就可以了。

如有理解不正确的地方,请各位指出,非常感谢!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐