The Nature of Code閱讀心得與Python實作:9.6 Evolving Forces:Smart..下

更新 發佈閱讀 65 分鐘
這一節的標題是
9.6 Evolving Forces: Smart Rockets
因為方格子標題字數限制,所以沒完整顯現
因超過方格子內容長度限制,故分成上、下兩部分

Making Improvements

到目前為止,我們已經讓火箭具有演化的能力,不管目標物的位置怎麼改變,都能夠自動調整飛行路線,朝目標物飛過去。這樣子的火箭雖然厲害,不過還是可以讓它再厲害一些。本節一開始的時候提到,在Thorp的範例中,火箭和目標物之間有許多障礙物,而火箭藉由演化的方式,可以自己找出避開障礙物的路線。接下來,就來升級我們的火箭,讓我們的火箭也擁有避開障礙物的能力。

在升級火箭之前,得先製作障礙物。為了方便處理,我們用矩形來當作障礙物。所以,描述障礙物的類別可以這樣設計:

class Obstacle:
def __init__(self, x, y, w, h):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

self.position = pygame.Vector2(x, y)
self.width = w
self.height = h

self.rect = pygame.Rect(x, y, w, h)

def show(self):
pygame.draw.rect(self.screen, (100, 100, 100), self.rect)

火箭在飛行途中有可能會擊中障礙物,所以在Obstacle類別裡,我們加入一個contains()方法,可以用來檢查某個點是否落在障礙物裡頭。當想要確認火箭是不是擊中障礙物時,就用這個方法來檢查火箭的位置是不是落在障礙物裡頭;如果是的話,就代表火箭擊中了障礙物。

def contains(self, spot):    
return self.rect.collidepoint(tuple(spot))

設計好描述障礙物的Obstacle類別之後,就可以來升級Rocket類別了。

由於障礙物可能不只一個,所以對於火箭而言,必須逐一檢查各個障礙物,才能知道是不是有擊中障礙物的情況發生。如果火箭擊中了任何一個障礙物,就把hit_obstacle這個屬性設定為True

def check_obstacles(self, obstacles):
for obstacle in obstacles:
if obstacle.contains(self.position):
self.hit_obstacle = True

當火箭擊中障礙物時,就停止更新其位置,讓它留在原地,所以把run()方法改成

def run(self, obstacles):
if not self.hit_obstacle:
self.apply_force(self.dna.genes[self.gene_counter])
self.gene_counter += 1
self.update()
self.check_obstacles(obstacles)

self.show()

火箭擊中障礙物,代表適應度極差,現有適應度的值失準;所以碰到這種情況時,應該要大幅調降算出來的適應度值,這樣才能反映出真實的狀況。說得直白一點,就是表現不好就罰的意思啦。

# 擊中障礙物要罰
if self.hit_obstacle:
self.fitness *= 0.1

在Example 9.2中,當火箭結束飛行時才會去計算適應度。這會有個問題:如果火箭在飛行途中一度非常接近目標物,但因飛行時間還沒結束,所以會繼續飛而飛過頭。因為適應度是根據火箭和目標物間的距離來計算的,所以如果火箭飛過頭,即便它曾經很接近過目標物,那算出來的適應度也不會因為這樣就變得比較好。換句話說,這樣子的做法,實際上可能會變成是在懲罰比較快飛近目標物的火箭,而讓飛得慢、穩紮穩打的火箭比較容易勝出。

如果想讓比較快飛近目標物的火箭比較容易勝出,那要怎麼做呢?這可以從時間和空間兩方面來考慮;時間指的是火箭飛了多少時間才擊中目標物,而空間則是指火箭在飛行過程中,最接近目標物的距離有多大。很顯然的,時間越短、距離越近,則火箭的適應度應該要越大,這樣才能達到我們的目的。因此,火箭的適應度計算方式,可以設定成

fitness = 1 / (擊中目標物所花的時間×離目標物最近的距離)

有了計算火箭適應度的公式之後,接下來就是要把公式中的「擊中目標物所花的時間」、「離目標物最近的距離」這兩個值給找出來。

要想知道火箭擊中目標物所花的時間,首要之務,就是要能夠判斷火箭是不是擊中了目標物。要知道火箭是否擊中目標物,方法很簡單,就把目標物當作是一種障礙物就可以了。這也就是說,如果設定目標物是Obstacle類別的實例,那就可以用contains()方法來判斷火箭是否擊中目標物了。我們在Rocket類別中,新增一個check_target()方法,用來判斷並記錄火箭是否擊中目標物:

def check_target(self, target):
if target.contains(self.position):
self.hit_target = True

在每幀畫面中,都會執行check_target()方法。因此,用個計數器來計數,執行了check_target()方法多少次之後,火箭才擊中目標,這樣就可以知道火箭擊中目標物所花的時間了;這可以在check_target()方法中加入下列程式來辦到:

if not self.hit_target:
self.finish_counter += 1

找出火箭擊中目標物所花的時間之後,再來就是要找出火箭離目標物最近的距離。同樣的,在check_target()方法中加入一段程式就可以了:

distance = self.position.distance_to(target.position)
if distance < self.record_distance:
self.record_distance = distance

所以,完整的check_target()方法會長這樣:

def check_target(self, target):
distance = self.position.distance_to(target.position)
if distance < self.record_distance:
self.record_distance = distance

if target.contains(self.position):
self.hit_target = True

if not self.hit_target:
self.finish_counter += 1

至於計算適應度的calculate_fitness()方法,則設計成這樣:

def calculate_fitness(self):
self.fitness = 1/(self.finish_counter*self.record_distance)
self.fitness **= 4

# 擊中障礙物要罰
if self.hit_obstacle:
self.fitness *= 0.1

# 擊中目標物要賞
if self.hit_target:
self.fitness *= 2

在這裡,我們改用4次式來計算適應度,試看看效果如何。另外,既然擊中障礙物要罰,那擊中目標物當然要賞囉!所以,如果火箭擊中了目標物,那就將算出的適應度值翻倍,讓它在GA的挑選步驟中,更容易雀屏中選。

依照上述升級Rocket類別的說明,升級後的Rocket類別程式碼如下:

class Rocket:
def __init__(self, x, y, dna, size=24, mass=1):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

# 讓傳遞進來的數值來決定火箭的質量
self.mass = mass

# 火箭的大小,長 x 寬 = size x size/2
self.size = size

# 火箭的初始位置、初始速度、初始加速度
self.position = pygame.Vector2(x, y)
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

# 設定火箭所在surface的格式為per-pixel alpha,並在上面畫出火箭
self.surface = pygame.Surface((self.size, self.size/2), pygame.SRCALPHA)
rocket_color = (100, 100, 100, 200)
body = [(self.size//5, 0), (self.size//5, self.size//2), (self.size, self.size//4)]
pygame.draw.polygon(self.surface, rocket_color, body)
# 推進器圖案座標為(0, self.size//4-self.size//8), (0, self.size//4+self.size//8),
# (self.size//5+self.size//4, self.size//4)
thruster = [(0, self.size//8), (0, 3*self.size//8), (9*self.size//20, self.size//4)]
pygame.draw.polygon(self.surface, rocket_color, thruster)

self.dna = dna
self.fitness = 0
self.gene_counter = 0

self.hit_obstacle = False
self.hit_target = False
self.record_distance = float('inf')
self.finish_counter = 0

def apply_force(self, force):
self.acceleration += force/self.mass

def update(self):
self.velocity += self.acceleration
self.position += self.velocity
self.acceleration *= 0

def show(self):
# 旋轉surface,讓火箭面朝前進方向
heading = math.atan2(self.velocity.y, self.velocity.x)
rotated_surface = pygame.transform.rotate(self.surface, -math.degrees(heading))
rect_new = rotated_surface.get_rect(center=self.position)

# 把火箭所在的surface貼到最後要顯示的畫面上
self.screen.blit(rotated_surface, rect_new)

def calculate_fitness(self):
self.fitness = 1/(self.finish_counter*self.record_distance)

self.fitness **= 4

# 擊中障礙物要罰
if self.hit_obstacle:
self.fitness *= 0.1

# 擊中目標物要賞
if self.hit_target:
self.fitness *= 2

def check_obstacles(self, obstacles):
for obstacle in obstacles:
if obstacle.contains(self.position):
self.hit_obstacle = True

def check_target(self, target):
distance = self.position.distance_to(target.position)
if distance < self.record_distance:
self.record_distance = distance

if target.contains(self.position):
self.hit_target = True

if not self.hit_target:
self.finish_counter += 1

def run(self, obstacles):
if (not self.hit_obstacle) and (not self.hit_target) :
self.apply_force(self.dna.genes[self.gene_counter])
self.gene_counter += 1
self.update()
self.check_obstacles(obstacles)

self.show()

Population類別也須新增、修改部分內容;完整的程式碼如下:

class Population:
def __init__(self, population_size, mutation_rate, life_span):
self.population_size = population_size
self.mutation_rate = mutation_rate

self.dna_length = life_span
# 發射場位置
screen = pygame.display.get_surface()
self.width, self.height = screen.get_size()
self.launch_site = pygame.Vector2(self.width//2, self.height+20)

self.population = [Rocket(self.launch_site.x, self.launch_site.y, DNA(self.dna_length)) for _ in range(population_size)]

self.generations = 0

def calculate_fitness(self):
for rocket in self.population:
rocket.calculate_fitness()

def evolve(self):
weights = [rocket.fitness for rocket in self.population]

next_generation = []
for i in range(self.population_size):
[parentA, parentB] = random.choices(self.population, weights, k=2)

child = parentA.dna.crossover(parentB.dna)
child.mutate(self.mutation_rate)
next_generation.append(Rocket(self.launch_site.x, self.launch_site.y, child))

self.population = next_generation
self.generations += 1

def get_generations(self):
return self.generations

def live(self, target, obstacles):
for rocket in self.population:
rocket.check_target(target)
rocket.run(obstacles)

def target_reached(self):
for rocket in self.population:
if rocket.hit_target:
return True

return False

DNA類別完全不需更動,不再列出。至於新增的Obstacle類別,完整的程式碼如下:

class Obstacle:
def __init__(self, x, y, w, h):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

self.position = pygame.Vector2(x, y)
self.width = w
self.height = h

self.rect = pygame.Rect(x, y, w, h)

def contains(self, spot):
return self.rect.collidepoint(tuple(spot))

def show(self):
pygame.draw.rect(self.screen, (100, 100, 100), self.rect)

接下來,就來讓升級版的火箭飛一飛,看看效果如何。

Example 9.3: Smarter Rockets

下面是三張模擬過程的截圖。從截圖可以看出來,火箭從一開始的四處亂竄,經過不斷地演化,自己進化成能夠避開障礙物而擊中目標。這就是演化式運算的威力!

vocus|新世代的創作平台
vocus|新世代的創作平台
vocus|新世代的創作平台

主程式如下:

# python version 3.10.9
import math
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 9.2: Smart Rockets")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

# 製造火箭族群
population_size = 150
mutation_rate = 0.01
life_span = 250
population = Population(population_size, mutation_rate, life_span)

font14 = pygame.font.SysFont('courier', 14)

target = Obstacle(WIDTH//2-12, 24, 24, 24)
obstacles = [Obstacle(WIDTH//2-75, HEIGHT//2, 150, 10)]

life_counter = 0
record_time = life_span

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

# 火箭存活life_span幀畫面之後才會進行演化
if life_counter < life_span:
population.live(target, obstacles)
if population.target_reached() and life_counter < record_time:
record_time = life_counter

life_counter += 1
else:
life_counter = 0
population.calculate_fitness()
population.evolve()

string = f'{"Generation #:"}{population.get_generations():>3}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 20))

string = f'{"Cycles left:"}{life_span-life_counter:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 40))

string = f'{"Record cycles:"}{record_time:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 60))

# 按滑鼠左鍵可更動目標物位置至滑鼠指標處
if pygame.mouse.get_pressed()[0]:
x, y = pygame.mouse.get_pos()
target = Obstacle(x, y, 24, 24)

target.show()
for obstacle in obstacles:
obstacle.show()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 9.9

在障礙物分佈比較複雜的環境中,不需調整GA的設定,火箭依然可以演化出能夠飛抵目標物的路徑;只不過所需的世代數會增加就是了。例如,如果目標物和障礙物的配置為

target = Obstacle(WIDTH//2-12, 24, 24, 24)

obstacle1 = Obstacle(WIDTH//2-85, HEIGHT//2, 100, 10)
obstacle2 = Obstacle(WIDTH//2+45, HEIGHT//2, 50, 10)
obstacle3 = Obstacle(WIDTH//2-15, HEIGHT//2-50, 75, 10)
obstacles = [obstacle1, obstacle2, obstacle3]

在完全沒有調整GA設定以及火箭性能的情形下,演化了超過250代,才有火箭擊中目標;跟Example 9.3比起來,多了非常多。

vocus|新世代的創作平台
vocus|新世代的創作平台
vocus|新世代的創作平台

Exercise 9.10

基因型,也就是DNA類別的genes,前5個元素為推進器,剩下的部分則為推進器作動序列。交配時,推進器部分和推進器動作序列部分視為兩組不同之基因,需分開處理。

燃料消耗計算方式:每1單位推力強度持續作用1幀畫面,會消耗1單位燃料。

燃料耗盡之火箭停止飛行,並在箭體中心標註紅色圓點;計算適應度時略施薄懲,將適應度降低50%。

一開始有許多火箭耗盡燃料,但隨著演化的進行,這種情況越來越少。

vocus|新世代的創作平台
vocus|新世代的創作平台
vocus|新世代的創作平台

改變目標物位置後,火箭很快就演化出新的飛行路線。

vocus|新世代的創作平台

程式部分,未更動之類別、方法略過不列出。

class Population:
def __init__(self, population_size, mutation_rate, life_span, fuel_amount):
self.population_size = population_size
self.mutation_rate = mutation_rate

# dna長度為5個推進器加上推進器作動序列
self.dna_length = 5 + life_span
# 發射場位置
screen = pygame.display.get_surface()
self.width, self.height = screen.get_size()
self.launch_site = pygame.Vector2(self.width//2, self.height+20)

self.fuel_amount = fuel_amount
self.population = [Rocket(self.launch_site.x, self.launch_site.y, DNA(self.dna_length), fuel_amount) for _ in range(population_size)]

self.generations = 0

def calculate_fitness(self):


def evolve(self):
weights = [rocket.fitness for rocket in self.population]

next_generation = []
for i in range(self.population_size):
[parentA, parentB] = random.choices(self.population, weights, k=2)

child = parentA.dna.crossover(parentB.dna)
child.mutate(self.mutation_rate)
rocket = Rocket(self.launch_site.x, self.launch_site.y, child, self.fuel_amount)
next_generation.append(rocket)

self.population = next_generation
self.generations += 1

def get_generations(self):


def live(self, target, obstacles):


def target_reached(self):



class DNA:
def __init__(self, length):
self.length = length

self.max_force = 0.1

self.genes = []
# 前5個為推進器
for _ in range(5):
r = random.uniform(0, self.max_force)
theta = random.uniform(0, 360)
# 推進器推力大小介於0~最大出力,方向為任意方向
thruster = pygame.Vector2.from_polar((r, theta))
self.genes.append(thruster)

# 推進器作動序列
self.genes += random.choices([0, 1, 2, 3, 4], k=self.length-5)

def crossover(self, partner):
child = DNA(self.length)

# 推進器部分
crossover_point = random.randint(0, 4)
# 在crossover_point之前的基因來自此DNA,之後的基因則來自partner這個DNA
for i in range(5):
if i < crossover_point:
child.genes[i] = self.genes[i]
else:
child.genes[i] = partner.genes[i]

# 推進器作動序列部分
crossover_point = random.randint(5, self.length-1)
# 在crossover_point之前的基因來自此DNA,之後的基因則來自partner這個DNA
for i in range(5, self.length):
if i < crossover_point:
child.genes[i] = self.genes[i]
else:
child.genes[i] = partner.genes[i]

return child

def mutate(self, mutation_rate):
for i in range(self.length):
if random.random() < mutation_rate:
if i <= 4:
# 推進器產生突變
r = random.uniform(0, self.max_force)
theta = random.uniform(0, 360)
# 推進器推力大小介於0~最大出力,方向為任意方向
thruster = pygame.Vector2.from_polar((r, theta))
self.genes[i] = thruster
else:
# 推進器作動序列產生突變
self.genes[i] = random.randint(0, 4)


class Rocket:
def __init__(self, x, y, dna, fuel_amount, size=24, mass=1):
# 取得顯示畫面
self.screen = pygame.display.get_surface()

# 讓傳遞進來的數值來決定火箭的質量
self.mass = mass

# 火箭的大小,長 x 寬 = size x size/2
self.size = size

# 火箭的初始位置、初始速度、初始加速度
self.position = pygame.Vector2(x, y)
self.velocity = pygame.Vector2(0, 0)
self.acceleration = pygame.Vector2(0, 0)

# 設定火箭所在surface的格式為per-pixel alpha,並在上面畫出火箭
self.surface = pygame.Surface((self.size, self.size/2), pygame.SRCALPHA)
rocket_color = (100, 100, 100, 200)
body = [(self.size//5, 0), (self.size//5, self.size//2), (self.size, self.size//4)]
pygame.draw.polygon(self.surface, rocket_color, body)
# 推進器圖案座標為(0, self.size//4-self.size//8), (0, self.size//4+self.size//8),
# (self.size//5+self.size//4, self.size//4)
thruster = [(0, self.size//8), (0, 3*self.size//8), (9*self.size//20, self.size//4)]
pygame.draw.polygon(self.surface, rocket_color, thruster)

self.dna = dna
self.fitness = 0
self.gene_counter = 5

self.hit_obstacle = False
self.hit_target = False
self.record_distance = float('inf')
self.finish_counter = 0

self.fuel_exhausted = False
self.fuel_guage = fuel_amount

def apply_force(self, force):


def update(self):


def show(self):
# 旋轉surface,讓火箭面朝前進方向
heading = math.atan2(self.velocity.y, self.velocity.x)
rotated_surface = pygame.transform.rotate(self.surface, -math.degrees(heading))
rect_new = rotated_surface.get_rect(center=self.position)

# 把火箭所在的surface貼到最後要顯示的畫面上
self.screen.blit(rotated_surface, rect_new)

# 燃料耗盡,在箭體中心標註紅色圓點
if self.fuel_exhausted:
pygame.draw.circle(self.screen, (255, 0, 0), self.position, self.size/8)

def calculate_fitness(self):
self.fitness = 1/(self.finish_counter*self.record_distance)
self.fitness **= 4

# 擊中障礙物要罰
if self.hit_obstacle:
self.fitness *= 0.1

# 擊中目標物要賞
if self.hit_target:
self.fitness *= 2

# 燃料耗盡,略施薄懲
if self.fuel_exhausted:
self.fitness *= 0.5

def check_obstacles(self, obstacles):


def check_target(self, target):


def run(self, obstacles):
thruster_id = self.dna.genes[self.gene_counter]
thrust = self.dna.genes[thruster_id]
self.check_fuel(thrust)

if (not self.hit_obstacle) and (not self.hit_target) and (not self.fuel_exhausted):
self.apply_force(thrust)
self.gene_counter += 1
self.update()
self.check_obstacles(obstacles)

self.show()

def check_fuel(self, thrust):
self.fuel_guage -= thrust.length()
if self.fuel_guage <= 0:
self.fuel_exhausted = True


class Obstacle:

主程式

# python version 3.10.9
import math
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 9.10")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 700, 500
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

# 製造火箭族群
population_size = 150
mutation_rate = 0.01
life_span = 250
fuel_amount = 10
population = Population(population_size, mutation_rate, life_span, fuel_amount)

font14 = pygame.font.SysFont('courier', 14)

target = Obstacle(WIDTH//2-12, 24+260, 24, 24)
obstacles = [Obstacle(WIDTH//2-75, 24+260+96, 150, 10)]

life_counter = 0
record_time = life_span

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

# 歷經life_span幀畫面之後進行演化
if life_counter < life_span:
population.live(target, obstacles)
if population.target_reached() and life_counter < record_time:
record_time = life_counter

life_counter += 1
else:
life_counter = 0
population.calculate_fitness()
population.evolve()

string = f'{"Generation #:"}{population.get_generations():>3}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 20))

string = f'{"Cycles left:"}{life_span-life_counter:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 40))

string = f'{"Record cycles:"}{record_time:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 60))

# 按滑鼠左鍵可更動目標物位置至滑鼠指標處
if pygame.mouse.get_pressed()[0]:
x, y = pygame.mouse.get_pos()
target = Obstacle(x, y, 24, 24)

target.show()
for obstacle in obstacles:
obstacle.show()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 9.11

vocus|新世代的創作平台
vocus|新世代的創作平台

參考4.4節的Exercise 4.4,在推進器尾端加入噴煙效果。

修改Rocket類別。在__init__()方法中,新增

# 飛行路徑
self.course = [self.position.copy()]

# 推進器煙霧產生器
self.emitter = Emitter(0, 0, 0.3)

update()方法加入記錄飛行路徑功能

def update(self):
self.velocity += self.acceleration
self.position += self.velocity
self.acceleration *= 0

# 記錄飛行路徑
self.course.append(self.position.copy())

show()方法加入推進器噴煙效果

def show(self):
# 旋轉surface,讓火箭面朝前進方向
heading = math.atan2(self.velocity.y, self.velocity.x)
rotated_surface = pygame.transform.rotate(self.surface, -math.degrees(heading))
rect_new = rotated_surface.get_rect(center=self.position)

# 把火箭所在的surface貼到最後要顯示的畫面上
self.screen.blit(rotated_surface, rect_new)

# 顯示推進器煙霧
heading_vec = self.velocity.normalize()
self.emitter.origin = self.position - (self.size/2)*heading_vec
self.emitter.add_particle()
self.emitter.particles[-1].velocity = -0.5*heading_vec
self.emitter.particles[-1].lifespan = 50

self.emitter.run()

主程式部分,在演化至下一代之前,先檢查是否有火箭擊中目標。若有火箭擊中目標,則進行最短路徑修正工作。

# python version 3.10.9
import math
import random
import sys

import pygame # version 2.3.0

pygame.init()

pygame.display.set_caption("Exercise 9.11")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 60
frame_rate = pygame.time.Clock()

# 製造火箭族群
population_size = 150
mutation_rate = 0.01
life_span = 250
population = Population(population_size, mutation_rate, life_span)

font14 = pygame.font.SysFont('courier', 14)

target = Obstacle(WIDTH//2-12, 24, 24, 24)
obstacles = [Obstacle(WIDTH//2-75, HEIGHT//2, 150, 10)]

life_counter = 0
record_time = life_span

gravity = pygame.Vector2(0, 0)

shortest_path = []
shortest_distance = float('inf')

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

screen.fill(WHITE)

# 火箭存活life_span幀畫面之後才會進行演化
if life_counter < life_span:
population.live(target, obstacles)
if population.target_reached() and life_counter < record_time:
record_time = life_counter

life_counter += 1
else:
# 找出最短路徑
for rocket in population.population:
if rocket.hit_target:
distance = 0
for i in range(1, len(rocket.course)-1):
distance += rocket.course[i+1].distance_to(rocket.course[i])

if distance < shortest_distance:
shortest_distance = distance
shortest_path = rocket.course.copy()

# 演化至下一代
life_counter = 0
population.calculate_fitness()
population.evolve()

# 畫最短路徑
if shortest_path:
pygame.draw.lines(screen, (255, 0, 0), False, shortest_path)

string = f'{"Generation #:"}{population.get_generations():>3}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 20))

string = f'{"Cycles left:"}{life_span-life_counter:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 40))

string = f'{"Record cycles:"}{record_time:>4}'
text = font14.render(string, True, (0, 0, 0))
screen.blit(text, (5, 60))

# 按滑鼠左鍵可更動目標物位置至滑鼠指標處
if pygame.mouse.get_pressed()[0]:
x, y = pygame.mouse.get_pos()
target = Obstacle(x, y, 24, 24)

target.show()
for obstacle in obstacles:
obstacle.show()

pygame.display.update()
frame_rate.tick(FPS)

Exercise 9.12

修改DNA類別。

class DNA:
def __init__(self, rows, cols, resolution):
self.rows = rows
self.cols = cols
self.resolution = resolution
self.length = self.rows*self.cols

# 基因型是流場向量
self.genes = [None]*self.length
for i in range(self.length):
theta = random.uniform(0, 360)
self.genes[i] = pygame.Vector2.from_polar((1, theta))

def crossover(self, partner):
child = DNA(self.rows, self.cols, self.resolution)

crossover_point = random.randint(0, self.length-1)
# 在crossover_point之前的基因來自此DNA,之後的基因則來自partner這個DNA
for i in range(self.length):
if i < crossover_point:
child.genes[i] = self.genes[i]
else:
child.genes[i] = partner.genes[i]

return child

def mutate(self, mutation_rate):
for i in range(self.length):
if random.random() < mutation_rate:
theta = random.uniform(0, 360)
self.genes[i] = pygame.Vector2.from_polar((1, theta))

Rocket類別新增look_field_up()方法:

def look_field_up(self):
x, y = self.position

column = int(x//self.dna.resolution)
if column < 0:
column = 0
elif column > self.dna.cols-1:
column = self.dna.cols-1

row = int(y//self.dna.resolution)
if row < 0:
row = 0
elif row > self.dna.rows-1:
row = self.dna.rows-1

return self.dna.genes[row*self.dna.cols+column]

並修改run()方法:

def run(self, obstacles):
if (not self.hit_obstacle) and (not self.hit_target):
force = 0.1*self.look_field_up()
self.apply_force(force)
self.update()
self.check_obstacles(obstacles)

self.show()

Population類別的__init__()方法也須修改;完整程式碼如下:

def __init__(self, population_size, mutation_rate, resolution):
self.population_size = population_size
self.mutation_rate = mutation_rate

# 發射場位置
screen = pygame.display.get_surface()
self.width, self.height = screen.get_size()
self.launch_site = pygame.Vector2(self.width//2, self.height+20)

# 流場大小
self.resolution = resolution
self.cols = self.width//self.resolution
self.rows = self.height//self.resolution

# 建立族群
self.population = [None]*self.population_size
for i in range(self.population_size):
dna = DNA(self.rows, self.cols, self.resolution)
self.population[i] = Rocket(self.launch_site.x, self.launch_site.y, dna)

self.generations = 0


留言
avatar-img
ysf的沙龍
32會員
168內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/10/27
這一節要用GA來設計一款具有演化功能的智慧火箭。
Thumbnail
2025/10/27
這一節要用GA來設計一款具有演化功能的智慧火箭。
Thumbnail
2025/09/15
GA的步驟很單純,所以在使用GA解決不同的問題時,基本上程式需要修改的部分很少。事實上,不管是要用GA解決什麼樣的問題,只把三個關鍵部分調整、修改一下就可以了。
2025/09/15
GA的步驟很單純,所以在使用GA解決不同的問題時,基本上程式需要修改的部分很少。事實上,不管是要用GA解決什麼樣的問題,只把三個關鍵部分調整、修改一下就可以了。
看更多
你可能也想看
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 Vaswani 等人 2017 年解決了設計 Transformer 時最困難的 NLP 問題之一,對於我們人機智慧設計師來說,機器翻譯的人類基準似乎遙不可及,然而,這
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 Vaswani 等人 2017 年解決了設計 Transformer 時最困難的 NLP 問題之一,對於我們人機智慧設計師來說,機器翻譯的人類基準似乎遙不可及,然而,這
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 如 AI說書 - 從0開始 - 78 所述,經過 AI說書 - 從0開始 - 74 到目前為止的實驗,應可以漸漸感受到 Transformer 模型如何從數學層面漸漸往
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 如 AI說書 - 從0開始 - 78 所述,經過 AI說書 - 從0開始 - 74 到目前為止的實驗,應可以漸漸感受到 Transformer 模型如何從數學層面漸漸往
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 在AI說書 - 從0開始 - 41中,我們提及 Transformer 的 Encoder 架構如下圖所示,同時我們羅列幾個要點於圖示右邊: 原始 Transform
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 在AI說書 - 從0開始 - 41中,我們提及 Transformer 的 Encoder 架構如下圖所示,同時我們羅列幾個要點於圖示右邊: 原始 Transform
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 Transformer 可以透過繼承預訓練模型 (Pretrained Model) 來微調 (Fine-Tune) 以執行下游任務。 Pretrained Mo
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 Transformer 可以透過繼承預訓練模型 (Pretrained Model) 來微調 (Fine-Tune) 以執行下游任務。 Pretrained Mo
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 先做個總回顧: Transformer 架構總覽:AI說書 - 從0開始 - 39 Attention 意圖說明:AI說書 - 從0開始 - 40 Transfo
Thumbnail
我想要一天分享一點「LLM從底層堆疊的技術」,並且每篇文章長度控制在三分鐘以內,讓大家不會壓力太大,但是又能夠每天成長一點。 先做個總回顧: Transformer 架構總覽:AI說書 - 從0開始 - 39 Attention 意圖說明:AI說書 - 從0開始 - 40 Transfo
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
這是一篇描述測試AI功能的文章,內容是一些隨心所欲的想法和想像,引導讀者思考現實世界及經歷。文章內容充滿了一些具有戲劇性和冒險色彩的詞彙和描述。
Thumbnail
這是一篇描述測試AI功能的文章,內容是一些隨心所欲的想法和想像,引導讀者思考現實世界及經歷。文章內容充滿了一些具有戲劇性和冒險色彩的詞彙和描述。
Thumbnail
Ae 小技巧:Orbit 環繞效果 + 物件面對鏡頭 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
Ae 小技巧:Orbit 環繞效果 + 物件面對鏡頭 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News