先に言っておくと、やろうと思っていたことが全くできておりません・・・
やろうとしていたことと経緯・進捗だけ書かせて頂きます。
とても悔しい。
やりたかったこと
・OpenAIGymを用いたポケモンの対戦環境構築
・強化学習を用いて強い立ち回りを学習
・実機での対戦に活かす
できたこと
・OpenAIGymのサンプルコードを流してイメージを掴む
・Q学習の考え方理解
・先駆者様の構築した環境の理解(https://github.com/select766/pokeai)
・用意したい対戦環境構築のイメージ ← ここに関する現状について記載させて頂く
背景とモチベーション
私は赤・緑世代からウルトラサンムーンまで全作プレイ済のポケモンフリークでして、レーティングバトルや対戦オフにのめり込んでいた程度には対戦ガチ勢を自負しています。
しかし頑張ってはいるものの対戦に勝てないので、強化学習のアプローチで勝利の期待値が最大になるような立ち回りができないものか、という話をずーっと考えていました。
事前準備が面倒で敬遠していたのですが、今回AdventCalenderに何か書こうという事になったので、重い腰をあげて取り組む事にしました。
ポケモンの対戦は、
・パラメータ計算式やダメージ計算式が定式化されている
・ターン制なので状態が離散値で表現できる
・取りうる行動は「4種の技から選ぶ or 控えの2匹と交代」の6パターン
と、とっつきやすい条件が揃っているため、強化学習の実装未経験、勉強はこれから始める自分でもなんとかなるのではと思っていました(甘い考えだったことにすぐに気付く)。
想定している対戦環境
今回はウルトラサンムーン環境におけるレベル50フラットのシングル63ルールを想定しています(6匹ずつ見せ合いをした上で3匹を選んでバトル)。
対戦のおおまかな流れとしては、以下のようなイメージです。
- 6匹で構成されたパーティを構築する(努力値だの性格だの細かい話は省略)
- 相手の6匹の情報が与えられた上で対戦に使う3匹を選ぶ
- 選択した3匹で戦う
- 3匹倒した方が勝ち
今回は3匹選択するところまでは人手で決めて「戦う」の部分を強化学習で上手にできないかを検討したかったのですが、問題の簡単化のためにさらに簡易的な状態になるよう設定しています(後述)
自前の環境を用意するにあたって
下記ページを参考にして方向性を検討しています。転記になりますが、最低限用意するものは以下になります。
https://qiita.com/ohtaman/items/edcb3b0a2ff9d48a7def
メソッド | 解説 |
---|---|
_step(self, action) | action を実行し、結果を返す |
_reset(self) | 状態を初期化し、初期の観測値を返す |
_render(self, mode='human', close=False) | 環境を可視化する |
プロパティ | 解説 |
---|---|
action_space | 行動(Action)の張る空間 |
observation_space | 観測値(Observation)の張る空間 |
reward_range | 報酬の最小値と最大値のリスト |
これらを満たすためにどうすべきか諸々検討したのですが、
様々な要件や急所などランダム要素を考慮して設計しようとすると一向に前に進まなかったため、極限まで状態をシンプルに落とし込んで、後から詳細化していこうと考えています。
ですので、
・そもそも3-3の対戦ではなく、まず1-1で殴り合う挙動が正しく動作するものを作る
・技は攻撃技4種、命中率100の技のみ、補助技一切無し
・技の追加効果、状態異常も割愛
・特性や持ち物も割愛
・当然メガ進化やZ技も割愛
のような形でまず動くものを作ることを優先することにします。
これを踏まえ各メソッド・プロパティは以下のような方針で記載しようと考えています。
_step(self, action)の中に先行、後攻の判定やシンプルなダメージ計算、HP管理、rewardの与え方等々記載。シンプル化したとは言えここが1番大変そう。
_resetは場に出すポケモンのHPを回復させてやればOK。
_renderは一旦後回し。。。
action_space: 技が4つなので4。交代の2は追々追加する。
observation_space: 今回自分と相手のHPのみ観測できる形でプロトタイプを作成。
reward_range: -1 ~ 100 くらいで適当に設定。与えたダメージに応じた小報酬と倒した際の大報酬の2通りの与え方をする。
書いたもの・作ったもの
・ポケモンマスタ:こちらからjsonデータを拝借し加工
make_simple_database.py
import pandas as pd
import numpy as np
import json
import math
### 元データのインポート
f = open("pokemon_data.json", 'r')
data_j = json.load(f)
data_df = pd.DataFrame(data_j)
### 図鑑番号で一意に参照したいので、メガ進化やフォームチェンジをいったん除外
### この仕様でボルトルネなどが図鑑から消失、すまぬ
data_df_rm_MegaEvo = data_df.query("isMegaEvolution == False and form == '' ")
### type情報をparseして2列に持たせ直す
data_df_rm_MegaEvo['type1'] = data_df_rm_MegaEvo['types'].apply(lambda x: x[0])
data_df_rm_MegaEvo['type2'] = data_df_rm_MegaEvo['types'].apply(lambda x: x[1])
### 種族値情報をparseして参照しやすい形にする
data_df_rm_MegaEvo['hp'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['hp'])
data_df_rm_MegaEvo['attack'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['attack'])
data_df_rm_MegaEvo['defence'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['defence'])
data_df_rm_MegaEvo['sp_attack'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['spAttack'])
data_df_rm_MegaEvo['sp_defence'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['spDefence'])
data_df_rm_MegaEvo['speed'] = data_df_rm_MegaEvo['stats'].apply(lambda x : x['speed'])
### 必要なカラムだけ抽出
new_data_df_zukan = data_df_rm_MegaEvo.loc[:,['no','name','type1','type2', 'hp', 'attack', 'defence', 'sp_attack', 'sp_defence', 'speed']]
### タイプを英語表記に変更
replace_dict = {
'ノーマル' : 'NORMAL'
,'ほのお' : 'FIRE'
,'みず' : 'WATER'
,'でんき' : 'ELECTR'
,'くさ' : 'GRASS'
,'こおり' : 'ICE'
,'かくとう' : 'FIGHT'
,'どく' : 'POISON'
, 'じめん' : 'GROUND'
,'ひこう' : 'FLYING'
,'エスパー' : 'PSYCHC'
,'むし' : 'BUG'
,'いわ' : 'ROCK'
,'ゴースト' : 'GHOST'
,'ドラゴン' : 'DRAGON'
,'あく' : 'DARK'
,'はがね' : 'STEEL'
,'フェアリー' : 'FAIRY'}
new_data_df_zukan = new_data_df_zukan.replace(replace_dict)
### 保存
new_data_df_zukan.to_csv('pokemon_zukan_rm_MegaEvo.csv', index=False)
・タイプ相性マスタ、技マスタ:自前で書いてjson.dumpで保存。技についてはいくつかピックアップしたもののみ。
・図鑑番号を入れたら努力値を振ったパラメータを返す
poke_generator.py
import pandas as pd
import math
### battle_poke['h_j'] = h_jの部分でCopyが云々のWarningが出るのでいったん無視する
pd.options.mode.chained_assignment = None
### 図鑑番号を指定して対象のポケモンの種族値やタイプ情報を取得する
### 努力値振りは簡単化のためにHA252,B4 or HC252, D4のみ
### 性格は意地っ張り or 控えめのみ
### ACの種族値が同じ場合は意地HA252に寄せる
### 今後詳細化していくポイント
def generator(no, is_player):
main_df = pd.read_csv('../pokemon_zukan_rm_MegaEvo.csv')
battle_poke = main_df.query("no == @no")
if battle_poke.attack.values[0] >= battle_poke.sp_attack.values[0]:
h_j, a_j, b_j, c_j, d_j, s_j = param_cal(battle_poke['hp'],
battle_poke['attack'],
battle_poke['defence'],
battle_poke['sp_attack'],
battle_poke['sp_defence'],
battle_poke['speed'],
252, 252, 4, 0, 0, 0, 0)
elif battle_poke.attack.values[0] < battle_poke.sp_attack.values[0]:
h_j, a_j, b_j, c_j, d_j, s_j = param_cal(battle_poke['hp'],
battle_poke['attack'],
battle_poke['defence'],
battle_poke['sp_attack'],
battle_poke['sp_defence'],
battle_poke['speed'],
252, 0, 0, 252, 4, 0, 1)
battle_poke['a_j'] = a_j
battle_poke['b_j'] = b_j
battle_poke['c_j'] = c_j
battle_poke['d_j'] = d_j
battle_poke['s_j'] = s_j
battle_poke['max_hp'] = h_j
battle_poke['now_hp'] = h_j
battle_poke['is_player'] = is_player
return battle_poke
def param_cal(h,a,b,c,d,s,h_d,a_d,b_d,c_d,d_d,s_d,personal):
### 本来的には性格に対するパラメータ補正値を辞書とかで持たせておけば良い気もするが、今回そこまで種類を用意しないのでゴリ押し
if personal == 0: ##意地っ張り
a_hosei = 1.1
b_hosei = 1
c_hosei = 0.9
d_hosei = 1
s_hosei = 1
elif personal == 1: ## 控えめ
a_hosei = 0.9
b_hosei = 1
c_hosei = 1.1
d_hosei = 1
s_hosei = 1
elif personal == 2: ##勇敢
a_hosei = 1.1
b_hosei = 1
c_hosei = 1
d_hosei = 1
s_hosei = 0.9
elif personal == 3: ## 冷静
a_hosei = 1
b_hosei = 1
c_hosei = 1.1
d_hosei = 1
s_hosei = 0.9
else: ### その他のケース
a_hosei = 1
b_hosei = 1
c_hosei = 1
d_hosei = 1
s_hosei = 1
h_j = math.floor((h + 31/2 + h_d/8)+60)
a_j = math.floor(((a + 31/2 + a_d/8)+5)*a_hosei)
b_j = math.floor(((b + 31/2 + b_d/8)+5)*b_hosei)
c_j = math.floor(((c + 31/2 + c_d/8)+5)*c_hosei)
d_j = math.floor(((d + 31/2 + d_d/8)+5)*d_hosei)
s_j = math.floor(((s + 31/2 + s_d/8)+5)*s_hosei)
return h_j, a_j, b_j, c_j, d_j, s_j
if __name__ == '__main__':
tmp_return = generator(445,1) ### ガブリアスのパラメータでテスト
print(tmp_return)
# no name type1 type2 hp attack defence sp_attack sp_defence \
#442 445 ガブリアス ドラゴン じめん 108 130 95 80 85
# speed h_j a_j b_j c_j d_j s_j
#442 102 215 200 116 90 105 122
もっとも重要であるgym.env環境についてですが、正直記載できる形までまとまりきっていないため、もう少し詳細を詰めた後コードは公開します、、、
参考にした・参考にしているサイト
OpenAIGymで自前の環境を用意する
自前環境作りのノウハウ。
すばらしきポケモンエコシステム
ポケモンの数値データなどはこちらを参考に。
[【強化学習初心者向け】シンプルな実装例で学ぶQ学習、DQN、DDQN【CartPoleで棒立て:1ファイルで完結、Kearas使用】] (https://qiita.com/sugulu/items/bc7c70e6658f204f85f9)
ゼロからDeepまで学ぶ強化学習
素晴らしき先駆者様
めちゃめちゃ参考にさせて頂きました。ほぼゲーム作ってるなこれって言いながらコード読んでます。
https://github.com/select766/pokeai
https://select766.hatenablog.com/entry/2018/11/30/210157
まとめ
ポケモンの対戦環境をOpenAIGymを用いて構築しようとしたのですが、この投稿日までに間に合わず、中途半端な形になってしまいました。
重い腰は上がったのですが、まだ強化学習にたどり着けてすらいないですし、もちろんこれで終わらせたくはないので引き続き地道に作っていこうと思っています。
コードはgithubにまとめてはいるのですが、もう少し見せるための整理をし終えたタイミングで公開します。