はじめに
今年もいろいろプログラミングしましたが、その中でも便利かと思ってdubで公開したライブラリの振り返りと紹介の記事です。
なんやかんやで今年7本のパッケージを公開していました。
簡単にですが作った動機や利用例などまとめたいと思います。
作ったもの
年始から作った順で以下のライブラリを公開していました。
ほとんどが実用のために作ったもので、目的に合わせて最低限の機能になっているものが多いです。
かなり小粒なものもあり、これくらいでもいいんだな、というのを感じ取ってもらえればと思います。
- lantern
- 構造体配列に対し統計情報を計算、表示する関数類を提供するライブラリ
- https://code.dlang.org/packages/lantern
- bindbc-onnxruntime
- 機械学習ランタイムであるONNXRuntimeのバインディング
- https://code.dlang.org/packages/bindbc-onnxruntime
- bindbc-mecab
- BindBC系列のインターフェースを持つ日本語形態素解析のMecabのバインディング
- https://code.dlang.org/packages/bindbc-mecab
- libmecab
- bindbc-mecab を扱いやすくするラッパーライブラリ
- https://code.dlang.org/packages/libmecab
- lbfgsd
- 数値最適化のL-BFGS法実装+Backward式自動微分を提供するライブラリ
- https://code.dlang.org/packages/lbfgsd
- golem
- mir-algorithmベースの自動微分フレームワーク
- https://code.dlang.org/packages/golem
- libfuture
- Fiberベースの非同期処理を実現する await のコンセプト実装ライブラリ
- https://code.dlang.org/packages/libfuture
紹介
lantern
dub : https://code.dlang.org/packages/lantern
GitHub : https://github.com/lempiji/lantern
今年2月のD言語もくもく会で作業していて作った簡便なデータ解析用のライブラリです。
任意の構造体の配列に対し、フィールド毎に平均などを計算してくれるものとなっています。
あとは英語でREADMEを書くとその日のうちに終わらないなと思ってガッツリ日本語で書いたのですが、これによって予期せず2月29日公開となり、言われて「あ~4年に1度か~」となったのでよく記憶に残っています。
こちら元ネタがあり、Python のデータ解析で使う Pandas というライブラリが提供している要約統計量を取るための describe
という関数を参考としています。
ローカルでデータをざっくり眺めたい、CSV出力して加工するのは面倒などあったため、とりあえずこれでいいかと同じようなことができる describe
関数と出力用の printTable
関数を作った次第です。
何よりもお手軽重視で、サンプルから抜粋すると以下のように使えます。
思考停止で使えるようにするため、やることは describe(dataArray)
からの printTable
の1パターンのみです。
import std;
import lantern;
void main()
{
// make 10 records
auto dataset = iota(10).map!(n => Test(uniform01()));
// get stats
auto result = describe(dataset);
writeln(result.value.min);
writeln(result.value.max);
writeln(result.value.mean);
writeln(result.value.std);
writeln(result.value.p25);
writeln(result.value.p50);
writeln(result.value.p75);
writeln();
// print as table
printTable(result);
}
struct Test
{
double value;
}
0.079207
0.865132
0.391348
0.237936
0.235163
0.376355
0.53751
| | value |
|-------|----------|
| count | 10 |
| min | 0.079207 |
| max | 0.865132 |
| p25 | 0.235163 |
| p50 | 0.376355 |
| p75 | 0.53751 |
| mean | 0.391348 |
| std | 0.237936 |
ポイント
内部的にはメタプログラミングの塊となっていて、任意の構造体配列に対し、それらしく計算できそうなフィールドは全部統計量を計算してくれるようになっています。
数値変数、順序変数、カテゴリ変数の一通りに対応し、float
や enum
だけでなく、 Duration
や SysTime
など標準ライブラリが提供するほとんどの型に対応しました。
(そういえば BigInt とか Nullable の対応やってなかった…)
ちなみに苦労したのは、どちらかというとテーブル表示する printTable
のほうでした。
整形出力というのはあまり書かないなと思った部分で、一度は頑張ってみるかと少しチャレンジした部分です。
east_asian_width
というライブラリ依存がありますが、これが全角文字などの幅もよろしく計算してくれるなかなか便利なライブラリで、これなしには作れなかったなと思います。大変感謝しています。
-
east_asian_width
bindbc-onnxruntime
dub : https://code.dlang.org/packages/bindbc-onnxruntime
GitHub : https://github.com/lempiji/bindbc-onnxruntime
Microsoftが作っているONNXRuntimeのC言語APIをD言語用バインディングとして仕立てたものになります。
ONNXRuntimeというのは、いわゆる深層学習のモデルを高速に実行できるようにした形式およびランタイムのことです。
通常はPythonで各種フレームワークを使って学習したものから、何かしら実行しやすい形式にエクスポートして使うのですが、その1つという感じです。
(PythonのままFlask等使ってWebAPIに仕立てることもあるかもしれませんが…)
ちょっとしたモデルを画像処理に使いたい用事があり、D言語で同じことできないのはなぜなのだ??と思って調べて作ったものがこれです。
比較的初歩のモデルである squeezenet
を使ったサンプルを用意しているので興味があれば参照してみてください。
また、もう少し扱いやすくするラッパーがないとダメだなと思っているのですが、ハンドルの扱いが結構特殊でうまい解決策が見いだせていません。
何かアイデアがあればぜひコメントやプルリク等いただければと思います。
ポイント
BetterC
モードでも動く BindBC
という統一的バインディング機構にならって作っており、静的リンクと動的リンクの両方に対応しています。
フォルダ構造などもある程度定まっており、複数バージョン対応も視野に入れているので結構ちゃんと作ったつもりです。
我ながら今後BindBCのライブラリを作ることになったらこれを参考にしようかなと思っています。
bindbc-mecab
dub : https://code.dlang.org/packages/bindbc-mecab
GitHub : https://github.com/lempiji/bindbc-mecab
日本語形態素解析のMecabをD言語用のバインディングとして仕立てたものになります。
こちらも bindbc-onnxruntime
と同じでBindBC系列の構成となっています。
後述の libmecab
の下地となっているもので、これ自体は特筆することがほとんどありません…
ポイント
C向けヘッダーからDへの変換にあたり、本格的に dstep
という変換ツールを使用しました。
ヘッダーファイルが1ファイルだけならこれが一番簡単かと思います。
使い方は以下の記事書いているので参照ください。
- C言語のヘッダーファイルをD言語用に変換する dstep の使い方
libmecab
dub : https://code.dlang.org/packages/libmecab
GitHub : https://github.com/lempiji/libmecab
bindbc-mecab
をもう少しだけ扱いやすくするラッパーライブラリです。
サンプルと同じもの実行したときのツイートが以下のようになります。
とりあえずMeCabでこれくらいの感じになれば他言語のバインディングと大差ないかな(D言語でちょっとした機械学習ネタを高速化しようと企んでいる) pic.twitter.com/QdqHky5Sph
— lempiji@思秋期 (@lempiji) July 24, 2020
これを使うことでちょっとした日常タスク(ニュース的文字列の解析)が少し高速化され楽になりました。
他にも自然言語処理で使えるので非常に良い感じです。
(いつかは transformers
の BertTokenizer
あたりDで書きたいと思っていますが、果たしていつになることやら…)
ポイント
bindbc-mecab
を前提としているので、ロードとアンロードの処理を自動化するために shared static this()
と shared static ~this()
を使用しました。
shared static this()
系を使うことで、プログラムの起動時と終了時に自動的に処理を行ってくれるので、使う側としては何も考えなくて良いため楽に使えるようになります。
(プロセスの生存期間を考えると別にアンロードはしなくても良い気がしてきた)
lbfgsd
dub : https://code.dlang.org/packages/lbfgsd
GitHub : https://github.com/lempiji/lbfgsd
自動微分+数値最適化における L-BFGS 法をD言語で実装したものです。
準ニュートン法の一種で、メモリ消費を抑えた(Limited-memory) BFGS 法ということで様々なライブラリで既定値として用いられる手法です。興味のある方はググってみてください。
元はExcel付属のソルバーが遅くてつらかったので作ったライブラリで、数年前から手元にはあったのですが何かの拍子にdubへの公開となりました。
サンプルなどは以下のツイート画像を見ていただくとなんとなく伝わるかと思います。
よし、D言語で自動微分とL-BFGS組み合わせたソルバーで最小二乗法も最尤推定もそれっぽく書けるようになった。
— lempiji@思秋期 (@lempiji) July 10, 2020
まぁイマドキ珍しいフォワードモードの自動微分なんで精度しょぼいんですけど、そこそこ綺麗に書けるんで遊び程度には使え…る…(´・ω・`) pic.twitter.com/WT2HGD0849
ポイント
使っている自動微分はフォワードモードで、サンプルによくあるような二重数を多次元拡張したものという感じです。
少ない変数で多数の計算をこなす場合に有効で、1桁変数しかないような確率分布の最尤推定がしたいような場合にフィットするかと思います。
作ってて感じたのは、フォワードモードだと全部スタックで済むからヒープの利用もなくGCも動かないのでかなり速いんだな~ということと、 const
や scope
といったアノテーションがそのまま付けられる、といったことです。
用途が適切に選べればこういう実装も十分実用になるというのは収穫でした。
あとは関数オブジェクトを作っておけばテンプレートで二重数を渡すのと通常の数値を渡す場合で処理が共通化できることが実証できたのは面白いポイントでした。
これも元は確かGoogle作のC++向け数値最適化ライブラリで、Gauss-Newton法を高速にやる際に微分形式は明らかだから~みたいな理由で利用者にこれと同じ方法で実装させていたのを見たから真似したものではあります。
オペレーターオーバーロードなど含め、自由度が高い言語だからこその表現力だな~と感じた次第です。
golem
dub : https://code.dlang.org/packages/golem
GitHub : https://github.com/lempiji/golem
最近作りはじめた自動微分+計算グラフのフレームワークです。
Pythonで色々書いているとつらいところが多く、やはりD言語で書きたいというのもあって先は長そうですが書き始めました。
コンセプトは、PyTorch寄りのDefine by runで静的型付け言語の機能を最大限に活用したい、というものです。
今のところCPUでしか動きませんが、個人的には時系列データの解析などが目的だったのでGPUを使わなくても結構頑張れています。
なお、使い方などは大体全部READMEに書いてるのと、サンプルで簡単に為替予測する等をやっているので興味がある方は頑張れば読めると信じています。
一応最小二乗法で線形回帰を書くと以下のようになります。
import golem;
// prepare datasets with dynamic batch sizes
auto data = tensor!([0, 2])([
[0.1, 0.2],
[0.1, 0.3],
[0.15, 0.4],
[0.2, 0.5],
]);
auto label = tensor!([0, 1])([
[0.4],
[0.5],
[0.6],
[0.7],
]);
// init
auto linear = new Linear!(double, 2, 1);
auto optimizer = createOptimizer!SGD(linear);
// train
foreach (epoch; 0 .. 10_000)
{
auto y = linear(data);
auto diff = label - y;
auto loss = mean(diff * diff);
optimizer.resetGrads();
loss.backward();
optimizer.trainStep();
}
// result
auto y = linear(data);
writeln(y.value);
ポイント
主に以下の点が静的言語の機能を使って上手くできたところかなと思っています。
- 微分値を持たないテンソルを静的な型として表現できる
- テンソルの形状が型情報に含まれており各種演算で静的に検証される
C++あたりでもできると思いますが、正直死にそうになって何度も挫折し実現していません。
他にも静的型付けな言語でできるよという話があればぜひ伺いたいです。
このライブラリでやっている形状に関する細かいことは以下の本に書いてあるので興味があればご参照ください。
- D言語と深層学習 / メタプログラミングを使った形状安全な演算
libfuture
dub : https://code.dlang.org/packages/libfuture
GitHub : https://github.com/lempiji/libfuture
D言語の強化プロジェクト案で、世にいう async / await と呼ばれる機能がほしいという話があり、そもそも非同期処理をちゃんと書くとどうなるんだっけ?というのでアイデア整理していたらできたライブラリです。
UFCSを使った後置await構文と JavaScriptのPromise風に使える Future
クラスを提供するものです。
サンプルとしては以下のような感じになります。
Future!string getHtmlAsync(string url) {
return FutureFactory.startNew!string({
import std.net.curl : get;
return get(url);
});
}
Future!size_t sumLength(string[] urls) {
return new Future!size_t({
size_t length = 0;
foreach (url; urls) {
const html = getHtmlAsync(url).await;
length += html.length;
}
return length;
});
}
ポイント
非同期処理といえば、D言語的には標準ライブラリの Fiber
というクラスが思い当たります。
これはスタックフルコルーチンなので、非同期処理が何段ネストしていようが強制的に処理を中断し、一発で呼び元に戻すような制御フローが実現できます。
これを使って非同期処理を書いてやろうと思うと、Fiberの中で別のFiberを呼び出して状態見て待機して…というのを繰り返していくことになります。
面倒なので、これを簡便に書けるようにしたのが await
という関数です。
まとめ
今年は結構公開したなと思って振り返ってみると、全体的に機械学習などで使うライブラリでした。
主目的は日ごろ実用しているタスクの効率化ですが、Excelの計算から見ると数十倍の効率化ができたりするのでやはり自分で書いてみるのは重要だと感じます。
来年はいくつライブラリが書けるかわかりませんが、引き続きいろいろな処理を効率化できる便利ライブラリを作っていきたいと思います。