OpenAIの最新モデル ChatGPT-5


先日, OpenAIの新世代モデルであるChatGPT-5が発表された.

そこで性能実験として今回もまたWeasel Programを出力させてみた.

実験結果としては, 確かに今まで以上により複雑なアニメーションを生成するようになった.

一方で, エラーの発生頻度も上がっている印象を受ける.

複雑なプログラムであるため仕方ないかもしれないが, むしろそれを含めて進化していくことを期待する.

プロンプトは下記.

Weasel Program を可能な限りリッチな表現を用いたコードで書いて.
その過程をアニメーションとして出力できるようにして.

出力結果は下記. (一部個人名が出ているのと, エラーが出たので修正したものを掲載.)

# ============================================
# Weasel Program: Rich Visual + Animation (Colab-ready)
# ============================================

import random
import string
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
from dataclasses import dataclass, field
from typing import List, Tuple, Optional

# ---------------------------
# 1) Parameters (feel free to tweak)
# ---------------------------
TARGET = "METHINKS IT IS LIKE A WEASEL"  # 目標文字列
ALPHABET = string.ascii_uppercase + " "  # 大文字+空白
POP_SIZE = 40                             # 母集団サイズ
OFFSPRING_PER_PARENT = 4                  # 最良個体が産む子孫数(POP_SIZE にクリップ)
MUTATION_RATE = 0.05                      # 1 文字ごとの突然変異確率
MAX_GENERATIONS = 300                     # 最大世代数(達成したら早期停止)
RNG_SEED = 42                             # 乱数種(再現用)
SAVE_GIF_PATH = "weasel.gif"              # 出力 GIF
FPS = 12                                  # アニメ FPS
DPI = 140                                 # GIF 解像度

# ---------------------------
# 2) Core Weasel classes
# ---------------------------
@dataclass
class Individual:
    genome: str
    fitness: int = 0
    last_mutated_positions: List[int] = field(default_factory=list)

def random_genome(target: str, alphabet: str) -> str:
    return "".join(random.choice(alphabet) for _ in range(len(target)))

def evaluate_fitness(ind: Individual, target: str) -> int:
    fit = sum(g == t for g, t in zip(ind.genome, target))
    ind.fitness = fit
    return fit

def mutate_genome(parent: str, mutation_rate: float, alphabet: str) -> Tuple[str, List[int]]:
    new_chars = []
    mutated_positions = []
    for i, ch in enumerate(parent):
        if random.random() < mutation_rate:
            choices = [c for c in alphabet if c != ch]
            new_ch = random.choice(choices)
            new_chars.append(new_ch)
            mutated_positions.append(i)
        else:
            new_chars.append(ch)
    return "".join(new_chars), mutated_positions

def make_offspring(parent: Individual, n_offspring: int, mutation_rate: float, alphabet: str) -> List[Individual]:
    kids = []
    for _ in range(n_offspring):
        g, muts = mutate_genome(parent.genome, mutation_rate, alphabet)
        child = Individual(genome=g, last_mutated_positions=muts)
        kids.append(child)
    return kids

# ---------------------------
# 3) Evolution loop (records history for animation)
# ---------------------------
random.seed(RNG_SEED)

# 初期集団
population = [Individual(random_genome(TARGET, ALPHABET)) for _ in range(POP_SIZE)]
for ind in population:
    evaluate_fitness(ind, TARGET)

history_best: List[Individual] = []
history_stats: List[dict] = []  # {"gen": int, "best": int, "mean": float, "max": int, "min": int, "all_f": List[int]}

generation = 0
best = max(population, key=lambda x: x.fitness)
history_best.append(Individual(genome=best.genome, fitness=best.fitness, last_mutated_positions=[]))
history_stats.append({
    "gen": generation,
    "best": best.fitness,
    "mean": float(np.mean([i.fitness for i in population])),
    "max": max(i.fitness for i in population),
    "min": min(i.fitness for i in population),
    "all_f": [i.fitness for i in population],
})

# 早期停止フラグ
done = best.genome == TARGET

while (generation < MAX_GENERATIONS) and (not done):
    generation += 1

    # 選択(エリート):最良個体 1 体から子孫を作る(Weasel の定番)
    parent = max(population, key=lambda x: x.fitness)

    # 子孫を作成(母集団サイズに収まるように調整)
    n_kids = min(POP_SIZE, OFFSPRING_PER_PARENT)
    children = make_offspring(parent, n_kids, MUTATION_RATE, ALPHABET)

    # 集団の残りはランダムリセット or 親のコピーなども可。ここでは「親コピー + 追加子孫」で埋める。
    next_population = [Individual(parent.genome)] + children
    while len(next_population) < POP_SIZE:
        # 多様性確保のためランダム個体も混ぜる
        g = random_genome(TARGET, ALPHABET)
        next_population.append(Individual(genome=g))

    # 評価
    for ind in next_population:
        evaluate_fitness(ind, TARGET)

    population = next_population
    best = max(population, key=lambda x: x.fitness)

    history_best.append(Individual(genome=best.genome, fitness=best.fitness, last_mutated_positions=best.last_mutated_positions))
    history_stats.append({
        "gen": generation,
        "best": best.fitness,
        "mean": float(np.mean([i.fitness for i in population])),
        "max": max(i.fitness for i in population),
        "min": min(i.fitness for i in population),
        "all_f": [i.fitness for i in population],
    })

    done = (best.genome == TARGET)

# ---------------------------
# 4) Matplotlib animation (rich visuals)
# ---------------------------
plt.close("all")
plt.rcParams["font.family"] = "DejaVu Sans Mono"  # 等幅に近いフォントで視認性UP

L = len(TARGET)
max_fit = L

fig = plt.figure(figsize=(12, 7), facecolor="#0f1117")
ax_text = plt.axes([0.05, 0.62, 0.9, 0.30], facecolor="#0f1117")  # ベスト文字列
ax_bar  = plt.axes([0.05, 0.50, 0.9, 0.08], facecolor="#0f1117")  # 一致率バー
ax_box  = plt.axes([0.05, 0.12, 0.9, 0.32], facecolor="#0f1117")  # 適応度分布

for ax in (ax_text, ax_bar, ax_box):
    ax.tick_params(colors="#c9d1d9")
    for spine in ax.spines.values():
        spine.set_color("#30363d")

# 上段:ベスト配列(色強調)
ax_text.axis("off")
text_objs = []
header = ax_text.text(0.0, 1.15, "WEASEL PROGRAM — Cumulative Selection",
                      transform=ax_text.transAxes, fontsize=16, color="#c9d1d9", weight="bold")
subtitle = ax_text.text(0.0, 1.02, f"Target: “{TARGET}”",
                        transform=ax_text.transAxes, fontsize=12, color="#8b949e")
geninfo = ax_text.text(0.0, -0.30, "", transform=ax_text.transAxes, fontsize=11, color="#8b949e")

# 中段:一致率バー
bar_rect = ax_bar.barh([0], [0], height=0.6)
ax_bar.set_xlim(0, max_fit)
ax_bar.set_yticks([])
ax_bar.set_xlabel("Matches (fitness)", color="#c9d1d9")
ax_bar.grid(axis="x", alpha=0.15, color="#30363d")

# 下段:分布
ax_box.set_title("Population fitness distribution", color="#c9d1d9")
ax_box.set_ylabel("Fitness", color="#c9d1d9")
ax_box.set_ylim(-1, max_fit + 1)
ax_box.grid(axis="y", alpha=0.15, color="#30363d")

# ベスト配列文字を初期生成(位置固定)
def init_text_row():
    for obj in text_objs:
        obj.remove()
    text_objs.clear()
    x0 = 0.02
    dx = 0.03  # 文字間隔
    y  = 0.58
    # プレースホルダ(全長で確保)
    for i, ch in enumerate(TARGET):
        t = ax_text.text(x0 + i*dx, y, ch, transform=ax_text.transAxes,
                         fontsize=32, family="DejaVu Sans Mono", color="#444c56", alpha=0.9)
        text_objs.append(t)

init_text_row()

# 変異点の点滅用(トグル)
blink_state = [False]

def color_for_char(i, ch, target, mutated_positions, tgl):
    if ch == target[i]:
        return "#2ea043"  # 濃い緑(正解)
    else:
        # 直近変異箇所は点滅(明→暗)
        if i in mutated_positions and tgl:
            return "#f0a400"  # 橙(変異を強調)
        return "#8b949e"     # グレー

def update(frame):
    # frame は世代インデックス
    rec = history_best[frame]
    stats = history_stats[frame]

    # 点滅トグル(毎フレーム反転)
    blink_state[0] = not blink_state[0]

    # 上段:文字列描画
    for i, ch in enumerate(rec.genome):
        text_objs[i].set_text(ch)
        text_objs[i].set_color(color_for_char(i, ch, TARGET, rec.last_mutated_positions, blink_state[0]))

    # 補助テキスト
    geninfo.set_text(f"Generation: {stats['gen']}   Best fitness: {stats['best']}/{max_fit}  "
                     f"Mean: {stats['mean']:.2f}   Min/Max: {stats['min']}/{stats['max']}")

    # 中段:一致率バー
    bar_rect[0].set_width(rec.fitness)
    bar_rect[0].set_color("#2ea043" if rec.fitness < max_fit else "#00e676")
    ax_bar.set_title(f"Match ratio: {rec.fitness/max_fit:.1%}", color="#c9d1d9")

    # 下段:分布(箱ひげ+散布を更新)
    ax_box.cla()
    ax_box.set_title("Population fitness distribution", color="#c9d1d9")
    ax_box.set_ylabel("Fitness", color="#c9d1d9")
    ax_box.set_ylim(-1, max_fit + 1)
    ax_box.grid(axis="y", alpha=0.15, color="#30363d")
    # 箱ひげ
    bp = ax_box.boxplot(stats["all_f"], vert=True, patch_artist=True, widths=0.3)
    for patch in bp['boxes']:
        patch.set_facecolor("#1f6feb")
        patch.set_alpha(0.35)
        patch.set_edgecolor("#58a6ff")
    for median in bp['medians']:
        median.set_color("#58a6ff")
        median.set_linewidth(2.0)
    # 散布(ばらつき)
    jitter_x = np.random.normal(loc=1.0, scale=0.02, size=len(stats["all_f"]))
    ax_box.scatter(jitter_x, stats["all_f"], s=18, alpha=0.65, edgecolor="#58a6ff", facecolor="#1f6feb")

    # 軸とスパインの色
    ax_box.tick_params(colors="#c9d1d9")
    for spine in ax_box.spines.values():
        spine.set_color("#30363d")

    return text_objs + [bar_rect, header, subtitle, geninfo]

# アニメーション作成
anim = FuncAnimation(fig, update, frames=len(history_best), interval=1000//FPS, blit=False)

# GIF 保存(PillowWriter は Colab で追加インストール不要)
anim.save(SAVE_GIF_PATH, writer=PillowWriter(fps=FPS), dpi=DPI)

# Jupyter/Colab でインライン表示
from IPython.display import Image, display
display(Image(filename=SAVE_GIF_PATH))
print(f"Saved animation -> {SAVE_GIF_PATH}")

# --- Optional: MP4 保存(ffmpeg 利用可能なら)
# anim.save("weasel.mp4", writer="ffmpeg", fps=FPS, dpi=DPI)

参考文献

  • Dawkins, R. (1996). The blind watchmaker: Why the evidence of evolution reveals a universe without design. WW Norton & Company.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です