DDPG by gymnasium 6日目

前回までの動き:

  1. agentがobsを受けてchoose_actionでactor(NN)をforwardしactionを出力する。
  2. actionを受けてenv.stepし結果としてnext_state,reward,doneを得る。
  3. agent.rememberで結果obs,action,rewerd,next_state,int(done)を保存する。
  4. rememberで64データ集まったらagent.learnで学習が始まる。
学習:
  1. sample_bufferで64データをランダムに取り出す。
  2. dtype=T.float32に変換する。
  3. target_actorへnext_states 64データを入力してtarget_actions 64データを得る。
ここまで作成しました。

今回

このtarget_actionsをnext_statesと共にtarget_criticへ入力するところからやっていきます。
この部分こそ連続値対応できるDDPGの核心部分なので十分に理解する必要があります。
引き続き 学習learn()メソド内での処理です。

やっていこう

AgentDDPG.learn()メソド内の

target_actions = self.target_actor.forward(next_states)

の直下に
target_critic_values
 = self.target_critic.forward(next_states, target_actions)
を入れます。
算出したてのtarget_actionsとnext_statesの2つを入力として、target_critic_valuesを出力します。

ちょっと説明をいれると、DDPGはTD法なのでTDターゲットとしてr + γ*V(w)[s_t+1]を考えます。target_critic_valuesはこれのことです。

この価値関数Vの部分をtarget_criticNNで表現します。

# 21.ターゲットクリティックネットワークインスタンスtarget_criticを作成します。AgentDDPG.__init__()内に定義します。
target_actorの引数 学習率alphaをcritic用にbetaへ変更しています
# 22.CriticNNクラスを作成します。
これでtarget_critic_values が返ってくる

#23.ベースラインとして機能するクリティックネットワーク(価値関数V(w)[s_t]ネットワーク)に 現在の状態observationsと行動actionsを入力してcritic_valueを算出する。
AgentDDPG.learn()メソドに戻ってさっきほどの
target_critic_values
 = self.target_critic.forward(next_states, target_actions)
の直下に
 critic_values
   = self.critic.forward(observations, actions)
を入れる。criticインスタンスはまだ作成していないので、AgentDDPG.__init__()に追加する
# 24. AgentDDPG.__init__()にクリティックインスタンス生成を追加する
これで4つのNNを導入することができた。

# 25.target_criticからTDターゲット(= r + γ*V(w)[s_t+1])を算出する。

AgentDDPG.learn()メソドに戻って、

まとめとこれまでのスクリプト

actorが行動して集めたデータから、経験再生を使って「次の状態」からtarget_actorが「次の行動」を出力し、「次の行動」と「次の状態」からtarget_criticが「次の状態価値」出力し、TDターゲットを算出しました。注意すべきはここで言う「次の行動」とはあくまでtarget_actorが生み出した「架空の行動」です。

一方で、criticは経験再生を使って「現在の状態」と「そのとき取った行動」から「現在の状態価値」を算出ししました。注意すべきは、こちらの「そのとき取った行動」とは実際にactorが行動して経験再生バッファに保存されたデータです。

また、この「現在の状態価値」をベースラインと呼びます。次回以降。「TDターゲット-ベースライン」の演算が出てくるので注目です。

ではまた次回

DDPG by gymnasium 5日目

次回は、actorNNへの引数修正、それに伴うagentDDPG引数修正、そしてリプレイバッファへの保存内容を修正しました。

今回は64データ集まった後に学習learn()メソドが走るとエラーが出るので修正していきます。

chatGPTより

このエラーメッセージは、F.linear関数を使用して乗算されている2つのテンソルのデータ型が一致しないことを示しています。この場合、inputテンソルとweightテンソルのデータ型が異なるようです。

この問題を解決するには、次の手順を試すことができます。

  1. inputテンソルとweightテンソルのデータ型を確認します。それらのdtype属性を出力して、同じかどうかを確認できます。
  2. データ型が異なる場合は、to()メソッドを使用してinputテンソルをweightテンソルと同じデータ型に変換します。たとえば、weightテンソルがfloat32テンソルの場合、input.to(torch.float32)を呼び出してinputテンソルをfloat32テンソルに変換できます。
  3. 代わりに、inputテンソルと同じデータ型にweightテンソルを変換することもできます。たとえば、inputテンソルがfloat64テンソルの場合、weight.to(torch.float64)を呼び出してweightテンソルをfloat64テンソルに変換できます。
  4. テンソル上で行われる他の操作も同じデータ型を維持するようにしてください。

テンソルのデータ型が一貫していることを確認することで、遭遇したRuntimeErrorを解決できるはずです。

とのこと。なるほど、入力データをpytorchの型に合わせる必要があるようです。

現状確認

データを保存するときにstore_transitionメソドで

        self.state_memory[index] = obs.detach().numpy().flatten()
        self.action_memory[index] = action.flatten()
        self.reward_memory[index] = reward.flatten()
        self.next_state_memory[index] = next_state.flatten()
        self.terminal_memory[index] = 1 – int(done)
としているので、type()で型を見てみます。
        print(‘type of state_memory :’, type(self.state_memory[0][0]))
        print(‘type of action_memory :’, type(self.action_memory[0][0]))
        print(‘type of reward_memory :’, type(self.reward_memory[0]))
        print(‘type of next_state_memory :’, type(self.next_state_memory[0][0]))
        print(‘type of memory.state_memory :’, type(self.terminal_memory[0]))
結果、値は 全てnumpy.float64になっています。
type of state_memory : <class ‘numpy.float64’>
type of action_memory : <class ‘numpy.float64’>
type of reward_memory : <class ‘numpy.float64’>
type of next_state_memory : <class ‘numpy.float64’>
type of memory.state_memory : <class ‘numpy.float64’>
取り出す際もsample_buffer(self, batch_size)メソドで
observations = self.state_memory[choosed_index]
として戻り値を得ているので変わりません。
戻り値はpytorchのテンソルに変換しています。troch.float64になっている。
        observations = T.tensor(observations, dtype=float)
        actions = T.tensor(actions, dtype=float)
        rewards = T.tensor(rewards, dtype=float)
        next_states = T.tensor(next_states, dtype=float)
        terminals = T.tensor(terminals, dtype=float)
それを
target_actions = self.target_actor.forward(next_states)
に入れたときに起こっているのか?
class ActorNN(nn.Module):
__init__: self.fc1 = nn.Linear(n_obs_space, layer1_size)
forward :      x = self.fc1(obs) ここでエラーが発生している
 obsはバッチサイズ64x観察空間17、を入力ノード17x次層ノード64で待ち受けている。数としては問題ない。
型が合わないということなので、重みパラメータの型を調べてみる
agent.actor.fc1.weight.dtype → torch.float32
agent.target_actor.fc1.weight.dtype → torch.float32
なるほど、torch.float32で入力しなければならないようなので、変更します。
修正前:observations = T.tensor(observations, dtype=float)
修正後:observations = T.tensor(observations, dtype=T.float32)
これで回るようになりました。

ここまでのスクリプト

target_actorへnext_states  バッチサイズ64データを入力し、target_actions 64データを得ることができました。
以降、actorが1ステップ行動するごとに、target_actorへnext_states 64データを入力してtarget_actions 64データを繰り返し出力する状態になりました。
次回は、このtarget_actionsをnext_statesと共にtarget_criticへ入力するところからやっていきます。
 

DDPG by gymnasium 4日目

前回はリプレイバッファを作りました。

今回はいよいよ、ニューラルネットワークの核心、学習部分を作っていきます。

12.メインスクリプトのagent.remember()直下にagent.learn()を作ります。

13.AgentDDPGクラス内にlearn()メソドを新規作成します。

14.バッチサイズ分のトランジションが集まるまでは何も実行しない。
        if self.memory.memory_count< self.batch_size:
            return
15.メモリバッファからデータを抜き出す sample_buffer()
        obs, action, reward, new_state, done = self.memory.sample_buffer(self.batch_size)
16.ReplayBufferのメソドとしてsample_bufferメソドを追加する。
    def sample_buffer(self, batch_size):
        # indexが最大メモリに到達していない場合を想定する。
        max_index = min(self.max_memory_size, self.memory_count)
        choosed_index = np.random.choice(max.index, batch_size)
        observations = self.state_memory[choosed_index]
        actions = self.action_memory[choosed_index]
        rewards = self.reward_memory[choosed_index]
        next_states = self.next_state_memory[choosed_index]
        terminals = self.terminal[choosed_index]
        return observations, actions, rewards, next_states, terminals
17.抜き出したデータをpytorchで微分可能なようにtorch.tensor化する。torch.tensor( obs, dtype=float)
        obs = T.tensor(obs, dtype=float)
        action = T.tensor(action, dtype=float)
        reward = T.tensor(reward, dtype=float)
        new_state = T.tensor(new_state, dtype=float)
        done = T.tensor(done, dtype=float)
18.ターゲットアクターネットワークインスタンスtarget_actorに
         次の状態next_satesを入れて、ターゲットアクションtarget_actionsとして取り出す。
        target_actions = self.target_actor.forward(next_states)
19.AgentDDPGクラスにターゲットアクターネットワークインスタンスtarget_actorを作成する。actorとtarget_actorのネットワークは同じActorNN構造で良い
        self.target_actor = ActorNN(input_dim=self.input_dim, output_dim=self.output_dim)
20.ターゲットクリティックネットワークインスタンスtareget_criticに
        # 次の状態next_statesと上記より算出したターゲットアクションの2つを入力して
        # 価値関数の推定値ターゲットバリューを出力する。
        # TDターゲット:r + γ*V(w)[s_t+1] の部分のこと。
        # ターゲットクリティックバリューはターゲットアクターネットワークを使う
        target_critic_values = self.target_critic.forward(next_states, target_actions)
21.AgentDDPGクラスにターゲットクリティックネットワークインスタンスtareget_criticを作成する
        self.target_critic = CriticNN(input_dim=self.input_dim, output_dim=self.output_dim)

問題発生

2.エージェントクラスのインスタンスを生成するところで
agent = AgentDDPG(input_dim=17, output_dim=6)
としていますが、これだけの引数ではDDPGを表現できないことに気が付きました。
ActorNNは入力obsと出力actionだけなので17と6だけの情報で良かったのですが、CriticNNは入力がactionと obsの2つ、また出力が状態価値state_valueの1つあるので、ニューラルネットワークに必要な入出力の数が異なります。
よって、入力の形、学習率、ニューラルネットワークの各層とノード数など、アクターとクリティックで異なるであろう部分はエージェントクラスから含めるように修正していきます。
以下のようにエージェントクラスの引数をたくさん増やしました。
agent = AgentDDPGalpha=0.000025, beta=0.00025, gamma=0.99, tau=0.001, n_obs_space=17 , n_action_space=6, layer1_size=64, layer2_size=64, batch_size=64)
またアクターニューラルネットワーククラスへ受け渡す引数を修正しました。
self.actor = ActorNN(alpha=0.000025, n_obs_space=17, n_action_space=6, layer1_size=64, layer2_size=64, batch_size=64)

さらなる問題

“””エラーメッセージ

このエラーメッセージは、行列の乗算に問題があることを示しています。具体的には、1×64の行列と17×64の行列を乗算しようとしていますが、この操作は許容されません。なぜなら、最初の行列の列数(64)が2番目の行列の行数(17)と異なるためです。

この問題を解決するには、乗算しようとしている行列の次元を確認し、行列乗算に対して互換性のある次元になるように調整する必要があります。あるいは、行列の次元に合わせて、適切な演算や変換を使用することも検討してみてください。

“””
バッファメモリーへ保存した観測情報obsを
observations = self.state_memory[choosed_index]
によって64個のバッチサイズで抜き出して、ニューラルネットワークへ入力した時点でエラーが発生しました。
流れを今一度おさらいします。
ひとつの観測情報obsをActorNNへ入力することによって、1つ行動actionが生成されます。
このobsの形は1×17なので、バッチ64個分をバッファから抜き出すと 64×17のはずです。しかし、エラーでは1×64となっているので根本的に間違っています。
患部リプレイバッファーの初期化部分でした。
self.state_memory = np.zeros((self.max_memory_size, self.n_obs_space))
これで、観測データ1000000 x 観測空間17 を確保するつもりが、
self.state_memory = np.zeros(self.max_memory_size)
となっており、観測空間17のメモリーしか確保されていませんでした。
さらに悪いことに保存データが
self.state_memory[index] = obs.detach().numpy().flatten()[0]
となっており、観測空間17個のうち先頭の1個しか保存されないという間違いがありました。
self.state_memory[index] = obs.detach().numpy().flatten()に修正しました。
ほかにも
self.new_state_memory[index]がありますので同様に修正が必要です。
ここまでの修正スクリプト
リプレイバッファが64データ蓄積されるまでは動きます。
学習learn()メソドが始まるとエラーが出る状態です。
次回はここを解消していきます。
 

pytorchで謎の部分は下記のサイトを参考にさせていただきました。

https://qiita.com/tatsuya11bbs/items/86141fe3ca35bdae7338