Posts Tagged with "AI"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

Pongと強化学習 (81)

posted by sakurai on November 28, 2024 #902

PongやBreakoutを観察すると、無駄にパドルが動いています。これはパドル動作にペナルティが無いためでしょうね。そのためにパドル動作(左移動、右移動)自体にペナルティを付加するにはどう改造したら良いですか?


Atariゲーム(例えばBreakoutやPong)においてパドルの動きにペナルティを付けることで、より効率的な動きを促すというアイデアは、特にエージェントが無駄な動きを減らすように学習するのに有効です。こうすることで、エージェントはより戦略的に行動を選択し、必要最小限の動きで最大の効果を達成しようとするでしょう。

ペナルティの実装方法

  1. 報酬関数の変更:

    • ゲーム環境の報酬関数を変更して、特定の行動(この場合はパドルの左移動や右移動)が行われるたびに小さなペナルティ(例えば-0.01点)を与えます。
  2. 環境のカスタマイズ:

    • gymnasiumやAtari Learning Environment (ALE) を使用している場合、環境のソースコードに手を加える必要があるか、環境をラップするラッパークラスを作成して、報酬を操作します。

カスタム環境ラッパーの実装例

以下はPythonを使用してgymnasiumの環境にペナルティを適用する簡単な例です。この例では、gymnasiumラッパーを使用してBreakoutやPongの環境に介入し、パドルの動きに対してペナルティを適用します。

import gymnasium as gym
    
    class PenalizePaddleMovement(gym.Wrapper):
        def __init__(self, env, penalty=-0.01):
            super(PenalizePaddleMovement, self).__init__(env)
            self.penalty = penalty
            self.last_action = None
    
        def step(self, action):
            observation, reward, done, info = self.env.step(action)
            if action in [LEFT, RIGHT] and action == self.last_action:  # 定義に基づく左右の行動
                reward += self.penalty
            self.last_action = action
            return observation, reward, done, info
    
        def reset(self, **kwargs):
            self.last_action = None
            return self.env.reset(**kwargs)
    
    # 環境の作成とラッパーの適用
    env = gym.make('Breakout-v0')
    env = PenalizePaddleMovement(env)
    
    # DQNや他の学習アルゴリズムで使用

このコードでは、PenalizePaddleMovementクラスが環境をラップし、パドルの連続した移動行動にペナルティを与える機能を追加しています。ここで、LEFTRIGHTはゲーム固有の行動コードに置き換える必要があります。このペナルティは、同じ方向に連続してパドルを動かすときにのみ適用されます。

注意点

  • ペナルティの値: ペナルティは非常に小さく設定することが重要です。これは、ペナルティが報酬を完全に打ち消さないようにするためです。
  • 行動の定義: LEFTRIGHTといった具体的な行動は、使用している環境のAPIドキュメントを参照して適切な値に設定してください。
  • 実験の検証: 変更を加えた後は、エージェントがまだ適切に学習できるかを確認するために、広範なテストが必要です。

この方法で、パドルの動きにペナルティを適用し、エージェントがより効率的な戦略を学習するよう促すことができます。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (80)

posted by sakurai on November 27, 2024 #901
env = ClipRewardEnv(env)
        env = gym.wrappers.ResizeObservation(env, (84, 84))
        env = gym.wrappers.GrayScaleObservation(env)
        env = gym.wrappers.FrameStack(env, 4)

を深掘りしてみます。


ご提示いただいたソースコードから、環境のラップ方法として複数の重要な変更が行われていることがわかります。これらは強化学習、特にDQNのようなアルゴリズムを用いる際に一般的に適用される変更です。それぞれのラッパーがどのような機能を果たしているかを以下に説明します。

1. ClipRewardEnv

このラッパーは環境から得られる報酬をクリップ(制限)します。多くの場合、報酬は+1, 0, -1のようにシンプルな形に変換されます。これにより、報酬のスケールが大きく異なる問題を防ぎ、学習プロセスを安定させることができます。

2. ResizeObservation

ResizeObservationラッパーは、環境から得られる観察(画像)を指定されたサイズ(この場合は84x84)にリサイズします。これにより、入力データの次元が減少し、計算負荷が軽減されると同時に、学習がより効率的に行えるようになります。

3. GrayScaleObservation

このラッパーは観察画像をグレースケールに変換します。カラー情報を削除することで、処理するデータ量をさらに削減し、重要な構造的情報にモデルが集中できるようにします。

4. FrameStack

FrameStackは連続する複数のフレーム(この場合は4フレーム)をスタックし、それを一つの観察として扱います。これにより、モデルは単一の画像からではなく、時間にわたる動きの情報を取得できるため、より複雑な環境のダイナミクスを学習することが可能になります。

5. env.action_space.seed(seed)

環境の行動空間にシードを設定して、再現性のあるランダム性を確保します。これは実験の一貫性を保つために重要です。

総合すると

これらのラッパーを組み合わせることで、学習アルゴリズムがより効率的かつ効果的に環境から学ぶことが可能になります。画像のサイズ変更、色の削減、報酬の正規化、そして時間的な情報の統合は、特に視覚的なデータを扱う場合において、モデルのパフォーマンスを大きく向上させることができます。このような準備を施すことで、ニューラルネットワークや他の機械学習モデルが、原始的なピクセルデータから有用な特徴を抽出しやすくなります。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (79)

posted by sakurai on November 25, 2024 #900

Atari_dqnのソースコードについて質問してみます。「def make_env(env_id, seed, idx, capture_video, run_name):という関数について教えてください」


make_env関数は、Atariゲームを含む環境を構成し、トレーニングのために適合させる一連の手順を包括的に示しています。この関数は特に、強化学習実験で広く使用されるような設定を含んでおり、異なるラッパーや設定を適用しています。ここで使われている具体的なラッパーとその目的について、さらに詳しく解説します。

関数の詳細解説

  1. ビデオ記録:

    • この関数は、特定の条件下(capture_videoTrueで、idxが0の場合)で環境のビデオを記録します。これは主に、学習の進行を視覚的に確認するために利用されます。
  2. エピソード統計の記録:

    • RecordEpisodeStatisticsラッパーを用いて、各エピソードの統計情報(得点など)を記録します。これにより、トレーニングの効果を定量的に評価することが可能になります。
  3. No-op リセット:

    • NoopResetEnvは、エピソードの開始時に最大30ステップの間何もしない(No-Operation, No-op)アクションをランダムに挿入することで、エージェントが単一の開始状態に過剰に適合するのを防ぎます。
  4. Max and Skip:

    • MaxAndSkipEnvは、指定された数(ここでは4)のフレームをスキップし、その間の画像の最大値を取ることで観察を行います。これにより、処理を高速化し、時間的な差異を処理しやすくします。
  5. エピソディックライフ:

    • EpisodicLifeEnvは、エージェントが失敗したときにゲームが終了したかのように環境をリセットしますが、実際のゲームオーバーまで続きます。これは、エージェントがより慎重な行動を学習するのを助けます。
  6. Fire リセット:

    • FireResetEnvは、ゲーム開始時に「FIRE」アクション(多くのAtariゲームで使用される開始アクション)を自動的に行うラッパーです。
  7. 報酬のクリップ、観察のリサイズとグレースケール化、フレームのスタック:

    • これらのラッパーは上述の通り、学習プロセスを安定させ効率化します。
  8. アクションスペースのシード設定:

    • 環境の行動選択にランダム性を導入する際の一貫性を保つために、シードを設定します。

総合的な構成

このmake_env関数は、特に複雑な環境で学習を行う強化学習モデル(特にDQN)にとって必要な多くの事前処理を自動的に設定します。これにより、モデルがより効果的に学習を進めることが可能となり、開発者は環境の細かい調整に時間


左矢前のブログ 次のブログ右矢

Pongと強化学習 (78)

posted by sakurai on November 22, 2024 #899

このコードにReLUを組み込むにはどうするのかを聞いてみます。


ReLU(Rectified Linear Unit)関数は、一般的にニューラルネットワークの活性化関数として使用されます。ReLU関数は入力が0以下の場合は0を出力し、入力が0より大きい場合はその入力をそのまま出力します。ハードウェア(特にBSVでの実装)においてReLUを組み込む場合、各計算ステップの後にこの単純な条件チェックを適用することになります。

以下の例では、畳み込み計算の結果に対してReLUを適用する方法を示します。ここでは、畳み込み演算の結果を格納する前にReLU関数を適用しています。

package ConvolutionFSMwithReLU;
import StmtFSM::*;

interface Conv2D_IF;
    method Action start();
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
endinterface

module mkConvolutionFSMwithReLU#(parameter Int#(32) width = 84,
                                  parameter Int#(32) height = 84,
                                  parameter Int#(32) kWidth = 8,
                                  parameter Int#(32) kHeight = 8)(Conv2D_IF);
    // ここで入力行列、カーネル行列、出力行列を定義
    Reg#(Bit#(8)) inputMatrix[0:width-1][0:height-1];
    Reg#(Bit#(8)) kernelMatrix[0:kWidth-1][0:kHeight-1];
    Reg#(Bit#(8)) resultMatrix[0:width-kWidth][0:height-kHeight];
    Reg#(Bool) startFlag <- mkReg(False);

    // ReLU関数の定義
    function Bit#(8) ReLU(Bit#(32) x);
        return (x > 0) ? truncate(x) : 0;
    endfunction

    // 畳み込み計算のFSM
    Stmt main = seq
        // x, y のループ
        for (x <= 0; x < width - kWidth + 1; x <= x + 1) seq
            for (y <= 0; y < height - kHeight + 1; y <= y + 1) seq
                // 畳み込み計算
                action
                    Bit#(32) sum = 0;
                    for (Int#(32) dx = 0; dx < kWidth; dx = dx + 1) begin
                        for (Int#(32) dy = 0; dy < kHeight; dy = dy + 1) begin
                            sum = sum + (inputMatrix[x+dx][y+dy] * kernelMatrix[dx][dy]);
                        end
                    end
                    resultMatrix[x][y] = ReLU(sum); // 結果にReLUを適用して格納
                endaction
            endseq
        endseq
    endseq;
    
    mkAutoFSM(main);

    method Action start();
        startFlag <= True;
    endmethod
    
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
        return resultMatrix[x][y];
    endmethod

endmodule
endpackage

この例では、ReLU関数を定義し、畳み込み計算の結果に適用してから結果をresultMatrixに格納しています。この方法により、畳み込み後の特徴マップに非線形性を導入し、ニューラルネットワークの表現力を高めることができます。ただし、ReLU関数の出力ビット幅に注意が必要です。ここでは出力をBit#(8)にトランケートしていますが、実際のアプリケーションに応じて適切なビット幅を選択する必要があります。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (77)

posted by sakurai on November 21, 2024 #898

2重のfor loopはAutoFSMを用いてStmt文で実装するとステートマシンに合成されるので、それを示唆します。


Bluespec SystemVerilog (BSV) で StmtFSM ライブラリを使用して、畳み込み演算を行うための自動有限状態機械 (FSM) を実装する例を示します。StmtFSM を使用すると、for ループや条件分岐などを含む複雑な制御フローを簡単に記述でき、これらが複数のクロックサイクルにわたって自動的に実行されるようにすることができます。

以下の例では、畳み込み計算を行うために、2重の for ループを使用しています。この計算は、入力行列とカーネル行列上で畳み込みを実行し、出力行列に結果を格納します。

package ConvolutionFSM;
import StmtFSM::*;

interface Conv2D_IF;
    method Action start();
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
endinterface

module mkConvolutionFSM#(parameter Int#(32) width = 84,
                         parameter Int#(32) height = 84,
                         parameter Int#(32) kWidth = 8,
                         parameter Int#(32) kHeight = 8)(Conv2D_IF);
    // ここで入力行列、カーネル行列、出力行列を定義
    Reg#(Bit#(8)) inputMatrix[0:width-1][0:height-1];
    Reg#(Bit#(8)) kernelMatrix[0:kWidth-1][0:kHeight-1];
    Reg#(Bit#(8)) resultMatrix[0:width-kWidth][0:height-kHeight];
    Reg#(Bool) startFlag <- mkReg(False);

    // 畳み込み計算のFSM
    Stmt main = seq
        // x, y のループ
        for (x <= 0; x < width - kWidth + 1; x <= x + 1) seq
            for (y <= 0; y < height - kHeight + 1; y <= y + 1) seq
                // 畳み込み計算
                action
                    Bit#(32) sum = 0;
                    for (Int#(32) dx = 0; dx < kWidth; dx = dx + 1) begin
                        for (Int#(32) dy = 0; dy < kHeight; dy = dy + 1) begin
                            sum = sum + (inputMatrix[x+dx][y+dy] * kernelMatrix[dx][dy]);
                        end
                    end
                    resultMatrix[x][y] = truncate(sum); // 結果を格納
                endaction
            endseq
        endseq
    endseq;
    
    mkAutoFSM(main);

    method Action start();
        startFlag <= True;
    endmethod
    
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
        return resultMatrix[x][y];
    endmethod

endmodule
endpackage

この例では、畳み込み計算を実行するFSMを mkAutoFSM(main); を用いて生成しています。for ループ内で行列の各要素に対して畳み込み計算を行い、結果を resultMatrix に格納しています。

このコードは概念的なものであり、実際のプロジェクトにそのまま適用する前には、入力行列とカーネル行列をどのように初期化・更新するか、出力行列の結果をどのように利用するかなど、さらに多くの詳細を考慮する必要があります。また、StmtFSM ライブラリを使用するには、Bluespecの環境にそれが含まれていることを確認する必要があります。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (76)

posted by sakurai on November 20, 2024 #897

2次元の畳み込み演算を実行するBSVのコードを要求してみます。


ここでは、2次元の畳み込み演算を行う簡単なBSVモジュールの例を示し、その後でこのBSVコードからVerilogコードを生成する一般的な手順を説明します。

BSVモジュールの例

下記は、2次元畳み込み演算を行う非常に基本的なBSVモジュールの概念的な例です。実際の畳み込み演算には、入力データ、カーネル(フィルター)、および出力データのためのメモリやバッファ、計算を行うためのロジックが必要ですが、ここではその基本的な枠組みだけを示します。

package Convolution2D;

// 簡単のために、具体的なパラメータは省略し、基本的な構造だけを示す
interface Conv2D_IF;
    method Action start();
    method Action loadInput(Bit#(8) input[][]);
    method Action loadKernel(Bit#(8) kernel[][]);
    method Bit#(8) getResult();
endinterface

module mkConvolution2D(Conv2D_IF);
    // 畳み込み計算を行うための内部状態とロジックを定義
    // ここに計算ロジックを実装

    method Action start();
        // 畳み込み計算を開始するためのメソッド
    endmethod

    method Action loadInput(Bit#(8) input[][]);
        // 入力データをロードするためのメソッド
    endmethod

    method Action loadKernel(Bit#(8) kernel[][]);
        // カーネル(フィルター)をロードするためのメソッド
    endmethod

    method Bit#(8) getResult();
        // 計算結果を取得するためのメソッド
        return result; // ここでは`result`は計算結果を保持する変数
    endmethod
endmodule
endpackage

Verilogコードの生成

BSVからVerilogコードを生成するには、Bluespecコンパイラ(bsc)を使用します。コンパイラはBSVソースファイルを入力として受け取り、指定されたモジュールのVerilogコードを生成します。

コマンドラインから以下のように実行します:

\$ bsc -verilog -u [BSVファイル名]

このコマンドは、指定されたBSVファイルからVerilogファイルを生成します。生成されたVerilogファイルは、同じディレクトリに.vの拡張子を持つファイルとして出力されます。

上記のBSVコードやコンパイルプロセスはあくまで概念的なものです。実際に畳み込み演算を行うBSVモジュールを設計する場合、入力データとカーネルのロード、畳み込み演算の実装、出力データの管理など、多くの詳細な設計と実装が必要になります。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (75)

posted by sakurai on November 18, 2024 #896

nn.Conv2d(4, 32, 8, stride=4)に相当するBSVコードを要求してみます。


Bluespec SystemVerilog (BSV)は、ハードウェア記述言語(HDL)であり、主にデジタル回路やシステムの設計に使われます。ニューラルネットワークのような複雑なアルゴリズムをBSVで直接記述するのは一般的ではありませんが、ハードウェアアクセラレーションのためのカスタムコンポーネントを設計する際に用いられることがあります。

nn.Conv2d(4, 32, 8, stride=4)に相当する機能をBSVで実装するには、畳み込み演算を行うモジュールを設計する必要があります。これには、入力データのロード、カーネル(フィルター)の適用、ストライドに従ったサンプリング、出力データの生成など、複数のステップが含まれます。

以下に、畳み込み演算の概念的なBSV実装の概要を示します。これは具体的なコードではなく、畳み込み演算を実装する際のアプローチを説明したものです。実際のプロジェクトでは、性能要件、使用するハードウェアリソース、並列性のレベルなどに応じて設計を調整する必要があります。

// 畳み込みモジュールの概念的な定義
module mkConvolution#(parameter DATA_WIDTH = 8,  // 入力データおよびカーネルのビット幅
                      parameter KERNEL_SIZE = 8,  // カーネルのサイズ (ここでは8x8)
                      parameter STRIDE = 4,      // ストライドのサイズ
                      parameter IN_CHANNELS = 4,  // 入力チャネル数
                      parameter OUT_CHANNELS = 32) // 出力チャネル数
                     (ConvolutionInterface);
    // モジュールの内部実装
    // 入力データのロード、カーネルの適用、ストライドに基づくサンプリング、
    // 出力データの生成などの処理を実装する

    // 入力データおよびカーネルのメモリ管理、畳み込み計算、結果の格納など
    // 必要なレジスタやメモリの宣言

    // 畳み込み演算の実行ロジック
    // forループや条件分岐を使ったデータの処理、計算の実行

endmodule

この概要から、畳み込み演算をBSVで実装するには、多くの詳細を考慮し、ハードウェアに適した設計を行う必要があります。また、高い性能を得るためには、データのフェッチ、計算の並列化、メモリアクセスの最適化など、ハードウェア設計の専門知識が必要となります。

BSVでニューラルネットワークや畳み込み層を実装する場合は、通常、特定のアプリケーションやタスクに特化したカスタムアクセラレータとして設計されます。そのため、全体のシステム設計との整合性や、実装におけるトレードオフを考慮することが重要です。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (74)

posted by sakurai on November 15, 2024 #895

nn.Conv2d(4, 32, 8, stride=4)において、各引数の意味を聞いてみます。


nn.Conv2dはPyTorchで2次元畳み込み層を定義するためのクラスです。このクラスのコンストラクタは複数の引数を受け取りますが、nn.Conv2d(4, 32, 8, stride=4)における各引数の意味は以下の通りです:

  1. in_channels (4): 入力チャネルの数です。この場合、4は入力画像が4つのチャネルを持つことを意味します。強化学習において、これは通常、直近の4フレームをスタックしたものを表し、動きなどの時間的な情報を捉えるために使用されます。

  2. out_channels (32): 出力チャネルの数、または畳み込みによって生成される特徴マップの数です。この場合、32はこの畳み込み層が32枚の特徴マップを生成することを意味します。各特徴マップは異なるフィルター(カーネル)によって得られる画像の特徴を捉えます。

  3. kernel_size (8): カーネル(フィルター)のサイズです。この場合、8x8ピクセルのカーネルサイズを示します。カーネルは入力画像を走査し、局所的な特徴を抽出します。

  4. stride (4): ストライドのサイズです。ストライドはカーネルが入力画像上を移動する際のステップサイズを指し、この場合、カーネルは水平方向および垂直方向に4ピクセルずつ移動します。ストライドが大きいほど、畳み込み後の特徴マップのサイズは小さくなり、計算量を削減できますが、画像の情報損失も大きくなります。

nn.Conv2dのこれらの引数を適切に設定することで、畳み込み層が入力画像から特徴を抽出する方法を制御します。畳み込み層は、画像認識タスクにおいて重要な役割を果たし、画像の局所的なパターンやテクスチャを捉えることができます。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (73)

posted by sakurai on November 14, 2024 #894

atari_dqnのレポジトリにおいて、Q-networkwork構造を記述する以下のpythonコードピースを見つけたので、同様な形式で解説して貰います。

class QNetwork(nn.Module):
    def __init__(self, env):
        super().__init__()
        self.network = nn.Sequential(
            nn.Conv2d(4, 32, 8, stride=4),
            nn.ReLU(),
            nn.Conv2d(32, 64, 4, stride=2),
            nn.ReLU(),
            nn.Conv2d(64, 64, 3, stride=1),
            nn.ReLU(),
            nn.Flatten(),
            nn.Linear(3136, 512),
            nn.ReLU(),
            nn.Linear(512, env.single_action_space.n),

このQNetworkクラスは、PyTorchを使用して定義された畳み込みニューラルネットワーク(CNN)で、特にAtariゲームのようなビジュアルベースの環境で強化学習エージェントのQ値を推定するために設計されています。以下に、各層の役割について説明します。

入力層

このネットワークは、4チャネルの84x84ピクセルの画像を入力として受け取ります。4つのチャネルは、現在のフレームと過去3フレームを表し、動きや時間的な変化を捉えることができます。

第1の畳み込み層

  • : nn.Conv2d(4, 32, 8, stride=4)
  • 機能: この層は、8x8のカーネル(フィルター)を32枚使用し、ストライド(ステップサイズ)は4です。4つの入力チャネル(前の4フレーム)に対して畳み込みを行い、特徴マップを32枚生成します。この畳み込みにより、画像の初期特徴が抽出されます。
  • 活性化関数: ReLU。非線形性を導入し、モデルの表現力を高めます。

第2の畳み込み層

  • : nn.Conv2d(32, 64, 4, stride=2)
  • 機能: 第1層で抽出された特徴マップをさらに精密化します。4x4のカーネルを64枚使用し、ストライドは2です。これにより、より複雑な特徴が抽出されます。
  • 活性化関数: ReLU。

第3の畳み込み層

  • : nn.Conv2d(64, 64, 3, stride=1)
  • 機能: 更に精緻な特徴を抽出するため、3x3のカーネルを64枚使用し、ストライドは1です。この層は、より細かな特徴を捉えることができます。
  • 活性化関数: ReLU。

全結合層

  • : nn.Linear(3136, 512)
  • 機能: 畳み込み層からの出力を平坦化し、3136個のユニットからなるベクトルを入力として受け取ります。この層は512個の隠れユニットを持ち、畳み込み層で抽出された特徴を基に高レベルの特徴を学習します。
  • 活性化関数: ReLU。

出力層

  • : nn.Linear(512, env.single_action_space.n)
  • 機能: 512個の隠れユニットから、行動空間のサイズに対応する数の出力(各行動に対するQ値)を生成します。env.single_action_space.nは、エージェントが選択可能な行動の総数を示します。この層は、各行動の期待される報酬の推定値を出力します。

このネットワークは、入力画像から直接、特定の行動に対するQ値を推定する能力を提供し、エージェントが最適な行動を選択するための情報を生成します。


左矢前のブログ 次のブログ右矢

Pongと強化学習 (72)

posted by sakurai on November 13, 2024 #893

Deep Q-Network (DQN) for Atari GamesというGithubリポジトリがあったので、それに基づいてPongのDQNによるトレーニングをやってみました。

https://github.com/adhiiisetiawan/atari-dqn?tab=readme-ov-file#installation

図893.1は始めたばかりで、緑の学習機のパドルはほとんど打ち返すことができません。ランダムにパドルを動かすだけなので、偶然に打ち返すことはあるものの一点も取れませんでした。

図%%.1
図893.1 エピソード8

図893.2は約26H経過した後で、緑の学習機のパドル操作は上手になっており、あまりボールを逸らすことはなくなりました。コンピュータ相手に大差で勝てるまで上達しています。

図%%.2
図893.2 エピソード5000

図893.3は約26H経過した後のQ値とTD損失の推移グラフです。横軸の単位は時間です。Q値は報酬と関係しているため、大きいほうが良く、TD損失は誤差であるため、小さいほうが良い行動ということができます。

図%%.3
図893.3 Q値とTD損失の推移

左矢前のブログ 次のブログ右矢


ページ: