【React】Tensorflow.js を使用してフロントエンドだけで手書き文字推測アプリケーションを作成する

<!-- ======================================================== -->
# 概要
<!-- ======================================================== -->
現在、機械学習を勉強しようとしたときに、使用言語の第一候補にはほぼ**Python**が上がると思います。それほど機械学習 × Pythonは、フレームワークやドキュメント、参考書が充実しています。

では、作成したモデルを実際のアプリケーションでどのように運用すればよいのでしょうか？

考えられるのは、Pythonを実行できるアプリケーションサーバーを用意して、フロントエンドとなるアプリケーションからAPIでデータをやり取りする方法です。しかし、フロントエンドで推測や分類をするたびにサーバーとのやり取りが発生するため、応答速度や費用面でコストがかかります。

フロントエンドだけで、Pythonで作成した機械学習モデルを実行できれば、このようなコストからは解放されます。

<font color="RoyalBlue">本記事では、Kerasで作成したMNIST学習モデル（CNNモデル）を、フロントエンドだけで実行するアプリケーションを作成します。</font>

![output(video-cutter-js.com) (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/500cf9b4-9960-d932-9a90-cb68f6e06c24.gif)


※ 多少殴り書きですが、ご容赦を。

<!-- ======================================================== -->
# 環境
<!-- ======================================================== -->
- windows 10
- VSCode

<!-- ======================================================== -->
# 参考にさせて頂いた記事
<!-- ======================================================== -->
参考にした記事です。
今回やりたいことは、この記事で完結しているので、開発の流れなど +α を説明したいと思います。

https://qiita.com/yukagil/items/ca84c4bfcb47ac53af99

記事のソースコード

https://github.com/yukagil/tfjs-mnist-cnn

<!-- ======================================================== -->
# MNIST学習モデルの作成
<!-- ======================================================== -->
Python実行環境を構築して、**Keras**を使ってMNIST学習モデルの作成します。
※ Kerasとは、機械学習フレームワーク Tensorflowを使いやすしたラッパーフレームワークです。

開発環境構築として、ローカルに構築するパターンと、Colabを使用するパターンの2つを紹介します。

### ローカルに構築する
GPUを使用するので、お使いのPCにNVIDIA製のGPUを積んでいるを持っている方が対象です。

https://qiita.com/nemutas/items/86563b164b72ca41c7d6

上記の`main.py`に記載しているソースは、MNISTのMPLモデルです。より精度の高いCNNモデルは、以下を参照してください。

https://keras.io/examples/vision/mnist_convnet/

### Colabを使用する
とくにこだわりがなければ、[Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を使うのがおすすめです。
デフォルトで、**tensorflow-gpu**、**keras**パッケージが導入されていて、GPU実行環境も提供されています。（神）
バージョンに気を使わなきゃいけない環境構築に悩まされないで済みます。

サンプルコード

https://github.com/nemutas/app-mnist/blob/main/from-Keras-to-Tensorflow.js.ipynb

### 学習モデルの保存
学習が終わったモデルを**model.h5**として保存します。

```.py
model.save('out/model.h5')
```

<!-- ======================================================== -->
# 学習モデルファイルの変換
<!-- ======================================================== -->
Tensorflow.js（JavaScript）で**model.h5**を扱うためには、jsonファイルに変換する必要があります。
変換にも、Pythonを使います。

https://www.tensorflow.org/js/tutorials/conversion/import_keras

ローカルで変換を行う場合は、専用の仮想環境を作ったほうがいいです。
<font color="RoyalBlue">Colabでも変換ができるので、model.h5の容量が大きくない場合は、こっちで済ますのが無難です。（サンプルコードに載せてます）</font>

変換を行うと、学習モデルの構造を保存した**model.json**と、重みを保存した**group1-shard1of1.bin**が出力されます。
重みファイルは、アプリケーション表示時の負荷軽減のために、容量によって自動的に分割されるみたいです。

<!-- ======================================================== -->
# MNIST学習モデルを React で使用する
<!-- ======================================================== -->
フロントエンドアプリケーションは、**React（CRA）**で作成します。使用言語は**TypeScript**です。

コードの詳細は、以下を参考にしてください。

https://github.com/nemutas/app-mnist/blob/main/src/components/PredButton.tsx

### パッケージインストール
以下をインストールします。

```
npm i @tensorflow/tfjs
```

### MNIST学習モデルの使用手順
変換した学習モデルファイルは、**public**フォルダ内に置きます。（ソースコードでは、public/assets/cnn）

推測までの手順は、以下です。

1. MNIST学習モデルをロードする
2. canvasの手書きデータを**28×28px**の**グレースケール**に変換する
3. 変換した画像データを**reshape**、**標準化**（0～255 → 0～1）に変換する
4. ロードしたモデルインスタンスを使用して推測する

```.tsx:モデルのロード
const loadedModel = await tf.loadLayersModel('./assets/cnn/model.json');
```
```.tsx:canvas画像の変換
/**
 * canvasデータを変換する
 * @returns 28×28のグレースケール画像データ
 */
const convertImageData = () => {
  if (!context) return;

  const inputWidth = 28;
  const inputHeight = 28;

  // resize
  const tmpCanvas = document.createElement('canvas').getContext('2d')!;
  tmpCanvas.drawImage(context.canvas, 0, 0, inputWidth, inputHeight);

  // convert grayscale
  let imageData = tmpCanvas.getImageData(0, 0, inputWidth, inputHeight);
  for (let i = 0; i < imageData.data.length; i += 4) {
    const avg = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3;
    imageData.data[i] = imageData.data[i + 1] = imageData.data[i + 2] = avg;
  }

  return imageData;
};
```
```.tsx:推測
/**
 * 推測
 * @param imageData 28×28のグレースケール画像データ
 * @returns 結果
 */
  const predict = (imageData: ImageData) => {
  if (!model) return;

  const score = tf.tidy(() => {
    // convert to tensor (shape: [width, height, channels])
    const channels = 1; // grayscale
    let input = tf.browser.fromPixels(imageData, channels).toFloat();
    // normalized
    input = input.div(tf.scalar(255));
    // reshape input format (shape: [batch_size, width, height, channels])
    input = input.expandDims();
    // predict
    return model.predict(input);
  });

  const result: number[] = [];

  const datas = Array.isArray(score) ? score[0].dataSync() : score.dataSync();
  datas.forEach(d => result.push(d));

  return result;
};
```


<!-- ======================================================== -->
# 成果物
<!-- ======================================================== -->

作成したアプリケーション

https://nemutas.github.io/app-mnist/

ソースコード

https://github.com/nemutas/app-mnist

<!-- ======================================================== -->
# まとめ
<!-- ======================================================== -->
数年前に機械学習の勉強をしていたときに、モチベーションを保てなくなったのが、「じゃあ、それをアプリケーションとしてどう表現するのか？」という自分の表現力（技術力）の無さでした。
それから時間は少し経ちましたが、フロントエンドを勉強して、このように形にできてひとまずフラストレーションが解消されました。
培ってきた技術が繋がっていくのは楽しいです。:upside_down:

<!-- ======================================================== -->
# Document
<!-- ======================================================== -->

https://js.tensorflow.org/api/latest/

https://www.tensorflow.org/js/tutorials/conversion/import_keras?hl=ja

https://keras.io/examples/vision/mnist_convnet/