5月26日の朝日新聞「スパコン使いこなすAI」という記事を読んだ。1年ほど前、理化学研究所の研究員がChatGPT(当時はGPT4)に、「スパコンを活用して新型コロナのパンデミックを抑えたい。どんな研究が効果的だろう?」と聞いたところ、「飛沫が舞うシミュレーション研究」を提案し、更に具体的なシミュレーション手法を提案したり、プログラムコードのサンプルも作成してくれたという内容だった。
何か新しいことをAIに頼んで見ようと思っていた時にこの記事を読み、10年程前に読んだ本「RとRubyによるデータ解析入門」の中に、鳥の群れの動きをシミュレーションするプログラムがあったのを思い出した。当時この本を読んだ時は、おもしろそうだが内容が難しくて動かすことができなかった。試しにこのテーマでAIにPythonのプログラムの作成を依頼することにした。
![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/singula2045/20240529/20240529152953.gif)
![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/singula2045/20240529/20240529205017.png)
Sakana AI 7b(70億パラメタ)
手始めにSakana AIの70億パラメタに頼んでみた。Sakana AIを選んだのは、数学の能力を強化したモデルということでこのテーマに相応しいと思ったからだ。
依頼したプロンプトは以下の通り。
Pythonを使って、鳥の群れの動きをシミュレーションするプログラムをコーディングしてください。
グレッグレイノルズの開発したBoidsの手法を使い、分離(separate)、連帯(align)、凝集(cohere)のルールを組み込み、画面上に鳥の群れの動きが表示できるようにしてください。
Boidsの手法と言われてもほとんどの人は知らないと思う。私も説明はできないので、参考情報に書いたネットの記事を見るか、上記の本を読んでいただきたい。
この程度の依頼(プロンプト)でまともなコードを返してくれるのかに興味があった。Sakana AIが返してくれたコードは以下の通り。
import pygame from pygame.locals import * class Bird: def __init__(self, x, y): self.position = (x, y) self.velocity = (0, 0) self.acceleration = (0, 0) self.radius = 4 self.color = (255, 0, 0) def update(self): self.velocity += self.acceleration self.position += self.velocity def draw(self, surface): pygame.draw.circle(surface, self.color, self.position, self.radius) class Boids: def __init__(self, num_birds, width, height): self.num_birds = num_birds self.width = width self.height = height self.birds = [Bird(random.randint(0, width), random.randint(0, height)) for _ in range(num_birds)] self.flock_center = (width / 2, height / 2) def update(self): for bird in self.birds: bird.update() def apply_rules(self, separation, alignment, cohesion): for bird in self.birds: separation(bird) alignment(bird) cohesion(bird) def draw(self, surface): for bird in self.birds: bird.draw(surface) def boids_simulation(width=800, height=600, num_birds=50): pygame.init() screen = pygame.display.set_mode((width, height), pygame.OPENGL | pygame.DOUBLEBUF) clock = pygame.time.Clock() flock = Boids(num_birds, width, height) while True: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() return flock.update() screen.fill((255, 255, 255)) flock.draw(screen) pygame.display.flip() clock.tick(60) def separation(bird): other_birds = [b for b in flock.birds if b != bird] separation_force = (0, 0) for other_bird in other_birds: d = distance(bird.position, other_bird.position) if d < 10: direction = (other_bird.position[0] - bird.position[0], other_bird.position[1] - bird.position[1]) separation_force += (direction / d**2) bird.acceleration += separation_force def alignment(bird): neighbors = [b for b in flock.birds if distance(bird.position, b.position) < 50] alignment_force = (0, 0) for neighbor in neighbors: direction = (neighbor.position[0] - bird.position[0], neighbor.position[1] - bird.position[1]) alignment_force += (direction / len(neighbors)) bird.acceleration += alignment_force def cohesion(bird): cohesion_force = (0, 0) cohesion_force += (flock.flock_center[0] - bird.position[0], flock.flock_center[1] - bird.position[1]) bird.acceleration += cohesion_force if __name__ == '__main__': boids_simulation()
依頼した内容をほぼ理解してコーディングしてくれているようだ。pygameライブラリで画面に表示しようとしているので、pip installでpygameをインストールする。しかしその後でコードを実行しても残念ながら動かなかった。以下の基本的ミスを修正すると画面は表示されるが、鳥の印(点)は表示されなかった。
- import randomを追加
- 変数position、velosity、accerationがタプルとして宣言されているので、+=で演算するとリストに追加されるだけになるため、numpy配列に変更する
pygameの仕様を確認して修正を試みたが何も表示されない。一旦あきらめて別のAIで試してみることにした。
Llama3 70b(700億パラメタ)
Gloqで実行できるLlama3 70bは、以前に株価の指数関数カーブフィッティングのコーディングを試した時は、ほぼほぼ正しいコードを返してくれたが今回はどうだろう。Sakana AIと同じプロンプトで頼んでみた。
Llama3 70bが返してくれたコードは以下の通り。
import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation # Define the number of birds in the flock num_birds = 100 # Define the initial positions and velocities of the birds positions = np.random.rand(num_birds, 2) * 100 velocities = np.random.rand(num_birds, 2) * 2 - 1 # Define the Boids algorithm parameters alignment_weight = 0.125 cohesion_weight = 0.05 separation_weight = 0.025 max_speed = 3 # Define the update function for the Boids algorithm def update_boids(positions, velocities): # Calculate the average velocity and position of the flock avg_velocity = np.mean(velocities, axis=0) avg_position = np.mean(positions, axis=0) # Calculate the alignment, cohesion, and separation forces alignment_force = (avg_velocity - velocities) * alignment_weight cohesion_force = (avg_position - positions) * cohesion_weight separation_force = (positions - np.expand_dims(positions, 1)) * separation_weight separation_force = np.sum(separation_force, axis=1) # Update the velocities and positions of the birds velocities += alignment_force + cohesion_force + separation_force velocities = np.clip(velocities, -max_speed, max_speed) positions += velocities return positions, velocities # Create a figure and axis for the animation fig, ax = plt.subplots() ax.set_xlim(0, 100) ax.set_ylim(0, 100) # Create a scatter plot of the birds scatter = ax.scatter(positions[:, 0], positions[:, 1]) # Define the animation function def animate(i): global positions, velocities positions, velocities = update_boids(positions, velocities) scatter.set_offsets(positions) # Create the animation ani = animation.FuncAnimation(fig, animate, frames=200, interval=50) # Show the animation plt.show()
上記コードはそのままで動作した。matplotlibを使い、アニメーションを表示してくれる。分離(separation_weight)、連帯(alignment_weight)、凝集(cohesion_weight)の基本パラメータが定義されており、そのまま実行した。結果は以下の通り。
![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/singula2045/20240529/20240529143930.png)
![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/singula2045/20240529/20240529143938.png)
![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/singula2045/20240529/20240529143945.png)
点がそれらしく動くのだが、集まる力が強すぎるようで、最後は一点に集まってしまう。パラメータで調整できるのだろうが、調整方法が分からない。いくらパラメータを調整しても集まってしまい、動き回ってくれない。(T_T)
仕方がないので、これも一旦あきらめ、現時点で一番有能なAIであるOpenAIのGPT-4oに頼んでみることにした。
GPT-4o
同じプロンプトで頼んでGPT-4oが返してくれたコードは以下の通り。
import pygame import numpy as np # Pygameの初期化 pygame.init() # 画面サイズの設定 WIDTH, HEIGHT = 800, 600 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Boids Simulation") # ボイド(Boid)クラスの定義 class Boid: def __init__(self, position, velocity): self.position = np.array(position, dtype='float64') self.velocity = np.array(velocity, dtype='float64') self.max_speed = 4 self.max_force = 0.1 def edges(self): if self.position[0] > WIDTH: self.position[0] = 0 elif self.position[0] < 0: self.position[0] = WIDTH if self.position[1] > HEIGHT: self.position[1] = 0 elif self.position[1] < 0: self.position[1] = HEIGHT def align(self, boids): perception_radius = 50 steering = np.zeros(2) total = 0 avg_vector = np.zeros(2) for boid in boids: if np.linalg.norm(boid.position - self.position) < perception_radius: avg_vector += boid.velocity total += 1 if total > 0: avg_vector /= total avg_vector = avg_vector / np.linalg.norm(avg_vector) * self.max_speed steering = avg_vector - self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def cohere(self, boids): perception_radius = 50 steering = np.zeros(2) total = 0 center_of_mass = np.zeros(2) for boid in boids: if np.linalg.norm(boid.position - self.position) < perception_radius: center_of_mass += boid.position total += 1 if total > 0: center_of_mass /= total vector_to_com = center_of_mass - self.position if np.linalg.norm(vector_to_com) > 0: vector_to_com = vector_to_com / np.linalg.norm(vector_to_com) * self.max_speed steering = vector_to_com - self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def separate(self, boids): perception_radius = 25 steering = np.zeros(2) total = 0 for boid in boids: distance = np.linalg.norm(boid.position - self.position) if distance < perception_radius and distance > 0: diff = self.position - boid.position diff /= distance steering += diff total += 1 if total > 0: steering /= total if np.linalg.norm(steering) > 0: steering = steering / np.linalg.norm(steering) * self.max_speed steering -= self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def flock(self, boids): alignment = self.align(boids) cohesion = self.cohere(boids) separation = self.separate(boids) self.velocity += alignment self.velocity += cohesion self.velocity += separation def update(self): if np.linalg.norm(self.velocity) > self.max_speed: self.velocity = self.velocity / np.linalg.norm(self.velocity) * self.max_speed self.position += self.velocity def show(self, screen): pygame.draw.circle(screen, (255, 255, 255), self.position.astype(int), 3) # ボイド群を初期化 boids = [Boid(position=[np.random.rand() * WIDTH, np.random.rand() * HEIGHT], velocity=[np.random.rand() * 2 - 1, np.random.rand() * 2 - 1]) for _ in range(100)] # メインループ running = True clock = pygame.time.Clock() while running: screen.fill((0, 0, 0)) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False for boid in boids: boid.edges() boid.flock(boids) boid.update() boid.show(screen) pygame.display.flip() clock.tick(60) pygame.quit()
さすがGPT-4oだ。初めから完璧な答えを返してくれた。
静止画ではこの素晴らしさが伝わらないと思う。動画を取りたいのだがその方法が私には分からない。そこでGPT-4oに追加で依頼した。
画面に表示した動きを動画として保存できるようにしてください。
すると、MP4形式でファイルに動画を保存してくれるコードを作成してくれた。
そのコードで作成した動画がこちらである。
雑感
簡単なプロンプトでAIに指示することで、シミュレーションプログラムを動かすことができた。頭の良い人を「一を聞いて十を知る」とよく表現するが、「Boidsの手法のシミュレーションプログラム」と依頼するだけで理解してくれた生成AIは、そのレベルに近い気がする。
同時にAIの能力の違いも実感することになった。GPT-4oの完成度の高さを体験すると、それ以外のAIは使う気がしない、というのが現時点の正直な感想だ。
70億パラメタのAIは、おもちゃのように遊びで使うならいいが、仕事では使えないだろう。700億パラメタのLlama3も無料で使い放題であることは魅力だが、9割程度の完成度と覚悟して使う必要がある。現時点で、ある程度高度な事をAIに頼むならGPT-4o一択というところか。
しかしこの図式も現時点の図式であり、半年後や1年後にどんな状況になるのかは全く予測できない。AIのビジネスをする企業には大変だろうが、我々利用者からするとおもしろいところだ。
参考情報
Boids手法によるシミュレーションについては以下のページがある。
Pygameで動かすBoidsモデルシミュレーションのコードがこのページにもある。
「RとRubyによるデータ解析入門」の8章にRubyで動かすBoidsモデルの説明がある。当時はRubyが言語の主流になると思っていてこの本を読んだ。
GPT-4oが作成してくれた、動きを動画ファイルに保存するPythonのコード。カレントディレクトリに"boids_simulation.mp4"という動画ファイルを作成する。
import pygame import numpy as np import moviepy.editor as mpy # Pygameの初期化 pygame.init() # 画面サイズの設定 WIDTH, HEIGHT = 800, 600 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Boids Simulation") # ボイド(Boid)クラスの定義 class Boid: def __init__(self, position, velocity): self.position = np.array(position, dtype='float64') self.velocity = np.array(velocity, dtype='float64') self.max_speed = 4 self.max_force = 0.1 def edges(self): if self.position[0] > WIDTH: self.position[0] = 0 elif self.position[0] < 0: self.position[0] = WIDTH if self.position[1] > HEIGHT: self.position[1] = 0 elif self.position[1] < 0: self.position[1] = HEIGHT def align(self, boids): perception_radius = 50 steering = np.zeros(2) total = 0 avg_vector = np.zeros(2) for boid in boids: if np.linalg.norm(boid.position - self.position) < perception_radius: avg_vector += boid.velocity total += 1 if total > 0: avg_vector /= total avg_vector = avg_vector / np.linalg.norm(avg_vector) * self.max_speed steering = avg_vector - self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def cohere(self, boids): perception_radius = 50 steering = np.zeros(2) total = 0 center_of_mass = np.zeros(2) for boid in boids: if np.linalg.norm(boid.position - self.position) < perception_radius: center_of_mass += boid.position total += 1 if total > 0: center_of_mass /= total vector_to_com = center_of_mass - self.position if np.linalg.norm(vector_to_com) > 0: vector_to_com = vector_to_com / np.linalg.norm(vector_to_com) * self.max_speed steering = vector_to_com - self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def separate(self, boids): perception_radius = 25 steering = np.zeros(2) total = 0 for boid in boids: distance = np.linalg.norm(boid.position - self.position) if distance < perception_radius and distance > 0: diff = self.position - boid.position diff /= distance steering += diff total += 1 if total > 0: steering /= total if np.linalg.norm(steering) > 0: steering = steering / np.linalg.norm(steering) * self.max_speed steering -= self.velocity if np.linalg.norm(steering) > self.max_force: steering = steering / np.linalg.norm(steering) * self.max_force return steering def flock(self, boids): alignment = self.align(boids) cohesion = self.cohere(boids) separation = self.separate(boids) self.velocity += alignment self.velocity += cohesion self.velocity += separation def update(self): if np.linalg.norm(self.velocity) > self.max_speed: self.velocity = self.velocity / np.linalg.norm(self.velocity) * self.max_speed self.position += self.velocity def show(self, screen): pygame.draw.circle(screen, (255, 255, 255), self.position.astype(int), 3) # ボイド群を初期化 boids = [Boid(position=[np.random.rand() * WIDTH, np.random.rand() * HEIGHT], velocity=[np.random.rand() * 2 - 1, np.random.rand() * 2 - 1]) for _ in range(100)] # 動画保存用設定 duration = 10 # 動画の長さ(秒) fps = 30 # フレームレート total_frames = duration * fps def make_frame(t): frame_idx = int(t * fps) screen.fill((0, 0, 0)) for boid in boids: boid.edges() boid.flock(boids) boid.update() boid.show(screen) return pygame.surfarray.array3d(screen).transpose([1, 0, 2]) # MoviePyを使って動画を作成 animation = mpy.VideoClip(make_frame, duration=duration) animation.write_videofile("boids_simulation.mp4", fps=fps) # Pygameを終了 pygame.quit()