自研贝叶斯优化算法,如何判断算法能拟合?我目前是在一个tiny的数据集上跑一下,看算法能否收敛到正确的的局部最小值。这里要有两个关键词:

  1. 收敛。算法是需要收敛的。黑盒优化的本质就是增加在优势样本附近的采样率。如果算法如同随机搜索不收敛,那么是有问题的。
  2. 正确。收敛点是正确的,如果收敛到错误的点,那还不如随机搜索。

自研SMAC

代理模型

SMAC的本质是用随机森林作为代理模型。这个代理模型调包就好了(前提是你熟读开源代码千百遍,知道调什么)

from skopt.learning.forest import RandomForestRegressor, ExtraTreesRegressor

众所周知,RandomForestRegressor不仅会对行做采样,也会对列做采样。ExtraTreesRegressor只会对行做采样。就我使用的经验来看,特征>样本的情况适合用RF,其余情况一般用ET。SMAC的论文提到,他使用的随机森林会用所有的样本,但特征的采样率是83%. (SMAC源码分析->代理模型的构建

skopt文档来看,一般来说ET要比RF表现好。

获益函数

就我的经验来看,EI肯定要比PI好,因为EI算的是期望,利用的比重其实比探索要大。PI更注重探索,更发散。

skopt实现了EI, PI, LCB, EIPS等。我目前实现了EI,LogEI。

LogEI的代码可以看SMACRoBO

就实验来看,感觉LogEI和EI差别不大。个人感觉上,无loss_transform+EI == log_scaled loss_transform+LogEI

SMAC的LogEI貌似就是搭配 log_scaled loss_transform的。

RoBO的LogEI与SMAC的实现有很大不同。

使用上来看,EI+ log_scaled loss_transform即可,xi( ξ \xi ξ) 这个参数设0.010好像都没什么区别

class EI():
    def __init__(self, xi=0.01):
        # in SMAC, xi=0.0,
        # smac/optimizer/acquisition.py:341
        # par: float=0.0
        # in scikit-optimize, xi=0.01
        # this blog recommend xi=0.01
        # http://krasserm.github.io/2018/03/21/bayesian-optimization/
        self.xi = xi

    def __call__(self, model, X, y_opt):
        mu, std = model.predict(X, return_std=True)
        values = np.zeros_like(mu)
        mask = std > 0
        improve = y_opt - self.xi - mu[mask]
        scaled = improve / std[mask]
        cdf = norm.cdf(scaled)
        pdf = norm.pdf(scaled)
        exploit = improve * cdf
        explore = std[mask] * pdf
        values[mask] = exploit + explore
        # You can find the derivation of the EI formula in this blog
        # http://ash-aldujaili.github.io/blog/2018/02/01/ei/
        return values
class LogEI():
    def __init__(self, xi=0.01):
        self.xi = xi

    def __call__(self, model, X, y_opt):
        mu, std = model.predict(X, return_std=True)
        var = std ** 2
        values = np.zeros_like(mu)
        mask = std > 0
        f_min = y_opt - self.xi
        improve = f_min - mu[mask]
        # in SMAC, v := scaled
        # smac/optimizer/acquisition.py:388
        scaled = improve / std[mask]
        values[mask] = (np.exp(f_min) * norm.cdf(scaled)) - \
                       (np.exp(0.5 * var[mask] + mu[mask]) * norm.cdf(scaled - std[mask]))
        return values

遇到过的坑

不当的loss scale会导致算法陷入错误的局部最优

experiment_id=39

实验代码:

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split


X, y = load_digits(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
pipe = SystemClassifier(
    DAG_workflow={
        "num->target": [
            "liblinear_svc",
            "libsvm_svc",
            "logistic_regression",
            "random_forest",
            # "catboost",
        ]
    },
    config_generator="ET",
    config_generator_params={
        "acq_func": "EI",
        "xi": 0,
        "loss_transformer":None,
        "min_points_in_model": 20
    },
    warm_start=False,
    random_state=0,
    min_n_samples_for_SH=50,
    concurrent_type="thread",
    # max_budget=1,
    n_jobs_in_algorithm=3,
    n_workers=1,
    SH_only=True,
    min_budget=1/16,
    max_budget=1/16,
    n_iterations=100,
    # min_budget=1 / 4,
    debug_evaluator=True,
)
pipe.fit(
    X_train, y_train,
    # is_not_realy_run=True,
    fit_ensemble_params=False)
# score = accuracy_score(y_test, y_pred)
score = pipe.score(X_test, y_test)
print(score)

实验结果:
自研贝叶斯优化算法遇到的坑_搜索红色部分是warming up(启动过程,即开始时的随机搜索,20次),蓝色是贝叶斯搜索。可以看到启动时最好的样本是 (random_forest, 0.977) ,但是算法却陷入了libsvm_svc的局部最优,真是匪夷所思。

排查后认为,是没有对loss(也就是代理模型拟合的label)做log_scaled 变换。


class LogScaledLossTransformer(LossTransformer):
    def fit_transform(self, y, *args):
        y = super(LogScaledLossTransformer, self).fit_transform(y)
        # Subtract the difference between the percentile and the minimum
        y_min = self.y_min - (self.perc - self.y_min)
        y_min -= 1e-10
        # linear scaling
        if y_min == self.y_max:
            # prevent diving by zero
            y_min *= 1 - (1e-10)
        y = (y - y_min) / (self.y_max - y_min)
        y = np.log(y)
        f_max = y[np.isfinite(y)].max()
        f_min = y[np.isfinite(y)].min()
        y[np.isnan(y)] = f_max
        y[y == -np.inf] = f_min
        y[y == np.inf] = f_max
        return y
TPE

我写的TPE基于一个假设:随机变量相互独立。

遇到的坑

experiment_id=39

不对deactivated的随机变量做impute导致算法前期部分收敛,后期收敛到错误的局部最小值

实验代码:

    config_generator="TPE",
    config_generator_params={
        "fill_deactivated_value":False,
        "min_points_in_model": 40
    },

前期
前期对除了random_forest以外的choices有所探索,这是正常现象
自研贝叶斯优化算法遇到的坑_最优解_02后期收敛到了错误的libsvm_svc(为什么都喜欢翻这个算法的牌子,logistic_regression哭晕在厕所)
自研贝叶斯优化算法遇到的坑_采样率_03

predict函数中加了这段代码后:

 if N_deactivated > 0 and self.fill_deactivated_value:
     good_pdf[~mask, i] = np.random.choice(good_pdf_activated)
     bad_pdf[~mask, i] = np.random.choice(bad_pdf_activated)

experiment_id=35

前期
自研贝叶斯优化算法遇到的坑_搜索_04后期
自研贝叶斯优化算法遇到的坑_采样率_05可以看到随着算法不断迭代,TPE的观测增多,于是算法逐渐从探索转为开发,最后变成了在RF附近做局部搜索。

随机采样次数的影响

随机采样5000
自研贝叶斯优化算法遇到的坑_git_06随机采样500
自研贝叶斯优化算法遇到的坑_搜索_07kde_sample_weight=True

if self.kde_sample_weight and y_good.std() != 0:
    scaled_y = (y_good - y_good.mean()) / y_good.std()
    sample_weight = np.exp(scaled_y)

自研贝叶斯优化算法遇到的坑_最优解_08和上上图比较,在优势样本聚集(0.007和0.011各多一个)上更好。

kde_sample_weight_scaler="normalize"
自研贝叶斯优化算法遇到的坑_采样率_09
"kde_sample_weight_scaler": None
采样采重的情况大大降低

bw_method 的影响

cv容易算出接近于1的bw,导致区分度不大,算法发散

scottsilverman几乎没有差别

TS+TPE 跳出局部最优解

ET+TS
自研贝叶斯优化算法遇到的坑_git_10
ET only
自研贝叶斯优化算法遇到的坑_拟合_11
可以看到ET+TS对0.011的样本增加了,当然也可能是随机的效果。