是AI就躲個飛機-純Python實現人工智慧
阿新 • • 發佈:2019-01-22
你要的答案或許都在這裡:小鵬的部落格目錄
程式碼下載:Here。
很久以前微信流行過一個小遊戲:打飛機,這個遊戲簡單又無聊。在2017年來臨之際,我就實現一個超級弱智的人工智慧(AI),這貨可以躲避從螢幕上方飛來的飛機。本帖只使用純Python實現,不依賴任何高階庫。
本文的AI基於neuro-evolution,首先簡單科普一下neuro-evolution。從neuro-evolution這個名字就可以看出它由兩部分組成-neuro and evolution,它是使用進化演算法(遺傳演算法是進化演算法的一種)提升人工神經網路的機器學習技術,其實就是用進化演算法改進並選出最優的神經網路。
neuro-evolution
定義一些變數:
import math
import random
# 神經網路3層, 1個隱藏層; 4個input和1個output
network = [4, [16], 1]
# 遺傳演算法相關
population = 50
elitism = 0.2
random_behaviour = 0.1
mutation_rate = 0.5
mutation_range = 2
historic = 0
low_historic = False
score_sort = -1
n_child = 1
定義神經網路:
遺傳演算法:# 啟用函式 def sigmoid(z): return 1.0/(1.0+math.exp(-z)) # random number def random_clamped(): return random.random()*2-1 # "神經元" class Neuron(): def __init__(self): self.biase = 0 self.weights = [] def init_weights(self, n): self.weights = [] for i in range(n): self.weights.append(random_clamped()) def __repr__(self): return 'Neuron weight size:{} biase value:{}'.format(len(self.weights), self.biase) # 層 class Layer(): def __init__(self, index): self.index = index self.neurons = [] def init_neurons(self, n_neuron, n_input): self.neurons = [] for i in range(n_neuron): neuron = Neuron() neuron.init_weights(n_input) self.neurons.append(neuron) def __repr__(self): return 'Layer ID:{} Layer neuron size:{}'.format(self.index, len(self.neurons)) # 神經網路 class NeuroNetwork(): def __init__(self): self.layers = [] # input:輸入層神經元數 hiddens:隱藏層 output:輸出層神經元數 def init_neuro_network(self, input, hiddens , output): index = 0 previous_neurons = 0 # input layer = Layer(index) layer.init_neurons(input, previous_neurons) previous_neurons = input self.layers.append(layer) index += 1 # hiddens for i in range(len(hiddens)): layer = Layer(index) layer.init_neurons(hiddens[i], previous_neurons) previous_neurons = hiddens[i] self.layers.append(layer) index += 1 # output layer = Layer(index) layer.init_neurons(output, previous_neurons) self.layers.append(layer) def get_weights(self): data = { 'network':[], 'weights':[] } for layer in self.layers: data['network'].append(len(layer.neurons)) for neuron in layer.neurons: for weight in neuron.weights: data['weights'].append(weight) return data def set_weights(self, data): previous_neurons = 0 index = 0 index_weights = 0 self.layers = [] for i in data['network']: layer = Layer(index) layer.init_neurons(i, previous_neurons) for j in range(len(layer.neurons)): for k in range(len(layer.neurons[j].weights)): layer.neurons[j].weights[k] = data['weights'][index_weights] index_weights += 1 previous_neurons = i index += 1 self.layers.append(layer) # 輸入遊戲環境中的一些條件(如敵機位置), 返回要執行的操作 def feed_forward(self, inputs): for i in range(len(inputs)): self.layers[0].neurons[i].biase = inputs[i] prev_layer = self.layers[0] for i in range(len(self.layers)): # 第一層沒有weights if i == 0: continue for j in range(len(self.layers[i].neurons)): sum = 0 for k in range(len(prev_layer.neurons)): sum += prev_layer.neurons[k].biase * self.layers[i].neurons[j].weights[k] self.layers[i].neurons[j].biase = sigmoid(sum) prev_layer = self.layers[i] out = [] last_layer = self.layers[-1] for i in range(len(last_layer.neurons)): out.append(last_layer.neurons[i].biase) return out def print_info(self): for layer in self.layers: print(layer)
# "基因組" class Genome(): def __init__(self, score, network_weights): self.score = score self.network_weights = network_weights class Generation(): def __init__(self): self.genomes = [] def add_genome(self, genome): i = 0 for i in range(len(self.genomes)): if score_sort < 0: if genome.score > self.genomes[i].score: break else: if genome.score < self.genomes[i].score: break self.genomes.insert(i, genome) # 雜交+突變 def breed(self, genome1, genome2, n_child): datas = [] for n in range(n_child): data = genome1 for i in range(len(genome2.network_weights['weights'])): if random.random() <= 0.5: data.network_weights['weights'][i] = genome2.network_weights['weights'][i] for i in range(len(data.network_weights['weights'])): if random.random() <= mutation_rate: data.network_weights['weights'][i] += random.random() * mutation_range * 2 - mutation_range datas.append(data) return datas # 生成下一代 def generate_next_generation(self): nexts = [] for i in range(round(elitism*population)): if len(nexts) < population: nexts.append(self.genomes[i].network_weights) for i in range(round(random_behaviour*population)): n = self.genomes[0].network_weights for k in range(len(n['weights'])): n['weights'][k] = random_clamped() if len(nexts) < population: nexts.append(n) max_n = 0 while True: for i in range(max_n): childs = self.breed(self.genomes[i], self.genomes[max_n], n_child if n_child > 0 else 1) for c in range(len(childs)): nexts.append(childs[c].network_weights) if len(nexts) >= population: return nexts max_n += 1 if max_n >= len(self.genomes)-1: max_n = 0
NeuroEvolution:
class Generations():
def __init__(self):
self.generations = []
def first_generation(self):
out = []
for i in range(population):
nn = NeuroNetwork()
nn.init_neuro_network(network[0], network[1], network[2])
out.append(nn.get_weights())
self.generations.append(Generation())
return out
def next_generation(self):
if len(self.generations) == 0:
return False
gen = self.generations[-1].generate_next_generation()
self.generations.append(Generation())
return gen
def add_genome(self, genome):
if len(self.generations) == 0:
return False
return self.generations[-1].add_genome(genome)
class NeuroEvolution():
def __init__(self):
self.generations = Generations()
def restart(self):
self.generations = Generations()
def next_generation(self):
networks = []
if len(self.generations.generations) == 0:
networks = self.generations.first_generation()
else:
networks = self.generations.next_generation()
nn = []
for i in range(len(networks)):
n = NeuroNetwork()
n.set_weights(networks[i])
nn.append(n)
if low_historic:
if len(self.generations.generations) >= 2:
genomes = self.generations.generations[len(self.generations.generations) - 2].genomes
for i in range(genomes):
genomes[i].network = None
if historic != -1:
if len(self.generations.generations) > historic+1:
del self.generations.generations[0:len(self.generations.generations)-(historic+1)]
return nn
def network_score(self, score, network):
self.generations.add_genome(Genome(score, network.get_weights()))
是AI就躲個飛機
import pygame
import sys
from pygame.locals import *
import random
import math
import neuro_evolution
BACKGROUND = (200, 200, 200)
SCREEN_SIZE = (320, 480)
class Plane():
def __init__(self, plane_image):
self.plane_image = plane_image
self.rect = plane_image.get_rect()
self.width = self.rect[2]
self.height = self.rect[3]
self.x = SCREEN_SIZE[0]/2 - self.width/2
self.y = SCREEN_SIZE[1] - self.height
self.move_x = 0
self.speed = 2
self.alive = True
def update(self):
self.x += self.move_x * self.speed
def draw(self, screen):
screen.blit(self.plane_image, (self.x, self.y, self.width, self.height))
def is_dead(self, enemes):
if self.x < -self.width or self.x + self.width > SCREEN_SIZE[0]+self.width:
return True
for eneme in enemes:
if self.collision(eneme):
return True
return False
def collision(self, eneme):
if not (self.x > eneme.x + eneme.width or self.x + self.width < eneme.x or self.y > eneme.y + eneme.height or self.y + self.height < eneme.y):
return True
else:
return False
def get_inputs_values(self, enemes, input_size=4):
inputs = []
for i in range(input_size):
inputs.append(0.0)
inputs[0] = (self.x*1.0 / SCREEN_SIZE[0])
index = 1
for eneme in enemes:
inputs[index] = eneme.x*1.0 / SCREEN_SIZE[0]
index += 1
inputs[index] = eneme.y*1.0 / SCREEN_SIZE[1]
index += 1
#if len(enemes) > 0:
#distance = math.sqrt(math.pow(enemes[0].x + enemes[0].width/2 - self.x + self.width/2, 2) + math.pow(enemes[0].y + enemes[0].height/2 - self.y + self.height/2, 2));
if len(enemes) > 0 and self.x < enemes[0].x:
inputs[index] = -1.0
index += 1
else:
inputs[index] = 1.0
return inputs
class Enemy():
def __init__(self, enemy_image):
self.enemy_image = enemy_image
self.rect = enemy_image.get_rect()
self.width = self.rect[2]
self.height = self.rect[3]
self.x = random.choice(range(0, int(SCREEN_SIZE[0] - self.width/2), 71))
self.y = 0
def update(self):
self.y += 6
def draw(self, screen):
screen.blit(self.enemy_image, (self.x, self.y, self.width, self.height))
def is_out(self):
return True if self.y >= SCREEN_SIZE[1] else False
class Game():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE)
self.clock = pygame.time.Clock()
pygame.display.set_caption('是AI就躲個飛機')
self.ai = neuro_evolution.NeuroEvolution()
self.generation = 0
self.max_enemes = 1
# 載入飛機、敵機圖片
self.plane_image = pygame.image.load('plane.png').convert_alpha()
self.enemy_image = pygame.image.load('enemy.png').convert_alpha()
def start(self):
self.score = 0
self.planes = []
self.enemes = []
self.gen = self.ai.next_generation()
for i in range(len(self.gen)):
plane = Plane(self.plane_image)
self.planes.append(plane)
self.generation += 1
self.alives = len(self.planes)
def update(self, screen):
for i in range(len(self.planes)):
if self.planes[i].alive:
inputs = self.planes[i].get_inputs_values(self.enemes)
res = self.gen[i].feed_forward(inputs)
if res[0] < 0.45:
self.planes[i].move_x = -1
elif res[0] > 0.55:
self.planes[i].move_x = 1
self.planes[i].update()
self.planes[i].draw(screen)
if self.planes[i].is_dead(self.enemes) == True:
self.planes[i].alive = False
self.alives -= 1
self.ai.network_score(self.score, self.gen[i])
if self.is_ai_all_dead():
self.start()
self.gen_enemes()
for i in range(len(self.enemes)):
self.enemes[i].update()
self.enemes[i].draw(screen)
if self.enemes[i].is_out():
del self.enemes[i]
break
self.score += 1
print("alive:{}, generation:{}, score:{}".format(self.alives, self.generation, self.score), end='\r')
def run(self, FPS=1000):
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
self.screen.fill(BACKGROUND)
self.update(self.screen)
pygame.display.update()
self.clock.tick(FPS)
def gen_enemes(self):
if len(self.enemes) < self.max_enemes:
enemy = Enemy(self.enemy_image)
self.enemes.append(enemy)
def is_ai_all_dead(self):
for plane in self.planes:
if plane.alive:
return False
return True
game = Game()
game.start()
game.run()
AI的工作邏輯
假設你是AI,你首先繁殖一個種群(50個個體),開始的個體大都是歪瓜裂棗(上來就被敵機撞)。但是,即使是歪瓜裂棗也有表現好的,在下一代,你會使用這些表現好的再繁殖一個種群,經過代代相傳,存活下來的個體會越來越優秀。其實就是仿達爾文進化論,種群->自然選擇->優秀個體->雜交、變異->種群->迴圈n世代。ai開始時候的表現:
經過幾百代之後,ai開始娛樂的躲飛機: