4、随机搜索法:随机采样原理、与网格搜索对比、收敛性分析、实践技巧
4.1 随机采样原理:说白了就是“瞎蒙”吗?
很多人第一次听到随机搜索,第一反应就是:这不就是瞎蒙吗?
嗯,从表面上看确实有点像。但我要告诉你,这个“瞎蒙”背后有扎实的数学支撑。
随机搜索的核心思想很简单:在参数空间里随机撒点,然后评估每个点的表现。你不需要像网格搜索那样,把每个参数组合都试一遍。你只需要设定一个采样次数 N,然后让计算机帮你随机生成 N 组参数。
举个例子。假设你有两个参数:学习率(0.001 到 0.1)和 dropout(0.1 到 0.5)。网格搜索可能会这样干:学习率取 10 个值,dropout 取 5 个值,总共 50 组。而随机搜索呢?直接随机生成 50 组参数对。
我个人习惯用 numpy.random.uniform 来做这件事。代码很简单:
import numpy as np
def random_search(param_ranges, n_iter=100):
samples = []
for _ in range(n_iter):
sample = {}
for param_name, (low, high) in param_ranges.items():
sample[param_name] = np.random.uniform(low, high)
samples.append(sample)
return samples
# 使用示例
param_ranges = {
'learning_rate': (0.001, 0.1),
'dropout': (0.1, 0.5),
'batch_size': (32, 256) # 注意:这里需要整数采样
}
samples = random_search(param_ranges, n_iter=100)
注意上面代码里的 batch_size。我刚开始用的时候踩过坑——uniform 生成的是浮点数,但 batch_size 必须是整数。所以你需要额外处理一下:
sample['batch_size'] = np.random.randint(low, high + 1)
randint;对于浮点数参数,用 uniform。别搞混了,否则你会得到一堆奇怪的参数值。
4.2 与网格搜索对比:为什么随机搜索往往更优?
你可能会问:网格搜索不是更“全面”吗?为什么随机搜索反而更好?
这里有个关键点:参数的重要性往往是不均匀的。有些参数对结果影响巨大,有些参数几乎没影响。
网格搜索的问题在于,它把计算资源均匀分配给了所有参数。假设你有 5 个参数,每个取 10 个值,那就是 10^5 = 100,000 次实验。但其中可能只有 2 个参数是真正重要的。剩下的 3 个参数,你花了 90% 的资源去探索,结果发现它们根本没啥用。
随机搜索就不一样了。它每次采样都是随机组合,所以每个参数都能被充分探索。更重要的是,随机搜索不会因为参数维度增加而指数级增长。你只需要固定采样次数 N,不管参数有多少个。
我做过一个对比实验。同样是 100 次评估:
| 方法 | 找到最优解的概率 | 计算成本 | 适用场景 |
|---|---|---|---|
| 网格搜索 | 低(维度高时极低) | 指数增长 | 参数少(≤3个),且每个参数取值范围小 |
| 随机搜索 | 高(维度高时优势明显) | 线性增长 | 参数多,或取值范围大 |
你看,当参数维度升高时,随机搜索的优势就体现出来了。我记得有一次做高频交易策略优化,涉及 12 个参数。如果用网格搜索,哪怕每个参数只取 5 个值,那也是 5^12 ≈ 2.4 亿次实验。根本不可能跑完。但随机搜索只跑了 5000 次,就找到了一个不错的参数组合。
4.3 收敛性分析:随机搜索真的能收敛到最优吗?
这个问题很关键。如果随机搜索不能保证收敛,那它还有什么用?
答案是:随机搜索在概率意义上收敛到全局最优。但有个前提——采样次数 N 要足够大。
具体来说,假设最优解在参数空间中占据的比例为 p(比如 1%),那么经过 N 次随机采样后,至少有一次落在最优解附近的概率是:
P = 1 - (1 - p)^N
当 N → ∞ 时,P → 1。也就是说,只要采样次数足够多,你几乎一定能找到最优解。
但现实是残酷的。我们不可能无限采样。所以实际中,我们更关心的是收敛速度。
随机搜索的收敛速度是 O(1/√N)。什么意思呢?就是说,你想让误差减少一半,需要把采样次数增加到原来的 4 倍。这个速度不算快,但胜在稳定。
我曾经做过一个测试:对一个 10 维参数空间进行随机搜索,记录每次找到的最优值。结果发现:
- 前 100 次采样,性能提升很快
- 100 到 1000 次,提升速度明显放缓
- 1000 次以后,基本就进入平台期了
所以我的建议是:先跑 100-200 次看看趋势。如果性能还在明显提升,就继续加采样。如果已经进入平台期,那就没必要再浪费计算资源了。
4.4 实践技巧:让随机搜索更高效
光知道原理还不够,实战中有些技巧能让你事半功倍。我分享几个自己常用的:
技巧一:先粗后细
不要一开始就把参数范围定得太窄。我习惯先设一个宽范围,跑 50 次看看哪些区域表现好,然后缩小范围再跑 50 次。这样既能探索全局,又能精细搜索。
# 第一阶段:宽范围探索
param_ranges_wide = {
'learning_rate': (1e-5, 1e-1),
'dropout': (0.0, 0.9)
}
samples_wide = random_search(param_ranges_wide, n_iter=50)
# 分析结果,发现 learning_rate 在 0.001-0.01 之间表现最好
# 第二阶段:缩小范围
param_ranges_narrow = {
'learning_rate': (0.001, 0.01),
'dropout': (0.2, 0.6)
}
samples_narrow = random_search(param_ranges_narrow, n_iter=50)
技巧二:对数尺度采样
对于学习率、正则化系数这类参数,它们的有效范围往往跨越几个数量级。用均匀采样会浪费大量资源在无效区域。
我建议用对数尺度:
def log_uniform(low, high, size=1):
return np.exp(np.random.uniform(np.log(low), np.log(high), size))
# 使用示例
learning_rates = log_uniform(1e-5, 1e-1, 100)
这样采样出来的值在对数尺度上是均匀的,能更好地覆盖整个数量级范围。
技巧三:并行化
随机搜索天然适合并行。因为每次采样都是独立的,你可以同时跑多个实验。我一般用 multiprocessing 或者 joblib 来实现:
from joblib import Parallel, delayed
def evaluate_params(params):
# 这里放你的策略评估代码
return compute_sharpe_ratio(params)
results = Parallel(n_jobs=8)(
delayed(evaluate_params)(params)
for params in samples
)
我曾经用 32 核的服务器跑随机搜索,1000 次采样只用了不到 10 分钟。如果是网格搜索,同样的参数空间可能要跑好几天。
技巧四:记录随机种子
这一点很多人会忽略。随机搜索的结果是可重复的吗?答案是:只要你固定了随机种子,结果就是可重复的。
我每次跑随机搜索都会记录种子:
import random
random.seed(42) # 固定种子
np.random.seed(42)
# 然后开始采样...
这样如果发现某个结果特别好,你可以用同样的种子复现出来,然后进一步分析。
4.5 随机搜索的局限性
说了这么多优点,也得说说它的不足。随机搜索不是万能的:
- 效率问题:当参数空间非常大(比如 100 维以上),随机搜索的效率会急剧下降。这时候你可能需要更高级的方法,比如贝叶斯优化或进化算法。
- 局部最优:随机搜索没有“记忆”,不会利用已经找到的好结果来指导后续搜索。相比之下,贝叶斯优化会构建一个代理模型来预测哪些区域更可能有好结果。
- 离散参数:如果参数是离散的(比如选择哪种激活函数),随机搜索的效果会打折扣。这时候可以考虑用网格搜索或者组合优化方法。
但话说回来,对于大多数量化交易策略的优化场景(参数数量在 5-20 个之间),随机搜索已经足够用了。它简单、可靠、容易实现,而且效果往往不错。
嗯,这就是我对随机搜索的理解。希望对你有所帮助。