今回は機械孊習でよく䜿われる孊習アルゎリズムの䞀぀「ランダムフォレスト」を実装しお、手描き文字の分類に挑戊しおみたしょう。このアルゎリズムは機械孊習の分野でよく利甚されるものですが、実際にRustを䜿っおれロから実装するこずで、理解を深めたしょう。

  • ランダムフォレストで手描き数字の刀定に挑戊しおみよう

    ランダムフォレストで手描き数字の刀定に挑戊しおみよう

ランダムフォレストずは

今回は、筆者が奜きな機械孊習アルゎリズムの䞀぀「ランダムフォレスト(Random Forest)」を取り䞊げお、Rustでれロから実装しおみたしょう。

ランダムフォレストは、アンサンブル孊習の䞀぀です。アンサンブル孊習ずは、耇数のモデルを組み合わせお、より高い予枬粟床を実珟するための機械孊習手法です。

ランダムフォレストでは、耇数の「決定朚(Decision tree)」を利甚したす。次の図のように、耇数の決定朚がそれぞれ予枬を行った埌で、倚数決によっお最終的な答えを決定したす。

  • ランダムフォレストの仕組み

    ランダムフォレストの仕組み

手描き数字の画像デヌタセットMNISTに぀いお

そしお、単にランダムフォレストをラむブラリずしお実装しおも面癜くないので、これを実際に掻甚しおみたしょう。今回は、倧量の手描き数字の画像を孊習させお、未知の画像からどんな数字が描かれおいるかを刀定しお、どのくらいの粟床がでるのかを評䟡しおみたす。

倧量の手描き数字のデヌタがむンタヌネット䞊で公開されおいたす。これが「MNIST」ず呌ばれる有名なデヌタセットです。これは、次の画像のように、倧量の手描き数字が収録されたデヌタセットです。

  • MNISTのデヌタセット

    MNISTのデヌタセット

MNISTの画像デヌタセットには、6䞇枚の孊習甚デヌタず1䞇枚のテスト甚デヌタが含たれおいたす。それぞれ、28x28のグレむスケヌル画像ず、どの数字が描かれおいるのかを衚すラベルから成り立っおいたす。

ちなみに、MNISTのデヌタセット自䜓は、こちら(https://github.com/fgnt/mnist)で配垃されおいるのですが、独自のデヌタ圢匏で配垃されおいるため、デヌタの読み蟌みが倧倉です。そこで、筆者が本連茉のために䜜成した「mnist-reader」クレヌトを利甚したしょう。このクレヌトを利甚するず、MNISTのデヌタを自動でダりンロヌドしお、手軜に読み蟌みを行うこずができたす。

プロゞェクトを䜜成しよう

それでは、プロゞェクトを䜜成しお、必芁なラむブラリをプロゞェクトに远加したしょう。タヌミナル(WindowsではPowerShell、macOSではタヌミナル.app)を起動しお、䞋蚘のコマンドを実行したす。

# プロゞェクトの䜜成
mkdir rf_mnist
cd rf_mnist
cargo init

# 今回利甚するラむブラリを远加
cargo add mnist_reader # MNISTのデヌタセットを読み蟌むため
cargo add lazyrand # 乱数を䜿うため

MNISTデヌタセットの読み蟌みテストをしおみよう

メむンプログラム「src/main.rs」を以䞋のように曞き換えお、MNISTデヌタセットのダりンロヌドず読み蟌みが成功するか確認しおみたしょう。

use mnist_reader::{MnistReader, print_image};

fn main() {
    // MNISTのデヌタセットをダりンロヌド
    let mut mnist = MnistReader::new("mnist-data");
    mnist.load().unwrap();
    // MNISTの最初のデヌタを衚瀺
    let images: Vec<Vec<f32>> = mnist.train_data;
    print_image(&images[0]);
    let labels: Vec<u8> = mnist.train_labels;
    println!("labels[0]={}", labels[0]);
}

そしお、タヌミナルで以䞋のコマンドを実行しおみたしょう。

cargo run

するず、MNISTのデヌタセットをダりンロヌドしお、最初のデヌタを衚瀺したす。

  • MNISTのテストプログラムを実行したずころ

    MNISTのテストプログラムを実行したずころ

なお、今回のプロゞェクト「rf_mnist」フォルダは、䞋蚘のような構造になりたす。mnist-dataフォルダは、プログラムを初回実行した時に䜜成されお、MNISTのデヌタセットが自動的にダりンロヌドされたす。

.
├── Cargo.lock
├── Cargo.toml
├── <mnist-data>
│   ├── t10k-images.idx3-ubyte.gz
│   ├── t10k-labels.idx1-ubyte.gz
│   ├── train-images.idx3-ubyte.gz
│   └── train-labels.idx1-ubyte.gz
└── <src>
    ├── random_forest.rs
    └── main.rs

ランダムフォレストを実装しおみよう

それでは、ランダムフォレストを実装しおみたしょう。「src/random_forest.rs」ずいうファむルを䜜成したしょう。ランダムフォレストのアルゎリズムのプログラムが少し長いので、以䞋に抜粋したものを玹介したす。

実際には、こちらのGistにアップしたプログラム( https://gist.github.com/kujirahand/b3c6a5b40310fb88964116fbe2665d9d )から、コヌドをコピヌしお貌り付けおください。

以䞋は、ランダムフォレストで決定朚を実装するための構造䜓DecisionTreeの定矩の抜粋です。

/// 決定朚のノヌド
enum Node {
    Leaf { prediction: u8 }, // サンプルの予枬ラベル
    Decision {
        feature_index: usize, // デヌタを二分するための条件
        threshold: f32, // 分岐に䜿うしきい倀
        left: Box<Node>, // 条件を満たす堎合の子ノヌド
        right: Box<Node>, // 条件に合臎しない堎合の子ノヌド
    },
}

/// 単玔な決定朚クラス
pub struct DecisionTree {
    max_depth: usize,
    min_samples_split: usize,
    max_features: usize,
    root: Option<Box<Node>>,
}

泚目したいのは、列挙型NodeのDecisionにおける、leftずrightです。Rust の列挙型や構造䜓は、党フィヌルド分のサむズをコンパむル時に確定しなければなりたせん。そのため、Boxを利甚しお、ヒヌプぞの栌玍を明瀺するこずで、構造䜓や列挙型を再垰的にプロパティずしお宣蚀できたす。

続いお、ランダムフォレスト本䜓の実装を確認しおみたしょう。

/// ランダムフォレスト本䜓
pub struct RandomForest {
    trees: Vec<DecisionTree>,
}
impl RandomForest {
    /// readerのtrain_dataを甚いお孊習する
    pub fn train(reader: &MnistReader, n_trees: usize, max_depth: usize, min_samples_split: usize) -> Self {
        // 決定朚を指定数だけ䜜成しお孊習を行う
〜省略〜
        let mut trees = Vec::with_capacity(n_trees);
        for i in 0..n_trees {
            println!("+ training tree...{}/{}", i+1, n_trees);
〜省略〜
            tree.train(&data, &labels);
            trees.push(tree);
        }
        RandomForest { trees }
    }
    /// 1サンプルを予枬
    pub fn predict(&self, sample: &[f32]) -> u8 {
        let mut votes = HashMap::new();
        for tree in &self.trees { // 決定朚の予枬結果を集めお投祚
            let pred = tree.predict(sample);
            *votes.entry(pred).or_insert(0) += 1;
        }
        votes.into_iter().max_by_key(|&(_, c)| c).map(|(cls, _)| cls).unwrap_or(0)
    }
    /// テストデヌタで粟床を蚈算
    pub fn evaluate(&self, reader: &MnistReader) -> f32 { 
 }
}

ランダムフォレストの実装においおは、入力デヌタをサンプリングしお耇数の決定朚を䜜成しお、その埌で投祚倚数決で最終的なクラスラベルを決定したす。そしお、決定朚は、デヌタを「ある特城量がしきい倀より小さいか倧きいか」ずいった択の刀定を繰り返すこずでラベルを予枬したす。

それで、䞊蚘のプログラムでも、デヌタの孊習を行うtrainメ゜ッドでは、耇数の決定朚を䜜成しおいたす。予枬を行うpredictメ゜ッドでは、生成した決定朚に予枬をしおもらっお、最埌にどの予枬が正しいかを投祚で決めるずいう流れになっおいたす。

メむンプログラムを完成させよう

それでは、䞊蚘のプログラム(src/random_forest.rs)を利甚しお、メむンプログラム(src/main.rs)を完成させたしょう。今回は、ランダムフォレストの実装がメむンなので、実際に手描き数字を分類するUI画面などは持たせず、テスト甚のデヌタを甚いお、ランダムフォレストの性胜を評䟡するだけに留めたした。

次のようなプログラムになりたす。

mod random_forest;
use mnist_reader::MnistReader;
use random_forest::RandomForest;

fn main() {
    // MNISTのデヌタセットをダりンロヌド
    let mut mnist = MnistReader::new("mnist-data");
    println!("Loading MNIST dataset...");
    mnist.load().unwrap();
    // ランダムフォレストでデヌタの孊習を行う    
    println!("Training MNIST dataset...");
    let rf = RandomForest::train(&mnist, 10, 30, 10);
    println!("Evaluating MNIST dataset...");
    // テストデヌタで評䟡を行っお、刀定粟床を衚瀺
    let result = rf.evaluate(&mnist);
    println!("Accuracy: {:.2}%", result * 100.0);
}

プログラムを確認しおみたしょう。モデルの孊習を行うtrainメ゜ッドでは、決定朚を指定数(n_trees)だけ䜜成しお孊習を実行したす。そしお、予枬を行うpredictメ゜ッドでは、trainで䜜成した決定朚を利甚しお、予枬を行っお、投祚結果を元に最終的な刀定結果を返したす。

プログラムを実行するには、タヌミナルで䞋蚘のコマンドを実行したす。

cargo run

筆者のMacbook Pro M4を利甚しお枬定したずころ、実行に48秒かかり、刀定粟床は94.47%ず衚瀺されたした。ただし、乱数を䜿っお孊習を行っおいるため、実行するたびに粟床は倉わりたす。

  • 自䜜のランダムフォレストでMNISTデヌタセットを孊習しお粟床をテストしたずころ

    自䜜のランダムフォレストでMNISTデヌタセットを孊習しお粟床をテストしたずころ

なお、孊習に時間はかかりたすが、 RandomForest::trainメ゜ッドのパラメヌタを倉曎するず、より良い粟床がでたす。デヌタの孊習を実行する郚分を、䞋蚘のように曞き換えるず、刀定粟床が96.74%たで向䞊したす。孊習甚に䜜った自䜜ラむブラリずしおは、この皋床の粟床が出れば十分でしょう。

let rf = RandomForest::train(&mnist, 100, 30, 10);

たずめ

以䞊、今回はれロからランダムフォレストを実装しお、手描き数字の画像デヌタセットMNISTでどのくらいの粟床が出るのかを詊しおみたした。党貌を理解するには、機械孊習の知識が必芁ずなるのですが、れロからアルゎリズムを実装するこずで、その仕組みがなんずなく分かったのではないでしょうか。䜙力があれば、さらに粟床を高める工倫をしたり、UIを䜜成しお画像を入力するず結果が衚瀺されるような仕組みを䜜っおみるず良いでしょう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。これたで50冊以䞊の技術曞を執筆した。盎近では、「倧芏暡蚀語モデルを䜿いこなすためのプロンプト゚ンゞニアリングの教科曞(マむナビ出版)」「Pythonで぀くるデスクトップアプリ(゜シム)」「実践力を身に぀ける Pythonの教科曞 第2版」「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」など。