前編のあらすじ
この記事は「Pytorchで自作ゲームにDQNのAIを組み込もう!(前編)」の続きとなります。
前編ではpygameでゲームを作成し、DQNのAIを組み込むためにゲームの環境をAIに入力する仕組みを用意しました。
この記事ではいよいよ、使用するモデルや学習する方法について解説していきます。
AIのモデル作成
AIに入力するデータの準備ができたので、ここからAIの説明に入ります。
モデルの定義
今回のゲームで使用するAIのモデルの定義部のソースコードは以下になります。
import torch.nn as nn
Outputs = 24
class Agent(nn.Module):
def __init__(self):
super().__init__()
self.relu = nn.ReLU()
self.conv1 = nn.Conv2d(12, 8, 3)
self.conv2 = nn.Conv2d(8, 4, 3)
self.pool = nn.MaxPool2d(2, stride=2)
self.fc1 = nn.Linear(1564,32)
self.fc2 = nn.Linear(32, Outputs)
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.pool(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return xPytorchでは、torch.nn.Moduleを継承したクラスを定義することでディープラーニングのモデルを作成します。Pythonではクラスの定義時にclass クラス名(継承したいクラスのクラス名): とすることでクラスを継承できます。モデルの構造や処理はこのクラス内で実装します。続いて、クラス内の関数について説明します。
まずは__init__関数です。__init__関数は他の言語では「コンストラクタ」などと呼ばれるもので、このクラスのインスタンス生成と同時に実行されます。Pytorchでは訓練や推論時に使用する線形変換の層や活性化関数の定義に使用します。それでは、__init__関数の中身について説明します。
super().__init__():super()は継承したクラス(親クラス)を返す関数で、super().__init__()とすることで継承したクラスのコンストラクタを呼ぶことができます。これを書き忘れると継承したクラスで本来必要だった初期化が行われずに処理が進んでしまい、思わぬエラーや不具合の原因になるので忘れずに書きましょう。self.relu = nn.ReLU():Agentクラスのメンバ変数に「活性化関数」を追加しています。活性化関数はニューラルネットワークの各層の間に入り、次の層へ渡す値を調整する役割を持っています。この活性化関数はどんなものでも良いというわけではなく、「非線形(グラフが直線ではない)関数であること」「微分可能な(関数が途切れていたり、尖っている点(尖点)や接線が垂直になるような点を含まない)関数であること」の2つの条件を満たす必要があります。今回使用しているのは入力値が0以下の時は0、それ以上の時は入力値をそのまま返すReLU関数です。ReLU関数はニューラルネットワークの活性化関数としてよく使用される関数で、活性化関数に求められる性質を全て満たしています。self.conv1 = nn.Conv2d(12, 8, 3):入力層(AIへの入力を受け取る層)を定義しています。Conv2dはPytorchが提供する2次元の畳み込みニューラルネットワーク(CNN)です。今回の入力であるゲーム画面のような画像データを扱う時はCNN、と覚えておけば良いかと思います。Conv2dでは、主に3つ引数を指定します。1個目は入力のチャンネル数、2個目はこのCNNを通過後に出力するデータのチャンネル数、3個目はカーネルと呼ばれる畳み込み演算を行う時に使用するフィルタのサイズです。今回の場合、入力される画像データのチャンネル数は1フレームにつき赤・緑・青の各成分が1次元ずつの合計3次元、それが連続4フレーム入力されるので3×4 = 12 チャンネルとなるため、1個目の引数は12となります。一方、2個目の引数である出力チャンネル数に特に縛りはないようです。好きな値を指定しましょう。3個目のカーネルのサイズについても自由に値を決めて大丈夫なようです(カーネルのサイズに1を指定すると処理として意味合いが変わってくるようなので、気になる方は1以外を指定しましょう)。今回は3を指定し、3×3のカーネルで畳み込みを行います。self.conv2 = nn.Conv2d(8, 4, 3):最初の中間層(入力層と出力層の間に入る層)を定義しています。今回は入力層のCNNの出力チャンネル数に8を指定しているので、1個目の引数には8を指定する必要があります(合っていないとエラーになります)。基本的にどんな種類の層を使用する場合でも、「前の層の出力の次元数と次の層の入力の次元数を同じにする」ということさえ分かっていればディープラーニングのモデルを組む時に困ることはないと思います。2個目の引数やカーネルのサイズについては自由な値を入れてOKです。self.pool = nn.MaxPool2d(2, stride=2):「プーリング」と呼ばれる、画像を縮小する処理を行う層です。位置のズレによってAIが同じ物体を「同じ」とみなせなくなるといった事態を防いだり、画像の縮小によって処理を軽量化する役割があります。今回定義しているのはMaxプーリングと呼ばれ、1個目の引数で指定されたサイズの領域内の最大値をその領域の代表値として取り出すことで縮小を行います。今回は2を指定しているので、2×2の領域でMaxプーリングを行います。stride=2はこの領域を何マス飛ばしで行うか(ストライド)の指定で、stride=2は2マスずつ飛ばして領域を作ります。self.fc1 = nn.Linear(1564,32):2番目の中間層であり、全結合層を定義します。前のCNNの層で学習した結果を受け取り、最後の出力層に渡します。1個目の引数はこの層に入力されるデータの次元数、2個目の引数は出力するデータの次元数です。先ほどと同様、1個目の引数の次元数を前の層の出力の次元数と揃える必要があるのですが、「CNN層と全結合層で種類が違うけど、どうやって揃えればいいの?」と思った方もいらっしゃるかもしれません。これについては公式が存在しており計算でも求められますが、公式を使わなくても入力次元数の指定が誤っていた場合には「mat1 and mat2 shapes cannot be multiplied (A × B and C × D)(A、B、C、Dの位置に入る実際の数値は条件によって異なります)」というエラーが出るため、このエラーメッセージ内のBの数値を1個目の引数に指定すれば大丈夫です(モデルによっては内部処理の微妙な違いなどで入ってくる次元数が変わってくることもあるため、実際にエラーメッセージから出力された値を使ったほうが確実な印象です)。self.fc2 = nn.Linear(32, Outputs):出力層(AIの実行結果を出力する層)の全結合層です。今回のAIの入力はゲーム画面の画像データですが、必要な出力はAIの行動であるため全結合層を出力とします。今回のゲームで行える行動は全部で24通りあるため、出力も24とします。
次はforward関数です。forward関数では、__init__関数で定義した各層と活性化関数、その他に必要な処理を順に実行します。基本的には入力層→活性化関数→その他の処理(必要な場合)→中間層→...→出力層 という順に処理を進めていきます。そして、x = x.view(x.size()[0], -1)の処理を2つ目のプーリングと全結合層の間に入れることでプーリングした結果の行列をベクトルに引き延ばしています。これを忘れると後でモデルの出力を元にAIを行動させる時やモデルの学習時にデータの形状がおかしくなります。CNN層から全結合層にデータを渡す時は、その直前に引き伸ばしの処理を入れておきましょう(次元数が合っていない場合などとは異なり、書き忘れてもそのまま処理が進んでしまうので特に注意しましょう!)。
学習用環境の用意
入力するデータや使用するモデルの説明が終わったので、次はモデルを学習させる方法について説明していきます。
1. 報酬の設定
DQNでは、行動した結果の得点(報酬)を設定することで行動を学習させていきます。報酬はAIにどのように行動すべきかを教える指針になります。
「ところで、報酬はどのように設定したらいいの?」と思った方もいらっしゃるかもしれません。ゲームタイトルやジャンルによって得点条件や得られる得点の数値は異なっていますが、強化学習に関して言えば得点の付け方には以下のようなセオリーが存在しているので、それらに従えばOKです。
- ゲーム1回ごとの報酬を
-1から1の範囲に収まるように調整(報酬クリッピング)する(学習の円滑化及び他のゲームでもハイパーパラメーターを変えずにそのまま学習可能にするため) - 目標を達成したら
1、失敗したら-1にする(強化学習は報酬を最大化するように学習するため)
上記を鑑みて、今回のゲームでは以下のように報酬を設定しました。
- ゲームに勝ったら報酬
1、負けたら報酬-1 - 1分経っても決着が付かない時は報酬
-1とする(弾を撃たずに相手の自滅を待つような消極的なプレイを防止するため)
2. 訓練の方法
報酬の設定ができたので、実際に訓練を行う方法について実際のソースコードを交えながら、順に説明します。
(0) 訓練用環境の概要
今回AIを作成するゲームは対戦型のゲームです。ゲームをプレイするには対戦相手が必要になります。今回強化学習でAIを作ったのはAIの行動パターン作成を自動化するためなので、わざわざ人間のプレイヤーに対戦してもらったのでは本末転倒です。また、強化学習でゲームをプレイできるようにするには何万回もプレイしないといけないので、何万回もずっとプレイし続けるというのも無理があります。
強化学習に頼らない「⚪︎⚪︎なら××する」の条件分岐によるAIと対戦させるという方法もありますが、条件分岐のAIとの対戦ばかり続けていると強さがどこかで頭打ちになったり、作成したAIの行動に最適化されすぎてしまうなどの問題が発生します。
そこで今回は全く同じ構造の強化学習モデルをもう1個用意し、2つの強化学習モデル同士で対戦してもらう形にしました。実際に強化学習AIをゲームに組み込む時は片方のAIのみを使用します(もう一方は人間のプレイヤーが操作します)。
…
記事の続きは下のURLをクリック!
https://rightcode.co.jp/blogs/50437
エンジニア積極採用中です!
現在、WEBエンジニア、モバイルエンジニア、デザイナー、営業などを積極採用中です!
採用ページはこちら:https://rightcode.co.jp/recruit
社員の声や社風などを知りたい方はこちら:https://rightcode.co.jp/blogs?category=life
社長と一杯飲みながらお話しませんか?(転職者向け)
特設ページはこちら: https://rightcode.co.jp/gohan-sake-president-talk
もっとワクワクしたいあなたへ
現在、ライトコードでは「WEBエンジニア」「モバイルエンジニア」「ゲームエンジニア」、「デザイナー」「WEBディレクター」「営業」などを積極採用中です!
ライトコードは技術力に定評のある受託開発をメインにしているIT企業です。
有名WEBサービスやアプリの受託開発などの企画、開発案件が目白押しの状況です。
- もっと大きなことに挑戦したい!
- エンジニアとしてもっと成長したい!
- モダンな技術に触れたい!
現状に満足していない方は、まずは、エンジニアとしても第一線を走り続ける弊社代表と気軽にお話してみませんか?
ネット上では、ちょっとユルそうな会社に感じると思いますが(笑)、
実は技術力に定評があり、沢山の実績を残している会社ということをお伝えしたいと思っております。
- ライトコードの魅力を知っていただきたい!
- 社風や文化なども知っていただきたい!
- 技術に対して熱意のある方に入社していただきたい!
一度、【Wantedly内の弊社ページ】や【コーポレートサイト】をのぞいてみてください。