DDPG by gymnasium 7日目

さて、日をあけてしまいましたが、続きをやっていきましょう。

前回

actorが行動して集めたデータから、経験再生を使って「次の状態」からtarget_actorが「次の行動」を出力し、「次の行動」と「次の状態」からtarget_criticが「次の状態価値」出力し、TDターゲットを算出しました。

一方で、criticは経験再生を使って「現在の状態」と「そのとき取った行動」から「現在の状態価値」別名ベースラインを算出しました。

今回

ここからは、本当の意味で学習・訓練、つまりパラメータ更新をやっていきます。

オプティマイザーを定義する

オプティマイザーをActorNNクラスとCriticNNクラスの__init__()に定義しておきます。
【ActorNN】#26
self.optimizer = optim.Adam(self.parameters(), lr=alpha)
【CriticNN】#27
self.optimizer = optim.Adam(self.parameters(), lr=beta)
引数のself.parameters()は、モデル自身が持っている重みやバイアスのパラメータです。それを学習率lrでAdamによって最適化(損失関数の最小化)するインスタンスself.optimizerを定義します。

criticの学習

lean()メソド内でのTDターゲット算出後からやっていきます。
クリティックの損失関数はtensor(0.0485, grad_fn=<MseLossBackward0>)の形で出力されます。

actorの学習

続けてactorを学習します。
Actorの目的は、Criticネットワークの出力(行動価値)を最大化するような行動を選択することです。
なので、actorNN→criticNNのDDPG構造全体の出力結果をactor_lossとして、actorNNとcriticNNの両方をbackwardすることによってactorにも勾配情報が届きパラメータの更新をすることができます。

全ニューラルネットワークのパラメータを更新する

learn()メソドの締めくくりとして、36の直後にself.update_network_parameters()を入れ、メソドとして定義します。
学習結果の確認
クリティックとアクタークリティックのパラメータ更新まで行ってアクターとターゲットアクターのパラメーター更新をしない状態で試してみました。
動作確認のつもりでやりましたが、学習は進んでいるようです。
10step x 100epsode で学習したところ、ハーフチーターはエピソード開始直後に前へ倒れこむような挙動を獲得し、リワードを稼ぐようになりました。10stepでは走り続ける動作を獲得するのは無理なようです。
続いて、アクターとターゲットアクターのパラメーター更新も追加して同じことを行いました。
こちらは、足を折りたたんで低い姿勢なることでリワードを稼ぎに行っているようです。しかし80エピソードから成績が悪化していっています。うまく学習が進んでいないようです。
まあ、ニューラルネットワーク構造もまだ適当に作っているので、改善の余地があります。
また下記のようにactor_lossesとcritic_lossesの変化も可視化してみると下記のように悪化していく方向にあります。

課題

  1. 計算の高速化(print文の無効化)
  2. 適切なニューラルネットワーク構造
  3. 適切なステップ数
  4. 適切なエピソード数
  5. パラメータのセーブとロード
  6. 途中で止まった時に続行可能にしたい
  7. 計算の高速化(GPUの利用)
  8. 学習の進行状況のリアルタイム可視化

現在までのスクリプト

 

以上。次回はニューラルネットワーク構造を見直しましょう。

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

DDPG by gymnasium 3日目

前回はActorのニューラルネットワークを作って、観測情報obsを入力することによってactionを得る、とうところまでできました。

今回はその結果であるtransition: obs, action, reward, next_state, doneを保存するところを作ります。

これは経験再生:ReplayBufferという方法で、方策πに従って行動した結果をいったんメモリーバッファーとして保存し、ニューラルネットワークのパラメータ学習のときに、そのメモリーバッファーからランダムに取り出して入力データとして使用するために使います、これをやらないで行動の結果の順番通りに入力データとして入れてしまうと、似たようなデータばかり入れて学習することになるのでパラメータが最適化されていきません。

一旦バッファーにいれて、あとで改めてバッチ学習させます。

agent.remember(s,a,r,s’,done)メソドを作成する

#7. トラジェクトを保存する。経験再生(ReplayBuffer)
        agent.remember(obs, action, reward, next_state, int(done))
#8.AgentDDPGクラスにremenberメソドを追加する
    def remember(self, obs, action, reward, next_state, done):
        self.memory.store_transition(self, obs, action, reward, next_state, done)
# 9.AgentDDPG.__init__()にmemoryインスタンスを追加する。
# 10.memoryインスタンスの元クラスReplayBufferクラスを作成する。
# 11.ReplayBufferクラスのメソドとしてトランジションを保存する実態であるstore_transitionメソドを作成する

ここまでのスクリプト

 

DDPG by gymnasium2日目

前回

前回はハーフチーター環境をランダムな行動で動かすところまでいきました。

actionの決定は、行動空間からのランダムサンプリング

action = env.action_space.sample()

になっています。いわゆる「方策:ポリシー」と呼ばれるものです。
エージェント(行動する者)はポリシー(方策・方針)を定めることによって、その状況(環境、観測情報)に応じて行動を選択します。その決定は確率的であったり一意的あるいは決定論的であったりします。
このポリシーを何かしらのアルゴリズムで調整・改善することで最大収益が得られるようにしていくのがDDPGなどの強化学習手法の目的です。
「収益が最大になるようにポリシーを改善していく」のほうが正しい言い方でしょうか。

改善案

Agentクラスを新規作成して、インスタンスagentを作り、DDPG的な学習ができるようなメソドを作成していく。

ではAgentDDPGクラスを作成していく。

メインスクリプトでagent = AgenDDPG()インスタンスを生成してから
action = agent.choose_action(obs)メソドを実行することで、行動空間action_spaceから行動をランダムに選択してactionをひとつ選択されたものを戻り値とすることができました。まだこの時点ではDDPG的な要素を入れていません。

DDPG部分を作っていく

DDPGは方策勾配法を基礎としており、目的関数J= E[ Σ G(τ) * grad log π(θ)]を最大化するために最初は適当な方策πをちょっとずつ自動調整していく方法です。

勾配 grad Jを使って、パラメータθを最適化していくのですがどう表現したらよいでしょうか。方策勾配法の発展経緯を追っていくと、下記のように読み取れます。

  1. 基本の方策勾配法: E[ Σ G(τ) * grad log π(θ)]
  2. REINFORCE的に収益ノイズ除去: E[Σ G(t) * grad log π(θ)]  ]
  3. ベースライン付き: E[Σ ( G(t)-b) * grad log π(θ)]  ]
  4. ベースラインを価値関数とする:E[Σ ( G(t)-V(w) )* grad log π(θ)]  ]
  5. TD法であること:( r+γV(w)[s_t+1] – V(w) [s_t]) *  grad log π(θ)
  6. 方策π(θ)をニューラルネットワークで表現:actorという。入力s、出力π(a|s)(行動確率probと表現することもある)
  7. 価値関数V(w)をニューラルネットワークで表現:criticという。※真の価値関数vは求めない。Vは中途半端な推定値でも方策πは学習できる。入力はactorと同じs、加えてactorの出力であるπがcriticの入力として使用されます。

「actorの出力をcriticの入力とする」部分がDDPGが連続値に対応できるポイントです。この要素を除くと出力が離散的になりそのアルゴリズムはactor-criticと呼んでいました。actor-criticは行動の選択肢が左右の2つある場合、「右に行く」と決めるような状況で使います。

DDPGは連続値なので、「車のハンドルを右へ15.2°回転させつつ、ブレーキを20%踏み込む」という出力が得られます。(のはず!)

では、ちょっとずつコーディングしていく。

python、プログラミング初級者でも理解できるようにちょっとずつ変えていきます。

 1.エージェントインスタンスのchoose_actionメソドを使って行動actionを得るように変更する。

        変更前:action = env.action_space.sample()
        変更後:action = agent.choose_action(obs)
 2.エージェントクラスのインスタンスを生成する
agent = AgentDDPG()
3.エージェントクラスを定義する
class AgentDDPG:
    def __init__(self):
    def choose_action(self, obs):

4.方策(アクター)はニューラルネットワークで表現する。ActorNNクラスを新規作成し、インスタンスactorとして使用する。

    def choose_action(self, obs):
        action = self.actor.forward(obs)
        return action

5.ActorNNクラスのインスタンスを生成する

    def __init__(self):
        self.actor = ActorNN()

6.ActorNNクラスを新規作成する

class ActorNN:
    def __init__(self):
    def forward(self, obs):
        action = [0.0 for i in range(6)]
        return action
まだニューラルネットワーク構造まで作成していないので、obsは入力として使っていませんし、actionも仮出力として[0,0,0,0,0,0]にしています。

ActorNNクラスを作りこんでいく

3層の全結合ネットワーク、そして活性化関数はreluにしています。

AgentDDPGクラスを作りこんでいく

 

メインスクリプトと整合性を合わせる

こんな感じで作っていきました。

訓練について

  1. actorは目的関数J(θ)が最大になるように学習する。実際の計算は-J(θ)が最小になるように学習する。
  2. target_actorを正解データとして教師あり学習する
  3. criticはtarget_actorを正解データとして教師あり学習する

DDPG by gymnasium1日目

深層強化学習をやっていこう

今日から強化学習AIの道場 gymnasiumを使って深層決定論的方策勾配DDPGを試していきたいと思います。

環境

  • windows10 python3.7.9
  • メモリ8GB
  • core i7 7700
  • RTX3070Ti
  • visual studio code/
  • python 3.7.9
  • venv 仮想環境

モジュール

多分もっと増えていきます。

 

とりあえず学習なしで動かしてみる。

gymnasiumの HalfCheetah-v4 半分チーター(動物)?のエージェントモデルを動かして描画するところまでやっていきます。

結果

エピソード数1、繰り返しステップ数1に変更した場合です。

観察空間は17個 マイナス無限からプラス無限までの連続値です。

行動空間は6個 -1から+1までの連続値です。

最後にscript is  done.と出力されているので、特に問題なさそうです。

が・・・

エラー発生

スクリプト自体は最後の行まで問題なく script is doneと表示されていますが、

なんか出てます。

chatGPTによると

と言われますが、どうにもならないので無視しました。

env.close()が本来こういったエラーが出ないようにするはずですが、メソドの中身を見ると、説明書きだけでコードは空でした・・・

次回

DDPGをちょっとずつ作っていきます。

maddpg_pytorch

MADDPGです。これもうまく動かないけど、メモ。

メイン

 

ddpg_pytorch

DDPGのコード例です。

念のため。

Set-ExecutionPolicy RemoteSigned -Scope Process -Force

しておきましょう。

うまく動かないかもしれません。

 

そしてメインスクリプトです。

 

 

pytorch_policy_gradient_method

方策勾配法にチャレンジしました。

オライリージャパンの ゼロから作るDeep Learning 4 強化学習編を参考にしながら作成しました。

本書ではニューラルネットワークをdezeroというオリジナルのフレームワークで記述されておりましたが、もっと汎用的に使えるように独自にpytorchへ変更しました。悩みながら2日かけてなんとか動いてくれました。

必要なモジュールをインポート

 

GPUを利用する

 

方策クラスの定義

 

 

エージェントクラスの定義

 

メインスクリプト

 

結果グラフ