libtorchのとてもざっくりした説明と小ネタとサンプルコード


※この記事は[PyTorch Advent Calendar 2020](https://qiita.com/advent-calendar/2020/pytorch)の17日目の記事です。
　とてつもなくどうでも良いですが、12月17日は僕の誕生日です。

# はじめに

qiitaの検索で、「pytorch」で検索すると、2020年12月15日の0時時点で2,212件もヒットしますが、「libtorch」で検索すると48件しかヒットしませんでした。libtorchってそれくらいの知名度？なので、そもそも「初めて聞いたよ」って人もいれば、「知っているけど使ったことない」という人もいると思います。

この記事では、そんなマイナー(？)なライブラリ「libtorch」について書きたいと思います。

* 最初に、libtorchについて**本当にざっくりと**説明します
* 次に、libtorchはどうやったら使えるか？を**本当にざっくりと**説明します
* 次に、**実際にlibtorchを使って思ったいくつかの小ネタ**について書いてみようと思います
* 最後に、libtorchを使ったプログラムの参考URLを紹介します

> 【注意】本記事はLinux(Ubuntu)を前提としています。Windowsの場合はまた違う可能性があるのでご注意ください
> 【注意】一応、それぞれ色々調べて検証もして問題ないと思っていますが、もしかしたら間違いとかあるかもしれないです。
> 　　　　その時はご指導いただけますと幸いですm(_ _)m

# libtorchって何？

libtorchについて知らない人に、一言でざっくりとした説明をすると・・・

**libtorchはC++でPyTorchが使えるライブラリだよ**

です。なぜPyTorchじゃなくてlibtorchを使うのか、それはだいたい下記の2つの理由だと思います。

* 1. PyTorchで作ったモデルをC++のアプリケーションで使う
* 2. PyTorchを使ったPythonのモジュールをC++を使って高速化する

# libtorchはどうやったら使えるの？

libtorchはどうやったら使えるのか？大きく分けると下記の3通りの方法があると思います

* **方法その1:** libtorchを公式からDL／インストールして使う
* **方法その2:** pipやcondaでpytorchをインストールして、同梱されているlibtorchを使う
* **方法その3:** 頑張ってソースからビルドする

qiitaの記事を見ていると、殆どがその1の「libtorchを公式からインストールして使う」という方法が多い気がしています。もしかしたらバージョンが古いと含まれていないのかもしれません。個人的にはPytorchに付属しているものを使ったほうが楽だと思うので、僕はその2の方法をおすすめします。その3については、環境によってはpipでPyTorchをインストールできないこともあるので、そんなときはコードからビルドしましょう。

```bash
# 共有ライブラリがおいているディレクトリ. libtorch.soがあることがわかると思います!
$  ls /usr/local/lib/python3.6/dist-packages/torch/lib
libc10.so       libcaffe2_detectron_ops_gpu.so    libcaffe2_nvrtc.so      libshm.so         libtorch.so      libtorch_cuda.so         libtorch_python.so  libuv.so.1
libc10_cuda.so  libcaffe2_module_test_dynamic.so  libcaffe2_observers.so  libtensorpipe.so  libtorch_cpu.so  libtorch_global_deps.so  libuv.so            libuv.so.1.0.0
# ヘッダファイルがおいているディレクトリ
$  ls /usr/local/lib/python3.6/dist-packages/torch/include/
ATen  TH  THC  THCUNN  c10  c10d  caffe2  pybind11  torch
# CMakeの設定がおいているディレクトリ
$ ls /usr/local/lib/python3.6/dist-packages/torch/share/cmake/
ATen  Caffe2  Gloo  Torch
```

なお、上記「その1」と「その2」は、「libtorchのインストール方法」が違うだけで、Make設定やコードの書き方はどちらも同じです。よって、ここでは説明を省略し、先人様たちの記事に解説をお願いしようと思いますm(_ _)m 
また、後述のサンプルPGも参考にしていただければと思います。

## libtorchの小ネタ

libtorchを使ってみて思ったいくつかの小ネタについて書きます。

### オススメlibtorchバージョン

libtorchのバージョンは**1.5以上**がオススメです。libtorchを初めて使ったときはたしか1.3か1.4かを使っていたのですが、Tensorのスライスがうまくできなかったりと、とても苦労した記憶があります。しかし、v1.5で色々と仕様変更があったらしく[[参考]](https://pytorch.org/blog/pytorch-1-dot-5-released-with-new-and-updated-apis/)、今は（1.3とか1.4に比べると）大分使いやすくなったと思います。よっぽどの理由がない限り、**libtorchは1.5以上、CMake使う人は(後述の理由により)1.6以上**を使うことをおすすめします。

### torch.utils.cmake_prefix_path

PyTorch1.6(多分)から、PytorchのAPIに`torch.utils.cmake_prefix_path`が追加されました[[参考1]](https://blog.exxactcorp.com/pytorch-1-6-0-now-available/) [[参考2](https://pytorch.org/cppdocs/installing.html)]。
`torch.utils.cmake_prefix_path`を使えば、libtorchのCMAKE_PREFIX_PATHを取得することができます。

これを下記のように`CMakeLists.txt`で使えば、わざわざ`CMAKE_PREFIX_PATH`を環境変数でセットするといったことをせずに、libtorchを使うことができます。

```
if (NOT PYTHON_EXECUTABLE)
    set(PYTHON_EXECUTABLE "python3")
endif()

# Pytorchのライブラリのcmake_prefix_pathを取得
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import torch; print(torch.utils.cmake_prefix_path, end='')"　OUTPUT_VARIABLE pytorch_path)
list(APPEND CMAKE_PREFIX_PATH "${pytorch_path}") # CMAKE_PREFIX_PATHに追加

# libtorch find
find_package(Torch REQUIRED)

```

### PyTorchで学習したモデルはどうやって使うの？？

PyTorchで学習したモデルを**torchscript**に変換してから使うのが一般的だと思います。
ただ、公式ドキュメントを確認してみた感じでは、C++でスクラッチでネットワークを構築することもできるようです。

やろうと思えば「モデルの学習」もlibtorchでできるっぽいです[[参考]](https://pytorch.org/tutorials/advanced/cpp_frontend.html)
(まあほとんどの人はやらないと思いますが、、、)。

よっぽどのことがない限り、libtorchでモデルを使うのなら前者の「**torchscriptに変換して使う**」ほうが良いと思います。
~~(というか、C++で推論したいのであればtorchscriptを使うより、TensorRTを使ったほういいです)~~

### libtorchのAPIリファレンス

libtorchのAPIリファレンスは[こちら](https://pytorch.org/cppdocs/api/library_root.html)です。僕だけかもしれないですが、ググってlibtorchのAPIリファレンスにたどり着くのが難しいですOTL。

### OpenCVのcv::Matとtorch::Tensorの相互変換

cv::Matとtorch::Tensorを相互に変換するときは、CPUの場合は`memcpy`、GPUの場合は`cudaMemcpy`を使ってデータをまるっとコピーすればいいです。このとき、ポイントになるは、下記の4つです。

1. cv::Matと、torch::Tensorのデータの型をあわせる
2. チャンネル数と画像サイズをあわせる
3. cv::Matとtorch::Tensorの並び順に注意
4. cudaMemcpyAsyncを使うときは要注意

こちらについては、やり方の一例が後述のサンプルにあるのでそちらを参考にしてください。

# libtorchを使ったプログラムの参考リンク

## ukyoda作成 サンプルPG

**URL: https://github.com/ukyoda/libtorch_tutorial**

libtorchを使ってサンプルPGを作成しました。torchvisionにあるモデルをtorchscriptに変換＋libtorchで画像分類ができます。
下記に書いているtrt_poseではsetup.pyを使ってビルドしていますが、こちらはCMakeを使っています。


## trt_pose

**URL: https://github.com/NVIDIA-AI-IOT/trt_pose**

NVIDIA社が公開している骨格推定のプログラム「trt_pose」では、後処理でlibtorchを使っています。
pybind11と組み合わせてPythonのモジュールとして実装しています。



# 最後に

libtorchを使えば、PytorchをC++で使うことができるようになります。**Python＋PyTorchが遅い!!**と感じている方はlibtorchを勉強してC++で高速化を目指してみてください。

ただ、正直、**DeepLearningのモデルをC++で高速化したいのであれば、TensorRTを使ったほうが良い**と思います。NVIDIA社が公開している[torch2trt](https://github.com/NVIDIA-AI-IOT/torch2trt/)を使えば、PyTorchのモデルをとても簡単にTensorRTに変換することができます。

torch2trtという、とても便利なOSSが出てきた今、多分よっぽどのことがない限り、libtorchを選択することはないかもしれません。ですが、この記事でも述べましたが、**libtorchはPytorchからテンソルをそのまま受け取ることができる**ので、TensorRTでは対応できない前処理とか後処理とかの処理をC++で高速化したいときには活躍してくれそうな気がします。

以上です。それでは皆様良いお年を。2021年も楽しい年であることを祈ります。