ChatGPT4を使ってチーム分けアルゴリズムについて考える

# はじめに
どうも僕です。先日、ChatGPTにてNuxtの質問がしたく、ChatGPT3.5では対応してなかったので４にバージョンアップ（課金）してみました。そこで、チーム分けアルゴリズムについて質問していい感じにしてもらったので、ChatGPT４の参考がてらつらつらと書きます

# 概要
次のような条件でチーム分けをしてみます。
- １チーム５人
- 参加者は１０名で２チーム作る
- 各チームにはロールがあり、Tank,DPS,Supportのロールがある
- ロールは1：2：2の割合
- 各ロールには７段階のランクが存在する
- 参加者は好きなロールに対してそのロールのランクを選択する
    - 全部埋める必要はなく、１つのみ選択も可


簡単なJSONを次に
```json:team
{
    "user": {
        "mani": {"tank": "master", "dps": "gold", "support": "silver"},
        "mani2": {"tank": "gold", "dps": "", "support": "silver"},
        "mani3": {"tank": "bronze", "dps": "master", "support": ""},
        "mani4": {"tank": "", "dps": "","support": "silver"},
        "mani5": {"tank": "silver","dps": "","support": ""},
        "mani6": {"tank": "","dps": "gold","support": ""},
        "mani7": {"tank": "","dps": "diamond","support": ""},
        "mani8": {"tank": "grandmaster", "dps": "bronze", "support": "platinum"},
        "mani9": {"tank": "platinum", "dps": "bronze", "support": "diamond"}, 
        "mani0": {"tank": "silver", "dps": "gold", "support": "silver"}
    }
}
```

# 一旦聞いてみる

先ほどの条件を入力して聞いてみるとこんな結果が帰ってきました。
```text
問題をよりよく理解しました。ありがとうございます。これは実際には多目的最適化問題となります。以下に一つのアプローチを提案します。

この問題は、可能な限り均等なチームを作るために、ランクとロールの両方を考慮しなければなりません。この問題を解くためには、「遺伝的アルゴリズム（Genetic Algorithm）」のようなヒューリスティックなアプローチが必要になるかもしれません。

まず、ランクを数値に変換するためにマッピングを行います。例えば：

ブロンズ: 1
シルバー: 2
ゴールド: 3
プラチナ: 4
ダイヤモンド: 5
マスター: 6
グランドマスター: 7
次に、「個体」を作成します。この場合、各個体は10人のプレイヤーを5対5に分ける特定の方法を表します。

それぞれの個体に対して、「適応度」を計算します。適応度は、それぞれのチームの平均ランク（全ロールの平均）の差と、各チームに必要なロールが適切に存在するかを基に評価します。ランクの差が少なく、適切なロールが均等に分布していれば、適応度は高くなります。

遺伝的アルゴリズムでは、次のステップとして「選択」、「交叉」、「突然変異」を行います。

選択：適応度が高い個体が次世代に残る確率が高くなるように選択します。
交叉：2つの個体を選び、それらを組み合わせて新しい個体を生成します。これにより、良い特性を持つ個体から新しい解が生まれる可能性があります。
突然変異：ランダムに個体を選び、ランダムに変更を加えます。これにより、新しい解を探索する可能性があります。
このプロセスを一定の世代数だけ繰り返します。最終的に最も適応度が高い個体が、最良のチーム分けを提供します。

ただし、遺伝的アルゴリズムはあくまでヒューリスティックな手法であり、必ずしも最適解を見つけ出す保証はありません。また、遺伝的アルゴリズムのパラメータ（交叉率、突然変異率、世代数など）を適切に設定する必要があります。
```

一旦よくわからんが、**遺伝的アルゴリズム**っての使えばいいんだな！

ではでは、
僕＞では、先ほどのデータから遺伝子アルゴリズムのソースコードを作ることはできますか？
返信↓
```python
import json
import random
import numpy as np
from deap import base, creator, tools, algorithms

# ランクの順序を定義
RANK_ORDER = {
    'bronze': 1,
    'silver': 2,
    'gold': 3,
    'platinum': 4,
    'diamond': 5,
    'master': 6,
    'grandmaster': 7,
}

# JSONデータを読み込む
with open('data.json') as f:
    data = json.load(f)

users = data['user']

# 適応度クラスと個体クラスを定義
creator.create('FitnessMin', base.Fitness, weights=(-1.0,))
creator.create('Individual', list, fitness=creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register('indices', random.sample, range(len(users)), len(users))
toolbox.register('individual', tools.initIterate, creator.Individual, toolbox.indices)
toolbox.register('population', tools.initRepeat, list, toolbox.individual)

# 適応度関数
def evaluate(individual):
    team1 = [list(users.keys())[i] for i in individual[:len(individual)//2]]
    team2 = [list(users.keys())[i] for i in individual[len(individual)//2:]]

    team1_score = sum(sum(RANK_ORDER[users[user][role]] for role in users[user] if users[user][role]) for user in team1)
    team2_score = sum(sum(RANK_ORDER[users[user][role]] for role in users[user] if users[user][role]) for user in team2)

    return abs(team1_score - team2_score),

toolbox.register('evaluate', evaluate)
toolbox.register('mate', tools.cxTwoPoint)
toolbox.register('mutate', tools.mutShuffleIndexes, indpb=0.05)
toolbox.register('select', tools.selTournament, tournsize=3)

# アルゴリズム
def main():
    pop = toolbox.population(n=50)
    hof = tools.HallOfFame(1)
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register('Avg', np.mean)
    stats.register('Min', np.min)
    stats.register('Max', np.max)

    pop, logbook = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, stats=stats, halloffame=hof, verbose=True)
    
    best_individual = tools.selBest(pop, 1)[0]
    print("Best individual is %s, %s" % (best_individual, best_individual.fitness.values))
    
    team1 = [list(users.keys())[i] for i in best_individual[:len(best_individual)//2]]
    team2 = [list(users.keys())[i] for i in best_individual[len(best_individual)//2:]]
    
    print("\nBest split is:")
    print("Team 1:")
    for user in team1:
        print(f"{user}: {users[user]}")
    print("Team 2:")
    for user in team2:
        print(f"{user}: {users[user]}")

    return pop, logbook, hof

if __name__ == "__main__":
    main()
```

:::note warn
実際には細かく会話していますが、今回は省略しています（長いので。。）
:::

実際に動かしてみると、

```bash
Best split is:
Team 1:
mani2: {'tank': 'gold', 'dps': '', 'support': 'silver'}
mani8: {'tank': 'grandmaster', 'dps': 'bronze', 'support': 'platinum'}
mani4: {'tank': '', 'dps': '', 'support': 'silver'}
mani9: {'tank': 'platinum', 'dps': 'bronze', 'support': 'diamond'}
mani6: {'tank': '', 'dps': 'gold', 'support': ''}
Team 2:
mani0: {'tank': 'silver', 'dps': 'gold', 'support': 'silver'}
mani5: {'tank': 'silver', 'dps': '', 'support': ''}
mani7: {'tank': '', 'dps': 'diamond', 'support': ''}
mani: {'tank': 'master', 'dps': 'gold', 'support': 'silver'}
mani3: {'tank': 'bronze', 'dps': 'master', 'support': ''}
```

いい感じに出る
僕はこのアルゴリズムについてChatGPTに聴いた分しか理解してないですが、それっぽくチーム分けができてる気がします。


# 結論
やっぱAIさんすごい

今までぼんやりしていて、「あーこれ作りたいけど、なんかいい方法ないと実装難しいなー」ってやつをChatGPTさんが教えてくれるので、勉強しやすくなりましたね。