StreamlitとTensorFlowで犬猫判定アプリを作りました

## 作ったもの

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119757/cd6cd525-13ce-731a-0602-164739d8c440.gif" width="600" alt="demo">

アプリURL：https://judge-cat-or-dog.herokuapp.com/
ソースコード：https://github.com/oddgai/cat_or_dog/
※サーバー代をケチっているのでアプリ起動までに時間がかかる場合があります。その間に画像を選ぶなどしてください。

[MobileNetV2](https://arxiv.org/abs/1801.04381)という高精度な画像分類モデルを転移学習・ファインチューニングしたものを使っているため、精度は95%くらいだと思われます。

## どうやったか（ざっくり）

ソースコードは上記GitHubにあげているのと基本的には下記を参考にしているため、オリジナルな部分のみ書きます。

* [TensorFlowの公式チュートリアル](https://www.tensorflow.org/tutorials/images/transfer_learning?hl=ja)
* [Streamlitのリファレンス](https://docs.streamlit.io/en/stable/api.html)

### TensorFlowでのモデル構築

ここは上記のTensorFlowの犬猫判定チュートリアルをGoogle Colaboratory上でそのままやりました。

理論的な説明こそあまりないものの、`簡単なモデル` -> `その課題を考察` -> `それを解決する発展的なモデル`のような自然な流れで書かれていて、本当に取り組みやすかったです。

モデル構築後に、アプリで使う用にモデルをファイルとして保存しています。

```python
from google.colab import drive
drive.mount("/content/drive")

model.save("drive/My Drive/Colab Notebooks/cat_dog_model.h5")
```

上記のようにファイルとして保存すると、下記のように読み込むだけで別の環境でモデルを復元でき、推論などが実行できます。

```python
model = tf.keras.models.load_model("src/cat_dog_model.h5")
```

### Streamlitでの実装
[Streamlit](https://streamlit.io/)とは、Pythonだけ（HTML不要）で簡単にいい感じのWebアプリを作ることができるフレームワークです。
Pythonの軽量フレームワークとしては[Flask](https://flask.palletsprojects.com/en/2.0.x/)が有名ですが、特に理由がない限り個人的にはStreamlitでいいんじゃ…？と思うくらい簡単にアプリが作れます。

どのくらい簡単かというと、今回のアプリの表示部分はこれだけで実装しています（関数などは省略）。

```python
import numpy as np
import streamlit as st
from PIL import Image, ImageOps

uploaded_file = st.file_uploader("判定したい画像を選んでね")
if uploaded_file is not None:
	try:
		# 画像を読み込む
		uploaded_img = Image.open(uploaded_file)
		uploaded_img = ImageOps.exif_transpose(uploaded_img)  # 画像を適切な向きに補正する

		# 犬猫判定
		pred = sample_predict(uploaded_img)

		# 結果表示
		st.info(f"これは**{get_result(pred)}**です！")
		score = np.int(np.round(pred, 2)*20)
		st.text(f"犬 0 |{'-'*score}*{'-'*(19-score)}| 100 猫")
		st.image(uploaded_img, use_column_width=True)
	except:
		st.error("判定できませんでした・・・適切な画像をアップロードしてください！")
```

HTMLやCSSなしでモダンっぽいUIにしてくれるのでありがたいですね。
今回はユーザーがアップロードした画像を表示していますが、これ以外にも

* ソートなどがインタラクティブにできるデータフレームを表示
* Matplotlib, Plotly, Bokehなどの基本的なグラフ、地図描画
* 動画、音声ファイル再生

などなど、いろんな機能があります。
[Streamlitのギャラリーページ](https://streamlit.io/gallery)には豊富なサンプルが載っているので、ぜひ見てみてください。

## 細かいエラーと対応
最終的には解決できましたが、いくつか詰まったことがあるのでメモ的に残しておきます。

#### TensorFlowがMac M1にインストールできない
少し前に奮発して買った`MacBook Air (M1, 2020)`を使いましたが、`Pipenv`上で`pip install tensorflow`しようとすると下記のエラーが出ました。

```
❯ pip install tensorflow
ERROR: Could not find a version that satisfies the requirement tensorflow
ERROR: No matching distribution found for tensorflow
```

調べてみると、

* Mac M1のPython環境構築は現状では[Miniforge](https://github.com/conda-forge/miniforge)がいいらしい
* AppleがMac M1用の[tensorflow-macos](https://github.com/apple/tensorflow_macos)をリリースしている

とのことなので、下記を参考にすると無事インストールできました。
https://zenn.dev/karaage0703/articles/0ab9e654cfb0ec

#### Google Colaboratoryで保存したモデルがロードできない
`tf.keras.models.load_model()`しようとすると下記のエラーが出ました。

```
tensorflow load model 'str' object has no attribute 'decode'
```

TensorFlowのバージョンがColab（2.5）とローカル（2.4）で違うのが原因っぽかったので、Colab上でバージョンを下げてモデルを保存し直しました。

```
!pip3 uninstall tensorflow
!pip3 install tensorflow==2.4.0
```

それをロードすると、また別のエラーが出ました。

```
NotImplementedError: Cannot convert a symbolic Tensor (gru/strided_slice:0) to a numpy array. This error may indicate that you’re trying to pass a Tensor to a NumPy call, which is not supported
```

調べたところ、`TensorFlow 2.x`と`numpy 1.20.x`の間で発生している不具合のようでした。

下記を参考に`numpy==1.19.5`をインストールし直すと、無事ロードできました。
https://chimesness.com/post-246/


#### アップロードした画像がたまに90度回転して表示されてしまう
スマホで撮影した画像でよく起こっていたもので、それによって判定にも良くない影響が出ていました（実家の猫の画像が`確実に猫` -> `ほぼ猫`にランクダウンするなど）。

PILまわりを調べてみると、JPEGにExifという表示方法を制御するタグがあって、それを適切に読み取れてないために起こっているようでした。

下記を参考に、読み込み時に1行追加したら解決しました
https://github.com/python-pillow/Pillow/issues/4703

```python
# 画像を読み込む
uploaded_img = Image.open(uploaded_file)
uploaded_img = ImageOps.exif_transpose(uploaded_img)  # 画像を適切な向きに補正する
```

#### Herokuにデプロイしようとすると`slug size`が500MBを超える
Herokuでは必要なパッケージを`slug`という形で保存するらしいのですが、それが500MB以下でないとデプロイできない仕様になっています。
どうやら`TensorFlow 2.x`ではGPUをサポートするために容量が大きくなっていて、ほぼ確実に500MBをオーバーするようです。
とはいえ今さら`1.x`系のモデルを作り直したくないので、下記を参考に`requirements.txt`を書き直すと300MBくらいに減ってデプロイできました。
https://qiita.com/pi0329/items/22e38713df1e008c6773

```
tensorflow==2.4.0
↓
tensorflow-cpu==2.4.0
```

## 感想

TensorFlowやStreamlitを真面目に触ったのは初めてでしたが、公式サイトが充実していたので非常にやりやすかったです（着手〜リリースまで実質半日くらい）。
また技術ブログ系は今までROM専だったのですが、いろんな方の記事に支えられていることを改めて実感したので自分も書いてみました。
今回はチュートリアルのつぎはぎ的なアプリでしたが、次はアイデアから自分で考えたものを作りたいです。