kyrie
kyrie
发布于 2025-11-16 / 17 阅读
0
0

随机重启和微型GA

问题: min_{x \in [0,5]^2} f(x)=(x_1-2)^2+(x_2-3)^2+sin(3x_1)sin(2x_2)

方法A:网格/随机重启的局部搜索

思路:

  1. [0,5]^2 里随机选一个点;

  2. 看四个方向 (x_1 \pm step,x_2 \pm step) 有没有更小的 f

  3. 有就走过去,继续找;

  4. 一直走到“周围都不下降为止“(局部最优);

  5. 为了避免卡在局部解,多随机重启几次,取最好的一次。

Code
# 目标函数
def f(x1, x2):
    return (x1 - 2) ** 2 + (x2 - 3) ** 2 + math.sin(3 * x1) * math.sin(2 * x2)


def f_vec(x):
    return f(x[0], x[1])


BOUNDS = [(0.0, 5.0), (0.0, 5.0)]


# 随机重启局部搜索
def local_search_once(max_iters=200, step=0.05):
    x = [random.uniform(*BOUNDS[0]), random.uniform(*BOUNDS[1])]
    fx = f_vec(x)

    for _ in range(max_iters):
        improved = False
        for dim in (0, 1):
            # 四个邻居:x1 +- step, x2 +- step
            for direction in (-1, 1):
                cand = x.copy()
                cand[dim] += step * direction
                # 边界截断到 [0,5]
                cand[dim] = max(BOUNDS[dim][0], min(BOUNDS[dim][1], cand[dim]))
                f_cand = f_vec(cand)
                if f_cand < fx:
                    x, fx = cand, f_cand
                    improved = True
                    break
        if not improved:
            break   # 附近没有更好的点,认为到达局部最优
    return x, fx


def random_restart(num_restarts=20, **kwargs):
    best_x, best_f  = None, float("inf")
    for _ in range(num_restarts):
        x, fx = local_search_once(**kwargs)
        if fx < best_f:
            best_f, best_x = fx, x
    return best_x, best_f

方法B:微型GA

Code
# 微型GA
def ga_once(pop_size=20, generations=20, crossover_p=0.8, mutation_p=0.3, mutation_sigma=0.1):
    
    # 初始化种群:均匀随机
    pop = [[random.uniform(*BOUNDS[0]), random.uniform(*BOUNDS[1])] for _ in range(pop_size)]
    
    def fitnses(ind):
        # 因为要 “最小化 f”,可以把适应度设成 -f
        return -f_vec(ind)
    
    for _ in range(generations):
        # 计算适应度
        fits = [fitnses(ind) for ind in pop]
    
        # 锦标赛选择
        def select():
            i, j = random.randrange(pop_size), random.randrange(pop_size)
            return pop[i] if fits[i] > fits[j] else pop[j]
        
        new_pop = []
        while len(new_pop) < pop_size:
            p1, p2 = select(), select()

            # 交叉:简单线性交叉
            if random.random() < crossover_p:
                alpha = random.random()
                c1 = [alpha * a + (1 - alpha) * b for a, b in zip(p1, p2)]
                c2 = [alpha * b + (1 - alpha) * a for a, b in zip(p1, p2)]
            else:
                c1, c2 = p1[:], p2[:]

            # 变异:对某一维加一点高斯噪声,再截断回区间
            for c in (c1, c2):
                if random.random() < mutation_p:
                    d = random.randrange(2)
                    c[d] += random.gauss(0, mutation_sigma)
                    c[d] = max(BOUNDS[d][0], min(BOUNDS[d][1], c[d]))
                new_pop.append(c)
                if len(new_pop) >= pop_size:
                    break
        pop = new_pop

    # 返回种群里 f 最小的个体
    best = min(pop, key=f_vec)
    return best, f_vec(best)

完整代码

import math
import random
import time
import statistics as st


# 目标函数
def f(x1, x2):
    return (x1 - 2) ** 2 + (x2 - 3) ** 2 + math.sin(3 * x1) * math.sin(2 * x2)


def f_vec(x):
    return f(x[0], x[1])


BOUNDS = [(0.0, 5.0), (0.0, 5.0)]


# 随机重启局部搜索
def local_search_once(max_iters=200, step=0.05):
    x = [random.uniform(*BOUNDS[0]), random.uniform(*BOUNDS[1])]
    fx = f_vec(x)

    for _ in range(max_iters):
        improved = False
        for dim in (0, 1):
            # 四个邻居:x1 +- step, x2 +- step
            for direction in (-1, 1):
                cand = x.copy()
                cand[dim] += step * direction
                # 边界截断到 [0,5]
                cand[dim] = max(BOUNDS[dim][0], min(BOUNDS[dim][1], cand[dim]))
                f_cand = f_vec(cand)
                if f_cand < fx:
                    x, fx = cand, f_cand
                    improved = True
                    break
        if not improved:
            break   # 附近没有更好的点,认为到达局部最优
    return x, fx


def random_restart(num_restarts=20, **kwargs):
    best_x, best_f  = None, float("inf")
    for _ in range(num_restarts):
        x, fx = local_search_once(**kwargs)
        if fx < best_f:
            best_f, best_x = fx, x
    return best_x, best_f

# 微型GA
def ga_once(pop_size=20, generations=20, crossover_p=0.8, mutation_p=0.3, mutation_sigma=0.1):
    
    # 初始化种群:均匀随机
    pop = [[random.uniform(*BOUNDS[0]), random.uniform(*BOUNDS[1])] for _ in range(pop_size)]
    
    def fitnses(ind):
        # 因为要 “最小化 f”,可以把适应度设成 -f
        return -f_vec(ind)
    
    for _ in range(generations):
        # 计算适应度
        fits = [fitnses(ind) for ind in pop]
    
        # 锦标赛选择
        def select():
            i, j = random.randrange(pop_size), random.randrange(pop_size)
            return pop[i] if fits[i] > fits[j] else pop[j]
        
        new_pop = []
        while len(new_pop) < pop_size:
            p1, p2 = select(), select()

            # 交叉:简单线性交叉
            if random.random() < crossover_p:
                alpha = random.random()
                c1 = [alpha * a + (1 - alpha) * b for a, b in zip(p1, p2)]
                c2 = [alpha * b + (1 - alpha) * a for a, b in zip(p1, p2)]
            else:
                c1, c2 = p1[:], p2[:]

            # 变异:对某一维加一点高斯噪声,再截断回区间
            for c in (c1, c2):
                if random.random() < mutation_p:
                    d = random.randrange(2)
                    c[d] += random.gauss(0, mutation_sigma)
                    c[d] = max(BOUNDS[d][0], min(BOUNDS[d][1], c[d]))
                new_pop.append(c)
                if len(new_pop) >= pop_size:
                    break
        pop = new_pop

    # 返回种群里 f 最小的个体
    best = min(pop, key=f_vec)
    return best, f_vec(best)


def repeat_exp(algo, runs=5, **kwargs):
    best_vals, times, points = [], [], []

    for _ in range(runs):
        start = time.time()
        x, fx = algo(**kwargs)
        end = time.time()
        best_vals.append(fx)
        times.append(end - start)
        points.append(x)

    result = {
        "best": min(best_vals),
        "mean": st.mean(best_vals),
        "var": st.pvariance(best_vals),
        "time_mean": st.mean(times),
        "time_var": st.pvariance(times),
        "points": points,
        "vals": best_vals
    }
    return result


if __name__ == '__main__':
    random.seed(0)

    print("=== 方法 A: 随机重启局部搜索 ===")
    res_A = repeat_exp(random_restart, runs=5, num_restarts=20, max_iters=200, step=0.05)
    print("最优值      :", res_A["best"])
    print("平均最优值  :", res_A["mean"])
    print("方差        :", res_A["var"])
    print("平均耗时(s):", res_A["time_mean"])
    print("各次解和函数值:")
    for p, v in zip(res_A["points"], res_A["vals"]):
        print("  x =", p, "f(x) =", v)

    print("\n=== 方法 B: 微型 GA ===")
    res_B = repeat_exp(ga_once, runs=5, pop_size=20, generations=20)
    print("最优值      :", res_B["best"])
    print("平均最优值  :", res_B["mean"])
    print("方差        :", res_B["var"])
    print("平均耗时(s):", res_B["time_mean"])
    print("各次解和函数值:")
    for p, v in zip(res_B["points"], res_B["vals"]):
        print("  x =", p, "f(x) =", v)

执行结果

=== 方法 A: 随机重启局部搜索 ===
最优值      : -0.41941422122325545
平均最优值  : -0.41922770847720386
方差        : 1.9129501726128993e-08
平均耗时(s): 0.0
各次解和函数值:
  x = [2.4985234320197747, 2.5941177111213416] f(x) = -0.4191540164008747
  x = [2.4969566427717798, 2.5836476412777745] f(x) = -0.41941422122325545
  x = [2.502945662811286, 2.5834839733110253] f(x) = -0.4190215546429824
  x = [2.4924103099766644, 2.5971230304873374] f(x) = -0.4192088959066057
  x = [2.495563697621402, 2.5930006503131655] f(x) = -0.4193398542123012

=== 方法 B: 微型 GA ===
最优值      : -0.4194651906149328
平均最优值  : -0.3933965480015764
方差        : 0.0026890479459860364
平均耗时(s): 0.0
各次解和函数值:
  x = [2.487235715773778, 2.591029838143908] f(x) = -0.41932901439782555
  x = [2.495758826529142, 2.586379125325827] f(x) = -0.4194651906149328
  x = [1.6636069947209504, 3.6057467494168303] f(x) = -0.28968466883561594
  x = [2.4985637149795124, 2.583793335776672] f(x) = -0.4193442196395164
  x = [2.500061980308704, 2.5790775462196165] f(x) = -0.41915964651999127

结论

  • 局部搜索(方法 A)

    • 每次都能找到 f ≈ -0.419 的解,位置在 (2.49, 2.59) 附近;

    • 5 次之间的方差极小 → 很稳,不太容易掉坑

    • 时间跟步长、重启次数成正比,一般也挺快。

  • 微型 GA(方法 B)

    • 好的时候也能找到差不多的最优值;

    • 但有几次会停在比较差的局部(比如 f ≈ -0.29)→ 方差更大,比较不稳定

    • 时间与种群大小、代数相关,通常也很快,但参数选得不好就白浪费代数。

  1. 谁更稳?
    从 5 次结果的方差看,局部搜索更稳定,GA 对参数比较敏感,偶尔掉坑。

  2. 是否陷入局部最优?

    • 局部搜索如果重启次数少、步长设得怪,容易卡局部(这就是为啥要随机重启);

    • GA 如果种群太小、变异率太低,也会早早收敛到局部最优。

  3. 参数敏感性?

    • 局部搜索:步长 step、重启次数 num_restarts;

    • GA:种群大小 pop_size、代数 generations、交叉概率、变异概率、变异强度 sigma。
      可以稍微改几个参数,看看最优值和方差怎么变,写两句感想就够了。


评论