2019年4月17日水曜日

クロネコヤマトの人工知能

おじさん、最近amazonでペットボトル入りの麦茶を買ったんです。
いつも宅配ロッカーに届けてもらえるのですが、なぜか宅配ロッカーに届けてもらえない。そこでWebから再配達の依頼をすることに。

いつものようにあまりにひどい会社のシステムは実名で書かせていただきます。
まず、クロネコメンバーズというWebサイトにアカウントを作って再配達依頼をすることに。

Lineで送られてきたサイトから、iphoneでアカウント作ろうとしても途中から画面真っ白で進まない。たぶんね、Lineから開くブラウザって、新しいタブ開けないんだよ。そんなの一回くらいテストすればわかるだろ。

途中から進めないので、Safariブラウザから検索してアカウントを作ることに。

メールアドレスとIDとパスワードを登録するらしい。
しかもIDとパスワードは「半角英数」しかつかえないらしい。
しかし、どうみてもドットとかの記号も入力できる。
はぁ、半角英数の意味わかってんの?
そして、アカウント登録がすんでも「idかパスワードが違います」と言われ、なぜかログインできない。なんなんだこのサイト。

しょうがないのでIDを忘れた方のボタンをクリック。
しばらくするとメールが送られてきました。
どうもIdはメールアドレスらしい。
なんなんだよ。ならIDなんのために入力したんだよ。
しかもログイン画面はIDを入力してくださいとなっている。

まぁいいや、IDがわかったのでメールアドレスとパスワードを入力。
それでも「idかパスワードが違います」と言われる。
今度はパスワード再発行の手続き。
めんどくさいなぁ。

画面にはPoweredByOracleと表示されてます。
オラクルって会社がこんなシステム作ってんのね。

パスワードをもう一回登録してやっと「クロネコメンバーズ」というサイトにログインができました。

早速伝票番号をいれて、宅配ロッカーに再配達を選ぶことに。


はぁ??なんで宅配ロッカーに配達を選べないんだよ。
そもそも不在通知には「このお荷物は店頭では受け取れない」って書いてあるからわざわざWebで再配達依頼をしているんだよ。なのにWebでは店頭で受け取れと。
おじさん怒りMAX。何時間かかるんだよ。

なのでチャットでお問合せ。
どうも人工知能がお答えしてくれるらしい。


まったく頓珍漢な答えが。
あんたバカなの?


とりあえず人工知能に切れときました。
メールの問い合わせ先ないし。どうやったらWebから宅配ロッカーに再配達してもらえるのでしょう。
そんなに再配達したくないなら、うけとってやんねーよ。amazonでキャンセルしてやる。
つづく。



2019年4月15日月曜日

最小のpytorchを作る。

前回のブログで、PyTorchのlibtorchをC++言語から使用する方法を紹介しました。
このlibtorchはtorch.dll、c10.dll、caffe2.dllの3つのモジュールからできています。torch.dllが20Mバイト、caffe2.dllが30Mバイトあります。
うーんでかすぎる。
ソースコードを見る限り、どうみてもcaffe2.dllなんて使っていないように見る。

また、C++の部分はソースコードがビルドされたら自動でモジュールを登録するように実装されているため、全部をビルドしなくてもある程度動くようになっています。

これ、もっと小さくできるんじゃない?
もっと小さくしないと組み込みの製品やスマホアプリに使えないじゃないか。

そこで本日はMNISTが動く、最小のlibtorchをCmakeを使わず、必要なファイルだけを手動でVisualStudioに追加していき、手動でビルドしてみたいと思います。


まず、c10、Aten、cpuinfo、eigen、TH、THNNの各ディレクトリのソースを全部手動でVisaualStudioに追加します。
ついでに、初回ビルド時にツールによって生成される、C++のヘッダーファイルとソースコードをbuild/c10、build/atenのディレクトリから取ってきて追加します。


次に今回はdllを作るわけではないので、ソースコード中のdllimportやdllexportのdefine部分を何か所か以下のように修正します。

修正前:

#define TH_CPP_API __declspec(dllimport)
#define TH_CPP_API __declspec(dllimport)

修正後:

#define TH_CPP_API
#define TH_CPP_API

ファイル名にcudaやmiopenやhipがついているものはGPU用のコードなのでビルドしなくて大丈夫です。
cudaなどの名前がついていないソースをビルドをします。
そんなにファイル数が多くないので、簡単ですね。

一か所「caffe2/hogeohoge.hがインクルードできません。」と言われますがcaffe2のソースなんて使っていないのでコメントアウトします。
一部の関数がダブっているとコンパイラに怒られますが、意外とスムーズに全部のソースをビルドすることができます。


上記のものをビルドすると、リンク時に「torch::*が見つかりません。」と多量のリンクエラーがでます。
torch/csrcディレクトリの中のapi,jit,autogradをビルドしていきます。
ここに一つ問題が。
torchのディレクトリはPythyon用のインタフェースコードとC++API用のコードが混在しています。
なので、#include<Pyton.h>などがないものをビルドしていきます。
jitの中にある、export.cppとimport.cppはProtoBufを使うようになっています。サンプルのMNIST.cppなど、jitのファイル入出力を行わない場合は空のオブジェクトを返す何もしない関数に修正します。

torchディレクトリには初回ビルド時に自動生成される以下の巨大ファイルがあります。

VariableTypeEverything.cpp
VariableType_0.cpp
VariableType_1.cpp
VariableType_2.cpp
VariableType_3.cpp
VariableType_4.cpp

これらのファイルがなくてもビルドはできますが、実行時にエラーになるため、これらのファイルも忘れずにビルドをします。

こんな適当なビルドでほんとうに動くのか自分でも疑問でしたが、とりあえずビルドとおるじゃん。しかもあっさり、MNIST.cppが動きました。

なんだ、PyTorchのlibtorchってc10、Aten、cpuinfo、eigen、TH、THNNと、torch/csrcディレクトリのなかのapi、jit、autogradだけコンパイルをすれば動くんですね。
この部分だけビルドして公開してくれればいいのに。
これで組み込みとかスマホアプリでC++版のPyTorchが使えます。

Python版のPyTorchとC++版のPyTorchってどれくらい差があるのでしょう。
両方のMNISTのソースコードを比べてみました。

Python版
--------------------
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)
--------------------


C++版
--------------------
struct Net : torch::nn::Module {
    Net()
    : conv1(torch::nn::Conv2dOptions(1, 10, /*kernel_size=*/5)),
    conv2(torch::nn::Conv2dOptions(10, 20, /*kernel_size=*/5)),
    fc1(320, 50),
    fc2(50, 10) {
        register_module("conv1", conv1);
        register_module("conv2", conv2);
        register_module("conv2_drop", conv2_drop);
        register_module("fc1", fc1);
        register_module("fc2", fc2);
    }
    torch::Tensor forward(torch::Tensor x) {
        x = torch::relu(torch::max_pool2d(conv1->forward(x), 2));
        x = torch::relu(
        torch::max_pool2d(conv2_drop->forward(conv2->forward(x)), 2));
        x = x.view({-1, 320});
        x = torch::relu(fc1->forward(x));
        x = torch::dropout(x, /*p=*/0.5, /*training=*/is_training());
        x = fc2->forward(x);
        return torch::log_softmax(x, /*dim=*/1);
    }
    torch::nn::Conv2d conv1;
    torch::nn::Conv2d conv2;
    torch::nn::FeatureDropout conv2_drop;
    torch::nn::Linear fc1;
    torch::nn::Linear fc2;
};
--------------------

なんと、ほとんど同じですね。
Pythonで書いてGPU付きのPCで学習させた結果をどんどんC++で使えそうですね。

今回紹介した方法で、Python版のPytorchで作ったモジュールを、即C++でアプリケーションや製品に組み込むことができます。これは便利。

本日は組み込みやスマホアプリ用の最小のPyTorchを作る方法でした。


2019年4月11日木曜日

Windowsでlibtorchをビルドする。

おじさん、OpenCVのことばっかりブログに書いていたら古いといわれました。
いつもgcc hoge.cのように1時間程度で簡単にビルドして実行して試せる記事を書いていたのに。
しかも勝手にC/C++縛りです。

くやしいので本日は最近はやっている、PyTorch。
ついにPythonの記事か!いや、そうではありません。
PythonのPytorchの記事はみんな書いてるしね。

PyTorchってPythonで書かれているように見えますが、ほとんどはC++で書かれているのでC++から使えます。C++で書かれている部分をlibtorchというようで、以下に使い方が書かれています。
https://pytorch.org/cppdocs/

また、ライブラリのバイナリーも以下の場所から入手できます。
https://pytorch.org/cppdocs/installing.html

だけど、おじさんの使っているWindsows版のlibtorchがない。
しかも<pytorch_root>/docs/libtorch.rstに書かれている、libtorchのビルド方法は全然更新されてないらしく、全然違うし。
ドキュメント整備されてないし、ソースコード汚いし、ドキュメント通りにやってもビルドどおらないし。。どうすりゃいいんだよ。

というわけで本日はPyTorchのコア部分であるlibtorchのビルドをしてみます。


まず、必要なツールですが、VisualStudio2017、Python3.7、CMake、PyYamlがいります。
PyYamlはPythonをインストールしたあとにコマンドプロンプトから以下のように入力するとインストールすることができます。

--------------------
pip3.7 install pyyaml
--------------------

cmakeはCygwinのものを使うと、Windowsビルド用のモジュールが入っていないので、本家のcmakeをダウンロードしてインストールします。
インストールがおわり、pythonとcmakeのパスを設定し、コマンドプロンプトから次のように入力するとcmakeによるビルドが開始されるようです。

--------------------
cd <pytorch_root>\tools
mkdir build
cd build
python ..\build_libtorch.py
--------------------

ビルドにはいろいろな外部ライブラリが必要です。おじさんのようにgithubからzipファイルをダウンロードしてそれを展開してビルドすると外部ライブラリうがないため、ビルド時に以下のようなエラーになります。

--------------------
CMake Error at cmake/Dependencies.cmake:1005 (add_subdirectory):
  The source directory

    D:/trash/pytorch-master/third_party/foxi

  does not contain a CMakeLists.txt file.
Call Stack (most recent call first):
  CMakeLists.txt:236 (include)
--------------------

外部ライブラリは、<pytorch_root>/third_party/<ライブラリ名>のところに足りない外部ライブラリをどんどん突っ込んでいきます。
ソースコードは、<pytorch_root>/third_party/<ライブラリ名>のところに、CmakeLists.txtがくるように配置しないとだめです。

外部ライブラリのソースコードはpytorchのgithubのthird_partyからリンクをたどって取ってきたほうがバージョンが一致するのでよいです。

protobuf、cpuinfo、googletest、benchmark、FP16、foxi、onnx、psimdなどの外部ライブラリがいるようです。
しかしここで一つ問題が、ベクトル演算ライブラリは、なんらかのBLASまたはEigenが必要です。なぜかベクトル演算ライブラリがなくてもビルドが開始されてしまいます。
なので、eigenもgithubからとってきます。

JavaのJVMとかのビルドもそうなのですが、Windows版のビルドはCドライブ以外でビルドを行うとよくファイルのコピーでビルドエラーになります。
libtorchもCドライブ以外でビルドを行うと、psimdのコピーができないようなので、
<pytorch_root>/tools/build/confu-srcs/psimdにthird_partyにある同じファイルをコピーします。
大規模なソースコードのビルドを行うときは絶対Cドライブでやったほうがいいです。Dドライブでは本当になぜかビルドが通りません。

これでやっとビルドが開始されます。
1時間くらいでビルドができ、<pytorch_root>/tools/build/bin/Releaseにtorch.dllが出来上がります。

これでC++からpytorchが使えるようになります。

PyTorchのexample/cppフォルダにはc++のサンプルがあります。
このなかに定番の文字認識「MNIST」のlibtorch版、mnist.cppがあります。
ファイル一つしかないんですね。なのでビルド簡単。
mnistのビルド方法やドキュメントもあるので、これをつかってPyTorchのlibtorch使い方を学ぶことができます。

めでたしめでたし。