每日算法 — 使用java实现人工鱼群:群体智能寻优与仿生行为建模

引言

在复杂优化问题中,传统梯度方法容易陷入局部最优。受自然界鱼群行为启发,李晓磊等人于2002年提出人工鱼群算法(Artificial Fish Swarm Algorithm, AFSA)。该算法模拟鱼类觅食、聚群和追尾三种本能行为,通过群体协作在解空间中全局搜索最优解。与遗传算法、蚁群算法相比,AFSA对初始值不敏感、鲁棒性强,尤其适合高维非线性优化问题。本文将用Java完整实现AFSA,深入解析三种核心行为模式与算法收敛机制。

算法原理

人工鱼群算法将每条”人工鱼”视为解空间中的一个候选解。每条鱼通过感知周围环境,执行以下三种行为之一:

1. 觅食行为(Prey)

鱼在其视野范围内随机选择一个位置,若该位置食物浓度(目标函数值)更高,则向该方向移动一步;否则继续随机尝试,直到达到最大尝试次数。

2. 聚群行为(Swarm)

鱼向其视野内伙伴的中心位置移动,但仅当中心位置的食物浓度高于当前位置且伙伴密度不过大时执行。这模拟了鱼群趋向群体中心但又避免过度拥挤的自然倾向。

3. 追尾行为(Follow)

鱼向其视野内食物浓度最高的伙伴方向移动,但仅当该伙伴周围的食物浓度高于当前位置且密度不过大时执行。这模拟了鱼群跟随”领头鱼”的行为。

每条鱼在每一步评估三种行为的”收益”,选择最优行为执行。若所有行为均不优于当前位置,则执行随机移动。

Java实现

import java.util.*;

public class ArtificialFishSwarm {

    /**
     * 人工鱼个体
     */
    static class Fish {
        double[] position;  // 位置(解向量)
        double fitness;     // 适应度(目标函数值)

        Fish(int dim) {
            this.position = new double[dim];
        }

        Fish cloneFish() {
            Fish f = new Fish(position.length);
            System.arraycopy(position, 0, f.position, 0, position.length);
            f.fitness = fitness;
            return f;
        }
    }

    // 算法参数
    private final int fishNum;      // 鱼群数量
    private final int dim;          // 解空间维度
    private final double visual;    // 视野范围
    private final double step;      // 最大移动步长
    private final double delta;     // 拥挤度因子
    private final int tryNumber;    // 觅食最大尝试次数
    private final int maxIter;      // 最大迭代次数
    private final double[] lowerBound; // 各维度下界
    private final double[] upperBound; // 各维度上界

    private Fish[] fishes;          // 鱼群
    private Fish bestFish;          // 全局最优鱼
    private final Random rand = new Random();

    // 目标函数接口
    @FunctionalInterface
    interface ObjectiveFunction {
        double evaluate(double[] x);
    }

    private final ObjectiveFunction objFunc;

    public ArtificialFishSwarm(int fishNum, int dim, double visual, double step,
                               double delta, int tryNumber, int maxIter,
                               double[] lowerBound, double[] upperBound,
                               ObjectiveFunction objFunc) {
        this.fishNum = fishNum;
        this.dim = dim;
        this.visual = visual;
        this.step = step;
        this.delta = delta;
        this.tryNumber = tryNumber;
        this.maxIter = maxIter;
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
        this.objFunc = objFunc;
    }

    /**
     * 初始化鱼群
     */
    private void initialize() {
        fishes = new Fish[fishNum];
        bestFish = new Fish(dim);
        bestFish.fitness = Double.POSITIVE_INFINITY;

        for (int i = 0; i < fishNum; i++) {
            fishes[i] = new Fish(dim);
            for (int j = 0; j < dim; j++) {
                fishes[i].position[j] = lowerBound[j] +
                    rand.nextDouble() * (upperBound[j] - lowerBound[j]);
            }
            fishes[i].fitness = objFunc.evaluate(fishes[i].position);
            if (fishes[i].fitness < bestFish.fitness) {
                bestFish = fishes[i].cloneFish();
            }
        }
    }

    /**
     * 计算两个位置之间的欧氏距离
     */
    private double distance(double[] a, double[] b) {
        double sum = 0;
        for (int i = 0; i < dim; i++) {
            sum += Math.pow(a[i] - b[i], 2);
        }
        return Math.sqrt(sum);
    }

    /**
     * 向目标位置移动一步
     */
    private void moveTowards(Fish fish, double[] target) {
        double[] newPos = new double[dim];
        double dist = distance(fish.position, target);
        if (dist < 1e-10) {
            // 若距离极近,随机移动
            for (int i = 0; i < dim; i++) {
                newPos[i] = fish.position[i] +
                    step * rand.nextDouble() * (upperBound[i] - lowerBound[i]) * 0.1;
            }
        } else {
            // 向目标方向移动 step * random 距离
            double ratio = step * rand.nextDouble() / dist;
            for (int i = 0; i < dim; i++) {
                newPos[i] = fish.position[i] + (target[i] - fish.position[i]) * ratio;
            }
        }

        // 边界处理:越界则投影到边界
        for (int i = 0; i < dim; i++) {
            newPos[i] = Math.max(lowerBound[i], Math.min(upperBound[i], newPos[i]));
        }

        fish.position = newPos;
        fish.fitness = objFunc.evaluate(fish.position);
    }

    /**
     * 觅食行为
     */
    private void prey(Fish fish) {
        for (int t = 0; t < tryNumber; t++) {
            // 在视野内随机选择一个位置
            double[] target = new double[dim];
            for (int i = 0; i < dim; i++) {
                double range = visual * (upperBound[i] - lowerBound[i]);
                target[i] = fish.position[i] +
                    (rand.nextDouble() * 2 - 1) * range;
                target[i] = Math.max(lowerBound[i], Math.min(upperBound[i], target[i]));
            }

            double targetFitness = objFunc.evaluate(target);
            if (targetFitness < fish.fitness) {
                moveTowards(fish, target);
                return;
            }
        }
        // 若尝试多次仍未找到更好位置,随机移动
        double[] randomTarget = new double[dim];
        for (int i = 0; i < dim; i++) {
            randomTarget[i] = lowerBound[i] +
                rand.nextDouble() * (upperBound[i] - lowerBound[i]);
        }
        moveTowards(fish, randomTarget);
    }

    /**
     * 聚群行为
     */
    private void swarm(Fish fish) {
        double[] center = new double[dim];
        int neighborCount = 0;

        // 找出视野内的伙伴
        for (Fish other : fishes) {
            if (other != fish && distance(fish.position, other.position) < visual) {
                for (int i = 0; i < dim; i++) {
                    center[i] += other.position[i];
                }
                neighborCount++;
            }
        }

        if (neighborCount > 0) {
            for (int i = 0; i < dim; i++) {
                center[i] /= neighborCount;
            }
            double centerFitness = objFunc.evaluate(center);
            // 若中心位置更优且不拥挤(密度因子判断)
            if (centerFitness < fish.fitness &&
                centerFitness * neighborCount < delta * fish.fitness) {
                moveTowards(fish, center);
                return;
            }
        }
        // 若聚群条件不满足,执行觅食
        prey(fish);
    }

    /**
     * 追尾行为
     */
    private void follow(Fish fish) {
        Fish bestNeighbor = null;
        double bestNeighborFitness = Double.POSITIVE_INFINITY;

        // 找出视野内最优的伙伴
        for (Fish other : fishes) {
            if (other != fish && distance(fish.position, other.position) < visual) {
                if (other.fitness < bestNeighborFitness) {
                    bestNeighborFitness = other.fitness;
                    bestNeighbor = other;
                }
            }
        }

        if (bestNeighbor != null) {
            // 统计该伙伴视野内的伙伴数量
            int neighborCount = 0;
            for (Fish other : fishes) {
                if (distance(bestNeighbor.position, other.position) < visual) {
                    neighborCount++;
                }
            }
            // 若追尾目标更优且不拥挤
            if (bestNeighbor.fitness < fish.fitness &&
                bestNeighbor.fitness * neighborCount < delta * fish.fitness) {
                moveTowards(fish, bestNeighbor.position);
                return;
            }
        }
        // 若追尾条件不满足,执行觅食
        prey(fish);
    }

    /**
     * 选择最优行为执行
     */
    private void chooseBehavior(Fish fish) {
        Fish preyFish = fish.cloneFish();
        Fish swarmFish = fish.cloneFish();
        Fish followFish = fish.cloneFish();

        prey(preyFish);
        swarm(swarmFish);
        follow(followFish);

        // 选择三种行为中效果最好的
        double preyFit = preyFish.fitness;
        double swarmFit = swarmFish.fitness;
        double followFit = followFish.fitness;

        if (preyFit <= swarmFit && preyFit <= followFit) {
            fish.position = preyFish.position;
            fish.fitness = preyFish.fitness;
        } else if (swarmFit <= preyFit && swarmFit <= followFit) {
            fish.position = swarmFish.position;
            fish.fitness = swarmFish.fitness;
        } else {
            fish.position = followFish.position;
            fish.fitness = followFish.fitness;
        }
    }

    /**
     * 执行优化
     */
    public double[] optimize() {
        initialize();
        System.out.println("=== 人工鱼群算法优化 ===");
        System.out.println("鱼群数量: " + fishNum);
        System.out.println("维度: " + dim);
        System.out.println("视野: " + visual);
        System.out.println("步长: " + step);
        System.out.println();

        for (int iter = 0; iter < maxIter; iter++) {
            for (Fish fish : fishes) {
                chooseBehavior(fish);
                if (fish.fitness < bestFish.fitness) {
                    bestFish = fish.cloneFish();
                }
            }

            if (iter % 50 == 0 || iter == maxIter - 1) {
                System.out.printf("迭代 %d: 最优适应度 = %.10f%n", iter, bestFish.fitness);
            }

            // 动态衰减视野和步长(增强收敛)
            if (iter > 0 && iter % 20 == 0) {
                // 此处可通过减小visual和step来增强局部搜索能力
            }
        }

        System.out.println("\n最优解:");
        for (int i = 0; i < dim; i++) {
            System.out.printf("  x[%d] = %.10f%n", i, bestFish.position[i]);
        }
        System.out.printf("最优适应度: %.10f%n", bestFish.fitness);

        return bestFish.position;
    }

    public static void main(String[] args) {
        // 测试1:Sphere函数(全局最小值在0点,f(0)=0)
        System.out.println("【测试1:Sphere函数 f(x) = sum(xi^2)】");
        int dim = 10;
        double[] lb = new double[dim];
        double[] ub = new double[dim];
        Arrays.fill(lb, -100);
        Arrays.fill(ub, 100);

        ArtificialFishSwarm afs1 = new ArtificialFishSwarm(
            50, dim, 2.0, 0.5, 10.0, 5, 200,
            lb, ub,
            x -> {
                double sum = 0;
                for (double v : x) sum += v * v;
                return sum;
            }
        );
        afs1.optimize();

        // 测试2:Rastrigin函数(多峰值,全局最小值在0点,f(0)=0)
        System.out.println("\n【测试2:Rastrigin函数(多峰值)】");
        ArtificialFishSwarm afs2 = new ArtificialFishSwarm(
            80, 5, 3.0, 0.8, 15.0, 8, 300,
            new double[]{-5.12, -5.12, -5.12, -5.12, -5.12},
            new double[]{5.12, 5.12, 5.12, 5.12, 5.12},
            x -> {
                double sum = 0;
                for (double v : x) {
                    sum += v * v - 10 * Math.cos(2 * Math.PI * v) + 10;
                }
                return sum;
            }
        );
        afs2.optimize();
    }
}

复杂度分析

指标 复杂度 说明
单步时间 (O(N^2 \cdot D)) (N)条鱼,每条约计算与其他 (N) 条鱼的距离,维度 (D)
总时间 (O(T \cdot N^2 \cdot D)) (T) 为迭代次数
空间复杂度 (O(N \cdot D)) 存储鱼群位置
并行潜力 高 每条鱼的行为可独立计算

实际应用中可通过空间索引(如k-d树)将邻居搜索优化到 (O(N \log N))。

应用场景

  1. 函数优化:高维非线性、多峰值、不可导函数的参数寻优。
  2. 神经网络训练:作为替代梯度下降的元启发式方法,避免陷入局部最优。
  3. 组合优化:旅行商问题、调度问题、背包问题的近似求解。
  4. 特征选择:在高维数据中选择最优特征子集。
  5. 聚类分析:将聚类中心优化问题转化为连续优化问题。

总结

人工鱼群算法以简洁的仿生规则实现了强大的全局搜索能力。三种行为的组合——觅食的探索性、聚群的平衡性、追尾的收敛性——构成了算法自适应搜索的核心。与遗传算法的”选择-交叉-变异”和蚁群算法的”信息素”相比,AFSA的随机视野和步长机制使其在解空间中的搜索更加灵活。掌握AFSA,就是掌握了一种从自然界借鉴智慧的优化范式。