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.