4
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

2020年に公開したライブラリ振り返り

はじめに

今年もいろいろプログラミングしましたが、その中でも便利かと思ってdubで公開したライブラリの振り返りと紹介の記事です。

なんやかんやで今年7本のパッケージを公開していました。
簡単にですが作った動機や利用例などまとめたいと思います。

作ったもの

年始から作った順で以下のライブラリを公開していました。

ほとんどが実用のために作ったもので、目的に合わせて最低限の機能になっているものが多いです。
かなり小粒なものもあり、これくらいでもいいんだな、というのを感じ取ってもらえればと思います。

紹介

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 |

ポイント

内部的にはメタプログラミングの塊となっていて、任意の構造体配列に対し、それらしく計算できそうなフィールドは全部統計量を計算してくれるようになっています。

数値変数、順序変数、カテゴリ変数の一通りに対応し、floatenum だけでなく、 DurationSysTime など標準ライブラリが提供するほとんどの型に対応しました。
(そういえば BigInt とか Nullable の対応やってなかった…)

ちなみに苦労したのは、どちらかというとテーブル表示する printTable のほうでした。
整形出力というのはあまり書かないなと思った部分で、一度は頑張ってみるかと少しチャレンジした部分です。

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ファイルだけならこれが一番簡単かと思います。
使い方は以下の記事書いているので参照ください。

libmecab

dub : https://code.dlang.org/packages/libmecab
GitHub : https://github.com/lempiji/libmecab

bindbc-mecab をもう少しだけ扱いやすくするラッパーライブラリです。

サンプルと同じもの実行したときのツイートが以下のようになります。

これを使うことでちょっとした日常タスク(ニュース的文字列の解析)が少し高速化され楽になりました。
他にも自然言語処理で使えるので非常に良い感じです。
(いつかは transformersBertTokenizer あたり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への公開となりました。

サンプルなどは以下のツイート画像を見ていただくとなんとなく伝わるかと思います。

ポイント

使っている自動微分はフォワードモードで、サンプルによくあるような二重数を多次元拡張したものという感じです。
少ない変数で多数の計算をこなす場合に有効で、1桁変数しかないような確率分布の最尤推定がしたいような場合にフィットするかと思います。

作ってて感じたのは、フォワードモードだと全部スタックで済むからヒープの利用もなくGCも動かないのでかなり速いんだな~ということと、 constscope といったアノテーションがそのまま付けられる、といったことです。
用途が適切に選べればこういう実装も十分実用になるというのは収穫でした。

あとは関数オブジェクトを作っておけばテンプレートで二重数を渡すのと通常の数値を渡す場合で処理が共通化できることが実証できたのは面白いポイントでした。

これも元は確か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++あたりでもできると思いますが、正直死にそうになって何度も挫折し実現していません。
他にも静的型付けな言語でできるよという話があればぜひ伺いたいです。

このライブラリでやっている形状に関する細かいことは以下の本に書いてあるので興味があればご参照ください。

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の計算から見ると数十倍の効率化ができたりするのでやはり自分で書いてみるのは重要だと感じます。

来年はいくつライブラリが書けるかわかりませんが、引き続きいろいろな処理を効率化できる便利ライブラリを作っていきたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?