MATHGRAM

主に数学とプログラミング、時々趣味について。

[Rust] 月型の2値データを生成する (機械学習の準備)

scikit-learnのmake_moons()を移植した感じです.

データの生成ができたので, 次から機械学習の様々な手法を本格的に実装していこうかと思います. こういうデータを見るとSVMがやりたくなるのでSVMからやろうかな.

あと, 次からはジェネリクスで関数や構造体を定義することを心がけたいです. 最初っからそうしろって感じですが, 一個一個理解して形にしていくほうが自分にあってるっぽいので・・・.

生成できたデータ

まずは結果から.
f:id:ket-30:20170204050247p:plain:w400:h400

データ数は50個ずつで合計100個です. xとyのそれぞれに, 平均0, 分散0.1のノイズを加えています.
このノイズは以前自分で作った

www.mathgram.xyz

こいつをそのまま使いました.

一番下で全コード張りますが, mainはこんな感じになってます.

fn main() {
    let generator = DataGenetator { ..DataGenetator::default() };

    // 本当はこのように P (座標), y(データのラベル) として
    // 受けたいが, gnuplot でラベルを認識させる方法がちょっと謎だったので
    // とりあえず, ラベルごとにベクトルを受けてます.
    //let (P, y) = generator.make_moons();

    let ((nox,noy), (nix,niy)) = generator.make_moons();
    let mut fg = Figure::new();

    //fg.axes2d().points(&P.x, &P.y, &[Color("blue")]);
    fg.axes2d()
      .points(&nox, &noy, &[Color("blue")])
      .points(&nix, &niy, &[Color("red")]);
    fg.set_terminal("png", "moons.png");
    fg.show();
}

今度からDataGeneraterに, 他のデータ生成方法を実装していこうかと思ってます.

また, Vecの要素それぞれに対してcosやsinの演算をしたかったので, 初めてtraitを使ってみました. まだまだ分かりきってないですが…

cos, sinの実装

// ベクトル専用の数値計算traitを作る.
trait Mathematics {
    fn cos(self) -> Vec<f32>;
    fn sin(self) -> Vec<f32>;
}

// 数値計算の実装
// numpyのようにベクトルのそれぞれの要素に対して演算してほしい.
impl Mathematics for Vec<f32> {

    fn cos(self) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(x.cos());
        }
        v
    }

    fn sin(self) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(x.sin());
        }
        v
    }
}

このMathematicsトレイトによって,

let x = vec![0.1, 0.4, 0.7];
x.cos()

のように, 要素それぞれにコサインを作用させることができるようになります.

あとは構造体のデフォルト値を設定する方法を以前の記事から変更しました.
これは長くなるので別の記事でまとめます.

全コード

さすがに長すぎるので, 次からモジュールとしてファイルを分けていこうと思います.

//extern crate nalgebra;
extern crate gnuplot;
extern crate rand;

//use nalgebra::Vector2;
use gnuplot::{Figure, Color};
use rand::{thread_rng, Rng};

use std::default::Default;
use std::f32;
use std::vec::Vec;

fn main() {
    let generator = DataGenetator { ..DataGenetator::default() };

    // 本当はこのように P (座標), y(データのラベル) として
    // 受けたいが, gnuplot でラベルを認識させる方法がちょっと謎だったので
    // とりあえず, ラベルごとにベクトルを受けてます.
    //let (P, y) = generator.make_moons();

    let ((nox,noy), (nix,niy)) = generator.make_moons();
    let mut fg = Figure::new();

    //fg.axes2d().points(&P.x, &P.y, &[Color("blue")]);
    fg.axes2d()
      .points(&nox, &noy, &[Color("blue")])
      .points(&nix, &niy, &[Color("red")]);
    fg.set_terminal("png", "moons.png");
    fg.show();
}

// 正規分布
// ノイズをつけるために必要
#[derive(Debug)]
struct NormalDistribution {
    mu : f32,
    sigma : f32,
}

impl Default for NormalDistribution {
    fn default() -> Self {
        NormalDistribution { mu: 0f32, sigma: 1f32}
    }
}

impl NormalDistribution {
    fn new(mu: f32, sigma: f32) -> NormalDistribution {
        NormalDistribution{mu: mu, sigma: sigma,}
    }

    fn calc(&mut self, x: &Vec<f32>) -> Vec<f32> {
        let mut y: Vec<f32> = vec![];
        for e in x {
            let a = - (e - self.mu).powi(2) / (2.0_f32 * self.sigma.powi(2));
            let b = (2.0_f32 * f32::consts::PI).sqrt() * self.sigma;
            y.push( a.exp() / b );
        }
        y
    }

    pub fn show_info(&mut self){
        println!("------- Information -------");
        println!("Average: {}, Variance: {}", self.mu, self.sigma);
        println!("---------------------------");
    }

    pub fn norm_random(&mut self, n: i32) -> Vec<f32> {
        let mut y = vec![];
        let mut rng = rand::thread_rng();
        for i in 0..n {
            let u1: f32 = rng.gen_range(0f32,1f32);
            let u2: f32 = rng.gen_range(0f32,1f32);
            let a = (- 2f32 * u1.ln()).sqrt();
            let b = (2f32*f32::consts::PI*u2).cos();
            y.push( self.mu + self.sigma*a*b )
        }
        y
    }

    fn plot(&mut self, x: Vec<f32>) {
        let mut fg = Figure::new();
        let y: Vec<f32> = self.calc(&x);

        fg.axes2d()
        .lines(&x, &y, &[Color("blue")]);
        fg.set_terminal("png", "NormalDistribution.png");
        fg.show();
    }
}


// ベクトル専用の数値計算traitを作る.
trait Mathematics {
    fn cos(self) -> Vec<f32>;
    fn sin(self) -> Vec<f32>;
    fn add(self, Vec<f32>) -> Vec<f32>;
    fn add_float(self, f32) -> Vec<f32>;
    fn sub(self, f32) -> Vec<f32>;
}

// 数値計算の実装
// numpyのようにベクトルのそれぞれの要素に対して演算してほしい.
impl Mathematics for Vec<f32> {

    fn cos(self) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(x.cos());
        }
        v
    }

    fn sin(self) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(x.sin());
        }
        v
    }

    fn add_float(self, i: f32) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(x + i);
        }
        v
    }

    fn add(self, u: Vec<f32>) -> Vec<f32> {
        let mut v = vec![];
        for (i, j) in self.iter().zip(u.iter()) {
            v.push(i + j);
        }
        v
    }

    fn sub(self, i: f32) -> Vec<f32> {
        let mut v = vec![];
        for x in &self {
            v.push(i - x);
        }
        v
    }

}

// 座標を表す構造体
// 結局使ってない.
struct Points {
    x: Vec<f32>,
    y: Vec<f32>,
}


// 連続なベクトルの生成
fn linspace(start: f32, limit: f32, n: i32) -> Vec<f32> {
    if n <= 1 {
        vec![limit]
    } else {
        let mut v = vec![];
            let s = start;
            let d = (limit - start) / (n - 1) as f32;
            for i in (0..n) {
                v.push(s + i as f32 * d);
            }
        v
    }
}

#[derive(Debug)]
struct DataGenetator {
    n_samples: i32,
    shuffle: bool,
    noise: bool,
    //random_state: Option<f32>,
}

impl Default for DataGenetator {
    fn default() -> Self {
        //DataGenetator { n_samples: 100, shuffle: true, noise: None, random_state: None}
        DataGenetator { n_samples: 100, shuffle: true, noise: true}
    }
}

impl DataGenetator {
    //fn make_moons (&self) -> (Points, Vec<i32>) {
    fn make_moons (&self) -> ((Vec<f32>, Vec<f32>),(Vec<f32>, Vec<f32>)) {
        let n_samples_out = self.n_samples / 2;
        let n_samples_in = self.n_samples - n_samples_out;

        let outer_circ_x = (linspace(0f32, f32::consts::PI, n_samples_out)).cos();
        let outer_circ_y = (linspace(0f32, f32::consts::PI, n_samples_out)).sin();

        let inner_circ_x = (linspace(0f32, f32::consts::PI, n_samples_in)).cos().sub(1f32);
        let inner_circ_y = (linspace(0f32, f32::consts::PI, n_samples_in)).sin().sub(1f32).add_float(-0.5f32);

        //outer_circ_x.append(&mut inner_circ_x);
        //outer_circ_y.append(&mut inner_circ_y);

        // ノイズの付加
        // 分散は小さくする.
        let mut norm = NormalDistribution{ sigma:0.2f32, ..NormalDistribution::default() };
        let nox = outer_circ_x.add(norm.norm_random(self.n_samples));
        let noy = outer_circ_y.add(norm.norm_random(self.n_samples));
        let nix = inner_circ_x.add(norm.norm_random(self.n_samples));
        let niy = inner_circ_y.add(norm.norm_random(self.n_samples));

        //let P = Points{ x: nx, y: ny};

        let mut y0 = vec![0; n_samples_in as usize];
        let mut y1 = vec![1; n_samples_out as usize];

        y0.append(&mut y1);

        //(P, y0)
        ((nox, noy), (nix, niy))
    }

}

まとめ

今回の収穫は

  • traitを初めて使った.
  • 自作の構造体にDefaultの値を設定する方法を理解した.
  • 結果的に使ってないけど, 演算子オーバーロードを理解した.

って感じです.

次回くらいからやっと本格的な機械学習手法の実装に取り組めそうです.

ある程度かけるようになってくると, 知っている書き方だけでどうにかしようとしてしまうので, 文法のinputと実装のoutputのバランスに気をつけて学習を続けていきたいです.

以上です.