M1 MacでObject Detection APIの環境構築

会社のパソコンでM1チップ搭載のMacを使用しており、そこでObject Detection APIを動かそうとしたらx86_64とArm64の違いにより色々苦労したので、備忘録も兼ねてまとめさせていただきます。間違い等あればご指摘いただけますと幸いです。

# 結論


M1 Macで、Object Detection APIを動かそうと思ったら、現状`Anaconda`一択。(miniforgeではなく)

- x86_64アーキテクチャ上(Rosetta2)で動かすAnacondaを使って、Tensorflowをはじめとするライブラリを、condaを使って入れる。
- または、Dockerの上にAnacondaを構築し、その上にcondaでライブラリを導入。

Tensorflowは、Arm64向けのものも出ていますが、apache-beamがArm64上ではどうしても入れられなかったためx86_64上に構築するしかなかったです。(apache-beamが依存関係にある、バージョン3.0.0以下のpyarrowに、arm対応のものが存在しないため?)
Tensorflowは、x86_64上にpipで入れた場合、「illegal...」となって動きません。また、ソースからのビルドもできる人がいたら大丈夫かもなのですが、自分の場合ビルドが最後の最後で毎回失敗してしまい、うまくいきませんでした。


# 前提知識
M1 Macは、armのチップを搭載していますが、x86_64のアプリケーションもRosetta2というエミュレータ上で動かせます。これに伴い、ややこしいことに、ターミナルもarmベースで動くモードとx86_64ベースで動くモードが用意されています。この切り替えには少なくとも2種類の方法があります。
1つ目が、ターミナル上でコマンドを打ってモード切り替えをする方法です。`arch -x86_64 zsh`と打ち込むとx86_64モードに、`arch -arm64e zsh`と打ち込むとarm64モードになります。デフォルトではarm64モードであり、現在のモードは`uname -m`と打ち込むと確認できます。
2つ目が、ファインダーでアプリケーション>ユーティリティにある「ターミナル」アプリを右クリックして、情報を見るを選択、その中にある「Rosettaを使用して開く」にチェックをつける方法です。これをするとデフォルトでx86_64モードでターミナルが開けるようになります。自分はターミナルアプリを複製して、x86_64版の方を「ターミナル(Intel)」という名前で使っています。

さらに、Macの定番のパッケージマネージャーである**Homebrew**にもarm64版とx86_64版があります。arm64版のパスは、`/opt/homebrew/bin/brew`で、x86_64版のパスは、`/usr/local/bin/brew`となっています。
両方入れた場合、`brew`コマンドではarm64版のパスが優先されて実行されるので、自分は`alias brew_x86=/usr/local/bin/brew`として登録し、`brew_x86 install 〇〇`のような形で実行しています。

インストールの仕方の詳細は、[こちら](https://qiita.com/chibiegg/items/43b7604a589e3760e88e)や[こちら](https://zenn.dev/_lambda314/articles/63b851221a7016)などを参考にさせていただきました。

# 手順解説
Anacondaの導入
1. [Anaconda](https://www.anaconda.com/products/individual)をインストール 
2. ターミナルを再起動[^1] (condaのパスが通るように。また、このターミナルはx86_64モードで行うのが安心)
[^1]: なお、Anaconda導入後、勝手にAnacondaの仮想環境(base)に入るようになるのが嫌な人は```conda config --set auto_activate_base false``` とターミナルで入力
3. 好きなディレクトリに移動
4. ``` conda create -n yourEnvName python=3.6 ``` で仮想環境を作成(python3.6でないと動かないスクリプトがあるため)
5. ``` conda activate yourEnvName``` で仮想環境の中に入る

Object Detection APIの導入
6.  ```git clone https://github.com/tensorflow/models.git ``` でリポジトリをクローン
7. ```cd models/research```
8. ```brew install protobuf ``` でprotocol buffersを導入(brewはx86_64版を使う必要あり)
9. ```protoc object_detection/protos/*.proto --python_out=.```
Google公式の[Github](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2.md)では、このあとsetup.pyを実行することになっているのですが、これはpipでライブラリを入れてしまうので、先にcondaを使って手動でライブラリ追加を行います。

各種ライブラリの導入
1つずつ入れていきます。参考：[Anaconda Org](https://anaconda.org)
10. ```conda install -c conda-forge apache-beam -y ```
11. ```conda install -c bioconda avro-python3 -y```
12. ```conda install -c conda-forge pillow -y```
13. ```conda install -c conda-forge lxml -y```
14. ```conda install -c conda-forge matplotlib -y```
16. ```conda install -c conda-forge contextlib2 -y```
18. ```conda install -c conda-forge pycocotools -y```
19. ```conda install -c conda-forge scipy -y``` 
残る3つは、condaでは入れられなかったのでpipで入れます
20. ```pip install lvis ```
21. ```pip install tf-slim ```
22. ```pip install tf-models-official ```

`tf-models-official`を入れる中で、pip版のTensorFlowが勝手に入るのですが(仮にこれ以前にconda版のTensorflowを入れていても上書きされてしまう)、これはM1チップでは動かないのでcondaで入れ直します。
23. ```conda install -c conda-forge tensorflow==2.4```(デフォルトだと1.14.0が入ってしまうため) 

最後にこれらのライブラリ群を`object-detection-0.1`という名前でまとめるために、setup.pyを実行します。これを行わないと、cannnot find 'object-detection-0.1'...と怒られます。ちなみにこの時のパスは`models/research`になっていることをご確認ください。
24. ```cp object_detection/packages/tf2/setup.py .```
25. ```python -m pip install --use-feature=2020-resolver .```

最後に、環境構築が成功しているかのテストを行います。
26. ```python object_detection/builders/model_builder_tf2_test.py ```

このテストで、無事に`OK`が出れば完了です！


# Anacondaに落ちつくまでの試行錯誤

それは長い道のりでした。
### Tensorflowという強敵
M1 Macは、対応しているアプリケーションがほとんど揃ってきているように思っていたため、油断していました。まさかTensorflowが素直に動いてくれないとは...。

Tensorflowを動かすには、いくつかの選択肢があります。以下は、自分が手を出していった順番です。
##### arm64で動かす場合
* Apple謹製[Tensorflow-macOS](https://github.com/apple/tensorflow_macos)を使う
* [miniforge](https://github.com/conda-forge/miniforge)というArmに対応したcondaインストーラを使う
* Dockerにminiforgeを入れて使う

##### x86_64で動かす場合
* Tensorflowの[ソースからのビルド](https://www.tensorflow.org/install/source?hl=ja)
* Rosetta2上でAnacondaを使う(本命)
* DockerにAnacondaを入れて使う

### arm64での挑戦
armネイティブで構築した方がきっと計算速いよね、という考えにより、最初に手を出したのは`Tensorflow-macOS`でした。import tensorflowが無事に動いたときは感動したものの、Object Detection APIには他にもたくさんの手強いライブラリ達がいます。pandasやmatplotlib、scipyなどpipで直接入れられないものが次々と出てきて、撃沈しました。

次に手を出したのが、`miniforge`です。tensorflowは無事に入るし、numpyやscipyなど計算系ライブラリもcondaで入り、滑り出しは良好だったものの、待ち構えていたのはapache-beamでした。まず、condaで探しても`PackageNameError`を宣告されます。めげずにpipを使って入れようとすると、画面が赤い文字で埋め尽くされます。エラーを解読して色々調べると、どうやらpipのapache-beam(2.30.0)は、pyarrowのバージョン3.0.0以下に依存しているらしい。しかし肝心のpyarrowはバージョン4.0.0以上でないとarmに対応していないようで、入らないという...。

### x86_64での挑戦
apache-beamやscipyを入れるには、x86_64を使うしか手がない(2021年6月時点)。そう悟ってからは、x86_64アーキテクチャにて構築する道を模索し始めました。ただ、x86_64はapache-beamをはじめとする他のライブラリには優しいのですが、Tensorflowとの相性がよくありません。そこで選択肢として浮上してきたのが、Tensorflowのソースからのビルドでした。ネット上にはそれを成し遂げた方をちらほら見かけたので、きっとできるに違いないとトライしてみたのですが、bazelでのビルドで最後の最後でエラーが出て失敗...。[公式](https://www.tensorflow.org/install/source?hl=ja)のやり方と、[sedをgsedに変えて](https://zenn.dev/_lambda314/articles/318d2622dcad85)行うやり方を両方試したものの、うまくいきませんでした。ちなみにbazelではなくbazeliskをbrewでインストールしないとバージョン関連で怒られます。

万事休すかと思われたそのとき、救いの光を差し伸べてくれたのが[この記事](https://qiita.com/ShinnnosukeNakamura/items/de8e1b79d3d04e3cc730)でした。Rosetta2上でAnaconda経由でtensorflowを入れれば動く、と。
ということで、miniforgeを削除して、本家のAnacondaを入れてみたところ、ばっちり動くではないですか。そして、condaでちゃんと色々見つかる(condaのchannelが、miniforgeだと`osx-arm64`(miniforge+Dockerだと`linux-aarch64`)、Anacondaだと`osx-64`のため)。

その後、Docker上に構築したUbuntu+Anacondaにも入れてみましたが、無事に動きました。ただしcondaのチャンネルは、ローカルだと`osx-64`ですが、Docker上だと`linux-64`なので、condaでインストールできるものが微妙に異なるようです。


# まとめ
M1でもちゃんと機械学習ができる。(ただしarmのネイティブではないのでパフォーマンスの保証なし)
Anacondaが必要なのは、apache-beamやopencvなどの手強いライブラリ達を入れないとダメな場合です。
Anacondaは、環境にガッツリ根付く感じが怖いのとpipとcondaのややこしさが理解できずに今まで避けていました。PATH関係の知識が少しずつつくにつれて大丈夫感が出てきたのとcondaのありがたみを深く実感することになったので、これからもお世話になろうと思います。