Dueling Network Architectures for Deep Reinforcement Learning [arXiv:1511.06581]

2016年03月27日

概要

はじめに

この論文は新しい強化学習のアルゴリズムを提案するのではなく、Q関数の内部構造に変更を加えたDueling Architectureを提案しています。

そのためQ関数を用いた強化学習全般に適用でき、導入する際のコードの変更も少なくて済みます。

Dueling Architecture

まずQ関数を以下のように分解します。

は状態価値関数を表し、$\hat{A}(s,a)$は行動優位関数を表します。

$Q$から$\hat{V}$を引くことで、各行動$a$の相対的な重要度を求めることができます。

Dueling Network

式((1))をニューラルネットで実装する場合、以下のような構成になります。

        +--> A(s,a) +--+
Conv +--+              +--> Q(s,a)
        +-->  V(s)  +--+

$\hat{V}$、$\hat{A}$はそれぞれパラメータの異なる個別のニューラルネット(パラメータ$\alpha,\beta$)で表しますが、画面入力を受け持つ畳み込みニューラルネット(パラメータ$\theta$)は共有します。

Dueling Networkでは、$\hat{V}$と$\hat{A}$の出力ベクトルを合成し$Q$を出力します。この合成を行う部分を$aggregator$と著者らは呼んでいます。

しかし、$aggregator$を素直に実装すると問題が生じます。

まず以下の式を見てください。

は真の行動価値、は真の状態価値、は真の行動優位値を表します。

は、それぞれ真の値にノイズ$\epsilon$を加算・減算したもので、真の値から外れた誤りを含む値であることを表しています。

式((4))は、その誤った$\hat{V}$と$\hat{A}$から、真の行動価値$Q^*$が得られてしまうことを意味します。

このように$aggregator$を単純な加算処理にしてしまうと、ニューラルネットが誤った関数を学習してしまうため、以下のように変更します。

は行動の総数です。

平均値を引くことで統計学的には自由度が1つ下がるのですが、私はあまり詳しくないので説明は控えます。

実装

Chainerによる実装はこちらです。

ここでは式((5))で表される$aggregator$の実装を抜き出して載せておきます。

class Aggregator(function.Function):
	def as_mat(self, x):
		if x.ndim == 2:
			return x
		return x.reshape(len(x), -1)
		
	def check_type_forward(self, in_types):
		n_in = in_types.size()
		type_check.expect(n_in == 3)
		value_type, advantage_type, mean_type = in_types

		type_check.expect(
			value_type.dtype == np.float32,
			advantage_type.dtype == np.float32,
			mean_type.dtype == np.float32,
			value_type.ndim == 2,
			advantage_type.ndim == 2,
			mean_type.ndim == 1,
		)

	def forward(self, inputs):
		value, advantage, mean = inputs
		mean = self.as_mat(mean)
		sub = advantage - mean
		output = value + sub
		return output,

	def backward(self, inputs, grad_outputs):
		xp = cuda.get_array_module(inputs[0])
		gx1 = xp.sum(grad_outputs[0], axis=1)
		gx2 = grad_outputs[0]
		return self.as_mat(gx1), gx2, -gx1

def aggregate(value, advantage, mean):
	return Aggregator()(value, advantage, mean)

forward時にvalueとmeanは総行動数の分だけコピーされるので、backwardするときに勾配のsumを取ります。

前回作ったDouble DQNのQ関数の出力を得る部分を以下のように変更しました。

	def compute_q_variable(self, state, test=False):
		output = self.conv(state, test=test)
		value = self.fc_value(output, test=test)
		advantage = self.fc_advantage(output, test=test)
		mean = F.sum(advantage, axis=1) / float(len(config.ale_actions))
		return aggregate(value, advantage, mean)

実験

ALEを使った実験はとにかく時間がかかる(数日以上)のであまりやりたくないのですが、とりあえずAtari Breakoutで4000プレイほどさせました。

DeepMindの論文では全てのゲームで2000万フレームを2週間かけて学習させていますが、私の環境では100万フレームの学習に25時間くらいかかります。

プレイ回数とスコアの関係:

Breakout episode-score

論文にも書いてありましたが、Dueling NetworkはBreakoutが苦手みたいです。

プレイ回数とハイスコア:

Breakout episode-highscore

平均スコア:

Breakout episode-average

そもそも訓練回数が圧倒的に足りていないので何ともいえません。

関連