rust
DeepLearning

[WIP]Rustで「ゼロからつくるDeep Learning」

More than 1 year has passed since last update.

Deep Learningを学ぶべくゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装を読んだ。理解を深めるのとRustの学習を兼ねて、本書に書かれた内容をRustに書いてみる。なお筆者はDeep LearningにもRustにも、行列にも精通していない。

p26 2.3.2 重みとバイアスの導入

パーセプトロンのANDゲートを実装するのに、行列の計算に置き換える箇所。
行列の内積後、バイアスを加える処理をするところ。
行列の計算にはnagebraを使うことにした。

#[test]
fn page_26() {
    let x = Matrix1x2::new(0.0, 1.0);
    println!("{}", x);
    let w = Vector2::new(0.5, 0.5);
    println!("{}", w);
    let b = Vector1::new(-0.7);  // x * w の結果はVector1になる。プリミティブ型を直接演算はできないので、Vector1に持ち上げておく。
    println!("{}", b);
    let sum = x * w + b;
    println!("{}", sum);

    let expect = Vector1::new(-0.2);
    let range = Vector1::new(0.0001);
    assert!(sum < expect + range);
    assert!(sum > expect - range);
}
$ cargo test page_26 -- --nocapture
running 1 test
  ┌             ┐
  │ 0.000 1.000 │
  └             ┘

  ┌       ┐
  │ 0.500 │
  │ 0.500 │
  └       ┘

  ┌        ┐
  │ -0.700 │
  └        ┘

  ┌        ┐
  │ -0.200 │
  └        ┘

test page_26 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out

Vector2は 2x1の行列のエイリアスになっているので、Vector2同士の内積は計算できない。
Matrix1x2をつかっていますが、Vector2をtransposeしてもOKです。

ソースコード

参考文献

p48 シグモイド関数の実装

ベクターにシグモイド関数を適用できればよさそうなので、スカラー値用のシグモイド関数を用意してmapメソッドがつかうとよさそう。

こんな感じで使えるようにする。

Vector3::new(-1.0, 1.0, 2.0).map(sigmoid)

シグモイド関数の実装はこんな感じ。expの計算はf64しかなさそうなので、f64のみの実装にする。

fn sigmoid(x: f64) -> f64 {
    1.0 / (1.0 + f64::exp(-x))
}

つかってみる。

#[test]
fn page_49() {
    let x = Vector3::new(-1.0, 1.0, 2.0).map(sigmoid);
    // let x = Vector3::new(-1.0, 1.0, 2.0).sigmoid();
    println!("{}", x);

    let range = 0.00000001;

    let expect_x1 = 0.26894142;
    assert!(x[0] < expect_x1 + range);
    assert!(x[0] > expect_x1 - range);

    let expect_x2 = 0.73105858;
    assert!(x[1] < expect_x2 + range);
    assert!(x[1] > expect_x2 - range);

    let expect_x3 = 0.88079708;
    assert!(x[2] < expect_x3 + range);
    assert!(x[2] > expect_x3 - range);
}

https://github.com/eiel/reading-deeplearning-for-rust/blob/4a9f0ffe5174265ade457a5c9f540553fd08d42d/src/main.rs#L8-L31

$ cargo test page_49 -- --nocapture 
running 1 test

  ┌                    ┐
  │ 0.2689414213699951 │
  │ 0.7310585786300049 │
  │ 0.8807970779778823 │
  └                    ┘


test page_49 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 3 filtered out

参考書の結果と一致する。

参考文献

p53 多次元配列

NumPyをつかった多次元配列の扱いを学ぶ部分です。

#[test]
fn page_53_1() {
    let a = Vector4::new(1.0,2.0,3.0,4.0);
    println!("{}", a);

    // 次元数の取得
    println!("{}", a.nrows()); // => 4

    // 行列の形状
    let (rows, columns) = a.shape();
    println!("{}, {}", rows, columns); // => 4, 1
}
$ cargo test page_53_1 -- --nocapture
running 1 test
         
   1.000 
   2.000 
   3.000 
   4.000 
         

4
4, 1
test page_53_1 ... ok

https://github.com/eiel/reading-deeplearning-for-rust/blob/67a99aabf3e9b1f2db4240e1cd3f19627809a826/src/main.rs#L26-L35

別の形状の行列を確認します。

#[test]
fn page_53_2() {
    let b = Matrix3x2::new(
        1,2,
        3,4,
        5,6);
    println!("{}", b.nrows()); // => 3
    let (rows, columns) = b.shape();
    println!("{}, {}", rows, columns); // =>s 3, 2
}
$ cargo test page_53_2 -- --nocapture
running 1 test
3
3, 2
test page_53_2 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out

https://github.com/eiel/reading-deeplearning-for-rust/blob/67a99aabf3e9b1f2db4240e1cd3f19627809a826/src/main.rs#L37-L46

参考文献

http://nalgebra.org/rustdoc/nalgebra/core/struct.Matrix.html

p64 3.4.3 実装のまとめ

3層ニューラルネットワークを実装しようという部分。

ネットワークの構築関数であるforward配下のように実装。

fn forward(
    w1: Matrix2x3<f64>,
    w2: Matrix3x2<f64>,
    w3: Matrix2<f64>,
    b1: Matrix1x3<f64>,
    b2: Matrix1x2<f64>,
    b3: Matrix1x2<f64>,
    x: Matrix1x2<f64>,
) -> Matrix1x2<f64> {
    let a1 = x * w1 + b1;
    let z1 = a1.map(sigmoid);
    let a2 = z1 * w2 + b2;
    let z2 = a2.map(sigmoid);
    let a3 = z2 * w3 + b3;
    identity_function(a3)
}

fn identity_function<T>(x: T) -> T {
    x
}

関数合成で実装してみたくなるところだけど、先に勧めたいので割愛。
書籍内では2引数であるが、第1引数は展開した状態にした。1引数を受け取るクロージャを返す用にすると意図が合うようになりそうだけど、割愛。

実行してみます。

#[test]
fn page_64() {
    let w1 = Matrix2x3::from_rows(
        &[
            RowVector3::new(0.1, 0.3, 0.5),
            RowVector3::new(0.2, 0.4, 0.6),
        ],
    );
    let b1 = Matrix1x3::new(0.1, 0.2, 0.3);
    let w2 = Matrix3x2::from_rows(
        &[
            RowVector2::new(0.1, 0.4),
            RowVector2::new(0.2, 0.5),
            RowVector2::new(0.3, 0.6),
        ],
    );
    let b2 = Matrix1x2::new(0.1, 0.2);
    let w3 = Matrix2::from_rows(&[RowVector2::new(0.1, 0.3), RowVector2::new(0.2, 0.4)]);
    let b3 = Matrix1x2::new(0.1, 0.2);

    let x = Matrix1x2::new(1.0, 0.5);

    let y = forward(w1, w2, w3, b1, b2, b3, x);
    println!(" = {}", y);
    let expect_y1 = 0.31682708;
    let expect_y2 = 0.69627908;
    let range = 0.00000001;
    assert!(y[0] < expect_y1 + range);
    assert!(y[0] > expect_y1 - range);
    assert!(y[1] < expect_y2 + range);
    assert!(y[1] > expect_y2 - range);
}
$ cargo test page_64 -- --nocapture
running 1 test
 = 
  ┌                                       ┐
  │ 0.3168270764110298 0.6962790898619668 │
  └                                       ┘


test page_64 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out

読みやすくなるようにfrom_rowsをつかった。

https://github.com/eiel/reading-deeplearning-for-rust/blob/798efb46038a45333191510ddcd2a40aab2aca50/src/main.rs#L12-L31
https://github.com/eiel/reading-deeplearning-for-rust/blob/798efb46038a45333191510ddcd2a40aab2aca50/src/main.rs#L96-L127

リポジトリ

参考文献