序章

ニューラルネットワークは、人工知能の多くのサブフィールドの1つであるディープラーニングの方法として使用されます。 それらは、はるかに単純化された形式ではありますが、人間の脳の働きをシミュレートする試みとして、約70年前に最初に提案されました。 個々の「ニューロン」は層状に接続されており、信号がネットワークを介して伝播されるときにニューロンがどのように応答するかを決定するために重みが割り当てられています。 以前は、ニューラルネットワークはシミュレートできるニューロンの数が限られていたため、達成できる学習の複雑さが制限されていました。 しかし、近年、ハードウェア開発の進歩により、非常に深いネットワークを構築し、巨大なデータセットでそれらをトレーニングして、機械知能の飛躍的進歩を達成することができました。

これらの画期的な進歩により、機械は特定のタスクを実行する際の人間の能力に匹敵し、それを超えることができました。 そのようなタスクの1つは、オブジェクトの認識です。 機械は歴史的に人間の視覚に合わせることができませんでしたが、最近の深層学習の進歩により、オブジェクト、顔、テキスト、さらには感情さえも認識できるニューラルネットワークを構築することが可能になりました。

このチュートリアルでは、オブジェクト認識の小さなサブセクションである数字認識を実装します。 ディープラーニング研究のためにGoogleBrainラボによって開発されたオープンソースのPythonライブラリであるTensorFlowを使用して、0〜9の数字の手描き画像を取得し、ニューラルネットワークを構築してトレーニングし、認識と予測を行います表示された数字の正しいラベル。

このチュートリアルに従うために、実践的なディープラーニングやTensorFlowの経験は必要ありませんが、トレーニングとテスト、機能とラベル、最適化、評価などの機械学習の用語と概念にある程度精通していることを前提としています。 これらの概念の詳細については、機械学習の概要をご覧ください。

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • ローカルのPython3.6開発環境。これには、Pythonパッケージをインストールするためのツールである pip と、仮想環境を作成するためのvenvが含まれます。

注:TensorFlowには3.5の最小Pythonバージョンが必要であり、3.8までのバージョンをサポートします。 古いバージョンまたは新しいバージョンのPythonはサポートされていません。 このチュートリアルは、Python3.6を搭載したTensorFlowバージョン1.4.0〜1.15.5で動作するように設計されています。 新しいバージョンまたは古いバージョンのいずれかを使用すると、インストールエラーまたはランタイムエラーが発生する可能性があります。 Windows、macOS、およびLinuxのバージョン要件の完全なリストについては、TensorFlowサイトの Install TensorFlow withpipページにアクセスしてください。

ステップ1—プロジェクトの構成

認識プログラムを開発する前に、いくつかの依存関係をインストールし、ファイルを保持するためのワークスペースを作成する必要があります。

Python 3仮想環境を使用して、プロジェクトの依存関係を管理します。 プロジェクトの新しいディレクトリを作成し、新しいディレクトリに移動します。

  1. mkdir tensorflow-demo
  2. cd tensorflow-demo

次のコマンドを実行して、このチュートリアルの仮想環境を設定します。

  1. python3 -m venv tensorflow-demo
  2. source tensorflow-demo/bin/activate

次に、このチュートリアルで使用するライブラリをインストールします。 これらのライブラリの特定のバージョンを使用するには、プロジェクトディレクトリにrequirements.txtファイルを作成して、要件と必要なバージョンを指定します。 requirements.txtファイルを作成します。

  1. touch requirements.txt

テキストエディタでファイルを開き、次の行を追加して、Image、NumPy、TensorFlowライブラリとそれらのバージョンを指定します。

requirements.txt
image==1.5.20 numpy==1.14.3 tensorflow==1.4.0

ファイルを保存して、エディターを終了します。 次に、次のコマンドを使用してこれらのライブラリをインストールします。

  1. pip install -r requirements.txt

依存関係がインストールされたら、プロジェクトの作業を開始できます。

ステップ2—MNISTデータセットのインポート

このチュートリアルで使用するデータセットはMNISTデータセットと呼ばれ、機械学習コミュニティの古典です。 このデータセットは、28×28ピクセルのサイズの手書き数字の画像で構成されています。 データセットに含まれる数字の例を次に示します。

Examples of MNIST images

このデータセットを操作するPythonプログラムを作成しましょう。 このチュートリアルでは、すべての作業に1つのファイルを使用します。 main.pyという名前の新しいファイルを作成します。

  1. touch main.py

次に、選択したテキストエディタでこのファイルを開き、このコード行をファイルに追加して、TensorFlowライブラリをインポートします。

main.py
import tensorflow as tf

次のコード行をファイルに追加して、MNISTデータセットをインポートし、画像データを変数mnistに保存します。

main.py
...
from tensorflow.examples.tutorials.mnist import input_data


mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)  # y labels are oh-encoded

データを読み込むときは、 one-hot-encoding を使用して、ラベル(実際に描画された数字など)を表します。 画像の「3」)。 ワンホットエンコーディングは、バイナリ値のベクトルを使用して数値またはカテゴリ値を表します。 ラベルは0〜9の数字用であるため、ベクトルには10個の値が含まれ、可能な数字ごとに1つずつあります。 これらの値の1つは1に設定され、ベクトルのそのインデックスの桁を表し、残りは0に設定されます。 たとえば、数字3は、ベクトル[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]を使用して表されます。 インデックス3の値は1として格納されるため、ベクトルは数字3を表します。

実際の画像自体を表すために、28×28ピクセルは、サイズが784ピクセルの1Dベクトルにフラット化されます。 画像を構成する784ピクセルのそれぞれは、0〜255の値として保存されます。 画像は白黒のみで表示されるため、これによりピクセルのグレースケールが決まります。 したがって、黒のピクセルは255で表され、白のピクセルは0で表され、その間のどこかにさまざまなグレーの色合いがあります。

mnist変数を使用して、インポートしたデータセットのサイズを確認できます。 3つのサブセットのそれぞれのnum_examplesを見ると、データセットがトレーニング用に55,000画像、検証用に5000画像、テスト用に10,000画像に分割されていることがわかります。 次の行をファイルに追加します。

main.py
...
n_train = mnist.train.num_examples  # 55,000
n_validation = mnist.validation.num_examples  # 5000
n_test = mnist.test.num_examples  # 10,000

データがインポートされたので、ニューラルネットワークについて考えてみましょう。

ステップ3—ニューラルネットワークアーキテクチャの定義

ニューラルネットワークのアーキテクチャとは、ネットワーク内のレイヤーの数、各レイヤーのユニットの数、ユニットがレイヤー間でどのように接続されているかなどの要素を指します。 ニューラルネットワークは人間の脳の働きに大まかに触発されているため、ここではユニットという用語は、生物学的にニューロンと考えるものを表すために使用されます。 ニューロンが脳の周りに信号を渡すように、ユニットは前のユニットからいくつかの値を入力として受け取り、計算を実行してから、新しい値を出力として他のユニットに渡します。 これらのユニットは、ネットワークを形成するために階層化されており、少なくとも値を入力するための1つの層と、値を出力するための1つの層から始まります。 非表示レイヤーという用語は、入力レイヤーと出力レイヤーの間のすべてのレイヤーに使用されます。 現実の世界から「隠された」もの。

パフォーマンスは、パラメータ、データ、トレーニング期間など、特にアーキテクチャの関数として考えることができるため、アーキテクチャが異なれば、劇的に異なる結果が得られる可能性があります。

次のコード行をファイルに追加して、レイヤーごとのユニット数をグローバル変数に格納します。 これにより、ネットワークアーキテクチャを一箇所で変更できます。チュートリアルの最後に、さまざまな数のレイヤーとユニットがモデルの結果にどのように影響するかを自分でテストできます。

main.py
...
n_input = 784  # input layer (28x28 pixels)
n_hidden1 = 512  # 1st hidden layer
n_hidden2 = 256  # 2nd hidden layer
n_hidden3 = 128  # 3rd hidden layer
n_output = 10  # output layer (0-9 digits)

次の図は、各レイヤーが周囲のレイヤーに完全に接続された、設計したアーキテクチャの視覚化を示しています。

Diagram of a neural network

「ディープニューラルネットワーク」という用語は隠れ層の数に関連し、「浅い」は通常1つの隠れ層のみを意味し、「深い」は複数の隠れ層を意味します。 十分なトレーニングデータがあれば、十分な数のユニットを備えた浅いニューラルネットワークは、理論的には、深いニューラルネットワークが実行できる任意の関数を表すことができるはずです。 しかし、指数関数的に多くの隠れユニットを持つ浅いネットワークを必要とする同じタスクを達成するために、より小さな深いニューラルネットワークを使用する方が計算効率が高いことがよくあります。 浅いニューラルネットワークは、過剰適合に遭遇することもよくあります。この場合、ネットワークは基本的に、見たトレーニングデータを記憶し、知識を新しいデータに一般化することができません。 これが、ディープニューラルネットワークがより一般的に使用される理由です。生の入力データと出力ラベルの間の複数のレイヤーにより、ネットワークはさまざまな抽象化レベルで機能を学習でき、ネットワーク自体をより一般化できるようになります。

ここで定義する必要があるニューラルネットワークの他の要素は、ハイパーパラメータです。 トレーニング中に更新されるパラメーターとは異なり、これらの値は最初に設定され、プロセス全体を通じて一定に保たれます。 ファイルで、次の変数と値を設定します。

main.py
...
learning_rate = 1e-4
n_iterations = 1000
batch_size = 128
dropout = 0.5

学習率は、学習プロセスの各ステップでパラメーターがどの程度調整されるかを表します。 これらの調整はトレーニングの重要な要素です。ネットワークを通過するたびに、重みをわずかに調整して損失を減らします。 学習率が高いほど収束が速くなりますが、更新時に最適値をオーバーシュートする可能性もあります。 反復回数はトレーニングステップを通過する回数を示し、バッチサイズは各ステップで使用しているトレーニング例の数を示します。 dropout変数は、一部のユニットをランダムに削除するしきい値を表します。 最後の隠しレイヤーでdropoutを使用して、各ユニットにすべてのトレーニングステップで50% cの確率で除去されるようにします。 これは、過剰適合を防ぐのに役立ちます。

これで、ニューラルネットワークのアーキテクチャと、学習プロセスに影響を与えるハイパーパラメータを定義しました。 次のステップは、TensorFlowグラフとしてネットワークを構築することです。

ステップ4—TensorFlowグラフを作成する

ネットワークを構築するために、TensorFlowが実行する計算グラフとしてネットワークを設定します。 TensorFlowのコアコンセプトは、 tensor であり、配列またはリストに似たデータ構造です。 初期化され、グラフを通過するときに操作され、学習プロセスを通じて更新されます。

まず、3つのテンソルをプレースホルダーとして定義します。これらのテンソルは、後で値をフィードします。 ファイルに以下を追加します。

main.py
...
X = tf.placeholder("float", [None, n_input])
Y = tf.placeholder("float", [None, n_output])
keep_prob = tf.placeholder(tf.float32)

宣言時に指定する必要がある唯一のパラメーターは、フィードするデータのサイズです。 Xには、[None, 784]の形状を使用します。ここで、Noneは任意の量を表し、未定義の数の784ピクセル画像をフィードします。 Yの形状は、[None, 10]です。これは、10の可能なクラスで、未定義の数のラベル出力に使用するためです。 keep_probテンソルはドロップアウト率を制御するために使用され、トレーニングに同じテンソルを使用するため(dropoutがに設定されている場合)、不変変数ではなくプレースホルダーとして初期化します。 0.5)およびテスト(dropout1.0に設定されている場合)。

ネットワークがトレーニングプロセスで更新するパラメータはweightbiasの値であるため、これらの場合、空のプレースホルダーではなく初期値を設定する必要があります。 これらの値は、ユニット間の接続の強さを表すニューロンの活性化関数で使用されるため、基本的にネットワークが学習を行う場所です。

値はトレーニング中に最適化されるため、現時点ではゼロに設定できます。 ただし、初期値は実際にはモデルの最終的な精度に大きな影響を与えます。 重みには、切断正規分布のランダムな値を使用します。 ゼロに近づけて、正または負の方向に調整できるようにし、わずかに異なるようにして、異なるエラーを生成するようにします。 これにより、モデルが何か有用なことを確実に学習できるようになります。 次の行を追加します。

main.py
...
weights = {
    'w1': tf.Variable(tf.truncated_normal([n_input, n_hidden1], stddev=0.1)),
    'w2': tf.Variable(tf.truncated_normal([n_hidden1, n_hidden2], stddev=0.1)),
    'w3': tf.Variable(tf.truncated_normal([n_hidden2, n_hidden3], stddev=0.1)),
    'out': tf.Variable(tf.truncated_normal([n_hidden3, n_output], stddev=0.1)),
}

バイアスについては、小さな定数値を使用して、テンソルが初期段階でアクティブになり、したがって伝播に寄与することを確認します。 重みとバイアステンソルは、アクセスしやすいように辞書オブジェクトに格納されます。 このコードをファイルに追加して、バイアスを定義します。

main.py
...
biases = {
    'b1': tf.Variable(tf.constant(0.1, shape=[n_hidden1])),
    'b2': tf.Variable(tf.constant(0.1, shape=[n_hidden2])),
    'b3': tf.Variable(tf.constant(0.1, shape=[n_hidden3])),
    'out': tf.Variable(tf.constant(0.1, shape=[n_output]))
}

次に、テンソルを操作する操作を定義して、ネットワークのレイヤーを設定します。 次の行をファイルに追加します。

main.py
...
layer_1 = tf.add(tf.matmul(X, weights['w1']), biases['b1'])
layer_2 = tf.add(tf.matmul(layer_1, weights['w2']), biases['b2'])
layer_3 = tf.add(tf.matmul(layer_2, weights['w3']), biases['b3'])
layer_drop = tf.nn.dropout(layer_3, keep_prob)
output_layer = tf.matmul(layer_3, weights['out']) + biases['out']

各隠れ層は、前の層の出力と現在の層の重みに対して行列の乗算を実行し、これらの値にバイアスを追加します。 最後の隠れ層で、keep_probの値0.5を使用してドロップアウト操作を適用します。

グラフを作成する最後のステップは、最適化する損失関数を定義することです。 TensorFlowプログラムでよく使用される損失関数は、クロスエントロピーで、 log-loss とも呼ばれ、2つの確率分布(予測とラベル)の差を定量化します。 完全な分類では、クロスエントロピーが0になり、損失が完全に最小化されます。

また、損失関数を最小化するために使用される最適化アルゴリズムを選択する必要があります。 勾配降下最適化という名前のプロセスは、負(降順)方向の勾配に沿って反復ステップを実行することにより、関数の(極値)最小値を見つけるための一般的な方法です。 TensorFlowにすでに実装されている勾配降下最適化アルゴリズムにはいくつかの選択肢があります。このチュートリアルでは、Adamオプティマイザーを使用します。 これは、勾配の指数関数的に加重された平均を計算し、それを調整に使用することにより、運動量を使用してプロセスを高速化することにより、勾配降下の最適化に拡張されます。 次のコードをファイルに追加します。

main.py
...
cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(
        labels=Y, logits=output_layer
        ))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

これでネットワークが定義され、TensorFlowで構築されました。 次のステップは、グラフを介してデータをフィードしてトレーニングし、実際に何かを学習したことをテストすることです。

ステップ5—トレーニングとテスト

トレーニングプロセスには、グラフを介してトレーニングデータセットをフィードし、損失関数を最適化することが含まれます。 ネットワークは、より多くのトレーニング画像のバッチを反復処理するたびに、表示される数字をより正確に予測するために、損失を減らすためにパラメーターを更新します。 テストプロセスでは、トレーニングされたグラフを介してテストデータセットを実行し、正確に計算できるように、正しく予測された画像の数を追跡します。

トレーニングプロセスを開始する前に、精度を評価する方法を定義して、トレーニング中にデータのミニバッチに印刷できるようにします。 これらの印刷されたステートメントにより、最初の反復から最後の反復まで、損失が減少し、精度が向上することを確認できます。 また、一貫性のある最適な結果に到達するのに十分な反復を実行したかどうかを追跡することもできます。

main.py
...
correct_pred = tf.equal(tf.argmax(output_layer, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

correct_predでは、arg_max関数を使用して、output_layer(予測)とY(ラベル)を見て、どの画像が正しく予測されているかを比較します。 equal関数を使用して、これをブール値のリストとして返します。 次に、このリストを浮動小数点数にキャストし、平均を計算して合計精度スコアを取得できます。

これで、グラフを実行するためのセッションを初期化する準備が整いました。 このセッションでは、トレーニング例をネットワークにフィードします。トレーニングが完了したら、同じグラフに新しいテスト例をフィードして、モデルの精度を判断します。 次のコード行をファイルに追加します。

main.py
...
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

深層学習のトレーニングプロセスの本質は、損失関数を最適化することです。 ここでは、画像の予測ラベルと画像の実際のラベルの違いを最小限に抑えることを目指しています。 このプロセスには、設定された反復回数で繰り返される4つのステップが含まれます。

  • ネットワークを介して値を転送する
  • 損失を計算する
  • ネットワークを介して値を逆方向に伝播する
  • パラメータを更新します

各トレーニングステップでは、次のステップの損失を減らすために、パラメーターがわずかに調整されます。 学習が進むにつれて、損失が減少するはずです。最終的には、トレーニングを停止し、新しいデータをテストするためのモデルとしてネットワークを使用できます。

次のコードをファイルに追加します。

main.py
...
# train on mini batches
for i in range(n_iterations):
    batch_x, batch_y = mnist.train.next_batch(batch_size)
    sess.run(train_step, feed_dict={
        X: batch_x, Y: batch_y, keep_prob: dropout
        })

    # print loss and accuracy (per minibatch)
    if i % 100 == 0:
        minibatch_loss, minibatch_accuracy = sess.run(
            [cross_entropy, accuracy],
            feed_dict={X: batch_x, Y: batch_y, keep_prob: 1.0}
            )
        print(
            "Iteration",
            str(i),
            "\t| Loss =",
            str(minibatch_loss),
            "\t| Accuracy =",
            str(minibatch_accuracy)
            )

ネットワークを介して画像のミニバッチをフィードする各トレーニングステップを100回繰り返した後、そのバッチの損失と精度を出力します。 値はモデル全体ではなくバッチごとであるため、ここでは損失の減少と精度の向上を期待するべきではないことに注意してください。 トレーニングプロセスをスピードアップし、パラメータを更新する前にネットワークがさまざまな例を確認できるようにするために、画像を個別にフィードするのではなく、ミニバッチの画像を使用します。

トレーニングが完了すると、テストイメージでセッションを実行できます。 今回は、keep_probドロップアウト率1.0を使用して、すべてのユニットがテストプロセスでアクティブであることを確認します。

次のコードをファイルに追加します。

main.py
...
test_accuracy = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels, keep_prob: 1.0})
print("\nAccuracy on test set:", test_accuracy)

今度は、プログラムを実行して、ニューラルネットワークがこれらの手書き数字をどれだけ正確に認識できるかを確認します。 main.pyファイルを保存し、ターミナルで次のコマンドを実行してスクリプトを実行します。

  1. python main.py

個々の損失と精度の結果はわずかに異なる場合がありますが、次のような出力が表示されます。

Output
Iteration 0 | Loss = 3.67079 | Accuracy = 0.140625 Iteration 100 | Loss = 0.492122 | Accuracy = 0.84375 Iteration 200 | Loss = 0.421595 | Accuracy = 0.882812 Iteration 300 | Loss = 0.307726 | Accuracy = 0.921875 Iteration 400 | Loss = 0.392948 | Accuracy = 0.882812 Iteration 500 | Loss = 0.371461 | Accuracy = 0.90625 Iteration 600 | Loss = 0.378425 | Accuracy = 0.882812 Iteration 700 | Loss = 0.338605 | Accuracy = 0.914062 Iteration 800 | Loss = 0.379697 | Accuracy = 0.875 Iteration 900 | Loss = 0.444303 | Accuracy = 0.90625 Accuracy on test set: 0.9206

モデルの精度を向上させるため、またはハイパーパラメーターの調整の影響について詳しく知るために、学習率、ドロップアウトしきい値、バッチサイズ、および反復回数を変更した場合の影響をテストできます。 また、非表示レイヤーのユニット数を変更したり、非表示レイヤー自体の量を変更したりして、さまざまなアーキテクチャがモデルの精度をどのように増減させるかを確認することもできます。

ネットワークが実際に手描きの画像を認識していることを示すために、私たち自身の単一の画像でそれをテストしてみましょう。

ローカルマシンを使用していて、独自の手描きの数字を使用したい場合は、グラフィックエディタを使用して、独自の28×28ピクセルの数字の画像を作成できます。 それ以外の場合は、curlを使用して、次のサンプルテストイメージをサーバーまたはコンピューターにダウンロードできます。

  1. curl -O https://raw.githubusercontent.com/do-community/tensorflow-digit-recognition/master/test_img.png

エディタでmain.pyファイルを開き、ファイルの先頭に次のコード行を追加して、画像操作に必要な2つのライブラリをインポートします。

main.py
import numpy as np
from PIL import Image
...

次に、ファイルの最後に次のコード行を追加して、手書き数字のテスト画像をロードします。

main.py
...
img = np.invert(Image.open("test_img.png").convert('L')).ravel()

Imageライブラリのopen関数は、3つのRGBカラーチャネルとアルファ透明度を含む4D配列としてテスト画像をロードします。 これは、TensorFlowを使用してデータセットを読み込むときに以前に使用した表現とは異なるため、形式に一致させるために追加の作業を行う必要があります。

まず、convert関数とLパラメーターを使用して、4DRGBA表現を1つのグレースケールカラーチャネルに縮小します。 これをnumpy配列として格納し、np.invertを使用して反転します。これは、現在の行列が黒を0、白を255として表すのに対し、逆が必要なためです。 最後に、ravelを呼び出して、配列をフラット化します。

画像データが正しく構造化されたので、以前と同じ方法でセッションを実行できますが、今回はテストのために単一の画像のみをフィードします。

次のコードをファイルに追加して、画像をテストし、出力されたラベルを印刷します。

main.py
...
prediction = sess.run(tf.argmax(output_layer, 1), feed_dict={X: [img]})
print ("Prediction for test image:", np.squeeze(prediction))

np.squeeze関数は、配列から単一の整数を返すために予測で呼び出されます(つまり、 [2]から2)に移動します。 結果の出力は、ネットワークがこの画像を数字2として認識したことを示しています。

Output
Prediction for test image: 2

より複雑な画像(たとえば、他の数字のように見える数字、または描画が不十分または不正確な数字)を使用してネットワークをテストし、ネットワークがどの程度うまくいくかを確認できます。

結論

このチュートリアルでは、MNISTデータセットを約92%の精度で分類するようにニューラルネットワークを正常にトレーニングし、独自の画像でテストしました。 現在の最先端の研究では、畳み込み層を含むより複雑なネットワークアーキテクチャを使用して、これと同じ問題を約99% on達成しています。 これらは、すべてのピクセルを784単位の1つのベクトルに平坦化する方法とは異なり、画像の2D構造を使用してコンテンツをより適切に表現します。 このトピックの詳細については、 TensorFlow Webサイトを参照してください。また、MNISTWebサイトで最も正確な結果を詳述している研究論文を参照してください。

ニューラルネットワークを構築してトレーニングする方法がわかったので、この実装を自分のデータで試して使用したり、 Google StreetView HouseNumbersなどの他の一般的なデータセットでテストしたりできます。より一般的な画像認識のための]CIFAR-10データセット。