【AI + pygame】pygameで作るインベーダー風ゲーム 第7回 設定改善編

(2019年5月22日更新)
エンジニアのMです。
pygame によるインベーダー風ゲームの改造記事の第7回です。
ネタがあったので続きました。
前回上手く動かなかった部分について改善できたので、どこを修正したのかについて書いていきたいと思います。
今回のコードはこちらからダウンロードできます。
-> pygame_invader_7.zip
修正点
以下の3点について修正しました。
- ビーム(敵弾)情報の取得範囲を拡大
- ミサイル(自機弾)とリロード時間の情報を省略
- それぞれの値の範囲を「0」を通らないように修正
上2つの影響は小さいと思います。
性能が向上したという結果があるので有効だったということにしていますが、ケースバイケースでしょう。
おそらく重要だったのは下の太字にした修正です。
自機との相対座標で設定しているパラメータがいくつかあったのですが、その仕様上「0」になることがあります。
値が「0」を通ることそれ自体は問題ないのですが、撃破されたエイリアンのパラメータにも「0」を使っていたのが最大の問題だったと考えています。
入力無しとしての「0」と、値としての「0」を混同した結果、学習が上手くいかなくなったものと思われます。
当時何気なく設定したパラメータですが、学習に使うデータの中身である以上、もっと丁寧に決めるべきでした。
今後に活かしたいと思います。
該当部分のコード
class Invader: def _get_observation(self): if self.image_flag: resize_x = 160 resize_y = 120 cut_y_rate = 0.06 pilImg = Image.fromarray(pygame.surfarray.array3d(self.screen)) resizedImg = pilImg.resize((resize_x, resize_y), Image.LANCZOS) self.observation_space = np.asarray(resizedImg)[:][int(resize_y*cut_y_rate):] return None observation_list = list() for index, alien in enumerate(self.alien_list): if alien.alive: observation_list.append(alien.rect.center[0]/640) observation_list.append(alien.rect.center[1]/480) observation_list.append(alien.speed/2) else: observation_list.extend([0, 0, 0]) observation_list.append(len(self.alien_list)) # ビーム位置情報 beam_check_range = np.array([128, 128, 256, 32]) # L,R,U,D(学習に使う範囲を指定) margin = 120 # ビーム位置用行列の余白 compressed_rate = 5 # ビーム位置行列の圧縮 comp_beam_check_range = beam_check_range // compressed_rate beam_pos_all = np.zeros(((640+ margin*2)//compressed_rate , (480+ margin*2)//compressed_rate), dtype=int) for index, beam in enumerate(self.beams): beam_pos_all[(beam.rect.center[0]+margin)//compressed_rate]\ [(beam.rect.center[1]+margin)//compressed_rate] = 1 comp_x_pos = (self.player.rect.center[0] + margin) // compressed_rate comp_y_pos = (self.player.rect.center[1] + margin) // compressed_rate beam_pos = beam_pos_all[ comp_x_pos - comp_beam_check_range[0]:comp_x_pos + comp_beam_check_range[1], comp_y_pos - comp_beam_check_range[2]:comp_y_pos + comp_beam_check_range[3] ] observation_list.extend(beam_pos.flatten()) ''' for index, shot in enumerate(self.shots): observation_list.append(shot.rect.center[0]/640) observation_list.append(shot.rect.center[1]/480) observation_list.extend([0 for _ in range(8- len(self.shots)*2)]) ''' for ufo in self.ufos: observation_list.append(ufo.rect.center[0]/640) observation_list.append(ufo.rect.center[1]/480) observation_list.append(ufo.speed) observation_list.extend([0 for _ in range(3 - len(self.ufos) * 3)]) observation_list.append(self.player.rect.center[0]/640) observation_list.append(self.player.rect.center[1]/480) #observation_list.append(self.player.reload_timer/15) self.observation_space = np.array(observation_list, dtype=float) pass
修正後の結果
修正後のパラメータを使って、隠れ層のサイズを 1024 、層数を 3 として 200,000 steps 学習させました。
何度か一から学習をさせて、上手く行った回で 50 ~ 70% 程度のクリア率となりました。
上々の結果と言って良いでしょう。
テスト時の動画を載せておきます。
テスト中は1stepあたりの処理が早いので、2倍速程度のプレイ速度になっています。
まとめ
機械学習において、少しの違いが大きな違いを生む例でした。
モデルに入れるデータの仕様について考えるのは重要なことなのだと、改めて認識しました。
皆さんも機械学習を試す際には、お気を付け下さい。
これが Azure や AWS の仮想マシンだったら、数千円くらい余計な出費が発生していたことでしょう。