群知能アルゴリズム

# 群知能

群知能（ぐんちのう、Swarm Intelligence）とは、個々の構成要素が単純なルールに従って行動することで、全体として高度で複雑な問題解決能力を持つシステムを指します。

主な特徴は以下の通りです。

- 個体は単純なルールに基づいて行動する
- 個体間の相互作用により、集団全体で知的な振る舞いが創発する
- 中央集権的な制御や指示がなくても機能する
- 外部環境の変化に適応し、柔軟かつ頑健である

### 群知能の代表例

- アリやハチなどの社会性昆虫のコロニー
- 鳥や魚の群れの集団行動
- 人工知能分野での最適化アルゴリズム（蟻コロニー最適化、粒子群最適化など）


## 代表的な群知能アルゴリズム

| アルゴリズム | 着想元 | 特徴 | 適用問題 |
|----------|----------|----------|----------|
| 蟻コロニー最適化（ACO） | アリの集団採餌行動 | フェロモンを用いた間接的コミュニケーション | 巡回セールスマン問題、組合せ最適化問題 |
| 粒子群最適化（PSO） | 鳥や魚の群れの社会的行動 | 個体が自身の最良解と群れの最良解を記憶し探索 | 連続最適化問題 |
| 人工蜂コロニー（ABC） | ミツバチの採餌行動 | 採蜜バチ、追随バチ、偵察バチの役割分担 | 連続最適化問題、組合せ最適化問題 |
| 人工免疫システム（AIS） | 生体の免疫システム | 抗体の多様性と自己組織化 | パターン認識、最適化問題 |
| バクテリア最適化 | バクテリアの化学走性と遺伝的変化 | 連続最適化問題に適用 |-|

### 群知能アルゴリズム：利点と課題

| 項目 | 説明 |
|------|------|
| **利点** | - **局所解に陥りにくい**：大域的な探索能力に優れているため、局所最適解に囚われにくい。<br>- **パラメータ調整が容易**：他の進化的アルゴリズムに比べて、調整が必要なパラメータが少ない。<br>- **数学的性質を要求しない**：目的関数が微分不可能であっても適用可能。<br>- **変数の種類に柔軟**：離散変数と連続変数が混在する問題に対しても適用可能。 |
| **課題** | - **収束速度が遅い**：特に複雑な問題において最適解に収束するまでの速度が遅くなることがある。<br>- **理論的解析の難しさ**：アルゴリズムの挙動や最適性に関する理論的解析が困難。 |


## 群知能アルゴリズムはどういう場合に使用し、どういう場合には使用しないか

以下のような場合に適しています。
- 最適化問題が複雑で、数学的に定式化が難しい場合
- 問題が非線形、非凸、多峰性を持つ場合
- 問題が大規模で、決定変数の数が多い場合
- 問題が離散変数と連続変数が混在する場合
- 大域的な最適解を見つける必要がある場合
- 問題の数学的性質（微分可能性など）が不明な場合
- 実装が比較的容易で、パラメータ調整が容易なアルゴリズムが求められる場合

一方、以下のような場合には使用が適切でない可能性があります。
- 問題が単純で、数学的に容易に定式化できる場合
- 問題が線形で、凸性を持つ場合
- 問題が小規模で、決定変数の数が少ない場合
- 厳密な最適解が要求される場合
- アルゴリズムの収束速度が重要な場合
- 問題の数学的性質が明らかで、その性質を利用した専用のアルゴリズムが利用可能な場合
- アルゴリズムの理論的な解析が重要な場合

# 各アルゴリズムの解説

ここからは、各アルゴリズムをひとつずつ見ていきます。

## 蟻コロニー最適化（ACO）

蟻コロニー最適化（Ant Colony Optimization, ACO）は、アリの集団採餌行動に着想を得た確率的最適化アルゴリズムです。ACOは、組合せ最適化問題を解くために提案されました。

### ACOの基本的な考え方
- アリは、フェロモンと呼ばれる化学物質を道に残しながら餌を探索
- フェロモンの濃度が高い道ほど、他のアリが追従する可能性が高くなる
- 時間の経過とともに、良い解（短い経路）にはフェロモンが蓄積され、悪い解（長い経路）ではフェロモンが蒸発
- この正のフィードバック機構により、集団全体で良い解を構築される

### ACOアルゴリズムの流れ
- 初期化：問題の解空間上に、フェロモン値を初期化します
- 解の構築：各アリが、確率的にフェロモン値を参照しながら、解を構築します
- フェロモンの更新：構築された解の質に基づいて、フェロモン値を更新します。良い解に対応する部分のフェロモン値を増加させ、悪い解に対応する部分のフェロモン値を蒸発させます
- 終了条件の判定：最大反復回数に達した、あるいは十分な解が得られたならば終了。そうでなければ、ステップ2へ戻る

### ACOの特徴
- 問題に依存しない汎用的な最適化アルゴリズムである
- 離散的な解空間を扱うことができる
- 大域的探索と局所的探索のバランスが取れている
- 並列化が容易である

### 実装例

```python

import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

class AntColonyOptimizer:
    def __init__(self, cities, num_ants, num_iterations, alpha, beta, evaporation_rate, Q):
        # コンストラクタ：初期化メソッド
        # cities: 都市の座標リスト、num_ants: アリの数、num_iterations: 繰り返し回数
        # alpha: フェロモンの影響度、beta: 距離の影響度、evaporation_rate: フェロモンの蒸発率、Q: フェロモン増加の強度
        self.cities = cities
        self.num_ants = num_ants
        self.num_iterations = num_iterations
        self.alpha = alpha
        self.beta = beta
        self.evaporation_rate = evaporation_rate
        self.Q = Q
        self.num_cities = len(cities)
        # 都市間の距離行列を計算
        self.distances = np.linalg.norm(cities[:, np.newaxis] - cities[np.newaxis, :], axis=2)
        # フェロモン行列の初期化（すべての値を1で初期化）
        self.pheromones = np.ones((self.num_cities, self.num_cities))

    def construct_path(self):
        # パス構築メソッド：各アリが都市間を巡るパスを構築
        path = [random.randint(0, self.num_cities - 1)]
        for _ in range(1, self.num_cities):
            current_city = path[-1]
            unvisited = set(range(self.num_cities)) - set(path)
            next_city = self.select_next_city(current_city, unvisited)
            path.append(next_city)
        return path

    def select_next_city(self, current_city, unvisited):
        # 次の都市を選択するメソッド：フェロモンと距離に基づく確率で次の都市を選択
        unvisited = list(unvisited)
        pheromone = self.pheromones[current_city][unvisited]
        heuristic = 1.0 / self.distances[current_city][unvisited]
        probabilities = (pheromone ** self.alpha) * (heuristic ** self.beta)
        probabilities /= probabilities.sum()
        return np.random.choice(unvisited, p=probabilities)

    def update_pheromones(self, paths):
        # フェロモン更新メソッド：各アリが辿ったパスに基づいてフェロモン行列を更新
        self.pheromones *= (1 - self.evaporation_rate)
        for path in paths:
            for i in range(self.num_cities - 1):
                self.pheromones[path[i], path[i+1]] += self.Q / self.distances[path[i], path[i+1]]

    def plot_path(self, best_path, i):
        # パスをプロットするメソッド：現在の最良パスを図示
        x, y = self.cities[best_path, 0], self.cities[best_path, 1]
        plt.cla()
        plt.plot(x, y, 'r-', label='Path')
        plt.scatter(x, y, c='blue', s=10)
        plt.title(f"Iteration: {i + 1}")
        plt.xlabel("X座標")
        plt.ylabel("Y座標")
        plt.legend()

    def optimize(self):
        # 最適化メソッド：アリコロニー最適化を実行し、最良のパスと距離を求める
        best_path = None
        best_distance = np.inf
        fig, ax = plt.subplots()
        for i in range(self.num_iterations):
            paths = [self.construct_path() for _ in range(self.num_ants)]
            self.update_pheromones(paths)
            distances = [self.calculate_distance(path) for path in paths]
            idx_min = np.argmin(distances)
            if distances[idx_min] < best_distance:
                best_distance = distances[idx_min]
                best_path = paths[idx_min]
            self.plot_path(best_path, i)
            plt.pause(0.1)  # 画面更新のための一時停止
        plt.show()
        return best_path, best_distance

    def calculate_distance(self, path):
        # 総距離計算メソッド：指定されたパスの総距離を計算
        return sum(self.distances[path[i], path[(i + 1) % self.num_cities]] for i in range(self.num_cities))

# 使用例
cities = np.array([[0, 0], [1, 1], [2, 2], [3, 3], [4, 4]])

optimizer = AntColonyOptimizer(cities, num_ants=10, num_iterations=50, alpha=1, beta=2, evaporation_rate=0.5, Q=100)
best_path, best_distance = optimizer.optimize()
print("最良パス:", best_path)
print("最良距離:", best_distance)

```



## 粒子群最適化（PSO）

粒子群最適化（Particle Swarm Optimization, PSO）は、鳥や魚の群れの社会的行動に着想を得た確率的最適化アルゴリズムです。PSOは、連続的な最適化問題を解くために広く用いられています。

### PSOの特徴

**群知能**：PSOは、個々の粒子（解候補）が互いに情報を共有し、協調的に探索を行うことで、群れ全体としての問題解決能力を発揮します。

**適応性**：各粒子は、自身の経験（自身の最良解）と群れ全体の経験（群れの最良解）を用いて探索方向を適応的に調整します。

**探索と利用のバランス**：PSOは、粒子の速度更新則によって、新たな領域の探索と良好な解の周辺の探索（利用）のバランスを取ります。

### PSOのアルゴリズムの流れ
- 1.初期化：各粒子の位置と速度をランダムに初期化します。
- 2.適応度評価：各粒子の位置での目的関数値を計算します。
- 3.粒子の最良解と群れの最良解の更新：各粒子の現在の位置と過去の最良位置を比較し、粒子の最良解を更新します。また、群れ全体での最良解も更新します。
- 4.速度と位置の更新：各粒子の速度を、現在の速度、自身の最良解方向、群れの最良解方向を用いて更新します。その後、更新された速度を用いて粒子の位置を更新します。
- 5.終了条件の判定：最大反復回数に達した、あるいは十分な解が得られたならば終了。そうでなければ、ステップ2へ戻ります。

PSOは、連続最適化問題に対して効果的であり、実装が比較的シンプルであるという利点があります。また、パラメータの調整が比較的容易で、さまざまな分野の最適化問題に適用されています。一方で、離散最適化問題への適用には工夫が必要であり、また、高次元の問題では探索性能が低下する可能性があるという課題もあります。


### Python実装例

粒子群最適化（PSO）アルゴリズムを用いて、2次元の目的関数の最小値を求めるプロセスを実装し、そのプロセスをアニメーションで可視化しています。

![pso_process.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348593/376a8585-c62d-7792-afa2-a04baa64dccc.gif)

```python

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# 目的関数の定義：x の各要素の二乗和を計算
def objective_function(x):
    return np.sum(x**2, axis=0)

# 粒子群最適化 (PSO) の実装
def pso(obj_func, dim, pop_size, max_iter, lb, ub, w=0.7298, c1=1.49618, c2=1.49618):
    # 初期粒子の位置と速度の設定
    particle_pos = np.random.uniform(lb, ub, (pop_size, dim))
    particle_vel = np.zeros((pop_size, dim))
    
    # 各粒子の最良位置と評価値の初期化
    particle_best_pos = particle_pos.copy()
    particle_best_scores = np.array([obj_func(p) for p in particle_pos])
    # 群れ全体の最良位置と評価値の初期化
    swarm_best_pos = particle_pos[np.argmin(particle_best_scores)]
    swarm_best_score = np.min(particle_best_scores)
    
    # アニメーションのフレームごとの動作を定義
    def animate(i):
        nonlocal particle_pos, particle_vel, particle_best_pos, particle_best_scores, swarm_best_pos, swarm_best_score
        
        r1, r2 = np.random.rand(2)
        # 粒子の速度と位置の更新
        particle_vel = w * particle_vel + c1*r1*(particle_best_pos - particle_pos) + c2*r2*(swarm_best_pos - particle_pos)
        particle_pos = particle_pos + particle_vel
        
        # 新しい評価値の計算と最良位置の更新
        scores = np.array([obj_func(p) for p in particle_pos])
        particle_best_pos = np.where(scores[:, np.newaxis] < particle_best_scores[:, np.newaxis], particle_pos, particle_best_pos)
        particle_best_scores = np.min(np.vstack((particle_best_scores, scores)), axis=0)
        
        # 群れ全体の最良位置の更新
        if np.min(scores) < swarm_best_score:
            swarm_best_score = np.min(scores)
            swarm_best_pos = particle_pos[np.argmin(scores)]
        
        # 描画のクリアと粒子、群れ全体の最良位置のプロット
        plt.clf()
        plt.scatter(particle_pos[:, 0], particle_pos[:, 1], c='b', label='Particles')
        plt.scatter(swarm_best_pos[0], swarm_best_pos[1], c='r', label='Global Best')
        
        # 目的関数の等高線プロット
        x = np.linspace(lb, ub, 100)
        y = np.linspace(lb, ub, 100)
        X, Y = np.meshgrid(x, y)
        Z = objective_function(np.array([X, Y]))
        plt.contour(X, Y, Z, 20, cmap='coolwarm')
        
        plt.xlim(lb, ub)
        plt.ylim(lb, ub)
        plt.legend()
        plt.title(f'Iteration {i+1}')
        plt.xlabel('x')
        plt.ylabel('y')
        plt.tight_layout()
    
    # アニメーションの設定と保存
    fig = plt.figure()
    anim = FuncAnimation(fig, animate, frames=max_iter, interval=100)
    anim.save('pso_process.gif', writer='pillow')
    plt.show()
    
    return swarm_best_pos, swarm_best_score

# パラメータの設定とPSOの実行
dim = 2
pop_size = 20
max_iter = 50
lb = -5.0
ub = 5.0

best_pos, best_score = pso(objective_function, dim, pop_size, max_iter, lb, ub)
print(f'Best position: {best_pos}, Best score: {best_score}')


```

## 人工蜂コロニー（Artificial Bee Colony, ABC）

ミツバチの群れの採餌行動に着想を得た確率的最適化アルゴリズムです。
ABCは、連続的な最適化問題を解くために提案されました。

ABCアルゴリズムでは、蜂の役割を以下の3つに分類します。
- 採蜜バチ（Employed Bee）：食物源（解候補）を持ち、その周辺を探索します。
- 追従バチ（Onlooker Bee）：採蜜バチが見つけた良い食物源の情報を基に、確率的に食物源を選択し、その周辺を探索します。
- 偵察バチ（Scout Bee）：一定回数の試行で解が改善されない食物源を見捨て、新しいランダムな食物源を探索します。

### ABCアルゴリズムの流れ
- 初期化：食物源（解候補）をランダムに生成します。
- 採蜜バチフェーズ：各採蜜バチが、自身の食物源の周辺を探索し、より良い解があれば更新します。
- 追従バチフェーズ：採蜜バチが見つけた食物源の情報を基に、追従バチが確率的に食物源を選択し、その周辺を探索します。より良い解があれば更新します。
- 偵察バチフェーズ：一定回数の試行で解が改善されない食物源を放棄し、新しいランダムな食物源を生成します。
- 終了条件の判定：最大反復回数に達した、あるいは十分な解が得られたならば終了。そうでなければ、ステップ2へ戻ります。

### ABCアルゴリズムの特徴
- 探索と利用のバランス：採蜜バチと追従バチによる探索で、解の周辺の探索と新たな領域の探索のバランスを取ります。
- 多様性の維持：偵察バチによる新しいランダムな解の生成により、解の多様性を維持します。
- シンプルで実装が容易：アルゴリズムの構造がシンプルで、実装が比較的容易です。

ABCアルゴリズムは、連続最適化問題に対して効果的であり、機械学習、画像処理、制御工学など、さまざまな分野で応用されています。一方で、離散最適化問題への適用には工夫が必要であるという課題もあります。


### 実装例

人工蜂群アルゴリズムを用いて多次元の目的関数を最適化するためのPythonスクリプトです。

![abc_process.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348593/a153598a-0a5d-b896-7594-30ba39e748c9.gif)


```python

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# 目的関数を定義する：ここでは引数 x の各要素の二乗の和を計算します。
def objective_function(x):
    return np.sum(x**2, axis=-1)

# 人工蜂群アルゴリズム (ABC) の実装
def abc(obj_func, dim, pop_size, max_iter, lb, ub, limit=100):
    # 蜂の位置と評価値の初期設定
    bee_pos = np.random.uniform(lb, ub, (pop_size, dim))
    bee_scores = np.array([obj_func(bee) for bee in bee_pos])
    # 群れの中で最も優れた位置とその評価値
    best_pos = bee_pos[np.argmin(bee_scores)]
    best_score = np.min(bee_scores)

    # 各蜂の試行回数カウンター
    trial_counter = np.zeros(pop_size)

    # アニメーションで各イテレーションを表示する関数
    def animate(i):
        nonlocal bee_pos, bee_scores, best_pos, best_score, trial_counter

        # 探索蜂のフェーズ
        for j in range(pop_size):
            k = np.random.randint(pop_size)
            while k == j:
                k = np.random.randint(pop_size)
            d = np.random.randint(dim)
            new_pos = bee_pos[j].copy()
            new_pos[d] = bee_pos[j][d] + np.random.uniform(-1, 1) * (bee_pos[j][d] - bee_pos[k][d])
            new_pos = np.clip(new_pos, lb, ub)
            new_score = obj_func(new_pos)
            if new_score < bee_scores[j]:
                bee_pos[j] = new_pos
                bee_scores[j] = new_score
                trial_counter[j] = 0
            else:
                trial_counter[j] += 1

        # 従者蜂のフェーズ
        fitness = np.exp(-bee_scores / np.max(bee_scores))
        probabilities = fitness / np.sum(fitness)
        for j in range(pop_size):
            if np.random.rand() < probabilities[j]:
                k = np.random.randint(pop_size)
                while k == j:
                    k = np.random.randint(pop_size)
                d = np.random.randint(dim)
                new_pos = bee_pos[j].copy()
                new_pos[d] = bee_pos[j][d] + np.random.uniform(-1, 1) * (bee_pos[j][d] - bee_pos[k][d])
                new_pos = np.clip(new_pos, lb, ub)
                new_score = obj_func(new_pos)
                if new_score < bee_scores[j]:
                    bee_pos[j] = new_pos
                    bee_scores[j] = new_score
                    trial_counter[j] = 0
                else:
                    trial_counter[j] += 1

        # 偵察蜂のフェーズ
        for j in range(pop_size):
            if trial_counter[j] >= limit:
                bee_pos[j] = np.random.uniform(lb, ub, dim)
                bee_scores[j] = obj_func(bee_pos[j])
                trial_counter[j] = 0

        # 全体の最良解の更新
        if np.min(bee_scores) < best_score:
            best_score = np.min(bee_scores)
            best_pos = bee_pos[np.argmin(bee_scores)]

        # 現在の群れの状況をプロット
        plt.clf()
        plt.scatter(bee_pos[:, 0], bee_pos[:, 1], c='b', label='Bees')
        plt.scatter(best_pos[0], best_pos[1], c='r', label='Best Solution')

        x = np.linspace(lb, ub, 100)
        y = np.linspace(lb, ub, 100)
        X, Y = np.meshgrid(x, y)
        Z = objective_function(np.stack((X, Y), axis=-1))
        plt.contour(X, Y, Z, 20, cmap='coolwarm')

        plt.xlim(lb, ub)
        plt.ylim(lb, ub)
        plt.legend()
        plt.title(f'Iteration {i+1}')
        plt.xlabel('x')
        plt.ylabel('y')
        plt.tight_layout()

    fig = plt.figure()
    anim = FuncAnimation(fig, animate, frames=max_iter, interval=100)
    anim.save('abc_process.gif', writer='pillow')
    plt.show()

    return best_pos, best_score

# パラメータ設定とアルゴリズムの実行
dim = 2
pop_size = 20
max_iter = 50
lb = -5.0
ub = 5.0

best_pos, best_score = abc(objective_function, dim, pop_size, max_iter, lb, ub)
print(f'Best position: {best_pos}, Best score: {best_score}')

```