Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

手動による学習

では手動でニューラルネットワークを構築していきます。使用するのは先ほどまで実装してきたLinear関数活性化関数二乗平均誤差関数です。これらを組み合わせ、一つの関数としてバックプロパゲーションを行い、勾配を用いてパラメーターを更新していきます。

学習の準備

では学習するに当たってデータを用意します。今回は非線型性のデータの代表的な\(sin\)関数を用意します。ここでは\(y = sin(2\pi\cdot x) + b\)とします。\(b\)はランダムな値で、\(y\)のデータにノイズを与えます。

use ndarray::{array, Array, IxDyn};
use std::array;
use std::time::Instant;
use stucrs::core_new::{F32ToRcVariable, RcVariable};
use stucrs::functions_new::{linear_simple,  sigmoid_simple};

use std::f32::consts::PI;

use stucrs::core_new::ArrayDToRcVariable;
use stucrs::functions_new::mean_squared_error;

use ndarray_rand::rand_distr::{StandardNormal, Uniform};
use ndarray_rand::RandomExt;

fn main() {
    let x_data = Array::random((100, 1), Uniform::new(0.0f32, 1.0));
    //let x2 = array![[11.0f32, 12.0, 13.0], [14.0, 15.0, 16.0]].rv();

    let y_data =
        x_data.mapv(|x| (2.0 * PI * x).sin()) + Array::random((100, 1), Uniform::new(0.0f32, 1.0));

    let x = x_data.rv();

    let y = y_data.rv();

    let I = 1;
    let H = 10;
    let O = 1;

    let mut W1 = (0.01f32 * Array::random((I, H), StandardNormal)).rv();

    let mut B1 = Array::zeros(H).rv();

    let mut W2 = (0.01f32 * Array::random((H, O), StandardNormal)).rv();

    let mut B2 = Array::zeros(O).rv();

    fn predict(
        x: &RcVariable,
        w1: &RcVariable,
        b1: &Option<RcVariable>,
        w2: &RcVariable,
        b2: &Option<RcVariable>,
    ) -> RcVariable {
        let t0 = linear_simple(&x, &w1, &b1);
        let t1 = sigmoid_simple(&t0);
        let y = linear_simple(&t1, &w2, &b2);
        y
    }

    //let y_data = 2.0*x_data.clone() +5.0 + Array::random((100, 1), Uniform::new(0.0f32, 1.0));
    //println!("y_data = {:?}",y_data.clone());

    //let x=x_data.rv();

    //let y=y_data.rv();

    //let mut w = Array::zeros((1,1)).rv();
    //let mut b = Array::zeros(1).rv();
    let lr = 0.2;
    let iters = 1000;
    for i in 0..iters {
        let y_pred = predict(&x, &W1, &Some(B1.clone()), &W2, &Some(B2.clone()));

        

        let mut loss = mean_squared_error(&y, &y_pred);

       

        W1.cleargrad();
        W2.cleargrad();
        B1.cleargrad();
        B2.cleargrad();

        loss.backward();
        

        let w1_data = W1.data();
        let b1_data = B1.data();
        let w2_data = W2.data();
        let b2_data = B2.data();

        let current_grad_w1 = W1.grad().unwrap().data();
        let current_grad_b1 = B1.grad().unwrap().data();
        let current_grad_w2 = W2.grad().unwrap().data();
        let current_grad_b2 = B2.grad().unwrap().data();

        

        W1.0.borrow_mut().data = w1_data - lr * current_grad_w1;
        B1.0.borrow_mut().data = b1_data - lr * current_grad_b1;
        W2.0.borrow_mut().data = w2_data - lr * current_grad_w2;
        B2.0.borrow_mut().data = b2_data - lr * current_grad_b2;
        
        if i % 100 == 0 {
            println!("i = {:?}", i);
            println!("loss = {:?}\n", loss.data());
        }
    }
}

はじめにこのニューラルネットワークで用いる変数(パラメーター)を用意します。\(x\)は学習データ、\(y\)は正解ラベル(xとyのペアのデータが教師データです。)、\(w\)と\(b\)は重み(パラメーター)です。この時パラメーターは乱数を用いて最初の重みを設定します。行列でランダムな値を持つ行列を作成するためにArray型で提供されるndarray-randを使用します。これは依存関係を別途追加しておく必要がありますが、これによりArrayにランダムな値を生成する機能が自動で追加されます。

パラメーターの初期値は0~1の範囲で乱数を用いて設定します。もし初期値をある値(0.0など)に設定すると、すべてのパラメーターが画一的なふるまいをしてしまいます。これでは深層学習の複雑な分類は行えません。

TODO:データ、エポック数の説明追加

次に関数を構築します。Linearと活性化関数のsigmoidを通すだけで、自動でモデルとなる関数が生まれます。それを正解ラベルとともにmean_squared_error関数に流すことで、誤差を求める関数となります。この関数を predict() 関数としてまとめ、推論を行います。

predictで出力したモデルの予測値\(y\)と正解ラベル\(y_pred\)をmean_squared_error関数に渡し、誤差をlossとして求めます。その後、この誤差の変数である loss を起点にバックプロパゲーションを行います。それが loss.backward() です。これにより各パラメーターの勾配を導出します。

最後に求めたパラメーターの勾配をもとに各パラメーターを一つずつ更新します。今回は勾配降下法を用います。更新方法は前の誤差逆伝播法で確認してください。

以上より手動による学習を行うことができます。最後のコードにある println!(“loss = {:?}\n”, loss.data()); より、学習する度に loss すなわち予測値と正解との誤差が小さくなるのがわかります。

線形、非線型変換を繰り返し行うことで、sin関数といった非線型な曲線に対応することができました。

今の状態では、パラメーターの初期化、更新を自分たちで行わなければなりません。これがものすごく深い層となった場合、私たちの手に負えなくなります。これから先は、学習にあたり今までの処理を自動化し、フレームワークとしての機能を加えていきます。