[英]Genetic algorithm /w Neural Network playing snake is not improving
我正在嘗試創建一個遺傳算法來訓練神經網絡,目標是玩蛇游戲。
我遇到的問題是,幾代人的適應度沒有提高,它要么停留在不給游戲提供任何輸入所期望的適應度上,要么在第一代之后變得更糟。 我懷疑這是神經網絡的問題,但我不知道它是什么。
24 個輸入節點
2隱藏層
每層8 個節點
4 個輸出節點(蛇可以采取的每個方向一個)
輸入是蛇可以看到的每個方向的數組。 對於每個方向,它都會檢查到牆壁、水果或自身的距離有多遠。 最終結果是一個長度為3*8 = 24
的數組。
權重和偏差是 -1 和 1 之間的隨機浮點數,在創建網絡時生成。
人口規模:50000
每代選擇的父母:1000
每代保持領先:25000(新變量,看到更好的結果)
每個孩子的突變幾率:5%
(我嘗試了許多不同的尺寸比例,盡管我仍然不確定典型的比例是什么。)
我正在使用單點交叉。 每個權重和偏差數組在父母之間交叉,並傳遞給孩子(每個“版本”交叉一個孩子)。
我正在使用我認為是輪盤賭選擇來選擇父母,我將在下面發布確切的方法。
一條蛇的適應度計算公式為: age * 2**score
(不再是,更多信息更新中),其中年齡是蛇存活了多少圈,分數是它收集的水果數量。
下面是一些偽代碼,試圖總結我的遺傳算法(應該)如何工作:
pop = Population(size=1000)
while True: # Have yet to implement a 'converged' check
pop.calc_fitness()
new_pop = []
for i in range(n_parents):
parent1 = pop.fitness_based_selection()
parent2 = pop.fitness_based_selection()
child_snake1, child_snake2 = parent1.crossover(parent2)
if rand() <= mutate_chance:
child_snake.mutate()
new_pop.append(child_snake1, child_snake2)
pop.population = new_pop
print(generation_statistics)
gen += 1
這是我用來選擇父級的方法:
def fitness_based_selection(self):
"""
A slection process that chooses a snake, where a snake with a higher fitness has a higher chance of being
selected
:return: The chosen snake's brain
"""
sum_fitnesses = sum(list([snake[1] for snake in self.population]))
# A random cutoff digit.
r = randint(0, sum_fitnesses)
current_sum = 0
for snake in self.population:
current_sum += snake[1]
if current_sum > r:
# Return brain of chosen snake
return snake[0]
值得注意的是, self.population
是一個self.population
的列表,其中每條snake 都是一個包含控制它的NeuralNet 以及網絡實現的適應度的列表。
這是從游戲輸出中獲取網絡輸出的方法,因為我懷疑我在這里做錯了什么:
def get_output(self, input_array: np.ndarray):
"""
Get output from input by feed forwarding it through the network
:param input_array: The input to get an output from, should be an array of the inputs
:return: an output array with 4 values of the shape 1x4
"""
# Add biases then multiply by weights, input => h_layer_1, this is done opposite because the input can be zero
h_layer_1_b = input_array + self.biases_input_hidden1
h_layer_1_w = np.dot(h_layer_1_b, self.weights_input_hidden1)
h_layer_1 = self.sigmoid(h_layer_1_w) # Run the output through a sigmoid function
# Multiply by weights then add biases, h_layer_1 => h_layer_2
h_layer_2_w = np.dot(h_layer_1, self.weights_hidden1_hidden2)
h_layer_2_b = h_layer_2_w + self.biases_hidden1_hidden2
h_layer_2 = self.sigmoid(h_layer_2_b)
# Multiply by weights then add biases, h_layer_2 => output
output_w = np.dot(h_layer_2, self.weights_hidden2_output)
output_b = output_w + self.biases_hidden2_output
output = self.sigmoid(output_b)
return output
當手動運行神經網絡並啟用游戲的圖形版本時,很明顯網絡幾乎不會多次改變方向。 這讓我很困惑,因為我的印象是,如果所有的權重和偏差都是隨機生成的,那么輸入將被隨機處理並給出隨機輸出,而輸出似乎在游戲的第一回合改變一次,然后永遠不會改變再次發生重大變化。
在運行遺傳算法時,每一代的最高適應度幾乎不會超過沒有輸入的蛇的適應度(在這種情況下為 16),我認為這與神經網絡的問題有關。 當它確實超過時,下一代將再次恢復為 16。
對他的問題的任何幫助將不勝感激,我還是這個領域的新手,我發現它真的很有趣。 如果需要,我很樂意回答更多細節。 我的完整代碼可以在這里找到,如果有人敢深入研究的話。
更新:
我改變了幾件事:
現在算法性能更好,第一代通常會找到一條適應度為 14-16 的蛇,這意味着蛇確實會轉彎以避免死亡,但它幾乎總是從那里走下坡路。 當靠近東邊和北/南邊時,第一條蛇實際上已經實現了轉彎的策略,但永遠不會到達西邊。 在第一代之后,適應度只會變得更糟,最終回到可能的最低適應度。 我對出了什么問題感到茫然,但我有一種感覺,這可能是我忽略的大問題。
更新#2:
我想我不妨提一些我嘗試過但沒有用的東西:
更新 #3:
我改變了一些事情並開始看到更好的結果。 首先,我停止了水果的產卵以簡化學習過程,而是給了蛇一個與其年齡相等的適應度(它們存活了多少圈/幀),在關閉輸入數組的歸一化后,我得到了一條蛇健身300! 300 是蛇在老年死亡之前可以擁有的最大年齡。
然而問題仍然存在,在前幾代之后,適應度會直線下降,前 1-5 代可能有 300 的適應度(有時他們沒有,而是適應度低,但我認為這是下降的到人口規模。),但在那之后,幾代人的適應度將下降到 ~20-30 並保持在那里。
此外,如果我重新打開水果,蛇會再次獲得糟糕的適應度。 有時第一代會實現一條能夠循環移動的蛇,因此在不拿起任何水果的情況下獲得 300 的適應度,但這幾乎從未轉移到下一個一代。
我注意到在你的偽代碼中,在創建每一代時,父代被完全消滅,只有子代被保留。 這自然會導致適應度水平降低,因為無法保證后代的適應度水平可與父代相媲美。 為了確保適應度水平不降低,您必須合並父代和子代並修剪最弱的成員(我建議這樣做),或者您可以要求后代生成函數至少產生適合的后代作為父母(通過多次嘗試和錯誤)。
如果您決定專注於后代生成器,(在某種程度上)保證改進后代的一種方法是通過簡單地向每個權重向量添加少量噪聲來實現無性繁殖。 如果噪音水平足夠小,您可以以高達 50% 的成功率生成改進的后代。 然而,更大的噪音水平允許更快的改進,並且它們有助於跳出局部最優,即使它們的成功率低於 50%。
你只變異了 5% 的人口,而不是 5% 的“基因組”。 這意味着您的人口將被快速修復 - https://en.wikipedia.org/wiki/Fixation_(population_genetics) 。
這是人口表現不佳的原因,因為您只是在探索健身景觀的一小部分區域 ( https://en.wikipedia.org/wiki/Fitness_landscape )。
您應該更改 mutate 函數以突變 5% 的基因組(即節點之間的權重)。 也可以隨意使用突變率——不同的問題在不同的突變率下表現更好。
如果您擔心丟失當前的“最佳基因組”,進化計算中的一種典型方法是將適應度最高的個體復制到下一代而不發生突變。
(對不起,這可能應該是評論,但我沒有足夠的聲譽)。
我有一個星期的確切問題,我才剛剛弄清楚問題是什么。 將一條蛇的大腦分配給另一條蛇時,請確保使用 array.copy。 例如這樣做:
a = np.array([1, 2, 3, 4])
b = a.copy()
而不是這個:
a = np.array([1, 2, 3, 4])
b = a
這是因為在第二種情況下,python 有時會使 b 和 a 共享相同的內存,因此每當您重新分配 b 時,a 也將被重新分配。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.