每日算法 — 使用java实现克隆选择算法:免疫启发的全局优化策略

问题背景

在复杂的组合优化和函数优化问题中,传统梯度下降等方法容易陷入局部最优。进化计算(Evolutionary Computation)提供了一类基于自然启发的全局优化方法,包括遗传算法、粒子群优化、蚁群算法等。

克隆选择算法(Clonal Selection Algorithm, CLONALG)由de Castro和Von Zuben于2002年提出,模拟生物免疫系统中的克隆选择原理:
– 当病原体入侵时,免疫系统产生大量抗体
– 与抗原亲和力高的B细胞被克隆扩增(大量复制)
– 克隆过程中发生高频体细胞突变(Hypermutation)
– 高亲和力克隆被保留,低亲和力克隆被清除

这一”选择-克隆-突变-再选择”的循环,使得免疫系统能够快速适应并消灭新入侵的病原体。将这一机制抽象为优化算法,可用于求解组合优化和连续函数优化问题。

核心原理

算法参数

参数 符号 含义
抗体种群大小 $N$ 候选解的数量
选择比例 $n$ 每代选择进行克隆的抗体数量($n \leq N$)
克隆系数 $\beta$ 控制克隆数量,克隆数 = round($\beta \cdot N / i$)
变异率 $\rho$ 控制抗体变异的强度
替换比例 $d$ 每代随机替换的最低亲和力抗体数量

算法流程

1. 初始化:随机生成N个抗体(候选解)
2. 计算亲和力:评估每个抗体与目标(抗原)的匹配程度
3. 选择:选出亲和力最高的n个抗体
4. 克隆:对选中的抗体进行克隆扩增
   - 亲和力越高的抗体,克隆数量越多
5. 高频突变:对克隆体进行变异
   - 亲和力越高的抗体,变异率越低(保持优势)
   - 亲和力越低的抗体,变异率越高(鼓励探索)
6. 重新评估:计算突变后克隆体的亲和力
7. 选择:从每个抗体的克隆群体中选择最优个体替代原抗体
8. 多样性维持:替换d个最低亲和力的抗体为随机新抗体
9. 重复步骤2-8,直到满足终止条件

亲和力计算

对于函数优化:亲和力通常是目标函数值的倒数或取反(最小化问题)。

$$affinity(x) = \frac{1}{1 + f(x)} \quad \text{或} \quad affinity(x) = -f(x)$$

对于组合优化(如TSP):亲和力可以是路径总长度的倒数。

高频突变策略

变异率与亲和力成反比:

$$mutation_rate_i = \exp(-\rho \cdot affinity_i)$$

  • 高亲和力抗体:变异率低,进行局部精细搜索
  • 低亲和力抗体:变异率高,进行全局探索

对于连续优化,常用高斯变异:

$$x’ = x + \alpha \cdot N(0, 1)$$

其中 $\alpha$ 与变异率成正比。

Java实现

import java.util.*;

/**
 * 克隆选择算法(CLONALG)
 * 模拟免疫系统克隆选择原理的全局优化算法
 * 适用于连续函数优化和组合优化问题
 */
public class ClonalSelectionAlgorithm {

    // 抗体(候选解)类
    public static class Antibody {
        double[] position;      // 解的位置(多维)
        double affinity;        // 亲和力(目标函数值越好,亲和力越高)
        int cloneCount;         // 该抗体的克隆数量

        public Antibody(int dim) {
            this.position = new double[dim];
            this.affinity = 0;
            this.cloneCount = 0;
        }

        public Antibody cloneAntibody() {
            Antibody clone = new Antibody(position.length);
            System.arraycopy(position, 0, clone.position, 0, position.length);
            clone.affinity = affinity;
            return clone;
        }

        @Override
        public String toString() {
            return String.format("Antibody[pos=%s, affinity=%.6f]", 
                Arrays.toString(position), affinity);
        }
    }

    // 目标函数接口
    public interface ObjectiveFunction {
        double evaluate(double[] x);
        int getDimension();
        double[] getLowerBounds();
        double[] getUpperBounds();
    }

    // 算法参数
    private int populationSize;     // 抗体种群大小N
    private int selectionSize;      // 选择克隆的抗体数量n
    private double cloneFactor;     // 克隆系数beta
    private double mutationRate;    // 基础变异率rho
    private int replacementCount;   // 每代替换的抗体数量d
    private int maxGenerations;     // 最大迭代次数
    private Random random;

    // 最优解记录
    private Antibody bestAntibody;
    private List<Double> convergenceHistory;

    public ClonalSelectionAlgorithm(int populationSize, int selectionSize,
                                     double cloneFactor, double mutationRate,
                                     int replacementCount, int maxGenerations) {
        this.populationSize = populationSize;
        this.selectionSize = selectionSize;
        this.cloneFactor = cloneFactor;
        this.mutationRate = mutationRate;
        this.replacementCount = replacementCount;
        this.maxGenerations = maxGenerations;
        this.random = new Random();
        this.convergenceHistory = new ArrayList<>();
    }

    /**
     * 主优化流程
     */
    public Antibody optimize(ObjectiveFunction func) {
        int dim = func.getDimension();
        double[] lower = func.getLowerBounds();
        double[] upper = func.getUpperBounds();

        // 步骤1:初始化种群
        List<Antibody> population = initializePopulation(dim, lower, upper);

        // 评估初始种群
        evaluatePopulation(population, func);
        updateBest(population);

        // 迭代优化
        for (int gen = 0; gen < maxGenerations; gen++) {
            // 步骤2:按亲和力排序
            population.sort((a, b) -> Double.compare(b.affinity, a.affinity));

            // 记录当前最优
            convergenceHistory.add(bestAntibody.affinity);

            // 步骤3:选择前selectionSize个抗体
            List<Antibody> selected = new ArrayList<>();
            for (int i = 0; i < selectionSize && i < population.size(); i++) {
                selected.add(population.get(i));
            }

            // 步骤4-6:克隆、突变、选择
            List<Antibody> newPopulation = new ArrayList<>();

            // 保留所有原始抗体
            for (Antibody ab : population) {
                newPopulation.add(ab.cloneAntibody());
            }

            // 对选中的抗体进行克隆扩增和突变
            for (int i = 0; i < selected.size(); i++) {
                Antibody parent = selected.get(i);
                // 克隆数量:亲和力越高(排名越靠前),克隆越多
                int clones = (int) Math.round(cloneFactor * populationSize / (i + 1));
                parent.cloneCount = clones;

                // 对该抗体的每个克隆进行突变
                for (int c = 0; c < clones; c++) {
                    Antibody clone = parent.cloneAntibody();

                    // 高频突变:亲和力越高,变异率越低
                    // mutation_strength = exp(-rho * affinity_normalized)
                    double affinityNorm = parent.affinity / (bestAntibody.affinity + 1e-10);
                    double mutStrength = Math.exp(-mutationRate * affinityNorm);

                    mutate(clone, lower, upper, mutStrength);

                    // 评估突变后的克隆体
                    double fitness = func.evaluate(clone.position);
                    clone.affinity = calculateAffinity(fitness);

                    newPopulation.add(clone);
                }
            }

            // 步骤7:从扩大的种群中选择最优N个
            newPopulation.sort((a, b) -> Double.compare(b.affinity, a.affinity));
            population = new ArrayList<>();
            for (int i = 0; i < populationSize && i < newPopulation.size(); i++) {
                population.add(newPopulation.get(i));
            }

            // 步骤8:多样性维持 - 替换最低亲和力的d个抗体
            for (int i = 0; i < replacementCount && i < population.size(); i++) {
                int idx = population.size() - 1 - i;
                Antibody newbie = new Antibody(dim);
                for (int j = 0; j < dim; j++) {
                    newbie.position[j] = lower[j] + random.nextDouble() * (upper[j] - lower[j]);
                }
                double fitness = func.evaluate(newbie.position);
                newbie.affinity = calculateAffinity(fitness);
                population.set(idx, newbie);
            }

            // 更新全局最优
            updateBest(population);

            if (gen % 50 == 0) {
                System.out.printf("Generation %d: Best Affinity = %.8f, Best Fitness = %.8f%n",
                    gen, bestAntibody.affinity, 
                    calculateFitness(bestAntibody.affinity));
            }
        }

        return bestAntibody;
    }

    /**
     * 初始化随机种群
     */
    private List<Antibody> initializePopulation(int dim, double[] lower, double[] upper) {
        List<Antibody> population = new ArrayList<>();
        for (int i = 0; i < populationSize; i++) {
            Antibody ab = new Antibody(dim);
            for (int j = 0; j < dim; j++) {
                ab.position[j] = lower[j] + random.nextDouble() * (upper[j] - lower[j]);
            }
            population.add(ab);
        }
        return population;
    }

    /**
     * 评估整个种群
     */
    private void evaluatePopulation(List<Antibody> population, ObjectiveFunction func) {
        for (Antibody ab : population) {
            double fitness = func.evaluate(ab.position);
            ab.affinity = calculateAffinity(fitness);
        }
    }

    /**
     * 计算亲和力(针对最小化问题)
     * 使用倒数变换,保证正值
     */
    private double calculateAffinity(double fitness) {
        return 1.0 / (1.0 + Math.abs(fitness));
    }

    /**
     * 从亲和力还原适应度值
     */
    private double calculateFitness(double affinity) {
        return 1.0 / affinity - 1.0;
    }

    /**
     * 高斯变异操作
     */
    private void mutate(Antibody antibody, double[] lower, double[] upper, double strength) {
        int dim = antibody.position.length;
        for (int i = 0; i < dim; i++) {
            // 高斯变异
            double mutation = strength * (upper[i] - lower[i]) * random.nextGaussian();
            antibody.position[i] += mutation;

            // 边界处理:反射法
            if (antibody.position[i] < lower[i]) {
                antibody.position[i] = lower[i] + (lower[i] - antibody.position[i]);
                if (antibody.position[i] > upper[i]) antibody.position[i] = lower[i];
            } else if (antibody.position[i] > upper[i]) {
                antibody.position[i] = upper[i] - (antibody.position[i] - upper[i]);
                if (antibody.position[i] < lower[i]) antibody.position[i] = upper[i];
            }
        }
    }

    /**
     * 更新全局最优
     */
    private void updateBest(List<Antibody> population) {
        for (Antibody ab : population) {
            if (bestAntibody == null || ab.affinity > bestAntibody.affinity) {
                bestAntibody = ab.cloneAntibody();
            }
        }
    }

    /**
     * 获取收敛历史
     */
    public List<Double> getConvergenceHistory() {
        return convergenceHistory;
    }

    // ============ 标准测试函数 ============

    /**
     * Sphere函数:f(x) = sum(x_i^2),全局最小值0在(0,0,...,0)
     */
    public static class SphereFunction implements ObjectiveFunction {
        private int dim;
        public SphereFunction(int dim) { this.dim = dim; }
        public int getDimension() { return dim; }
        public double[] getLowerBounds() { double[] b = new double[dim]; Arrays.fill(b, -5.12); return b; }
        public double[] getUpperBounds() { double[] b = new double[dim]; Arrays.fill(b, 5.12); return b; }
        public double evaluate(double[] x) { double s = 0; for (double v : x) s += v * v; return s; }
    }

    /**
     * Rastrigin函数:多峰函数,f(x) = sum(x_i^2 - 10*cos(2*pi*x_i) + 10)
     * 全局最小值0在(0,0,...,0),有大量局部极小值
     */
    public static class RastriginFunction implements ObjectiveFunction {
        private int dim;
        public RastriginFunction(int dim) { this.dim = dim; }
        public int getDimension() { return dim; }
        public double[] getLowerBounds() { double[] b = new double[dim]; Arrays.fill(b, -5.12); return b; }
        public double[] getUpperBounds() { double[] b = new double[dim]; Arrays.fill(b, 5.12); return b; }
        public double evaluate(double[] x) {
            double s = 0;
            for (double v : x) s += v * v - 10 * Math.cos(2 * Math.PI * v) + 10;
            return s;
        }
    }

    /**
     * Rosenbrock函数:香蕉函数,f(x) = sum(100*(x_{i+1}-x_i^2)^2 + (1-x_i)^2)
     * 全局最小值0在(1,1,...,1),峡谷地形难以优化
     */
    public static class RosenbrockFunction implements ObjectiveFunction {
        private int dim;
        public RosenbrockFunction(int dim) { this.dim = dim; }
        public int getDimension() { return dim; }
        public double[] getLowerBounds() { double[] b = new double[dim]; Arrays.fill(b, -2.048); return b; }
        public double[] getUpperBounds() { double[] b = new double[dim]; Arrays.fill(b, 2.048); return b; }
        public double evaluate(double[] x) {
            double s = 0;
            for (int i = 0; i < dim - 1; i++) {
                s += 100 * Math.pow(x[i + 1] - x[i] * x[i], 2) + Math.pow(1 - x[i], 2);
            }
            return s;
        }
    }

    /**
     * Ackley函数:多峰函数,f(x) = -20*exp(-0.2*sqrt(1/d*sum(x_i^2))) - exp(1/d*sum(cos(2*pi*x_i))) + 20 + e
     * 全局最小值0在(0,0,...,0)
     */
    public static class AckleyFunction implements ObjectiveFunction {
        private int dim;
        public AckleyFunction(int dim) { this.dim = dim; }
        public int getDimension() { return dim; }
        public double[] getLowerBounds() { double[] b = new double[dim]; Arrays.fill(b, -32.768); return b; }
        public double[] getUpperBounds() { double[] b = new double[dim]; Arrays.fill(b, 32.768); return b; }
        public double evaluate(double[] x) {
            double sum1 = 0, sum2 = 0;
            for (double v : x) {
                sum1 += v * v;
                sum2 += Math.cos(2 * Math.PI * v);
            }
            return -20 * Math.exp(-0.2 * Math.sqrt(sum1 / dim)) 
                   - Math.exp(sum2 / dim) + 20 + Math.E;
        }
    }

    /**
     * 主函数:测试克隆选择算法
     */
    public static void main(String[] args) {
        // 算法参数
        int populationSize = 100;
        int selectionSize = 20;
        double cloneFactor = 2.0;
        double mutationRate = 2.0;
        int replacementCount = 5;
        int maxGenerations = 500;
        int dim = 10;  // 问题维度

        ClonalSelectionAlgorithm clonalg = new ClonalSelectionAlgorithm(
            populationSize, selectionSize, cloneFactor, mutationRate,
            replacementCount, maxGenerations
        );

        // 测试1:Sphere函数
        System.out.println("=== 测试1:Sphere函数(简单单峰)===");
        Antibody best1 = clonalg.optimize(new SphereFunction(dim));
        System.out.println("最优解: " + best1);
        System.out.println("目标函数值: " + new SphereFunction(dim).evaluate(best1.position));

        // 测试2:Rastrigin函数
        System.out.println("\n=== 测试2:Rastrigin函数(多峰,大量局部最优)===");
        ClonalSelectionAlgorithm clonalg2 = new ClonalSelectionAlgorithm(
            populationSize, selectionSize, cloneFactor, mutationRate,
            replacementCount, maxGenerations
        );
        Antibody best2 = clonalg2.optimize(new RastriginFunction(dim));
        System.out.println("最优解: " + best2);
        System.out.println("目标函数值: " + new RastriginFunction(dim).evaluate(best2.position));

        // 测试3:Rosenbrock函数
        System.out.println("\n=== 测试3:Rosenbrock函数(峡谷地形)===");
        ClonalSelectionAlgorithm clonalg3 = new ClonalSelectionAlgorithm(
            populationSize, selectionSize, cloneFactor, mutationRate,
            replacementCount, maxGenerations
        );
        Antibody best3 = clonalg3.optimize(new RosenbrockFunction(dim));
        System.out.println("最优解: " + best3);
        System.out.println("目标函数值: " + new RosenbrockFunction(dim).evaluate(best3.position));

        // 测试4:Ackley函数
        System.out.println("\n=== 测试4:Ackley函数(复杂多峰)===");
        ClonalSelectionAlgorithm clonalg4 = new ClonalSelectionAlgorithm(
            populationSize, selectionSize, cloneFactor, mutationRate,
            replacementCount, maxGenerations
        );
        Antibody best4 = clonalg4.optimize(new AckleyFunction(dim));
        System.out.println("最优解: " + best4);
        System.out.println("目标函数值: " + new AckleyFunction(dim).evaluate(best4.position));

        // 测试5:参数敏感性分析
        System.out.println("\n=== 测试5:参数敏感性分析(Sphere函数,不同populationSize)===");
        int[] popSizes = {20, 50, 100, 200};
        for (int popSize : popSizes) {
            ClonalSelectionAlgorithm test = new ClonalSelectionAlgorithm(
                popSize, popSize / 5, cloneFactor, mutationRate,
                popSize / 20, 200
            );
            long start = System.currentTimeMillis();
            Antibody b = test.optimize(new SphereFunction(dim));
            long time = System.currentTimeMillis() - start;
            System.out.printf("PopSize=%d: Fitness=%.10f, Time=%dms%n",
                popSize, new SphereFunction(dim).evaluate(b.position), time);
        }
    }
}

复杂度分析

时间复杂度

设种群大小为 $N$,选择数量为 $n$,问题维度为 $d$,最大迭代次数为 $G$。

  • 初始化:$O(N \cdot d)$
  • 每代评估:$O(N \cdot d)$(假设目标函数评估为 $O(d)$)
  • 克隆扩增:克隆总数约为 $\sum_{i=1}^{n} \beta N / i = O(\beta N \log n)$
  • 突变操作:$O(\text{克隆总数} \cdot d)$
  • 选择替换:$O(N \log N)$

每代总复杂度:$O(\beta N \log n \cdot d)$

总复杂度:$O(G \cdot \beta N \log n \cdot d)$

空间复杂度

  • 种群存储:$O(N \cdot d)$
  • 克隆群体:$O(\beta N \log n \cdot d)$(峰值)

参数调优指南

参数 推荐范围 调优建议
populationSize (N) 50-500 问题越复杂,取值越大
selectionSize (n) N/5 ~ N/2 过小导致早熟收敛,过大降低选择压力
cloneFactor ($\beta$) 0.5-5.0 控制克隆总数,影响计算开销
mutationRate ($\rho$) 0.5-5.0 较大值增强全局搜索能力
replacementCount (d) 2-10% of N 维持种群多样性
maxGenerations 100-1000 根据收敛情况调整

应用场景

  1. 函数优化:多峰函数、非凸函数、不连续函数的全局优化
  2. 组合优化:TSP旅行商问题、背包问题、调度问题
  3. 特征选择:从高维数据中选择最优特征子集
  4. 神经网络训练:替代梯度下降进行权重优化
  5. 模式识别:自动分类器和聚类中心优化

与其他进化算法的对比

算法 核心机制 优势 劣势
克隆选择 克隆扩增+高频突变 局部搜索能力强,收敛快 全局探索能力一般
遗传算法 交叉+变异+选择 全局探索能力强 局部搜索能力弱
粒子群 群体协作+速度更新 实现简单,收敛快 易陷入局部最优
蚁群算法 信息素正反馈 适合离散优化 收敛慢,参数敏感
差分进化 差分变异 鲁棒性强 对高维问题效果下降

混合策略:将克隆选择算法与遗传算法的交叉操作结合,或作为粒子群算法的局部搜索组件,可以获得更好的整体性能。