TensorFlow.js の備忘録

EMNISTをPythonで学習した後,TensorFlow.jsでブラウザ上で文字認識を行うところまでの備忘録.

環境

  • Ubuntu 24.04
  • Python3.8

Jupyter環境の整備

jupyterhubをインストールしてサービスを作成します.

# apt install nodejs npm
# npm install -g configurable-http-proxy
# python3 -m venv /usr/local/share/jupyterhub
# source /usr/local/share/jupyterhub/bin/activate
(jupyterhub)# pip install jupyter jupyterhub jupyterlab matplotlib numpy

jupyterhubの設定ファイルを作成します.venvを利用しているので,jupyterhub-singleuserのパスを通すか以下編集します.

(jupyterhub)# mkdir -p /etc/jupyterhub
(jupyterhub)# cd /etc/jupyterhub
(jupyterhub)# jupyterhub --generate-config
(jupyterhub)# vi /etc/jupyterhub/jupyterhub_config.py

c.Spawner.cmd = ['/usr/local/share/jupyterhub/bin/jupyterhub-singleuser']

jupyterhubのサービスを作成します.ServiceUserを作成した方が良いでしょう.

# vi /etc/systemd/system/jupyterhub.service

[Unit]
Description=JupyterHub
After=network.target

[Service]
User=root
ExecStart=/usr/local/share/jupyterhub/bin/jupyterhub -f /etc/jupyterhub/jupyterhub_config.py
WorkingDirectory=/var/lib/jupyterhub
Restart=always

[Install]
WantedBy=multi-user.target

jupyterhubのサービスを起動します.

# mkdir -p /var/lib/jupyterhub
# systemctl daemon-reload
# systemctl start jupyterhub

Python3.8とTensorFlowのインストール

TensorFlow.jsがPython3.8でないとうまく動かないという情報があったため,Python3.8をインストールして利用する.またjupyterから利用するためにkernelを登録する

# add-apt-repository ppa:deadsnakes/ppa
# apt update
# apt install -y python3.8 python3.8-distutils python3.8-venv
$ python3.8 -v venv tensorflow
$ source tensorflow/bin/activate
(tensorflow)$ pip install tensorflow tensorflowjs matplotlib ipykernel
(tensorflow)$ python3 -m ipykernel install --user --name tensorflow --display-name "Python 3.8 (TensorFlow)" 

学習

kaggleのemnistデータセットをダウンロードします.

$ #!/bin/bash
curl -L -o ~/Downloads/archive.zip\
https://www.kaggle.com/api/v1/datasets/download/crawford/emnist
$ unzip archive.zip

適当にモデルを作成,学習します.

import tensorflow as tf
import numpy as np
from tensorflow.keras import layers
from tensorflow.keras import losses
import os
import pandas as pd
DATA_DIR = "."
TRAIN_CSV = os.path.join(DATA_DIR, "emnist-letters-train.csv")
TEST_CSV = os.path.join(DATA_DIR, "emnist-letters-test.csv")

# データの読み込み
def load_emnist_data(train_csv, test_csv):
    # トレーニングデータの読み込み
    train_df = pd.read_csv(train_csv, header=None)
    test_df = pd.read_csv(test_csv, header=None)

    # 特徴量(画像データ)とラベルに分割
    train_images = train_df.iloc[:, 1:].values.astype(np.float32)
    train_labels = train_df.iloc[:, 0].values.astype(np.int32)  # 最初の列がラベル
    test_images = test_df.iloc[:, 1:].values.astype(np.float32)
    test_labels = test_df.iloc[:, 0].values.astype(np.int32)

    # ピクセル値を 0-1 に正規化
    train_images /= 255.0
    test_images /= 255.0

    # 画像を28x28にリシェイプ
    train_images = train_images.reshape(-1, 28, 28).transpose(0,2,1)
    test_images = test_images.reshape(-1, 28, 28).transpose(0,2,1)

    return train_images, train_labels, test_images, test_labels

train_images, train_labels, test_images, test_labels = load_emnist_data(TRAIN_CSV, TEST_CSV)

# モデルの定義
model = tf.keras.models.Sequential([
    layers.Flatten(input_shape=(28, 28)),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.2),
    layers.Dense(62)
])

# モデルのコンパイル
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
# モデルの学習
model.fit(train_images, train_labels, epochs=10, batch_size=64, validation_split=0.1)
# モデルの評価
test_loss, test_accuracy = model.evaluate(test_images, test_labels, verbose=2)
print(f"Test accuracy: {test_accuracy}")
# モデルの保存
model.save('emnist_model.keras')

変換

tensorflowjs_convertを利用してkerasモデルをtensorflowjsモデルに変換します.

$ tensorflowjs_converter --input_format=keras emnist_letters_model.keras tfjs_model

推論

変換したモデルを読み込みtensorを投げてレスポンスが帰ってくるところまで確認します.

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>TFJS</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
</head>

<body>
    <script>
        let canvas = document.createElement("canvas")
        canvas.width = 120
        canvas.height = 120
        let ctx = canvas.getContext("2d")
        console.log(ctx)
        console.log(tf)
        tf.loadLayersModel("/model/model.json").then((model) => {
            console.log(model)
            ctx.fillStyle = "black"
            ctx.fillRect(0, 0, 120, 120)
            let image = ctx.getImageData(0, 0, 120, 120)
            let tensor = tf.browser.fromPixels(image, 3)
                .toFloat()
                .resizeBilinear([28, 28])
                .mean(2, true)
                .div(tf.scalar(255))
                .reshape([1, 28, 28])
            let data = model.predict(tensor)
                .dataSync()
            console.log(tf.argMax(data).arraySync())
        })
    </script>
</body>

</html>

動作確認だけ.