ゲームヘビをプレイすることを目的として、ニューラルネットワークをトレーニングする遺伝的アルゴリズムを作成しようとしています。
私が抱えている問題は、世代のフィットネスが改善されていない、ゲームに入力を与えないことから期待できるフィットネスにとどまっている、または最初の世代の後になって悪化することです。私はそれがニューラルネットワークの問題であると思いますが、それが何であるかについて私は途方に暮れています。
24入力ノード
2非表示のレイヤー
8レイヤーあたりのノード
4出力ノード(蛇が取ることができる各方向に1つ)
入力は、ヘビが見ることができるあらゆる方向の配列です。各方向について、壁、果物、またはそれ自体のいずれかまでの距離がチェックされます。最終結果は、長さが3*8 = 24
の配列です。
重みとバイアスは、ネットワークの作成時に生成される、-1〜1のランダムな浮動小数点数です。
人口:50000
世代ごとに選択された親:1000
世代ごとに上位を維持:25000(新しい変数、より良い結果が表示されます)
子供あたりの突然変異の確率:5%
(私は多くの異なるサイズの比率を試しましたが、典型的な比率が何であるかはまだわかりません。)
シングルポイントクロスオーバーを使用しています。重みとバイアスのすべての配列は、親の間で交差され、子に渡されます(クロスオーバーの「バージョン」ごとに1つの子)。
私はルーレットの選択だと思うものを使用して親を選択しています。以下に正確な方法を投稿します。
ヘビの適応度は次のように計算されます: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
がヘビのリストであることは注目に値します。各ヘビは、それを制御する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
ニューラルネットワークを手動で実行し、ゲームのグラフィカルバージョンを有効にすると、ネットワークが方向を2回以上変更することはほとんどありません。これは、すべての重みとバイアスがランダムに生成される場合、入力はランダムに処理され、ランダムな出力が得られるという印象を受けたため、混乱します。代わりに、ゲームの最初のターンに出力が1回変化し、その後決して再び大幅に変更します。
遺伝的アルゴリズムを実行するとき、各世代の最高の適合度は、入力なしのヘビから予想される適合度(この場合は16)をほとんど超えません。これは、ニューラルネットワークの問題と相関していると思います。それを超えると、次世代は再び16に戻ります。
彼の問題についての助けは非常にありがたいです、私はまだこの分野に不慣れで、私はそれが本当に興味深いと感じています。詳細については、必要に応じて喜んでお答えいたします。私の完全なコードは here で見つけることができます。
更新:
私はいくつか変更しました:
これでアルゴリズムのパフォーマンスが向上します。最初の世代は通常、14-16の適応度を持つヘビを見つけます。つまり、ヘビは死を回避するために順番を回しますが、ほとんど常にそこから下り坂になります。最初のヘビは、実際には東端と北端/南端の近くで曲がる戦術を達成していますが、西端では決してありません。最初の世代の後、フィットネスは悪化する傾向にあり、最終的には可能な限り低いフィットネスに戻ります。何がうまくいかないのか途方に暮れていますが、見過ごされてきた大きなものかもしれません。
更新#2:
私が試したいくつかのことにも言及するかもしれないと考えましたうまくいきませんでした:
更新#3:
私はいくつかの点を変更し、より良い結果が得られるようになりました。まず、果物を産むのを止めて学習プロセスを簡略化し、代わりにヘビに彼らの年齢(彼らが生き残ったターン/フレームの数)に等しいフィットネスを与え、入力配列の正規化をオフにした後、 300のフィットネス! 300は、ヘビが高齢になる前の最大年齢です。
ただし、問題はまだ最初の数世代後にフィットネスが急落し、最初の1〜5世代が300のフィットネスを持っている可能性があるという点でまだ存在します(そうでない場合があり、代わりに低いフィットネスを持っていますが、これはダウンしていると思います)人口サイズまで)。しかし、その後、世代の適性は20〜30に落ち込み、そこにとどまります。
また、果物を再びオンにすると、ヘビは再び最悪のフィットネスを獲得します.1世代目は、ループで移動できるヘビを実現し、果物を拾わずに300のフィットネスを獲得することがありますが、これはほとんど次の世代に転送されません世代。
あなたの疑似コードでは、新しい世代を作成するたびに、親世代が完全に消去され、子世代のみが保持されていることに気付きました。子孫が親世代と同等のフィットネスレベルを持つことを保証するものは何もないので、これは当然、フィットネスレベルの低下につながる可能性があります。フィットネスレベルが減少していないことを確認するには、親と子の世代をマージし、最も弱いメンバーをプルーニングする(推奨します)か、または子孫生成関数が子孫を少なくとも適切に生成するように要求できます。両親として(多くの試行錯誤により)。
子孫ジェネレーターに焦点を合わせることにした場合、子孫の改善を(ある程度)保証する1つの方法は、各重みベクトルに少量のノイズを追加するだけで無性生殖を実装することです。ノイズレベルが十分に小さい場合、最大50%の成功率で改善された子孫を生成できます。ただし、ノイズレベルが大きいほど、改善が速くなり、成功率が50%未満であっても、ローカルオプティマから飛び出すことができます。
変異するのは人口の5%だけで、「ゲノム」の5%は変異しません。これは、人口が信じられないほど迅速に修正されることを意味します- https://en.wikipedia.org/wiki/Fixation_(population_genetics) 。
これは、フィットネスの風景の非常に小さな領域( https://en.wikipedia.org/wiki/Fitness_landscape )のみを調査しているため、人口があまりうまくいっていない理由です。
突然変異関数を変更して、ゲノムの5%を突然変異させる必要があります(つまり、ノード間の重み)。突然変異率も自由に試してみてください。異なる突然変異率では、さまざまな問題のパフォーマンスが向上します。
現在の「最良のゲノム」を失うことを心配している場合、進化的計算の典型的なアプローチは、最高の適応度を持つ個体を突然変異なしで次世代にコピーすることです。
(申し訳ありませんが、これはおそらくコメントでしたが、十分な評判がありません)。