型を使って書く機械学習:Hasktorchの紹介

# 背景と目的

機械学習工学 / MLSE Advent Calendar 2018 23日目の記事です。
[Hasktorch](https://github.com/hasktorch/hasktorch)というテンソルの型をコンパイル時にチェックできる機械学習のライブラリの話です。

機械学習で使用するベクトル、行列、テンソルの型をコンパイル時にチェックしたいという要求があります。ここでいう型はベクトルの場合は数字の型（intやfloatなど）とその大きさ・長さ(RGBの色であれば3）です。

コンパイルでなくて実行時チェックだと検証に漏れがでてくる恐れがありますし、[JaSST Tokyo 2018の招待講演](https://yshibata.blog.so-net.ne.jp/2018-06-10?fbclid=IwAR2MAiPn86bW5PKqxks1WStivDeu9Rh91s4Eois1IAFk7_GyhkEhf61Odqs)の資料にもありますがコンパイルでテストできればテストファースト開発におけるフィードバックループが短くなるので開発の効率が高いです。

しかし、コンパイル時にテンソルの型をチェックするのは困難で[依存型](https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E5%9E%8B)を使う必要があります。依存型を扱える言語はCoq、Agda、[Idris](https://www.idris-lang.org/)、Haskellがあります。現状、依存型が使えるプログラム言語で各種ライブラリや汎用的な用途で使える実用的な言語はHaskellだけです。

そういう中、[ICFP2018での発表](https://icfp18.sigplan.org/event/npfl-2018-papers-hasktorch-a-comprehensive-haskell-library-for-differentiable-functional-programming)([動画](https://www.youtube.com/watch?v=SjxP1NpoP2c))でPythonのpytorchにあたる機械学習のライブラリであるHaskellの[Hasktorch](https://github.com/hasktorch/hasktorch)がでました。

残念ながら後にも問題を書きますが、Hasktorchは動かすのが難しく、全く評価できませんでした。今回はビルドしてDockerイメージを作成しました。

# Hasktorch とは

Hasktorchはテンソルやニューラルネットワークを扱うHaskellのライブラリです。PyTorchで使っているコアのC++言語のライブラリ(ATen)を使っています。新しい[cabal](https://cabal.readthedocs.io/en/latest/nix-local-build-overview.html) (newがつくコマンド)と[Backpack](https://ghc.haskell.org/trac/ghc/wiki/Backpack)を使って開発しています。

# Backpack?（ちょっと脱線）

[Backpack](https://ghc.haskell.org/trac/ghc/wiki/Backpack)はひな形のパッケージから特定の型（intとかfloat）のパッケージを作るのに利用します。例えば下記のC言語のaddの例のように型Tに対して関数を作っておき、defineで置き換えて特定の型の関数をつくるようなものです。

```c
T add(T a,T b){
  return a+b;
}

//上記のファイルから下記の関数をdefineで作る。
#define T int
int add(int a,int b){
  return a+b;
}
```

ひな形から特定の型をするのにcabalファイルのmixinsを使います。
下記の例だとStr(ひな形)をStr.String.Partial(特定の型)に置き換えています。

```
  mixins:
    str-sig requires (Str as Str.String.Partial)
```

[backpack-str](https://github.com/haskell-backpack/backpack-str)により実践的な使い方があります。

HasktorchはBackpackを使うためstackでなくてcabalを使って開発しています。stackがBackpackをサポートしたらstackに切り替わるかもしれません。

# 型の書き方

型の書き方ですが、[公式のサンプル](https://github.com/hasktorch/hasktorch/blob/master/examples/static-tensor-usage/Main.hs)によるとdoubleの3x2の行列で値が1,2,3,4,5,6なテンソルは下記のように書きます。```:: DoubleTensor '[3, 2]```がテンソルの型です。

```haskell
Just (listVec2 :: DoubleTensor '[3, 2])
<- fromList [1, 2, 3, 4, 5, 6]
```

値の宣言は簡単ですが、関数はどうでしょうか。flattenというテンソルからベクトルに変換する関数は下記のようになっています。入力のテンソルの次元が```d```で出力のテンソルの次元が```[Product d]```となっています。
```d=[1,2,3]```なら```[Product d]=[6]```となります。

```haskell
-- | flatten a tensor (pure, dupable)
flatten :: (Dimensions d, KnownDim (Product d)) => Tensor d -> Tensor '[Product d]
flatten = resizeAs
```

もっと[実用的な例](https://github.com/hasktorch/hasktorch/tree/master/examples)は10以上ありますので、次回評価したいと思います。

# 環境構築

Hasktorchはインストールが難しいです。関連するソフトも特定のバージョンでないと動作しません。そのことが今回の試行の障害になりました。nvidia-docker上で動作する[Dockerfile](https://github.com/junjihashimoto/hasktorch-docker/blob/master/Dockerfile)を用意しました。
簡単にまとめると下記のプログラムが必要です。

* ghc-8.4.4 (haskellのコンパイラ）
* cabal-2
* cuda-9 (cuda-10では動きません)
* python-3 (python-2では動きません)
* cmake-3
* ATen

遭遇した問題は下記のようなものです。

* [ATenがcuda-10に対応していない。](https://github.com/pytorch/pytorch/issues/12075)
* ATenのビルドでcmakeがSSE3などの拡張命令を検出するところでpythonを使っているがpython2だとCMAKE_REQUIRED_FLAGSが動かないため検出に失敗する。
* ghc-8.4.3だとcabalのパッケージの依存関係のチェックに失敗する。
* [nvidiaのドライバの問題でCPUが常時高負荷](https://askubuntu.com/questions/1035528/ubuntu-18-04-systemd-udevd-uses-high-cpu-conflict-with-nvidia-graphics)（そのためドライバ再インストール）

特定のバージョンでないと動かない問題に対してDockerはいいソリューションであることを再認識しました。