Posts Issued in June, 2024

Pongと強化学習 (35)

posted by sakurai on June 28, 2024 #827

活性化関数についてBSVコードを要求してみます。


Bluespec SystemVerilog (BSV) で活性化関数であるReLU (Rectified Linear Unit) を実装する例を示します。ReLUは、入力が正であればその値を出力し、負であれば0を出力する非常にシンプルな関数です。この特性は、ハードウェアでの実装においても比較的直接的に表現することが可能です。

以下は、簡単なReLU活性化関数のBSVモジュールの例です。この例では、入力として32ビットの固定小数点数を想定していますが、実際のアプリケーションに応じてデータタイプやビット幅は調整する必要があります。

module mkReLU#(Integer dataWidth = 32)(ReLUIface);
    // ReLUIfaceは、入力と出力を含むインターフェイスを提供します。
    method ActionValue#(Bit#(dataWidth)) apply(Bit#(dataWidth) input);
        // ReLUの計算: 入力が0より大きい場合はそのまま、そうでなければ0を返す
        Bit#(dataWidth) output = (input > 0) ? input : 0;
        return output;
    endmethod
endmodule

このコードスニペットでは、mkReLUという名前のモジュールが定義されています。このモジュールは、ReLUIfaceというインターフェイスを通じて、ReLU関数のapplyメソッドを提供します。このメソッドは、入力値を受け取り、ReLUの計算を行った結果を返します。

注意点と拡張

  • データタイプとビット幅: 上記の例では、入力と出力のデータタイプをBit#(dataWidth)としていますが、実際のアプリケーションによっては、符号付き数や固定小数点数を使用する方が適切な場合があります。また、ビット幅(dataWidth)は、使用するデータの範囲や精度に応じて調整してください。
  • 性能と最適化: BSVで記述されたハードウェアモジュールの性能は、FPGAやASICでの実装によって異なります。特に、複数のReLUユニットを並列に実行することで、高いスループットを実現することが可能です。また、リソース使用量を抑えるための最適化も重要です。

この例は、BSVを用いたReLU活性化関数の基本的な実装を示しています。


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

Pongと強化学習 (34)

posted by sakurai on June 27, 2024 #826

次のステップのPointwise Convolutionのコードを要求してみます。


Pointwise Convolution(1x1の畳み込み)は、チャネル間の組み合わせを学習するために使用されます。この操作では、各位置において、入力チャネルの深さ方向に1x1のカーネルを適用して、出力チャネルを生成します。しかし、Bluespec SystemVerilog (BSV) における具体的なコードの提供は直接行えませんが、Pointwise Convolutionの実装に向けた疑似コードと概念説明を提供できます。

Pointwise Convolutionの概念

  • 入力: $H \times W \times C_{in}$ のサイズを持つ入力特徴マップ。ここで、$H$ は高さ、$W$ は幅、$C_{in}$ は入力チャネル数です。
  • カーネル: $1 \times 1 \times C_{in} \times C_{out}$のサイズを持つカーネル。ここで、$C_{out}$ は出力チャネル数です。
  • 出力: $H \times W \times C_{out}$ のサイズを持つ出力特徴マップ。

疑似コード

module PointwiseConvolution#(parameter WIDTH = 256, HEIGHT = 256, C_IN = 1, C_OUT = 1)(
    input bit [WIDTH-1:0][HEIGHT-1:0][C_IN-1:0] inputFeatureMap,
    input bit [C_IN-1:0][C_OUT-1:0] kernel,
    output bit [WIDTH-1:0][HEIGHT-1:0][C_OUT-1:0] outputFeatureMap
);
    // 点畳み込み処理の実装
    for (Integer h = 0; h < HEIGHT; h = h + 1) {
        for (Integer w = 0; w < WIDTH; w = w + 1) {
            for (Integer co = 0; co < C_OUT; co = co + 1) {
                bit convolutionResult = 0;
                for (Integer ci = 0; ci < C_IN; ci = ci + 1) {
                    convolutionResult = convolutionResult + inputFeatureMap[h][w][ci] * kernel[ci][co];
                }
                outputFeatureMap[h][w][co] = convolutionResult;
            }
        }
    }
endmodule

実装のヒント

  • パラメータ化: カーネルサイズやチャネル数など、異なるネットワーク設定に柔軟に対応できるように、モジュールのパラメータを適切に定義します。
  • 並列処理: 出力チャネルごと、あるいはピクセル位置ごとに処理を並列化することで、計算の高速化を図ります。BSVの強力な並列処理能力を活用してください。
  • リソース管理: FPGAのリソース(メモリ、演算ユニットなど)の限界を考慮しながら、効率的な実装を心がけます。不要なデータの転送や演算を避けるために、データの局所性を高める工夫が必要です。

この疑似コードと概念説明が、BSVでPointwise Convolutionを設計する際の出発点となり、より詳細な実装に向けた理解の助けになれば幸いです。


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

Pongと強化学習 (33)

posted by sakurai on June 26, 2024 #825

前稿で、Depthwise ConvolutionとPointwise Convolutionの2つのステップが出てきたので、ひとつずつコードを要求してみます。


直接的なBSV(Bluespec SystemVerilog)のコードを提供することは私の能力を超えていますが、Depthwise Convolutionを設計するための一般的なアプローチと概念を説明し、それに基づいた疑似コードを提供することで、BSVでの実装に向けた理解を深めるお手伝いができるかと思います。

Depthwise Convolutionの概要

Depthwise Convolutionでは、各入力チャネルに対して個別の畳み込みフィルターが適用されます。256x256x1の画像に対してこの処理を行う場合、各ピクセルに対して指定されたカーネル(例えば、3x3や5x5)を適用し、出力画像の対応する位置に畳み込み結果を格納します。1チャネルの画像の場合、Depthwise Convolutionは通常の畳み込みと同様になりますが、処理はチャネルごとに分離して行われます。

疑似コード

以下の疑似コードは、256x256の画像に対する3x3カーネルを用いたDepthwise Convolutionの概念を示しています。この例では、シンプルさを保つためにパディングとストライドは考慮していません。

module DepthwiseConvolution#(parameter WIDTH = 256, HEIGHT = 256, KERNEL_SIZE = 3)(
    input bit [WIDTH-1:0][HEIGHT-1:0] image,
    input bit [KERNEL_SIZE-1:0][KERNEL_SIZE-1:0] kernel,
    output bit [WIDTH-2:0][HEIGHT-2:0] outputImage // 出力サイズはパディングなしのため調整
);
    // 畳み込み処理の実装
    for (Integer row = 1; row < HEIGHT-1; row = row + 1) {
        for (Integer col = 1; col < WIDTH-1; col = col + 1) {
            bit convolutionResult = 0;
            for (Integer kr = -1; kr <= 1; kr = kr + 1) {
                for (Integer kc = -1; kc <= 1; kc = kc + 1) {
                    convolutionResult = convolutionResult + image[row+kr][col+kc] * kernel[kr+1][kc+1];
                }
            }
            outputImage[row-1][col-1] = convolutionResult > threshold; // 閾値を超えたら1, そうでなければ0
        }
    }
endmodule

実装のヒント

  • パラメータ化: 畳み込み層をパラメータ化することで、異なるサイズのカーネルや入力サイズに柔軟に対応できるようにします。
  • 並列処理: BSVの強力な並列処理機能を活用し、複数のピクセルに対する畳み込みを同時に実行できるようにします。特に、FPGAなどのハードウェアでは、この特性を最大限に利用することで高速な処理が可能になります。
  • 最適化: ハードウェア資源の利用を最適化するために、不要な計算の削減やメモリアクセスの最適化に注意を払います。また、固定小数点演算などのテクニックを用いて精度とリソース使用量のバランスを取ることも重要です。

BSVでの実際のコーディングには、Bluespec SystemVerilogの構文と特性に従って、上記の概念を具体的なコードに落とし込む必要があります。


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

Prismの拡張

posted by sakurai on June 25, 2024 #824

bsvのコードハイライティングを行う際に、Prism.jsを利用しました。ただしbsvはなくverilogまでなので、以下のjsを追加して文法を拡張しています。

bsv対応のcssやjsはChatGPTに作成してもらい人間がデバッグしました。ChatGPTはあっという間にほぼ完成度の高いcssやjsを作成するのですが、どうしてもバグが残ります。それをChatGPTに取らせるには却って工数がかかるので、人間がとってやったほうが今のところ全体工数が少なくて済みます。

そのためにはChatGPTをスーパーバイズする必要があるので、ChatGPTよりも知識が上である必要があります。つまり「ChatGPTには知ってることを聞け=知らないことを聞くな」と言うことです。

ソースコード

Prism.languages.bsv = {
  'comment': [
    {
      pattern: /\/\/.*/,
      alias: 'comment'
    },
    {
      pattern: /\/\*[\s\S]*?\*\//,
      alias: 'comment'
    }
  ],
  'module-name-outer': {
    pattern: /(\bmodule\s+|endmodule\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:module|endmodule)\b/,
      'punctuation': /:/,
      'module-name': /\b[a-z_]\w*\b/
    }
  },
  'interface-name-outer': {
    pattern: /(\binterface\s+|endinterface\s*:\s*)([A-Z]\w*)/,
    inside: {
      'keyword': /\b(?:interface|endinterface)\b/,
      'punctuation': /:/,
      'interface-name': /\b[A-Z]\w*\b/
    }
  },
  'package-name-outer': {
    pattern: /(\bpackage\s+|endpackage\s*:\s+|import\s+)([A-Z]\w*)/,
    inside: {
      'keyword': /\b(?:package|endpackage|import)\b/,
      'punctuation': /:/,
      'package-name': /\b[A-Z]\w*\b/
    }
  },
  'rule-name-outer': {
    pattern: /(\brule\s+|endrule\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:rule|endrule)\b/,
      'punctuation': /:/,
      'rule-name': /\b[a-z_]\w*\b/
    }
  },
  'method-name-outer': {
    pattern: /(\bmethod\s+|endmethod\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:method|endmethod)\b/,
      'punctuation': /:/,
      'method-name': /\b[a-z_]\w*\b/
    }
  },
  'return-variable': {
    pattern: /(\breturn\s+)([a-z_]\w*)/,
    inside: {
      'keyword': /\breturn\b/,
      'variable': /\b[a-z_]\w*\b/
    }
  },
  'keyword': {
    pattern: /\b(?:action|endaction|await|clocked_by|default_clock|deriving|else|function|endfunction|import|if|inout|input|interface|endinterface|match|method|endmethod|module|endmodule|no_clock|output|package|parameter|provisos|register|rule|endrule|rules|struct|type|typedef|union|value_of|var|while|seq|endseq|endpackage|return|let|case|endcase|delay|noAction|for|continue)\b/,
  },
  'right-hand-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<=)/,
    alias: 'variable'
  },
  'right-hand-variable-operator': {
    pattern: /(\s*<=\s*)([a-z_]\w*)/,
    inside: {
      'operator': /<=/,
      'variable': /\b[a-z_]\w*\b/
    }
  },
  'generator-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<-\s*)/,
    alias: 'variable'
  },
  'generator-operator': {
    pattern: /(\s*<-\s*)([a-z_]\w*)/,
    inside: {
      'operator': /<-/,
      'function': /\b[a-z_]\w*\b/
    }
  },
  'assignment-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<=)/,
    alias: 'variable'
  },
  'function-definition': {
    pattern: /(?:\bfunction\s+\w+\s+)([a-z_]\w*)/,
    alias: 'function'
  },
  'system-function': {
    pattern: /\$\w+/,
    alias: 'function'
  },
  'function-call': {
    pattern: /(\b[a-z_]\w*)(?=\s*\()/,
    alias: 'function'
  },
  'function-call-no-parens': {
    pattern: /\b[a-z_]\w*(?=\s*;)/,
    alias: 'function'
  },
  'type': {
    pattern: /\b(Bit#\(\d+\)|UInt#\(\d+\)|Int#\(\d+\)|Bool|Maybe|Tuple2|Tuple3|Tuple4|Tuple5|Tuple6|Tuple7|Tuple8|Vector|Vector#\(\d+\)|Integer|Real|String|Fmt|Reg|FIFO|Clock|Reset|Inout|Action|ActionValue|Rules|UInt|Int)\b/,
    alias: 'type'
  },
  'directive': {
    pattern: /\(\*[\s\S]*?\*\)/,
    inside: {
      'comment': {
        pattern: /(\(\*|\*\))/,
        alias: 'comment'
      },
      'string': {
        pattern: /[\s\S]+/,
        alias: 'string'
      }
    }
  },
  'number': {
    pattern: /\b\d+(\.\d+)?([eE][+-]?\d+)?\b/,
    alias: 'number'
  },
  'class-name': {
    pattern: /\b[A-Z][a-zA-Z0-9_]*\b/,
    alias: 'class-name'
  },
  'string': {
    pattern: /(^|[^\\])(["'`])(?:\\.|(?!\2)[^\\\r\n])*\2/
  },
  'operator': {
    pattern: /(\+|\-|\!|\~|\&|\~\&|\||\~\||\^|\~\^|\^\~|\*|\/|\%|\<\<|\>\>|\<\=|\>\=|\<|\>|\=\=|\!\=|\&\&|\|\|)/
  }
};

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

BSVの例題 (4)

posted by sakurai on June 24, 2024 #823

もともとinuseが不定だったのはバグと言えます。ただ、初期値を設定する代わりに最初にputdownシーケンスを入れたら良いと考え、実行したところうまく動作しました。ついでにChatGPTにコメントを付けてもらいました。以下にソースコードを示します。

ソースコード

//
// CBG SQUANDERER : Dining Philosophers In Bluespec
// (C) 2012 David Greaves, University of Cambridge

// 必要なモジュールをインポート
import StmtFSM::*;

// スプーンのインターフェースを定義
interface Spoon_if;
   method Action pickup;             // スプーンを持ち上げるメソッド
   method Action putdown;            // スプーンを置くメソッド
   method Action putnumber(UInt#(5) value); // スプーンに番号を設定するメソッド
endinterface

// スプーンのモジュールを定義
(* synthesize *)
module spoon (Spoon_if);
   Reg#(Bool) inuse <- mkReg(?);       // スプーンの使用状況を表すレジスタ
   Reg#(UInt#(5)) number <- mkReg(?);  // スプーンの番号を保持するレジスタ

   // スプーンを持ち上げるメソッドの実装
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

   // スプーンを置くメソッドの実装
   method Action putdown;
     inuse <= False;
   endmethod

   // スプーンに番号を設定するメソッドの実装
   method Action putnumber(UInt#(5) value);
     number <= value;
   endmethod
endmodule

// 哲学者のベンチモジュールを定義
(* synthesize *)
module philoBENCH (Empty);
   // 5つのスプーンインスタンスを作成
   Spoon_if spoon0 <- spoon; 
   Spoon_if spoon1 <- spoon; 
   Spoon_if spoon2 <- spoon; 
   Spoon_if spoon3 <- spoon; 
   Spoon_if spoon4 <- spoon; 

   // 5人の哲学者インスタンスを作成
   Diner_if din0 <- mkDiner(0, 7, 7, spoon0, spoon1);
   Diner_if din1 <- mkDiner(1, 6, 4, spoon2, spoon1); // <---- 逆順で持ち上げる
   Diner_if din2 <- mkDiner(2, 5, 9, spoon2, spoon3);
   Diner_if din3 <- mkDiner(3, 6, 6, spoon3, spoon4);
   Diner_if din4 <- mkDiner(4, 8, 8, spoon4, spoon0);

   // タイマーのレジスタを作成
   Reg#(UInt#(15)) timer <- mkReg(1000);

   // タイマーをカウントダウンするルール
   rule foo;
      timer <= timer - 1;
      if (timer == 0) $finish;
   endrule
endmodule: philoBENCH

// ランダム数生成器のインターフェースを定義
interface Random_if;
   method ActionValue#(UInt#(15)) gen; // ランダムな値を生成するメソッド
endinterface    

// ランダム数生成器のモジュールを定義
module mkRandom_gen #(UInt#(15) seed) (Random_if);
   Reg#(UInt#(15)) prbs <- mkReg(seed); // ランダム数を保持するレジスタ

   // ランダムな値を生成するメソッドの実装
   method ActionValue#(UInt#(15)) gen;
      prbs <= (prbs << 1) | (((prbs >> 14) ^ (prbs >> 13)) & 1); // シフトとXORを使用してランダム値を生成
      return prbs;
   endmethod
endmodule 

// 哲学者のインターフェースを定義
interface Diner_if;
endinterface

// 哲学者のモジュールを定義
module mkDiner #(UInt#(5) number, UInt#(15) on, UInt#(15) seed) (Spoon_if left, Spoon_if right, Diner_if i);
   Reg#(Bool) eating <- mkReg(?);      // 食事中かどうかを表すレジスタ
   Reg#(UInt#(15)) timer <- mkReg(0);  // タイマーのレジスタ
   Random_if random<- mkRandom_gen(seed); // ランダム数生成器のインスタンスを作成

   // タイマーをカウントダウンするルール
   rule foo (timer != 0);
      timer <= timer - 1;
   endrule

   // 哲学者の動作シーケンスを定義
   Stmt seq_behaviour = (seq
      while (True) seq
         action // 初期状態を確定するサイクル(食事の後だったのを前に持ってきた)
            eating <= False;
            left.putdown; 
            right.putdown;
            left.putnumber(?);
            right.putnumber(?);
         endaction
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         left.pickup; // 左のスプーンを持ち上げる
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         right.pickup; // 右のスプーンを持ち上げる
         action
            eating <= True; // 食事を開始
            timer <= on;    // 食事時間を設定
            left.putnumber(number);  // スプーンに哲学者の番号を設定
            right.putnumber(number); // スプーンに哲学者の番号を設定
         endaction
         await(timer==0);
      endseq
   endseq);

   // シーケンスを自動的に実行するFSMを作成
   mkAutoFSM(seq_behaviour);
endmodule

以下にシミュレーション波形を示します。putdownは終了処理だった(pickup→putdown)ものをシーケンスの最初に持ってくる(putdown→pickup)ことにより、初期状態が確定し不定が継続しなくなりました。

図%%.1
図823.1 verilogシミュレーション

図らずも元のコードのバグを発見しましたが、あえて不定を設定しないと見つからないため、シミュレーションは不定も込みで実施したほうが良いと思います。


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

BSVの例題 (3)

posted by sakurai on June 21, 2024 #822

どの哲学者がどのスプーンを使っているかを見やすくするため、スプーンを使用中に哲学者の番号を付けることにします。初期状態は'X'ですが、bsvのシミュレータであるbsimでは'X'は取れないようです。

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -sim philo.bsv
$ bsc -sim -e philoBENCH -o philoBENCH.exe
$ ./philoBENCH.exe -V bsim.vcd
$ gtkwave -A bsim.vcd

図%%.1
図822.1 bsimシミュレーション

spoonのナンバーを見ると、哲学者の取得するスプーンの様子が良くわかります。最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)を用いて食事をしています。

verilogシミュレーション

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -verilog philo.bsv
$ bsc -verilog -e philoBENCH -o philoBENCH.exev
$ ./philoBENCH.exev +bscvcd=verilog.vcd
$ gtkwave -A verilog.vcd

図%%.2
図822.2 verilogシミュレーション

最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)に食事をしています。不定が赤なので確定信号が良く分かります。

なお当初全面Xになって動作しなかったのですが、原因はspoonモジュールのinuseの初期値がXとなっていたためでした。Falseにしたところ正しく動作しました。

(* synthesize *)
module spoon (Spoon_if) ;
   Reg#(Bool) inuse <- mkReg(?);
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

この記述で分かるように、inuseの初期値は?(bsvで言うX)にも関わらず、次の行のpickupというメソッドに if (!inuse);条件が付いています。条件が不定なのでpickupメソッドをコールすると結果が不定となり、不定が伝搬することでシミュレーションが真っ赤になってしまいます。


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

BSVの例題 (2)

posted by sakurai on June 20, 2024 #821

食事する哲学者全員が左手→右手の順にスプーンを取るシミュレーションです。当然ですが全員左手にスプーン持った状態でデッドロックします。

図%%.1
図821.1 シミュレーション1

ソースを修正し、2番目の哲学者のみ右手→左手の順のシミュレーションです。この場合はランダム的に全員が食事することができます。

図%%.1
図821.2 シミュレーション2

spoon0とテストベンチは親子モジュールとなっており、その間にpickupとputdownのEN-RDYインタフェースが自動生成されます。まずspoon0にpickup指示を出すと、RDYが落ちます(=busy)。同時にspoon0のinuseがTrueとなり使用中を示します。哲学者の食事が終了するとputdown指示がされ、inuseはFalseになります。

スプーンを取得する場合は当然使用中で無いことを確認してから取得するコードとなっていますが、一方、スプーンをリリースする場合は、直前まで自分が使用していることはシーケンスから明らかなため特に使用中であることは確認していません。


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

BSVの例題

posted by sakurai on June 19, 2024 #820

強化学習シリーズの途中ですが、BSVの例題を見つけたので、紹介します。

「哲学者の食事」をBSVでシミュレーションするものです。

一度にスプーンは片手で1つしか持てず、2つ取ろうとすると、右手⇒左手、もしくは左手→右手の順にスプーンを取るしかありません。ここで、全員が右手⇒左手のように取ると、全員が右手に1つを持ったまま、左側のスプーンが空くのを永久に待つことになり、いわゆるデッドロックが起きます。

ソースは一人の哲学者が他人とは反対の順で取るようにプログラムしてあり、それにより全員がなんらかの形で食事をすることができる様子を示しています。

ソースコード

//
// CBG SQUANDERER : Dining Philosophers In Bluespec
// (C) 2012 David Greaves, University of Cambridge

import StmtFSM::*;

interface Spoon_if;
   method Action pickup;
   method Action putdown;
endinterface

(* synthesize *)
module spoon (Spoon_if) ;
   Reg#(Bool) inuse <- mkReg(?);
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod
   method Action putdown;
     inuse <= False;
   endmethod
endmodule

(* synthesize *)
module philoBENCH (Empty) ;
   Spoon_if spoon0 <- spoon; 
   Spoon_if spoon1 <- spoon; 
   Spoon_if spoon2 <- spoon; 
   Spoon_if spoon3 <- spoon; 
   Spoon_if spoon4 <- spoon; 
   Diner_if din0 <- mkDiner (7, 7, spoon1, spoon0); // <---- Reverse pickup
   Diner_if din1 <- mkDiner (6, 4, spoon1, spoon2);
   Diner_if din2 <- mkDiner (5, 9, spoon2, spoon3);
   Diner_if din3 <- mkDiner (6, 6, spoon3, spoon4);
   Diner_if din4 <- mkDiner (8, 8, spoon4, spoon0);
   Reg#(UInt#(15)) timer <- mkReg(5000);
   rule foo;
      timer <= timer - 1;
      if (timer == 0) $finish;
   endrule
endmodule: philoBENCH

interface Random_if;
   method ActionValue#(UInt#(15)) gen;
endinterface    

module mkRandom_gen #(UInt#(15) seed) (Random_if);
   Reg#(UInt#(15)) prbs <- mkReg(seed);
   method ActionValue#(UInt#(15)) gen;
      prbs <= (prbs << 1) | (((prbs >> 14) ^ (prbs >> 13)) & 1);   
      return prbs;
  endmethod
endmodule 

interface Diner_if;
endinterface

module mkDiner #(UInt#(15) on, UInt#(15) seed) (Spoon_if left, Spoon_if right, Diner_if i) ;
   Reg#(Bool) eating <- mkReg(?);    
   Reg#(UInt#(15)) timer <- mkReg(0);
   Random_if random <- mkRandom_gen(seed);
   rule foo (timer != 0);
      timer <= timer - 1;
   endrule
   Stmt seq_behaviour = (seq
      while (True) seq
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31;
         endaction
         await(timer== 0);
         left.pickup;
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31;
         endaction
         await(timer== 0);
         right.pickup;
         action
            eating <= True;
            timer <= on;
         endaction
         await(timer==0);
         eating <= False;
         left.putdown; 
         right.putdown;
      endseq
   endseq);
   mkAutoFSM(seq_behaviour);
endmodule

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

Pongと強化学習 (32)

posted by sakurai on June 18, 2024 #819

CNNの各段について細かく聞いてみます。まずDepthwise Separable Convolutionについて。


Bluespec SystemVerilog (BSV) で Depthwise Separable Convolution(深さ方向分離可能な畳み込み)の具体的なコーディング例を提供するのは、直接的なコードの提供が可能な範囲を超えるため難しいです。しかし、深さ方向分離可能な畳み込みを実装する際の基本的な考え方と、BSVでのハードウェア設計における一般的なアプローチについて説明できます。

Depthwise Separable Convolution の概要

Depthwise Separable Convolution は、通常の畳み込み操作を2つの部分に分割します。

  1. Depthwise Convolution: 各入力チャネルに対して個別に畳み込みを行います。つまり、入力のチャネルごとにフィルターが適用され、出力のチャネル数は入力と同じになります。
  2. Pointwise Convolution: その後、1x1の畳み込み(Pointwise Convolution)を使用して、Depthwise Convolution の出力のチャネルを組み合わせて新たな特徴を生成します。このステップでチャネル間の情報が統合されます。

BSV でのアプローチ

BSVで Depthwise Separable Convolution を設計する際には、以下のようなステップを考慮します。

  • モジュール定義: Depthwise Convolution と Pointwise Convolution のための個別のモジュールを定義します。各モジュールは、特定の入力、フィルター、および出力を持つように設計します。
  • パラメータ化: フィルターサイズ、ストライド、パディングなどのパラメータをモジュールの引数として設定できるようにします。これにより、異なるサイズや設定で畳み込みを再利用できます。
  • 並列処理: Depthwise Convolution では、異なる入力チャネルに対する畳み込みを並列に実行できます。BSV の強力な並列処理機能を利用して、各チャネルの処理を同時に行います。
  • ポイントワイズ畳み込み: Pointwise Convolution では、各位置での1x1畳み込みを行い、チャネル間の特徴を結合します。このステップも、複数の出力位置に対して並列に実行できます。

コーディングのヒント

実際のコーディングに際しては、以下の点に注意してください。

  • フィルターと入力の格納: FPGA のオンチップメモリ(例えば BRAM)を使用して、畳み込みフィルターと入力データを効率的に格納します。
  • データフローの管理: データフローを適切に管理し、パイプライン処理を最大限に活用することで、レイテンシを最小限に抑え、スループットを最大化します。
  • テストと検証: シミュレーション環境を使用して、畳み込みモジュールの挙動を検証し、正確な計算が行われていることを確認します。

直接的なBSVコードの例は提供できませんが、上記のガイドラインは、BSVを使用したハードウェア設計におけるDepthwise Separable Convolution の実装に向けた出発点となるはずです。ハードウェアでのCNN実装は高度な知識と経験を要しますので、小規模な部分から始めて徐々にシステムを構築していくことをお勧めします。


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

Pongと強化学習 (31)

posted by sakurai on June 17, 2024 #818

正解ラベルとの誤差を伝搬するとのことですが、ビデオゲームには正解ラベルは有りません。そこを追及します。


Pongゲームのような強化学習タスクでは、確かに伝統的な教師あり学習とは異なり、具体的な「正解ラベル」がありません。強化学習では、エージェントは環境からのフィードバック(報酬)に基づいて学習します。このフィードバックは、エージェントが取った行動の結果として得られる報酬であり、その行動がどれだけ「良い」か、あるいは「悪い」かを示します。

強化学習の学習プロセス

  1. エージェントが環境と相互作用: エージェントは、ある状態である行動を選択し、その行動を実行します。
  2. 報酬の受領: 行動の結果として、エージェントは環境から報酬を受け取ります。Pongゲームでは、ボールを打ち返した場合に正の報酬を、ボールを失った場合に負の報酬を受け取るかもしれません。
  3. ポリシーの更新: 受け取った報酬をもとに、エージェントはそのポリシー(状態に対する行動の選択戦略)を更新します。目標は、期待される報酬の合計を最大化するポリシーを見つけることです。

ポリシー学習とバックプロパゲーション

強化学習においては、CNNなどのニューラルネットワークがエージェントのポリシーを表現するために使用される場合があります。このような場合、CNNは状態(例えば、Pongゲームの画面)から行動を決定するための関数として機能します。CNNを通じてポリシーを最適化するプロセスでは、バックプロパゲーションが使用されることがありますが、このときの「損失関数」は、伝統的な教師あり学習で使用されるものとは異なり、報酬を基にしたものになります。

強化学習における損失関数

強化学習における損失関数は、エージェントの行動選択がどれだけ「良い」結果をもたらしたかを評価するために設計されます。例えば、行動価値関数(Q関数)を用いるQ学習では、Q値の予測と得られた報酬(及び将来のQ値)との差(TD誤差)を最小化することを目指します。このプロセスを通じて、ニューラルネットワークの重みが更新され、エージェントのポリシーが徐々に改善されていきます。

結論

Pongゲームのような強化学習タスクでは、「正解ラベル」は存在せず、エージェントは環境からの報酬に基づいて行動を学習します。CNNを使用してポリシーを表現し、バックプロパゲーションを用いて学習を行う場合でも、このプロセスは報酬に基づいて行われ、最終的には最適なポリシーを見つけることを目指します。


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


ページ: