🗂

1ファイルでニューラルネットワークを作れる MiniDNN を試してみた。

2022/01/31に公開

はじめに

C++ ヘッダオンリーのライブラリが大好きなので、日々 GitHub を散策しています。

https://zenn.dev/mattn/articles/b43444214e06f3
https://zenn.dev/mattn/articles/e871dab58be002
https://zenn.dev/mattn/articles/7f994dd1198cde

今日も探索していたら MiniDNN というライブラリを見つけたので試してみました。

MiniDNN とは

https://github.com/yixuan/MiniDNN

MiniDNN は Eigen を使ったニューラルネットワーク構築ライブラリです。行列の操作は Eigen::Matrix 等を使い、各レイヤの構築からバッチの実行、推論まで行えます。

特徴としては

  • ヘッダーのみで移植性が高い
  • CPU だけど高速
  • モジュール化されていて拡張可能
  • 詳細なドキュメントを提供
  • DNN がどのように機能するかを理解するのに役立つ
  • DNNの良い部分と汚い部分の両方を学んで実践する素晴らしい機会を提供 (笑)

サンプル書いてみた

いつもながら iris のサンプル

#include <iostream>
#include <string>
#include <algorithm>
#include <MiniDNN.h>

static std::vector<std::string>
split(std::string& fname, char delimiter) {
  std::istringstream f(fname);
  std::string field;
  std::vector<std::string> result;
  while (getline(f, field, delimiter)) {
    result.push_back(field);
  }
  return result;
}

int
main() {
  std::ifstream ifs("iris.csv");
  std::string line;
  std::getline(ifs, line);

  std::vector<double> rows;
  std::vector<std::string> names;
  while (std::getline(ifs, line)) {
    // sepal length, sepal width, petal length, petal width, name
    auto cells = split(line, ',');
    rows.push_back(std::stof(cells.at(0)));
    rows.push_back(std::stof(cells.at(1)));
    rows.push_back(std::stof(cells.at(2)));
    rows.push_back(std::stof(cells.at(3)));
    names.push_back(cells.at(4));
  }
  Eigen::Map<Eigen::MatrixXd> x(rows.data(), 4, rows.size()/4);

  std::map<std::string, std::size_t> labels;
  std::vector<double> counts;
  std::vector<std::string> tmp;
  for (auto& name : names) {
    std::size_t &label = labels[name];
    if (label == 0) {
      label = labels.size();
      tmp.push_back(name);
    }
    counts.push_back((double) (label - 1));
  }
  names = tmp;

  Eigen::Map<Eigen::MatrixXd> y(counts.data(), 1, (int) counts.size());
  y /= 3;

  MiniDNN::Network net;
  net.add_layer(new MiniDNN::FullyConnected<MiniDNN::Identity>(4, 4));
  net.add_layer(new MiniDNN::FullyConnected<MiniDNN::ReLU>(4, 4));
  net.add_layer(new MiniDNN::FullyConnected<MiniDNN::Identity>(4, 1));
  net.set_output(new MiniDNN::RegressionMSE());

  MiniDNN::RMSProp opt;
  opt.m_lrate = 0.001;

  MiniDNN::VerboseCallback callback;
  net.set_callback(callback);

  net.init(0, 0.01);

  net.fit(opt, x.matrix(), y.matrix(), 100, 1000);
  Eigen::MatrixXd pred = net.predict(x.matrix());

  for (auto v : pred.colwise()) {
    std::cout << v[0] << " " << names[(int)(v[0] * 3)] << std::endl;
  }

  return 0;
}

試してないけど Eigen なので SYCL のセレクタを使えば GPU も行けたりするんかもしれない。

おわりに

インタフェースが keras ぽく分かりやすいので、keras 触った事がある方であれば、すぐ触れると思います。

Discussion