ホーム » MNIST » TensorFlow : ML 初心者向けの MNIST (コード解説)

TensorFlow : ML 初心者向けの MNIST (コード解説)

TensorFlow : コード解説 : ML 初心者向けの MNIST

* 本ページのベースとなっている TensorFlow : Get Started : ML 初心者向けの MNIST は TensorFlow のバージョンアップに伴い、大幅に加筆修正されましたが本ページには反映されておりません。必要ならばリンク先を参照してください。(03/19/2017)
* (Obsolete) TensorFlow : Tutorials : ML 初心者向けの MNIST に、数式は排除/コード重視の方針で詳細な解説を加筆したものです。

 
本文

MNIST は簡単なコンピュータ・ビジョンのデータセットで、このような手書き数字の画像から成ります :

データセットはまた、どの数字かを教えてくれる各画像へのラベルを含みます。例えば、上の画像へのラベルは 5, 0, 4 そして 1 です。

このチュートリアルでは、画像を見てどの数字であるか予測できるようにモデルを訓練していきます。我々の目的は TensorFlow の利用に足を踏み入れることですので、非常に簡単なモデルから始めます、これは Softmax 回帰と呼ばれます。

このチュートリアルのコードは非常に短く、全ての興味深いことはわずか3行の内に発生します。しかし背後のアイデアを理解することは非常に重要です : どのように TensorFlow が動作するか、そして機械学習の中心的な概念です。それ故に注意深くコードに取り組んでいきましょう。

 

MNIST データ

【補遺】MNIST データの扱いについては、先に TensorFlow : MNIST データ・ダウンロード (コード解説) を読むことをお勧めします。

MNIST データは Yann LeCun’s Web サイト にホストされていて、便宜を図るため、データを自動的にダウンロードしてインストールするための python コードを含まれています。コード (input_data.py) をダウンロードして次のように import しても良いですし、単にコードをコピー&ペーストしてもかまいません。

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

ダウンロードされたデータは3つのパートに分かれます、訓練データの 55,000 データ・ポイント (mnist.train)、テスト・データの 10,000 ポイント (mnist.test) そして検証データの 5,000 ポイント (mnist.validation) です。
分割は非常に重要で、機械学習において学習には使わない隔離データを持つことは本質的です。これにより学習したモデルが実際に一般化されていることを確かなものにできます。

全ての MNIST データポイントは2つのパートを持ちます : 手書き数字の画像と該当ラベルです。画像を “xs” そしてラベルを “ys” と呼ぶことにします。訓練セットとテストセットの両方とも xs と ys を含み、例えば訓練イメージは mnist.train.images で訓練ラベルは mnist.train.labels です。

各画像は 28 ピクセル x 28 ピクセルです。これは数値の大きな配列と解釈することができます。

この配列を 28 x 28 = 784 数値のベクタに平坦化できます。画像間で一貫していればどのように配列を平坦化するかは問題ではありません、この見地からは MNIST 画像は very rich structure を持つ、784-次元のベクタ空間のたくさんのポイントになります。

データの平坦化は画像の 2D 構造についての情報を捨てています。ベストなコンピュータ・ビジョンの方法ではこの構造を利用しますが、ここで使う単純な方法 – softmax 回帰では利用しません。

結果は mnist.train.images は [55000, 784] の 形状を持つテンソルになります。最初の次元は画像へのインデックスで2つめの次元は各画像のピクセルへのインデックスです。テンソルの各要素は特定の画像の特定のピクセルのための 0 と 1 の間のピクセル濃度です。

MNIST の該当するラベルは 0 から 9 の間の数字で、与えられた画像がどの数字であるかを示します。このチュートリアルの目的のためにはラベルには「one-hot ベクタ」を要求します。one-hot ベクタはほとんどの次元で 0 で一つの次元で 1 であるベクタです。この場合、n 番目の数字は n 番目の次元が 1 のベクタとして表されます。例えば 3 は [0,0,0,1,0,0,0,0,0,0] になります。結果的に mnist.train.labels は float の [55000, 10] 配列になります。

今、我々のモデルを実際に作成する準備ができました!

 

Softmax 回帰

MNIST の全ての画像が、それが 0 であろうと 9 であろうと、数字であることを我々は知っています。我々は画像を見て(その画像に)各々の数字である確率を与えることができるようにしたいのです。例えば、我々のモデルは 9 の画像を見て、その画像が 9 であることが 80 % 確かであり、しかし((数字の形状の)上部のループゆえに) 8 である 5 % の可能性を与え、そして確信が持てないために全ての他の数字にも少しの可能性を与えます。

これは softmax 回帰が自然で簡単なモデルとなる古典的なケースです。幾つかの異なるものの一つであるオブジェクトに確率を割り当てたいならば softmax がこれを行なってくれます。後に、より洗練されたモデルを訓練する時にさえも最終ステップは softmax 層になります。

softmax 回帰は2つのステップを持ちます : 一つは、入力の、確かと考えるクラスに属するための証拠 (evidence) を集計すること、そして証拠を確率に変換します。

与えられた画像がある特定のクラスに属す証拠を計算するために、ピクセル強度の加重和(重みつき和)を取ります。高い強度を持つピクセルが画像があるクラスに属することへの反証となるのであれば重みはネガティブ(負値)です。そして補強する証拠であればポジティブ(正値)です。

次の図は一つのモデルがこれらのクラスについて学習した重みを示しています。赤色はネガティブな重みを表し、青色はポジティブな重みを表しています。

 

更にバイアスと呼ばれる特別な証拠も追加しますが、これは入力から独立的にある事象が起きる可能性を考慮してのものです。結果的に入力 \(x\) が与えられた時のクラス \(i\) の証拠は :

\[\text{evidence}_i = \sum_j W_{i,~ j} x_j + b_i\]

ここで \(W_i\) は重みで \(b_i\) はクラス \(i\) のためのバイアス、そして \(j\) は入力画像 \(x\) のピクセルについて集計するためのインデックスです。そして証拠の合計を “softmax” 関数を使って予測確率 \(y\) に変換します :

\[y = \text{softmax}(\text{evidence})\]

コンパクトに書くこともできます :

\[y = \text{softmax}(Wx + b)\]

ここで softmax は “活性化” 関数として機能しますが、線形関数の出力を望ましい形式に整形するだけです – この場合は 10 ケースの確率分布になります。これは証拠の合計を、入力が各クラスに属する確率に変換するものとして考えれば十分です。

softmax 回帰は一応次のような感じで図示できます。実際にはより多くの xs を持ちますが。
各出力に対して xs の重み合計を計算してバイアスを加え、そして softmax を適用します。

 

MNIST Softmax のコード全体

以後はコード: examples/tutorials/mnist/mnist_softmax.py の解説が中心になりますが、コード全体でも以下のようなシンプルなものです。(入力の実装は冒頭で説明したように input_data.py にありますが。)

# データのインポート
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
sess = tf.InteractiveSession()

# モデルの作成
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)

# 損失とオプティマイザーを定義
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# 訓練
tf.initialize_all_variables().run()
for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  train_step.run({x: batch_xs, y_: batch_ys})

# 訓練モデルのテスト
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels}))

注釈 : セッションの作成について、上のマスターコードでは InteractiveSession が使用されていますが、このチュートリアルでは標準的なセッションが使われています。

 

回帰を実装する

TensorFlow を使うために、まずは import する必要があります。

import tensorflow as tf

シンボリックな変数を操作することで演算を記述します、一つ作成してみます :

x = tf.placeholder(tf.float32, [None, 784])

ここで x は特定な値ではなく、これはプレースホルダーです。TensorFlow に計算の実行を依頼する時に入力することになる値です。私たちは、各々 784-次元ベクタに平坦化された、MNIST 画像の任意の個数を入力できることを望んでいるわけですが、これを形状 [None, 784] を持つ浮動小数点数値の 2-D テンソルとして表します。(ここで None は、次元が任意の長さを取りうることを意味しています。)

私たちのモデルには重みとバイアスも必要です。TensorFlow はこれを処理するための良い方法を持っています : 変数 (Variable) です。変数は変更可能なテンソルで TensorFlow のグラフの中で有効です。機械学習のアプリケーションは一般に、モデルパラメータを変数として持ちます。

W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

tf.Variable に初期値を与えることによってこれらの変数を作成します : この例の場合、全てゼロのテンソルとして W も b も初期化しています。W と b は学習しますので、最初にどのような値であるかはそれほど重要ではありません。(訳注 : より高度なモデルでは初期値も考察が必要となりますが。)

W は [784, 10] の形状を持つことに注意しましょう、これを 784-次元画像ベクタに乗算することで異なるクラスのための証拠 (evidence) としての 10-次元ベクタを生成することになるからです。b は [10] の形状を持ち、出力に加算できます。

ここにおいて我々のモデルが実装可能になります。何とわずか1行だけです!

y = tf.nn.softmax(tf.matmul(x, W) + b)

最初に tf.matmul(x, W) という式で x に W を乗算します。この式では、\(Wx\) であった、上述の式で乗算した時とは順序が逆になっていますが、これは x を複数の入力をもつ 2D テンソルとして扱うための小さなトリックでしかありません。
それから b を加算し、最後に tf.nn.softmax を適用します。

これで全部です。短い数行のセットアップ後、私たちのモデルを定義するのに1行しかかかりません。

これは TensorFlow が softmax 回帰を特に簡単にするように設計されているからではありません : 機械学習から物理シミュレーションまで、TensorFlow が数値計算の多くの種類を記述するために柔軟性をもつからです。そして一度定義されれば、このモデルは異なるデバイスで実行されます : 貴方のコンピュータの CPU、GPU そしてスマホでさえもです!

 

訓練

モデルを訓練するためにはモデルの良さを定義してやる必要がありますが、機械学習では損失 or コストと呼ばれる、モデルの悪さを通常は定義します。そして悪さの程度を最小化します。

良く使われるコスト関数は「交差エントロピー」で一応以下で定義されますが :

\[H_{y'}(y) = -\sum_i y'_i \log(y_i)\]
\(y\) は予測した確率分布で、\(y'\) は(one-hot ベクタ入力の)真の分布
 

数式の意味は分からなくても良いです、大雑把に言えば、予測が真実を記述するのにどのくらい非効率であるかの程度を交差エントロピーは計測しています。

交差エントロピーを実装するために、正解を入力しておくためのプレースホルダーを先に追加します :

y_ = tf.placeholder(tf.float32, [None, 10])

これで交差エントロピー \(-\sum y'\log(y)\) が実装できます :

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

数式の意味は分からなくても、上のコードが交差エントロピーの数式を忠実になぞって実装していることは分かるかと思います。

最初に tf.log で y の各要素の対数を計算し、次に _y の各要素と tf.log(y) の相対する要素を乗算し、最後に tf.reduce_sum でテンソルの全ての要素について加算しています。

これは単一の予測と正解についての交差エントロピーではなく、全ての画像のための交差エントロピーの合計である点に注意してください。扱っている例で言えば各バッチは 100 の画像を持っています : 100 のデータポイント上でどの程度上手くやっているかを計測することは、単一のデータポイントで判断するよりも、モデルがどの程度良いかを表すために遥に良い記述となります。

【参考】興味があれば、交差エントロピーの詳細は Visual Information Theory を参照のこと。

 

モデルに何をすることを望むかを決めてやれば、そのために TensorFlow で訓練させることは容易です。TensorFlow は計算グラフ全体を知っていますので、パラメータ変数がどのように(最小化したい)損失コストに影響を与えるかを効率的に決定するために、バックプロパゲーション を使用することができます。そして選択された最適化アルゴリズムを適用してパラメータ変数を変更し損失コストを減少させていきます。

# 損失コストとオプティマイザーを定義
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

この例では、0.01 の学習率で勾配降下アルゴリズムを使って交差エントロピーを最小化させることを TensorFlow に依頼しています。勾配降下は単純な手続きで、ここではコストを減少させる方向に各変数をほんの少しだけシフトしていきます。

TensorFlow はまた 多くの他の最適化アルゴリズム を提供しています : 他の一つを使用するためには一行調整する程度ですから簡単です。

これで訓練するためにモデルがセットアップされましたが、launch する前に、作成した変数の初期化をする演算を一つだけ追加する必要があります :

init = tf.initialize_all_variables()

今やセッションの中でモデルを launch し、変数を初期化する演算を実行することができます。

sess = tf.Session()
sess.run(init)

訓練してみましょう — 訓練ステップを 1000 回実行します。

for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

ループの各ステップで訓練セットから 100 のランダムなデータの “バッチ” を取得します。そしてプレースフォルダーを置き換えるバッチ・データを供給して train_step を実行します。

ランダム・データの小さなバッチを使用することを確率的訓練 – この例では確率的勾配降下法と呼びます。理想的には訓練の全てのステップで全てのデータを使用したいのですが、このアプローチは高コストになります。そこで代わりに、毎回異なるサブセットを使用するわけです。これを行なうのは安価でかつ同様な多くの恩恵があります。

【補足】マスターコードでは、セッションには InteractiveSession() を使用していて run() メソッドの使い方が異なっていますが、流れとしては大きな違いはありません。

sess = tf.InteractiveSession()
...
# 訓練
tf.initialize_all_variables().run()
for i in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  train_step.run({x: batch_xs, y_: batch_ys})
 

モデルを評価する

我々のモデルはどの程度上手くやるでしょうか?

# 訓練モデルのテスト
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels}))

最初に正解のラベルを予測した場所を把握しましょう。
tf.argmax は有用な関数で、ある軸に沿ったテンソルのもっとも高い要素の「インデックス」を与えます。
例えば、tf.argmax(y,1) はモデルが各入力に対して最も尤もらしい(= 一番ありそう)と考えるラベルで、一方 tf.argmax(y_,1) は正解のラベルです。

予測が正解と合っているか確認するためには tf.equal が利用できます。

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

これはブール値のリストを与えます。どのくらいの割合が正しいかを知るために、浮動小数点数値にキャストして平均を取ってみます。例えば、[True, False, True, True] は [1,0,1,1] ですから、平均は 0.75 となります。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最後に、テストデータ上で精度を求めます。

print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

これは約 91 % になるでしょう。

これは良いで結果とは言えません、むしろかなり悪いです。これは非常に単純なモデルを使用したからです。幾つかの小さな変更で 97% にまで到達できます。ベストなモデルは 99.7% を超えた精度にも到達可能です。(更なる情報は、結果のリスト を見てください。)

重要なのはこのモデルから学んだことです。今回のモデルの結果について少しがっかりしているのであれば、TensorFlow : Tutorials : 専門家のための深層 MNIST をチェックしてください。ここでは多くの改良を行ない、より洗練されたモデルを TensorFlow で構築する方法を学びます!

 

以上

AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com