无奈中的聪慧——退火算法

9次阅读
没有评论

共计 3011 个字符,预计需要花费 8 分钟才能阅读完成。

导引

少年啊,你是否发现自己的贪心不够优秀?

少年啊,你是否发现自己不会此题的 DP?

少年啊,你是否发现自己对正解毫无头绪?

少年啊,想不想再向上提高自己的分数?

如果你也有以上问题,那就来看看这里吧。


过时的果实——爬山算法

一个很简单的题目类似于这样:

有一个复杂的未知函数 \(f(x)\),变量 \(x\) 控制它的结果,需要尝试在 \([l, r]\) 上找到它的最大值。

你可能会说:枚举不行吗?

那如果,未知函数 \(f(x)\) 运行超慢?

人类完蛋啦!

其实非也,如果函数图像呈现一个山峰的形状,那就意味着有一种算法相当简单:一直往高处走就好了(也就是尝试将 \(x\) 固定调小和调大一个值(步长),取结果好的那个调整 \(x\),然后把步长缩小一点点,如此循环)。

不难发现,这样的算法其实相当优秀,只要步长到达一个极小值后结束爬山,自己其实就在山顶了。

无奈中的聪慧——退火算法

但是不难发现它的弊端和限制:

  1. 如果函数有多个峰谷,极大可能找不到最优解,而是找到了一个局部最优解;
  2. 很多时候,我们遇到的问题都是多个参数,甚至一个数组的调整。
无奈中的聪慧——退火算法

聪慧的调整——模拟退火

让我们上一点高难度的说法:化步长为 温度。

众所周知,一个物体的 温度 越高,它就越不稳定。

不难发现,爬山算法之所以常会陷入局部最优解,是因为它 只会接受较优解 ,而不会 接受可能对跳出局部最优有帮助的较差解。

那么,解决问题的方法就很简单了:设置一个温度 \(T \in [0, 1)\),以 \(T\) 的概率接受较差解。

这样的话,一开始就自然而然地学会尝试,之后就慢慢求稳。

但是……步长就不能直接用了吧,毕竟参数可能有好多,调整范围可能较大……

那就——上达尔文的《进化论》!

生物基因发生随机的基因突变,然后被自然选择。

我们已经创建了“自然选择”,“基因突变”如何处理?呵呵 o(*~︶~*)o,答案是——

随机扰动

随机在当前状态改变一点东西,看看结果如何呗,最多就是回退嘛。

那么你将得到:

无奈中的聪慧——退火算法

那么这就很简单了,参考代码如下:

// 随机数生成函数
double rand_double() {
    return rand() / (RAND_MAX + 1.0);  // [0, 1)
}
double rand_range(double lo, double hi) {
    return lo + (hi - lo) * rand_double();
}

double perturb(double v, double T, double vmin, double vmax) { // 随机扰动(不一定要这么写)
    double delta = T * (vmax - vmin);
    double nv = v + rand_range(-delta, delta);
    if (nv < vmin) nv = vmin;
    if (nv > vmax) nv = vmax;
    return nv;
}

void simulate_annealing() { // 模拟退火
    // 初始解
    double cur_x = rand_range(X_MIN, X_MAX);
    double cur_y = rand_range(Y_MIN, Y_MAX);
    double cur_f = f(cur_x, cur_y);

    // 答案初始(因为有可能我的最后状态正好随机接受了劣解,所以答案更新和状态更新异步)
    ans_x = cur_x; ans_y = cur_y; ans_f = cur_f;

    for (double T = 1; T > 1e-7; T *= DELTA) { // DELTA 一般取 0.95 左右,1e-7 也是可以改的
        // 随机扰动一下
        double nx = perturb(cur_x, T, X_MIN, X_MAX);
        double ny = perturb(cur_y, T, Y_MIN, Y_MAX);
        double nf = f(nx, ny);

        // 是更优解 或 生成随机数看看接不接受劣解
        if (nf > cur_f || rand_double() < T) {
            cur_x = nx; cur_y = ny; cur_f = nf;
            if (cur_f > ans_f) {
                ans_x = cur_x; ans_y = cur_y; ans_f = cur_f;
            }
        }
    }
}

C++

随机扰动甚至可以随机交换数组元素等,\(f\) 一般就是把题目给的贡献计算函数翻译成代码。

所以说,这样的退火即为 模拟退火,是很有可能达到全局最优解而不超时的,又可以称为——“优雅的暴力”。


优美的优化——余弦退火

但是,真实考试中计算梯度下降次数太难了,很难具体控制退火几次,此时数学就可以发力了。

目前,我们第 \(i\) 次的温度是:\(\Delta^i\)。

我们发现,其实 \(\cos(x)\) 也符合类似的规律:一开始下降快,后面下降慢。

恭喜你,发明了 余弦退火 ,只需要使用一个优美的数学公式(\(N\) 是 目标迭代次数):

\[
T_i = T_{min} + \frac{1 – T_{min}}{2}\left(1 + \cos\left(\frac{\pi \cdot i}{N – 1}\right)\right)
\]

看晕了?而 \(T_{min}\) 为 \(0\),所以 \(T_i\) 是:

\[
T_i = \frac{1}{2}\left(1 + \cos\left(\frac{\pi \cdot i}{N – 1}\right)\right)
\]

因此,模板就成了:

// 随机数生成函数
double rand_double() {
    return rand() / (RAND_MAX + 1.0);  // [0, 1)
}
double rand_range(double lo, double hi) {
    return lo + (hi - lo) * rand_double();
}

double perturb(double v, double T, double vmin, double vmax) { // 随机扰动(不一定要这么写)
    double delta = T * (vmax - vmin);
    double nv = v + rand_range(-delta, delta);
    if (nv < vmin) nv = vmin;
    if (nv > vmax) nv = vmax;
    return nv;
}

void simulate_annealing() { // 余弦退火
    // !!! 没有 T 啦!

    // 初始解
    double cur_x = rand_range(X_MIN, X_MAX);
    double cur_y = rand_range(Y_MIN, Y_MAX);
    double cur_f = f(cur_x, cur_y);

    // 答案初始(因为有可能我的最后状态正好随机接受了劣解,所以答案更新和状态更新异步)
    ans_x = cur_x; ans_y = cur_y; ans_f = cur_f;

    // !!! 只从这里改了两行 !!!
    for (int i = 0; i < MAX_ITER; i++) { // MAX_ITER 是退火次数
        double T = 0.5 * (1.0 + cos(3.1415926 * i / (MAX_ITER - 1)));

        // 随机扰动一下
        double nx = perturb(cur_x, T, X_MIN, X_MAX);
        double ny = perturb(cur_y, T, Y_MIN, Y_MAX);
        double nf = f(nx, ny);

        // 是更优解 或 生成随机数看看接不接受劣解
        if (nf > cur_f || rand_double() < T) {
            cur_x = nx; cur_y = ny; cur_f = nf;
            if (cur_f > ans_f) {
                ans_x = cur_x; ans_y = cur_y; ans_f = cur_f;
            }
        }
    }
}

C++

所以,这就是——退火!


例题

分类:模拟退火

注:模拟退火的题也可以使用余弦退火哦~


进食后人

记得初始化随机种子哦。

正文完
 0
梨灵
版权声明:本站原创文章,由 梨灵 于2026-09-29发表,共计3011字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)