[M1Mac]TensorFlow実行環境の構築

#はじめに(+背景)
今回の記事の内容は、タイトルの通り**M1 Mac**で**TensorFlow**を実行するための環境構築です。
筆者の場合はBert(Keras Bert)を使いたかったのですが、KerasのバックエンドとしてTensorFlowが動くため先ずはTensorFlowを使えるようにする必要がありました。
しかし、未だにM1 Mac上での環境構築は引っかかる点が多く今回も大変苦労しました。
四苦八苦しながらも、一通り問題なく使える環境構築が出来たので今回はそちらをこの記事にまとめます。

#目次
- 使用機器＆前提条件(Pre Requirements)
    - 使用Macのスペック&OSのバージョン
- TensorFlowをインストールするための仮想環境の構築
    - pyenvの導入
    - miniforgeの導入
    - miniforgeを利用した仮想環境の構築
- TensorFlowのインストール
    - TensorFlow-MacOSの設置
- その他+テスト
    - その他主要ライブラリの設置
    - MNIST Datasetを利用してモデルの訓練
    - 訓練における性能の確認、Google Colabとの比較

# 使用機器＆前提条件(Pre Requirements)
### 使用Macのスペック&OSのバージョン
- Macのスペック
    - MacBook Pro(13-inch, M1, 2020)
    - Chip: Apple M1
    - Memory: 16GB
- OS
    - macOS Monterey: Version 12.0.1

今回は発表されたばかりの**Monterey**を使っています。
発表直後のOSを使うことに抵抗のある人も多いと思います。
筆者の場合はこの環境構築直前に一度初期化をしていること、Appleの公開しているドキュメントでは**OS Requirements**として`macOS 12.0+`が記載されている為、このような条件下での構築となりました。

# TensorFlowをインストールするための仮想環境の構築
### pyenvの導入
次の項目にて再度触れますが、筆者は今回**pyenv**を使ってminiforgeを導入します。
pyenvについては、

**1. pythonのバージョン管理が容易にできる点
2. condaとpipを併用することによるリスクの最小化の為
3. pyenvを利用したminiforgeの導入が簡単**

という目的で使用することにしました。
詳しくは、下記資料などをご参考ください。
[Managing Multiple Python Versions With pyenv](https://realpython.com/intro-to-pyenv/#why-use-pyenv)
[condaとpip：混ぜるな危険](https://onoz000.hatenablog.com/entry/2018/02/11/142347)

では、導入の手順について説明していきます。
`まずは、Homebrewを使ってpyenvをインストールします。`
<font color="grey">*Homebrewが未インストールという方は、下記資料などを参考にインストールして下さい。</font>
[Homebrewのインストール](https://qiita.com/zaburo/items/29fe23c1ceb6056109fd)

```shell:
$ brew install pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zprofile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zprofile
$ echo 'eval "$(pyenv init -)"' >> ~/.zprofile
$ source ~/.zprofile
```
`次にpyenvを使ってpythonのインストールとバージョンの変更をします。`
この記事を書いている現在では最新のバージョンは**3.10.0**ですが、今回は参考にした多くの資料で3.9系を使用していた為、**3.9.7**を使用します。

```shell:
$ pyenv install 3.9.7
$ pyenv global 3.9.7
```

これで、pyenvのインストールからpythonのバージョン変更までが完了しました。

### miniforgeの導入
次に**miniforge**の導入をします。
先述した通り、pyenvを使ってインストールをします。
`まずは、pyenvでインストールできるバージョンを確認します。`

```shell:
$ pyenv install -l
#出力
#省略
  miniforge3-4.9.2
  miniforge3-4.10
  miniforge3-4.10.1-1
  miniforge3-4.10.1-3
  miniforge3-4.10.1-5
  pypy-c-jit-latest
  pypy-dev
#省略
```
`今回はこの中からminiforge3-4.10.1-5をインストールし有効にします。`

```shell:
$ pyenv install miniforge3-4.10.1-5
$ source ~/.pyenv/versions/miniforge3-4.10.1-5/bin/activate

# 出力
(base) someone@SomeonenoMacBook-Pro %
```
このように`(base)`という仮想環境に入ってる状態になれば成功です。

### miniforgeを利用した仮想環境の構築
下記資料にまとまっていますが、miniforgeというのは```conda-forgeをデフォルトのチャネルとして使うminiconda```と考えると分かりやすいです。
なので、基本的に仮想環境の構築の際に使うコマンドは一般的な**condaコマンド**となります。
[What is the difference between miniconda and miniforge?](https://jpn.fitforlearning.org/973913-what-is-the-difference-between-SYSHGH)

```shell:
$ conda create --name test python=3.9.7
$ conda activate test
# 出力
(test) someone@SomeonenoMacBook-Pro %
```
このように仮想環境名が`(base)`から変更されていれば成功です。 
test の部分は仮想環境名なので、各々好きな名前に設定して下さい。
また、pyenvと同様にpythonのバージョンは**3.9.7**にします。

# TensorFlowのインストール
まずは、TensorFlowの依存関係をインストールします。

```shell:
$ conda install -c apple tensorflow-deps
```
次にTensorFlow-MacOSの本パッケージをインストールします。

```shell:
$ pip install tensorflow-macos
```
最後にMacGPUを利用して学習スピードを上げる為、metalプラグインをインストールします。

```shell:
$ pip install tensorflow-metal
```
インストールされているパッケージを下記同様に確認できれば成功です。

```shell:
$ conda list

# 出力
# 省略
tensorflow-deps           2.6.0                         0    apple
tensorflow-estimator      2.6.0                    pypi_0    pypi
tensorflow-macos          2.6.0                    pypi_0    pypi
tensorflow-metadata       1.4.0                    pypi_0    pypi
tensorflow-metal          0.2.0                    pypi_0    pypi
# 省略
```
# その他+テスト
### その他主要ライブラリの設置
今回はとりあえず、この後のテストの為にpandasとjupyterだけを入れておきます。

```shell:
$ conda install -c conda-forge -y pandas jupyter
```
### MNIST Datasetを利用してモデルの訓練
参考文献[How To Install TensorFlow on M1 Mac (The Easy Way)](https://caffeinedev.medium.com/how-to-install-tensorflow-on-m1-mac-8e9b91d93706)内で紹介されているコードを利用してテストをします。
まず、データセットをインストールして**Jupyter Notebook**を開きます

```shell:
$ mkdir test
$ cd test
$ pip install tensorflow_datasets
$ jupyter notebook
```
新しいnotebookファイルを作成して下記コードを書きます。

```python:
import tensorflow as tf

print("Num GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU')))

# 出力
Num GPUs Available:  1

%%time
import tensorflow.compat.v2 as tf 
import tensorflow_datasets as tfds 

tf.enable_v2_behavior() 

from tensorflow.python.framework.ops import disable_eager_execution 

disable_eager_execution() 
(ds_train, ds_test), ds_info = tfds.load( 
    'mnist', split=['train', 'test'], 
    shuffle_files=True, 
    as_supervised=True, 
    with_info=True, 
) 

def normalize_img(image, label):
    """Normalizes images: `uint8` -> `float32`.""" 
    return tf.cast(image, tf.float32) / 255., label 

batch_size = 128 

ds_train = ds_train.map( 
    normalize_img, num_parallel_calls=tf.data.experimental.AUTOTUNE)
ds_train = ds_train.cache()
ds_train = ds_train.shuffle(ds_info.splits['train'].num_examples)
ds_train = ds_train.batch(batch_size)
ds_train = ds_train.prefetch(tf.data.experimental.AUTOTUNE)

ds_test = ds_test.map( 
    normalize_img, num_parallel_calls=tf.data.experimental.AUTOTUNE) 
ds_test = ds_test.batch(batch_size) 
ds_test = ds_test.cache() 
ds_test = ds_test.prefetch(tf.data.experimental.AUTOTUNE)

model = tf.keras.models.Sequential([ 
    tf.keras.layers.Conv2D(32, kernel_size=(3, 3),
                           activation='relu'),
    tf.keras.layers.Conv2D(64, kernel_size=(3, 3),
                           activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
# tf.keras.layers.Dropout(0.25),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
# tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(10, activation='softmax') ])
model.compile(
    loss='sparse_categorical_crossentropy',
    optimizer=tf.keras.optimizers.Adam(0.001),
    metrics=['accuracy'],
) 
model.fit(
    ds_train, epochs=12,
    validation_data=ds_test, )
```
### 訓練における性能の確認、Google Colabとの比較
上記コードを**今回作成した環境**で動かした場合の結果は以下の通りです。
![Screen Shot 2021-10-29 at 14.18.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1811712/bf0c8462-edac-de5a-2716-4c371bc87a32.png)

次に、同じコードを**Google Colab(Hardware accelerator : GPU)**で動かした場合は以下の通りです。![Screen Shot 2021-10-29 at 14.35.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1811712/f9b4a93e-3c7d-219f-550a-256c531f78f3.png)

今回作成した環境: **2min 41s**
Google Colab(Hardware accelerator : GPU): **4min 24s**

以上でTensorFlow実行環境の構築と性能比較テストに関する記事を終えます。

#参考
- [How To Install TensorFlow on M1 Mac (The Easy Way)](https://caffeinedev.medium.com/how-to-install-tensorflow-on-m1-mac-8e9b91d93706)
- [Getting Started with tensorflow-metal PluggableDevice
](https://developer.apple.com/metal/tensorflow-plugin/)
- [TensorflowとKeras、PyTorchの比較](https://www.tdi.co.jp/miso/tensorflow-keras-pytorch)
- [Managing Multiple Python Versions With pyenv](https://realpython.com/intro-to-pyenv/#why-use-pyenv)
- [Simple Python Version Management: pyenv](https://pythonrepo.com/repo/pyenv-pyenv-python-version-environment-management)
- [What is the difference between miniconda and miniforge?](https://jpn.fitforlearning.org/973913-what-is-the-difference-between-SYSHGH7)
- [condaとpip：混ぜるな危険](https://onoz000.hatenablog.com/entry/2018/02/11/142347)
- [[M1 맥북] GPU에서 tensorflow 실행하기 (tensorflow 2.5 설치)](https://gmnam.tistory.com/271)