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 次,就找到了一个不错的参数组合。

核心结论:随机搜索在低维空间(≤3维)可能不如网格搜索,但在高维空间(≥5维)几乎总是更优。这是经验之谈,也是理论证明过的。

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 个之间),随机搜索已经足够用了。它简单、可靠、容易实现,而且效果往往不错。

嗯,这就是我对随机搜索的理解。希望对你有所帮助。


蓝海数据掘金营,专注资料整理