LoginSignup
20
14

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-06

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);
}

$ 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

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

#[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

参考文献

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

リポジトリ

参考文献

20
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
14