20191230のTensorFlowに関する記事は7件です。

TensorflowのFreezeGraph済み.pbファイルのINPUTのPlaceholderを置き換えつつモデルを再生成するTips [置換・置き換え・変換・変更・更新・差し替え]

[1, ?, ?, 3] の入力サイズで定義されている .pbファイル の Placeholder[1, 513, 513, 3]Placeholder に強制的に置き換えて、.pbを再生成するサンプルプログラム。

name='image' の部分は置き換え後のPlaceholderの名前を自由に指定する。
input_map={'image:0': inputs}image:0 の部分は、変換前のモデルのPlaceholder名を指定する。

replacement_of_input_placeholder.py
import tensorflow as tf
from tensorflow.tools.graph_transforms import TransformGraph

with tf.compat.v1.Session() as sess:

    # shape=[1, ?, ?, 3] -> shape=[1, 513, 513, 3]
    # name='image' specifies the placeholder name of the converted model
    inputs = tf.compat.v1.placeholder(tf.float32, shape=[1, 513, 513, 3], name='image')
    with tf.io.gfile.GFile('./model-mobilenet_v1_101.pb', 'rb') as f:
        graph_def = tf.compat.v1.GraphDef()
    graph_def.ParseFromString(f.read())

    # 'image:0' specifies the placeholder name of the model before conversion
    tf.graph_util.import_graph_def(graph_def, input_map={'image:0': inputs}, name='')
    print([n for n in tf.compat.v1.get_default_graph().as_graph_def().node if n.name == 'image'])

    # Delete Placeholder "image" before conversion
    # see: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/tools/graph_transforms
    # TransformGraph(
    #     graph_def(),
    #     input_op_name,
    #     output_op_names,
    #     conversion options
    # )
    optimized_graph_def = TransformGraph(
                              tf.compat.v1.get_default_graph().as_graph_def(),
                              'image',
                              ['heatmap','offset_2','displacement_fwd_2','displacement_bwd_2'],
                              ['strip_unused_nodes(type=float, shape="1,513,513,3")'])

    tf.io.write_graph(optimized_graph_def, './', 'model-mobilenet_v1_101_513.pb', as_text=False)
Result
[name: "image"
op: "Placeholder"
attr {
  key: "dtype"
  value {
    type: DT_FLOAT
  }
}
attr {
  key: "shape"
  value {
    shape {
      dim {
        size: 1
      }
      dim {
        size: 513
      }
      dim {
        size: 513
      }
      dim {
        size: 3
      }
    }
  }
}
]
  • 変換前 Screenshot 2019-12-30 23:39:02.png
  • 変換後 Screenshot 2019-12-30 23:38:04.png

Graph Transform Tool
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/tools/graph_transforms

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlow2 + Keras による画像分類に挑戦3 ~MNISTデータを可視化してみる~

はじめに

TensorFlow2 + Keras を利用した画像分類(Google Colaboratory 環境)についての勉強メモ(第3弾)です。題材は、ド定番である手書き数字画像(MNIST)の分類です。

前回は、MNISTデータを取得し、そのデータの構造や内容について確認しました。手書きの数字の画像データに相当する入力データは、28x28pixelの256段階グレースケールでした。このデータの型は numpy.ndarray の2次元配列で、そのまま print するだけでも、なんとか内容(画像イメージ)をつかむことができましたが、今回は matplotlib を使って、次のようにきれいに表示させてみたいと思います。

xtrain3.png

データの正規化

MNISTのデータは、0~255の整数値を使って、256段階グレースケール(白を0、黒を255に割り当てたグレースケール)を表現していました(詳しくは前回参照)。しかし、TensorFlow を使った画像分類のサンプルコード(公式HPのチュートリアル参照)では、機械学習させる都合上、次のように 0.0~1.0 の範囲になるように正規化を施しています。

import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0 # 正規化処理

ここから先は、0.0~1.0 に正規化されたデータを対象に進めていきたいと思います。

とりあえず表示

トレーニング用の入力データの1個目 x_train[0] をグレースケール画像として出力してみたいと思います。このデータは、正解データ y_train[0] に格納されているように「5」を表現した画像になります。

import matplotlib.pyplot as plt
plt.figure(dpi=96)
plt.imshow(x_train[0],interpolation='nearest',vmin=0.,vmax=1.,cmap='Greys')

xtrain0_1.png

実行環境によっては、interpolation='nearest'は省略できます(Google Colab.では省略してもOKです、他環境で実行してぼやけた出力になったら、このオプションを明示しましょう)。

また、vmin=0.,vmax=1. は、当該データ x_train[?] の内部要素の最小値が 0.0、最大値が 1.0 の場合は省略してもOKです(cmap='Greys' により0.0に白、1.0に黒が割り当てられます)。そうでない場合、例えば、薄文字などを表現していてx_train[?]の内部要素の最大値が0.7のようなときは、このオプションを指定しないと、薄文字の感じが反映されません。

キーワード引数 cmap の値を変えると、出力に使用するカラーマップを変えることができます。プリセットとして用意されているカラーマップ一覧は、matplotlibのリファレンスで確認することができます。例えば、cmap='Greens'とすると次のような出力になります(0.0のところも薄緑になります)。

xtrain0_1g.png

カラーマップをカスタマイズすることも可能です。具体的な方法は「相関行列をキレイにカスタマイズしたヒートマップで出力したい。matplotlib編 @ Qiita」を参照ください。

特定の数字についての手書き画像を並べて出力

特定の数字(例えば「7」)について、どんなで手書きデータが存在するのか確認したいときには、次のようなコードで出力することができます。

import numpy as np
import matplotlib.pyplot as plt
x_subset = x_train[ y_train == 7 ]   # (1)
fig, ax = plt.subplots(nrows=8, ncols=8, figsize=(5, 5), dpi=120)
for i, ax in enumerate( np.ravel(ax) ):
  ax.imshow(x_subset[i],interpolation='nearest',vmin=0.,vmax=1.,cmap='Greys')
  ax.tick_params(axis='both', which='both', left=False, 
                 labelleft=False, bottom=False, labelbottom=False) # (2)

実行結果は次のようになります。7以外の数値について出力したい場合は、上記コードの (1) の y_train == 7 の数値を変更してください。(2) の ax.tick_params(...) は、X軸・Y軸の目盛を消すためのものです。

xtrain1.png

一覧で眺めてみると、この64枚のなかであっても、どうみても「1」にしか見えないものが少なくとも2、3個は含まれているということが分かります(つまり、正答率 1.0000 は極めて難しい)。

整形して表示

非常に短いコードで入力データを画像化して出力できることが分かりました。

ここでは、次のように、各入力データの何行何列目の要素がどんな値になっているのか?までを確認できるように手を加えていきます。左上の赤文字は、対応する正解データの値です。

xtrain3.png

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patheffects as pe
import matplotlib.transforms as ts

i = 2 # 表示するデータのインデックス

plt.figure(dpi=120)
plt.imshow(x_train[i],interpolation='nearest',vmin=0.,vmax=1.,cmap='Greys')

h, w = 28, 28
plt.xlim(-0.5,w-0.5) # X軸方向の描画範囲
plt.ylim(h-0.5,-0.5) # Y軸方向の・・・

#
plt.tick_params(axis='both', which='major', 
                left=False, labelleft=False, 
                bottom=False, labelbottom=False)
plt.tick_params(axis='both', which='minor',
                left=False, labelleft=True,
                top=False, labeltop=True, 
                bottom=False, labelbottom=False)

# 各軸のグリッド設定
plt.gca().set_xticks(np.arange(0.5, w-0.5,1)) # 1ドット単位でグリッド
plt.gca().set_yticks(np.arange(0.5, h-0.5,1))
plt.grid( color='tab:green', linewidth=1, alpha=0.5)

# 各軸のラベル設定
plt.gca().set_xticks(np.arange(0, w),minor=True)
plt.gca().set_xticklabels(np.arange(0, w),minor=True, fontsize=5)
plt.gca().set_yticks(np.arange(0, h),minor=True)
plt.gca().set_yticklabels(np.arange(0, h),minor=True, fontsize=5)

# ラベルの位置の微調整
offset = ts.ScaledTranslation(0, -0.07, plt.gcf().dpi_scale_trans)
for label in plt.gca().xaxis.get_minorticklabels() :
    label.set_transform(label.get_transform() + offset)
offset = ts.ScaledTranslation(0.03, 0, plt.gcf().dpi_scale_trans)
for label in plt.gca().yaxis.get_minorticklabels() :
    label.set_transform(label.get_transform() + offset)

# 正解データを左上に表示(白色で縁取り)
t = plt.text(1, 1, f'{y_train[i]}', verticalalignment='top', fontsize=20, color='tab:red')
t.set_path_effects([pe.Stroke(linewidth=5, foreground='white'), pe.Normal()])

plt.colorbar( pad=0.01 ) # 右側にカラーバー表示

グレースケール値のヒストグラム

入力データは、$28\times 28 = 784$ 個の要素から構成され、各要素には 0.0 から 1.0 の値が含まれますが、それはどんな分布になっているかヒストグラムを作成してみたいと思います。

import numpy as np
import matplotlib.pyplot as plt

i = 0 # 表示するデータのインデックス

h = plt.hist(np.ravel(x_train[i]), bins=10, color='black')
plt.xticks(np.linspace(0,1,11))
print(h[0]) # 実行結果 -> [639.  11.   6.  11.   6.   9.  11.  12.  11.  68.]
print(h[1]) # 実行結果 -> [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]

hist.png

plt.hist(...) の戻値には、各階級の度数が含まれます。上記の例だと、範囲 $0.0\le v < 0.1 $ の値を持つピクセルが 639個存在することが分かります。なお、一番右端のみ、範囲は $0.9\le v \le 1.0 $ となり、値がちょうど 1.0 のデータも含んだものになります。

実際に print(h[0].sum()) にすれば、784.0 が得られ、値がちょうど 1.0 の要素もちゃんとカウントされていることが確認できます。

次回

  • 学習済みのモデルを使って実際に予測を行ないます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ubuntu18.04 LTS + RTX2060 + CUDA + tensorflow で機械学習環境を構築する

はじめに

こんにちは, @tbashiyy です. 普段はwebアプリケーションエンジニアをしています.
今回は,研究用途で手元に機械学習環境が欲しくなり,自宅PCにRTX2060を導入しました.
(GTX1660tiと悩みましたが,最終的にはtensor coreが搭載されているRTX2060にしました.)
そこで,tensorflowとCUDAを使って,Ubuntu上に機械学習環境を構築する手順を備忘録としてまとめておきます.

環境

  • OS: Ubuntu 18.04.3 LTS
  • Memory: 8GB
  • CPU: Core i5-6500(3.2GHz x 4)
  • GPU: GeForce RTX2060
  • python: 3.7.5
  • 仮想環境: pipnev

python仮想環境上にtensorflowをインストール

まず,tensorflowを使えるように,任意のディレクトリにpythonの仮想環境を作成していきます.
仮想環境は,pipenvを使います.

任意のディレクトリ上で

$ pipenv --python 3.7
$ pipenv shell

で,仮想環境に入ります.そして,

$ pipenv install tensorflow-gpu

仮想環境にGPUを利用するtensorflowのパッケージをインストールします.

$ pip freezeで下記のようにインストールが確認できれば成功です.

tensorboard==2.0.2
tensorflow-estimator==2.0.1
tensorflow-gpu==2.0.0

※後ほどjupyter notebookを利用します. もしインストールされていない場合は,

$ pipenv install jupyter

で,jupyterも入ります.

GPU周りに必要なソフトウェア群のインストール

(参考) https://www.tensorflow.org/install/gpu
上記URLのtensorflow公式通り,必要なソフトウェアをインストールしていきます.

1. 環境変数の設定

$ vi ~/.bashrc.bashrcを開いて,下記を追加します.

./bashrc
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/extras/CUPTI/lib64

追加して保存したら,

$ source ~/.bashrc

します.

2. NVIDAのdriver,CUDA類のインストール

ここでは下記の5つをインストールしていきます.
- NVIDIA GPU drivers
- CUDA Toolkit
- CUPTI
- cuDNN SDK
- TensorRT 5.0

下記を実行.

$ wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
$ sudo dpkg -i cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
$ sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
$ sudo apt-get update
$ wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
$ sudo apt install ./nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
$ sudo apt-get update
$ sudo apt-get install --no-install-recommends nvidia-driver-418

ここまで終わった段階で,

$ sudo reboot

して再起動する.再起動後,

$ nvidia-smi

を実行して下記のような表示が出れば成功.

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.33.01    Driver Version: 440.33.01    CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 2060    On   | 00000000:01:00.0  On |                  N/A |
| 47%   30C    P8    12W / 160W |    470MiB /  5933MiB |      3%      Default |
+-------------------------------+----------------------+----------------------+                                                                           

そして,下記コマンドを実行する.

$ sudo apt-get install --no-install-recommends \
    cuda-10-0 \
    libcudnn7=7.6.2.24-1+cuda10.0  \
    libcudnn7-dev=7.6.2.24-1+cuda10.0

$ sudo apt-get install -y --no-install-recommends libnvinfer5=5.1.5-1+cuda10.0 \
    libnvinfer-dev=5.1.5-1+cuda10.0

ここまでで,インストールが完了です.(この段階で再度rebootすると良いかもしれません.)

tensorflowでGPUが使えているか確認する

(参考)https://thr3a.hatenablog.com/entry/20180113/1515820265

先程作ったpipenvの仮想環境に入り,

$ jupyter notebook

で,ブラウザが立ち上がり,下記のような画面が出てきます.

Screenshot from 2019-12-30 18-12-21.png
右上のNew -> Python3 で新規ファイルを作成します.

作成したファイルで,

from tensorflow.python.client import device_lib
device_lib.list_local_devices()

を実行して,下記のような結果の中に, device_type: "GPU"があればtensorflow上からGPUが認識できています.

[name: "/device:CPU:0"
 device_type: "CPU"
 memory_limit: 268435456
 locality {
 }
 incarnation: 7162619330375723357, name: "/device:XLA_CPU:0"
 device_type: "XLA_CPU"
 memory_limit: 17179869184
 locality {
 }
 incarnation: 9655519139365664409
 physical_device_desc: "device: XLA_CPU device", name: "/device:XLA_GPU:0"
 device_type: "XLA_GPU"
 memory_limit: 17179869184
 locality {
 }
 incarnation: 16357788976776791373
 physical_device_desc: "device: XLA_GPU device", name: "/device:GPU:0"
 device_type: "GPU"
 memory_limit: 5294129152
 locality {
   bus_id: 1
   links {
   }
 }
 incarnation: 3083761924213793354
 physical_device_desc: "device: 0, name: GeForce RTX 2060, pci bus id: 0000:01:00.0, compute capability: 7.5"]

実際にGPUを使っているかモニタリングしてみる

tensorflowにあるチュートリアルを実行してみます.
その際に,

$ nvidia-smi -l

をしておくと,リアルタイムでGPU使用率をモニタリングできます.

結果

ProcessesのPID6933で5233MiBのビデオメモリを利用していることが確認できますね.

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.33.01    Driver Version: 440.33.01    CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 2060    On   | 00000000:01:00.0  On |                  N/A |
| 47%   34C    P2    33W / 160W |   5818MiB /  5933MiB |     21%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      6660      C   ...rew/.linuxbrew/opt/python/bin/python3.7    83MiB |
|    0      6933      C   ...rew/.linuxbrew/opt/python/bin/python3.7  5233MiB |
+-----------------------------------------------------------------------------+

最後に

  • 実はかなり試行錯誤して,何度もやり直しをしました.最終的には公式ドキュメントにたどり着きました. 公式ドキュメントをはじめからちゃんと読みましょう.(自戒)
  • 公式にはCUDA10.0が推奨,調べていると10.2で動作しないなどの報告をちらほら見つけましたが,公式のとおりにインストールするとなぜかCUDA10.2になってしまいました. 動いているっぽいのでいいのか...?(詳しい方教えていただけると嬉しいです.)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlow2 + Keras による画像分類に挑戦2 ~入力データを詳しくみてみる~

はじめに

手書き数字画像(MNIST)の分類を、Google Colaboratory 環境の TensorFlow2 + Keras でやってみよう(+Pythonや深層学習の理解も深めよう)という内容です。前回 は、TensorFlow の 公式HPのチュートリアル からサンプルコードを持ってきて、実際に実行してみる、というところまでやりました。

なお、MNIST(エムニスト)は、「図解速習DEEP LEARNING(著:増田知彰)」によれば、次のような由来があるデータだそうです。ここでは直接関係ありませんが、生のデータは、http://yann.lecun.com/exdb/mnist/ から入手できます。

NIST(National Institute of Standards and Technology database)の1つに、米国の国勢調査局職員と高校生が手書きした数字を持つデータセットがありました。それを機械学習でより使いやすく改変(Modified)したものが、"M"NISTです。

今回は、前回に示したサンプルコードのなかの トレーニング用データx_trainy_train)、テスト用データx_testy_test)について、その内容を詳しく見てみたり、matplotlib を使って可視化してみたりします。

それにあたり、まずは「多クラス分類問題」と「深層学習」について整理しておきます(トレーニング用データとテスト用データの位置づけを確認します)。

多クラス分類問題

手書き数字の認識は、多クラス分類問題というものに属します。多クラス分類問題とは、入力データに対して、そのカテゴリ(クラス)を予測するという問題です。カテゴリは、問題設定のなかで「犬」「猫」「鳥」のようにあらかじめ与えられており、入力データ(例えば画像)に対してそれが「犬」「猫」「鳥」のうち、どのカテゴリになるかを求める、といった問題になります。

多クラス分類.png

多クラス分類問題に対して様々なアプローチが提案されていますが、ここでは深層学習(ディープラーニング)を使って解決していきます。

深層学習

深層学習(ディープラーニング)は、教師付き機械学習という手法に属します。教師付き機械学習は、大きく「学習フェーズ」と「予測フェーズ(推論フェーズ、適用フェーズ)」という2段階から構成されます。

フェーズ.png

はじめに、学習フェーズでは、入力データ正解データ(=教師データ、正解データ、正解値、正解ラベル)をペアにしたものをモデルに大量に与えて、それらの関係を学習させます。これらの入力データと正解データのペア集合をトレーニング用データ(=学習用データ)と呼びます。そして、トレーニング用データを使って学習させたモデルを「学習済みモデル」といいます。

イメージ.png

つづく予測フェーズでは、学習済みモデルに対して、未知の入力データを与えて出力の予測(Predict)を行ないます。多クラス問題であれば、カテゴリ(例えば「犬」など)が予測出力となります。

そして、「学習済みモデルにどの程度の性能があるか」を測るのが評価(Evaluate)というプロセスになります。評価では、まず、トレーニングに使ったものとは異なる入力データと正解データを用意して、このうち入力データだけを学習済みモデルに与えて、予測データを得ます。そして、この得られた予測データについて、正解データを使って答え合わせ、採点をして評価値とします。具体的な評価指標としては、前回出てきた正答率(accuracy)、損失関数値(loss)のほかに、適合率や再現率など必要に応じて様々なものが採用されます。

MNISTのトレーニング用データ、テスト用データ

次のコードで、MNISTデータをダウンロードして、各変数(x_trainy_trainx_testy_test)に格納しています(プログラム全体は前回 を参照)。

mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

ここで、*_train がトレーニング用(学習用)に割り当てられた入力&正解データ、*_test がテスト用(モデル評価用)に割り当てられた入力&正解データとなります。トレーニング用は 60,000件、テスト用は 10,000件 あります。

また、x_*** には入力データ(つまり手書き画像を表すデータ:28x28の256段階グレースケール)、y_*** には正解データ(「0」から「9」までのカテゴリ)が、配列的に格納されています。

まずは、実際に、それぞれが 60,000件、10,000件 のデータから構成されていることを len() で確認してみます。

# トレーニング用データ
print(len(x_train))  # 実行結果 -> 60000
print(len(y_train))  # 実行結果 -> 60000
# テスト用データ
print(len(x_test))   # 実行結果 -> 10000
print(len(y_test))   # 実行結果 -> 10000

次に、各データのタイプ(型)を確認してみます。

print(type(x_train)) # 実行結果 -> <class 'numpy.ndarray'>
print(type(y_train)) # 実行結果 -> <class 'numpy.ndarray'>
print(type(x_test))  # 実行結果 -> <class 'numpy.ndarray'>
print(type(y_test))  # 実行結果 -> <class 'numpy.ndarray'>

次に、y_train(=トレーニング用の正解データ)の内容を確認してみます。

print(y_train) # 実行結果 -> [5 0 4 ... 5 6 8]

0件目のデータの正解値は「5」、1件目のデータの正解値は「0」・・・、59,999件目のデータの正解値は「8」ということが分かりました。

次に、x_train(=トレーニング用の手書き画像を表すもの)の内容を確認してみます。全件を表示するととんでもないことになるので、先頭の x_train[0] のみを対象にします。

(x_train, y_train), (x_test, y_test) = mnist.load_data()
print(x_train[0].shape) # 実行結果 -> (28, 28)
print(x_train[0])       # 実行結果 -> 下記参照 

numpy.ndarray のデータは、.shape で大きさが確認できます。 (28, 28)、ということは、x_train[0]28行28列の2次元配列で構成されていることが分かります。また、print(x_train[0])の出力は次のようになります。

薄眼で眺めていただくと、ややいびつな手書きの「5」という数字が浮かんできます。これは、y_train[0] に格納されている「5」と一致しますね。

無題.png

各ピクセルデータは、0から255の範囲の値で構成されて、0が背景(白)で、255が最も濃い文字部(黒)になっていることが分かります。

60,000個の全てのデータについて、それを確認してみたいと思います。

import numpy as np
print(x_train.min())  # 最小値を抽出 # 実行結果 -> 0
print(x_train.max())  # 最大値を抽出 # 実行結果 -> 255

すべてのデータは0から255の範囲で構成されていることが確認できます。

ところで、60,000件のトレーニング用データのなかに、「0」から「9」までの各数字は何件ずつ存在するのでしょうか?基本的には、0から9までの10パターンがほぼ均等に存在していると思いますが、確認してみます。集計にpandasを利用します。

pandas版
import pandas as pd

tmp = pd.DataFrame({'label':y_train})
tmp = tmp.groupby(by='label').size()
display(tmp)
print(f'総数={tmp.sum()}')
実行結果
label
0    5923
1    6742
2    5958
3    6131
4    5842
5    5421
6    5918
7    6265
8    5851
9    5949
dtype: int64
総数=60000

「5」が少なくて「1」が多いといった多少のバラつきがあるようです。

なお、次のように pandas を使わなくても求めることができます。

numpy版
import numpy as np
tmp = list([np.count_nonzero(y_train==p) for p in range(10)])
print(tmp)                # 実行結果 -> [5923, 6742, 5958, 6131, 5842, 5421, 5918, 6265, 5851, 5949]
print(f'総数={sum(tmp)}') # 実行結果 -> 総数=60000

次回

  • matplotlib を使って入力データをグラフィカルに表示するところまで進めたかったのですが、記事が長くなってしまったので、それは次回にしたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + TensorFlow.jsで手書き数字認識アプリを作ってみた

はじめに

サークルの講習会で、kivyとTensorFlow?を使って手書きの数字を認識するアプリを作っている先輩がいたので、これをReactでやってみたいなと思ったので、今回やってみました。このアプリを作る中で、こちらのサイトを非常に参考にさせていただきました。また、僕は機械学習についてあまり知識がないので、今回はpythonを全く書きません。機会があれば、モデルの作成からやってみたいと思います。

完成物

tegaki2.gif

インストールしたパッケージ

TensorFlow.js: https://www.tensorflow.org/js/
react-signature-canvas: https://www.npmjs.com/package/react-signature-canvas
material-ui: https://material-ui.com/

"@material-ui/core": "^4.8.1",
"@tensorflow/tfjs": "^0.12.6",
"react-signature-canvas": "^1.0.3",

※ tensorflow/tfjsは上記のバージョンに合わせてください。

ファイル構造

create-react-appコマンドで作成したプロジェクトをベースに話を進めます。以下のファイル構造は、編集または新規作成したファイルのみ書いています。

src-
   |-components
   |      |-Accuracy.js
   |      |-AccuracyTable.js
   |
   |-App.js
   |-App.css
components/Accuracy.js
import React from "react";

const Accuracy = props => {
  const { no, content } = props;

  return (
    <tr>
      <th>{no}</th>
      <td className="accuracy" data-row-index={`${no}`}>
        {content}
      </td>
    </tr>
  );
};

export default Accuracy;
components/AccuracyTable.js
import React from "react";
import Accuracy from "./Accuracy";

const AccuracyTable = () => (
  <table className="table">
    <thead>
      <tr>
        <th>数字</th>
        <th>精度</th>
      </tr>
    </thead>
    <tbody>
      <Accuracy no={0} content="-" />
      <Accuracy no={1} content="-" />
      <Accuracy no={2} content="-" />
      <Accuracy no={3} content="-" />
      <Accuracy no={4} content="-" />
      <Accuracy no={5} content="-" />
      <Accuracy no={6} content="-" />
      <Accuracy no={7} content="-" />
      <Accuracy no={8} content="-" />
      <Accuracy no={9} content="-" />
    </tbody>
  </table>
);

export default AccuracyTable;
App.js
import React from "react";
import "./App.css";
import * as tf from "@tensorflow/tfjs";
import SignatureCanvas from "react-signature-canvas";
import { Button } from "@material-ui/core";
import AccuracyTable from "./components/AccuracyTable";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      is_loading: "is-loading",
      model: null,
      maxNumber: null,
      maxScore: null
    };
    this.onRef = this.onRef.bind(this);
    this.getImageData = this.getImageData.bind(this);
    this.getAccuracyScores = this.getAccuracyScores.bind(this);
    this.predict = this.predict.bind(this);
    this.reset = this.reset.bind(this);
  }

  componentDidMount() {
    tf.loadModel(
      "https://raw.githubusercontent.com/tsu-nera/tfjs-mnist-study/master/model/model.json"
    ).then(model => {
      this.setState({
        is_loading: "",
        model
      });
    });
  }

  onRef(ref) {
    this.signaturePad = ref;
  }

  getAccuracyScores(imageData) {
    const scores = tf.tidy(() => {
      const channels = 1;
      let input = tf.fromPixels(imageData, channels);
      input = tf.cast(input, "float32").div(tf.scalar(255));
      input = input.expandDims();
      return this.state.model.predict(input).dataSync();
    });
    return scores;
  }

  getImageData() {
    return new Promise(resolve => {
      const context = document.createElement("canvas").getContext("2d");
      const image = new Image();
      const width = 28;
      const height = 28;

      image.onload = () => {
        context.drawImage(image, 0, 0, width, height);
        const imageData = context.getImageData(0, 0, width, height);

        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] = avg;
          imageData.data[i + 1] = avg;
          imageData.data[i + 2] = avg;
        }
        resolve(imageData);
      };

      image.src = this.signaturePad.toDataURL();
    });
  }

  predict() {
    this.getImageData()
      .then(imageData => this.getAccuracyScores(imageData))
      .then(accuracyScores => {
        const maxAccuracy = accuracyScores.indexOf(
          Math.max.apply(null, accuracyScores)
        );
        const elements = document.querySelectorAll(".accuracy");
        elements.forEach(el => {
          el.parentNode.classList.remove("is-selected");
          const rowIndex = Number(el.dataset.rowIndex);
          if (maxAccuracy === rowIndex) {
            el.parentNode.classList.add("is-selected");
          }
          el.innerText = Math.round(accuracyScores[rowIndex] * 1000) / 1000;
        });
        this.setState({
          maxNumber: maxAccuracy,
          maxScore: accuracyScores[maxAccuracy]
        });
        console.log(accuracyScores);
      });
  }

  reset() {
    this.setState({
      maxNumber: null
    });
    this.signaturePad.clear();
    const elements = document.querySelectorAll(".accuracy");
    elements.forEach(el => {
      el.parentNode.classList.remove("is-selected");
      el.innerText = "-";
    });
  }

  render() {
    let text = "数字を入力してください";
    if (this.state.maxNumber !== null) {
      if (this.state.maxScore > 0.999) {
        text = `この数字は確実に${this.state.maxNumber}です。`;
      } else if (this.state.maxScore > 0.9) {
        text = `この数字はほぼ間違いなく${this.state.maxNumber}です。`;
      } else if (this.state.maxScore > 0.5) {
        text = `この数字は多分${this.state.maxNumber}です。`;
      } else {
        text = `この数字は${this.state.maxNumber}かもしれないです。`;
      }
    }
    return (
      <div className="container">
        <h2>{text}</h2>
        <div className="canbas">
          <SignatureCanvas
            ref={this.onRef}
            minWidth={15}
            maxWidth={15}
            penColor="white"
            backgroundColor="black"
            canvasProps={{
              width: 420,
              height: 420,
              className: "sigCanvas"
            }}
            onEnd={this.predict}
          />
        </div>
        <div className="button">
          <Button variant="contained" onClick={this.reset}>
            reset
          </Button>
        </div>
        <AccuracyTable />
      </div>
    );
  }
}

export default App;
App.css
.container {
  margin-bottom: 120px;
  text-align: center;
}

.canbas {
  display: inline;
}

.button {
  display: block;
  margin-top: 20px;
  margin-bottom: 60px;
}

.table {
  display: inline;
  border-collapse: collapse;
  border-spacing: 0;
}

.table th, .table td {
  padding: 10px 0;
  width: 200px;
  text-align: center;
}

.table tr:nth-child(odd) {
  background-color: #eee;
}

.is-selected {
  color: red;
}

終わりに

PCでは、うまくいったのですが、スマホからアクセスするとうまくいきませんでした。今後の課題としては、スマホアプリ(Expo+ReactNative)にしてみたいなと思っています。あとは、機械学習を勉強してモデルの作成もいちからやってみたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlow2 + Keras による画像分類に挑戦1 ~とりあえず動かす~

はじめに

TensorFlow2 + Keras を利用した画像分類(Google Colaboratory 環境)についての勉強メモ(第1弾)です。題材は、ド定番である手書き数字画像(MNIST)の分類です。

具体的には、こんな手書き数字を取り込んだ画像(28x28pixel)を対象に、
MNIST-1.png
それぞれの画像が「0」から「9」のどれに分類できるか?という問題(=多クラス分類問題)について、TensorFlow2 + Keras によるディープラーニング(深層学習)でアプローチしてみようという内容です。

開発・実行の環境には、簡単・便利で無料の Google Colabo. を利用します。Google Colabo. の導入については、こちら を参照ください。

今回の記事では、TensorFlow の 公式HP に掲載されているサンプルコードを持ってきて、Google Colab. のコードセルに貼り付け、問題なく実行できることを確認します。

そのうえで「コードの各部分では何をやっているのか」「実行時にで表示されるテキストは何を伝えているのか」を緩くぼんやりと解説しています。

TensorFlowとは

  • 「テンソルフロー」または「テンサーフロー」と読む。
  • Googleが開発した機械学習ライブラリで、ニューラルネットワーク(NN)の構築とトレーニング(=学習/訓練)ができる。無論、トレーニングしたNNモデルを使って予測もできる。
  • 2017年2月に 1.0 がリリース、2019年10月に 2.0 がリリースされた。
  • TF2.0 では、Keras(後述)を統合して Pythonとの親和性が高まり、より使いやすく洗練されたものとなった(とのこと)。GPU対応も強化された(とのこと)。
    • PyTorch などの後発の機械学習ライブラリ勢力に負けないように開発が続いている。

Keras

  • 「ケラス」と読む。
  • TensorFlow のほか、Theano にも対応したハイレベルのAPI。ラッパー。
  • Python で書かれている。
  • Keras 経由で TF を使うことで、簡潔で短いコードにより機械学習が実現できる。

サンプルコードを試す

TensorFlowの公式HPの「初心者のための TensorFlow 2.0 入門」に、手書き数字の画像データセット(MINIST)を対象とした画像分類のサンプルコード(わずか十数行)があります。これを Google Colab. に貼り付けて実行します。

TFのバージョンを 1.x から 2.x に切り替える

TensorFlow2 を利用するため、次のマジックコマンドをコードセルのなかで実行します(コードセルに貼り付けて Ctrl+Enter)。これは、Google Colab.(2019/12/27の時点)では TensorFlow 1.x がデフォルトになっているので、それを 2.x を利用するように切り替えるためのものです。

GoogleColab.での準備
%tensorflow_version 2.x

問題なければ「TensorFlow 2.x selected.」と表示されます。なお、1.x の TF を実行すると「The default version of TensorFlow in Colab will soon switch to TensorFlow 2.x.」とメッセージがでるので、近いうちにこの手続きは不要になると思います(2.x がデフォルト環境になると思います)。

サンプルコードと実行

公式HPのサンプルコードに少しだけコメントを付けています。

import tensorflow as tf

# (1) 手書き数字画像のデータセット(MNIST)をダウンロード、変数に格納
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# (2) データの正規化(データに対する前処理)
x_train, x_test = x_train / 255.0, x_test / 255.0

# (3) NNモデルの構築
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation='softmax')
])

# (4) モデルのコンパイル(学習に関する設定も含む)
model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['accuracy'])

# (5) モデルのトレーニング(学習・訓練)
model.fit(x_train, y_train, epochs=5)

# (6) モデルの評価
model.evaluate(x_test,  y_test, verbose=2)

この短いプログラムのなかで、次のことを行なっています。

  • 手書き数字画像のデータセットをダウンロードして、各変数に格納(データの準備)
    • *_train:トレーニング用(学習用、訓練用)のデータ
    • *_test:テスト用(評価用)のデータ
    • これらデータについての解説は次回で
  • データの正規化(データに対する前処理)
    • 0~255 の範囲の整数値を、0.0~1.0 の範囲の実数値に変換
  • 機械学習のためのニューラルネットワークモデルの構築
  • モデルのコンパイル(学習に関する設定も含む)
  • トレーニング用データを使ったモデルの学習(→ 学習済みモデルが完成)
  • テスト用データ(*_test)を使ったモデルの評価(学習済みモデルによる画像分類の実行と、答え合わせ(採点))

プログラムの実行結果は、次のようになります。

実行結果
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 5s 82us/sample - loss: 0.2992 - accuracy: 0.9134
Epoch 2/5
60000/60000 [==============================] - 5s 78us/sample - loss: 0.1457 - accuracy: 0.9561
Epoch 3/5
60000/60000 [==============================] - 5s 78us/sample - loss: 0.1096 - accuracy: 0.9659
Epoch 4/5
60000/60000 [==============================] - 5s 78us/sample - loss: 0.0876 - accuracy: 0.9730
Epoch 5/5
60000/60000 [==============================] - 5s 80us/sample - loss: 0.0757 - accuracy: 0.9765
10000/10000 - 0s - loss: 0.0766 - accuracy: 0.9762
[0.07658648554566316, 0.9762]

意味的には・・・

  • Train on 60000 samples :60,000枚の手書き文字画像を使ってトレーニングしますよ。
  • Epoch x/5:全体で5回繰り返して学習させるうちの x回目の学習ですよ。
  • 5s 82us/sample - loss: 0.2992 - accuracy: 0.9134:画像1枚あたり82$\mu$秒、全体(60,000枚)では約5秒の学習時間がかかりましたよ。そうやって学習させたモデルについて(トレーニング用データを使って評価した)性能は、損失関数値(loss)が 0.2992、正答率(accuracy)が0.9134 でしたよ。
    • 0.9134 ということは、$60,000\times0.9134=54,804$ 枚の画像について正しく 0~9 の分類できて、残りの $60,000-54,804=5,196$ 枚の画像については正しい分類ができなかったということ。
  • 10000/10000 - 0s - loss: 0.0766 - accuracy: 0.9762:(トレーニング用として使ったものとは別の)テスト用の 10,000枚の画像で分類予測のテストをしました。テストには 0秒の時間がかかって、損失関数値(loss)が 0.0766、正答率(accuracy)が 0.9762 でしたよ。

正答率(accuracy)とは

「精度」や「正解率」とも呼ばれます。正しく分類できた画像の割合を表します。例えば、100枚の画像のうち、98枚について正しく分類できれば正答率は 0.98(=98%)となります。0.0 から 1.0 までの範囲をとり、値が大きいほど(1.0に近いほど)優れたモデルといえます。

損失関数値(loss)とは

正答率という観点だけではモデル(分類器)の優劣を測れない部分があります。例えば、次のような1枚の画像(正解は「3」)について、異なる2つのモデルを使って分類(予測)を行なうとします。

ダウンロード.png

この画像に対して、モデルAは「3」と予測し、モデルBも「3」と予測したとします。正解は「3」なので、いずれのモデルも正答率は 1.0 となります。この正答率という指標を見れば、2つのモデルは同じ程度に優れたモデルといえます。

しかし、モデルAの予測は「8である確信が10%、3である確信が90%というなかで、3を選択したもの」であり、一方で、モデルBの予測は「8である確信が45%、3である確信が55%というなかで、3を出力したもの」だったとしたら、どうでしょうか?

同じ正答率 1.0 でも、モデルAのほうが優れていると言えます。

しかし、正答率という指標では、このようなことは考慮できません。それを評価するためのものが損失関数値(loss)になります。

今回の手書き数字分類は「多クラス分類問題」というタイプに属し、その場合、損失関数には交差エントロピー(クロスエントロピー)という指標がよく使用されます。交差エントロピーは、ニューラルネットワークの出力層の各値と正解データを使って計算します)。詳しくは丁寧に解説している記事が多数ありますので、そちらを参照してください。

基本的に損失関数値は0以上の値をとり、損失関数値が小さいほど(0.0に近いほど)優れたモデルと見なします。

次回

  • 今回はここまでで、次回は、トレーニング用データ(x_trainy_train)、テスト用データ(x_testy_test)の解説と matplotlib を使った可視化をしたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tensorflowのインストール

tensorflowのインストール

tensorflowのインストールはpythonのバージョンとtensorflowのバージョンが適合することが必要になる。
(anacondaを使用すると楽だが、ここでは直で入れる場合とする)

今回Ubuntu18.04.3にpython3.6.9が入っていた。

https://qiita.com/yasushi00/items/3e5a299b0a6a808e4af3
このサイトによると、python3.6.9に適合するtensorflowのバージョンは2.0.0とあったので、以下を実行。

pip3 install tensorflow==2.0.0

※pipではなくpip3でやらないとダメだった。
すると下記のようなメッセージが出た。

Collecting tensorflow==2.0.0
Could not find a version that satisfies the requirement tensorflow==2.0.0 (from versions: 0.12.1, 1.0.0, 1.0.1, 1.1.0rc0, 1.1.0rc1, 1.1.0rc2, 1.1.0, 1.2.0rc0, 1.2.0rc1, 1.2.0rc2, 1.2.0, 1.2.1, 1.3.0rc0, 1.3.0rc1, 1.3.0rc2, 1.3.0, 1.4.0rc0, 1.4.0rc1, 1.4.0, 1.4.1, 1.5.0rc0, 1.5.0rc1, 1.5.0, 1.5.1, 1.6.0rc0, 1.6.0rc1, 1.6.0, 1.7.0rc0, 1.7.0rc1, 1.7.0, 1.7.1, 1.8.0rc0, 1.8.0rc1, 1.8.0, 1.9.0rc0, 1.9.0rc1, 1.9.0rc2, 1.9.0, 1.10.0rc0, 1.10.0rc1, 1.10.0, 1.10.1, 1.11.0rc0, 1.11.0rc1, 1.11.0rc2, 1.11.0, 1.12.0rc0, 1.12.0rc1, 1.12.0rc2, 1.12.0, 1.12.2, 1.12.3, 1.13.0rc0, 1.13.0rc1, 1.13.0rc2, 1.13.1, 1.13.2, 1.14.0rc0, 1.14.0rc1, 1.14.0, 2.0.0a0, 2.0.0b0, 2.0.0b1)
No matching distribution found for tensorflow==2.0.0

そのため、下記を実行

pip3 install tensorflow==2.0.0a0

インストールが実行された。

pip3 list

でバージョンを確認すると
tensorflow (2.0.0a0)
と表示されたのでこれでOK。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む