闲话 22.9.29
闲话
发现很多人都会很用心来写闲话。
gtm数了数muel的闲话 一共514字
是不是我写个114字就达到标准了呢?
gtm在闲话里不断记录他人的话语。
似乎闲话就是这种东西,记录你当时想写的东西
某人提醒我说有相册这种东西
似乎是的 我保存了很多人的很多相册
所以不用担心图荒了
但我更喜欢在blog里放歌词
所以和我无关
教室の廃材が宙に浮かぶ
やっぱどうしたって嫌なもんは
嫌なんだろうなきっと
ひとりぼっちを選べない私の
馬鹿げたモノローグ
……
模拟退火(SA)
今天lyin退火切了T4
我认为随机化是很好的东西
于是来学一学
模拟退火是什么?
随机化。
我们着眼的是工业上的退火程序。由于退火的规律引入了更多随机因素,那么我们得到最优解的概率会大大增加。于是我们可以将目标函数作为能量函数,模拟这个程序。
怎么退火?
首先要有一个当前温度 \(T_0\),然后再来一个终止温度 \(T_e\) ,接着再给一个降温系数 \(d\)。其中 \(T_0\) 是一个很大的数(一般取 \(10^9\) 或 \(10^{12}\) ),\(T_e\) 是一个较小的数(一般取 \(10^{-12}\) 或 \(10^{-16}\),当然你取 \(10^{-66}\) 也没人管你),降温系数是一个接近 \(1\) 小于 \(1\) 的数。
参数部分很玄学,请自行尝试更多的可能性。
假设我们当前有了一个状态,它的解是 \(S_0\)。然后我们随机更改这个状态,得到新的解 \(S_1\)。假如 \(S_1\) 优于 \(S_0\) 则接受这个新的答案,若 \(S_1\) 劣于 \(S_0\) 则以概率接受这个答案。后半部分很重要,是退火的关键。
假设劣解与优解的差值为 \(\Delta S>0\),当前温度为 \(T\),则我们接受这个解的概率是 \(\large e^{\frac {-\Delta S} T}\)。容易发现这个概率的取值为 \((0,1)\)。由于指数上 \(\frac 1T\) 的存在,当温度很高时劣解的劣势会被减小,我们就有更多的可能跳出当前次优解,并在温度减小的过程中逐渐稳定,最终以很大的概率到达最优位置。
我们称这样的随机更改为一次转移。每次转移后执行 \(T_0 = d\times T_0\),当 \(T_0 < T_e\) 时结束退火,反之继续转移。
通常维护所有转移后得到解中最优解作为答案。
保证能切吗?
不保证。
但是有很多技巧可以尝试。玄学
1. 玄学调参
一眼扫过去我们能发现很多数值,像是 \(T_0,d\) 啥的。随便改改数,拟合一下大样例是很好的选择。
注意不要自己落入次优解。
2. 分段模拟退火
把值域分开,每段单独跑一次退火。
如果这个函数的峰很多那可以优化求解,但是如果你在跑单峰那还是别麻烦自己了。
3. 多跑几次
跑一遍退火得到当前解,再把它作为初始解扔到退火函数里再跑一次。
一般跑个三五次就够了。但如果你觉得不过瘾——
4. 卡时
暴力做法必备。
使用 clock()
函数返回程序运行时间,如果再跑一次退火就超时就直接输出答案并结束程序。
这里假设你的退火函数叫 SA()
。
do {
SA() ;
} while(1.0 * clock() / CLOCKS_PER_SEC < 0.95); // 0.95 可以换成时间限制减去0.1或0.05,具体由跑一次退火的时间决定。
实例
by LYinMX
const double d = 0.99, T0 = 1e9, Te = 1e-12;
double res; // 答案
mt19937 mt( (ull)(&N) );
int Rand ( int l, int r ) { return uniform_int_distribution<>(l,r)(mt); }
db Randdb ( db l, db r ) { return uniform_real_distribution<>(l,r)(mt); }
void SA () {
{ // 初始化状态
for( int i = 1; i <= n; ++i ) arr[i] = Randdb(0,1), arr[0] += arr[i];
for( int i = 1; i <= n; ++i ) arr[i] = arr[i] / arr[0] * x;
}
double t = T0, now = 0;
for( int i = 1; i <= n; ++i ) for( auto it : son[i] ) now += arr[i] * arr[it] / 2; // 初始化答案
while( t > Te )
{
{ // 计算当前解
int x = Rand(1,n), y = Rand(1,n);
double e = arr[x] * Randdb( Lim( t * 1e-6 ), Lim(t) ), sum = now;
for( auto it : son[x] ) sum -= e * arr[it]; arr[x] -= e;
for( auto it : son[y] ) sum += e * arr[it]; arr[y] += e;
}
double delta = sum - now;
res = max( res, sum );
if( Randdb(0,1) <= exp(delta/t) ) now += delta; // 当 delta > 0 时必定接受解 反之按随机概率接受解
else arr[x] += e, arr[y] -= e; // 不接受
t *= d; // 冷却
}
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat220929.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。