手書き数字認識のための畳み込みニューラルネットワーク(CNN)

元のリンク:https//blog.csdn.net/polyhedronx/article/details/94476824

1.はじめに
前回のブログ投稿では、単一の隠れ層を持つ完全に接続されたニューラルネットワークを、指数関数的減衰学習率、正則化、Relu活性化関数、Adam最適化アルゴリズムなどのニューラルネットワークの最適化戦略と組み合わせて使用​​しています。100個の隠れ層が含まれています。ニューロンのニューラルネットワークは、MNISTデータセットで手書きの数字認識の98%の精度を実現します。ただし、完全に接続されたニューラルネットワークにも制限があります。深いネットワーク、多くの隠れノード、および多数の反復が使用されたとしても、MNISTデータセットで99%を超える精度を取得することは困難です。ただし、畳み込みニューラルネットワークの出現によりこの問題は解決され、最終的に99%を超える精度に達することができます。これは、一部の高精度認識システムのニーズを満たしています。

2.畳み込みニューラルネットワークの基本原理
2.1畳み込み演算
画像の各ピクセルは周囲のピクセルと密接に関連していますが、必ずしも遠すぎるピクセルとは関連していません。これが人間のビジョンです。受容野の概念、それぞれ受容野は、小さな領域からの信号のみを受信します。この小さな領域のピクセルは相互に関連しています。各ニューロンはすべてのピクセル情報を受信する必要はありませんが、入力としてローカルピクセルを受信し、これらのニューロンが受信したローカル情報を統合するだけで済みます。グローバル情報を取得します。

画像の畳み込み操作とは、画像の左上隅から開始し、畳み込みテンプレートを使用して画像上をスライドし、画像ピクセルのピクセルグレー値に各位置の対応する畳み込みカーネルの値を乗算することです。乗算されたすべての結果のうち、畳み込みカーネルの中心ピクセルに対応する畳み込み結果値として使用され、このステップに従って、画像のすべての位置で畳み込み結果を取得するためのスライドプロセスが完了します。この畳み込みテンプレートは、通常、畳み込みニューラルネットワークでは畳み込みカーネルまたはフィルターと呼ばれます。次の図は、画像畳み込み操作プロセスの一部の概略図を示しています。この図は、3〜3の畳み込みカーネルを使用して5〜 5サイズの画像。畳み込み操作。

画像の畳み込み演算は、次のように表すことができます。

その中で、は畳み込み対象の画像の行列、は畳み込みカーネル関数、は画像畳み込み演算の出力画像です。深層学習では、入力画像行列と出力結果行列の両方が特徴マップと呼ばれます。

2.2プーリング操作
畳み込み層を介して2次元の特徴マップを取得した後でも、通常の状況では特徴マップのサイズは非常に大きくなります。これらの特徴を分類のために分類器に直接送信すると、計算量は非常に大きくなります。さらに、オーバーフィットの問題もある可能性があるため、これらの特徴マップを直接分類に使用するのは不便です。プーリング操作は、このような問題を解決するために設計されたテクノロジーであり、フィーチャマップマトリックス上のさまざまな位置にあるフィーチャを集約およびカウントして、フィーチャを凝縮します。

プーリング操作の概略図を上の図に示します。一般的に使用されるプーリング操作は2つあります。1つは平均プーリング、平均プーリング操作の出力は、プーリングコアの対応する範囲内の入力フィーチャマップ内のフィーチャの平均値です。もう1つは、最大プーリング、最大プーリング操作出力は、プーリングカーネルの対応する範囲内の入力フィーチャマップ内のフィーチャの最大値です。プーリング操作は特別な画像畳み込み操作であることがわかります。

プーリング操作は、畳み込みニューラルネットワークの効果を大幅に改善できます。これは主に、特徴の集中、特徴マップの次元の減少、および畳み込みニューラルネットワークの頻繁な過剰適合現象によるものです。したがって、特定の範囲の特徴情報が凝縮されるため、プーリング操作は、畳み込みニューラルネットワークの狭い範囲での並進不変性を強化する効果もあります。

2.3畳み込み層
一般的な畳み込みニューラルネットワークは複数の畳み込み層で構成されており、通常、各畳み込み層で次の操作が実行されます。

画像は複数の異なる畳み込みカーネルによってフィルタリングされ、局所的な特徴を抽出するためにバイアスがかけられます。各畳み込みカーネルは新しい2D画像をマッピングします。
以前の畳み込みカーネルのフィルタリング出力結果は、非線形活性化関数で処理されます。現在、ReLU関数が一般的に使用されています。
次に、活性化関数の結果がプールされます(つまり、ダウンサンプリング、たとえば、22画像が11画像に削減されます)。現在、最大プーリングは、最も重要な機能を保持し、モデルの歪み耐性を向上させるために一般的に使用されます。 。
各畳み込みカーネルは1種類の画像特徴しか抽出できず、畳み込みカーネルの数を増やして一部の特徴を抽出できるため、畳み込み層には一般に複数の異なる畳み込みカーネルがあることに注意してください。各畳み込みカーネルは、フィルタリング後にマッピングされた新しい画像に対応し、同じ新しい画像の各ピクセルは、同じ畳み込みカーネルから取得されます。これは、畳み込みカーネルの重み共有です。畳み込みカーネルの重みパラメーターを共有する目的は非常に単純であり、モデルの複雑さを軽減し、過剰適合を減らし、計算量を減らします。

畳み込み層がトレーニングする必要のある重みの数は、畳み込みカーネルのサイズと畳み込みカーネルの数にのみ関係します。任意のサイズの画像を処理するために使用できるパラメーターはごくわずかです。各畳み込み層によって抽出された特徴は、後続の層で抽象的に高次の特徴に結合されます。

2.4畳み込みニューラルネットワーク
畳み込みニューラルネットワークと多層パーセプトロンネットワークの違いは、畳み込みニューラルネットワークには、畳み込み層とプーリング層で構成されるいくつかの特徴抽出器が含まれていることです。これにより、パラメーターの数を効果的に減らし、パラメーターの数を大幅に減らすことができます。モデルの複雑さにより、過剰適合のリスクが軽減されます。同時に、並進とわずかな変形に対する畳み込みニューラルネットワークの許容度を与え、モデルの一般化能力を向上させます。

有名なLeNet5の構造を次の図に示します。これには、完全に接続された層とガウス接続された層の3つの畳み込み層が含まれています。一般的に言えば、さまざまな畳み込みニューラルネットワークは、さまざまな実際の問題に対処するために、さまざまなデータセットとさまざまなサイズの入力画像に対して合理的に設計できます。

3.手書き数字認識問題のための畳み込みニューラルネットワークの設計
手書き数字認識問題は比較的単純であるため、2つの畳み込み層と完全に接続された層を使用して単純な畳み込みニューラルネットワークを構築します。

3.1主なTensorFlow関数の解釈
(1)tf.nn.conv2d

4次元の入力とフィルターテンソルが与えられた場合、2次元の畳み込みを計算します。

tf.nn.conv2d(
    input,
    filter=None,
    strides=None,
    padding=None,
    use_cudnn_on_gpu=True,
    data_format='NHWC',
    dilations=[1, 1, 1, 1],
    name=None,
    filters=None
)

input:
畳み込みが必要な入力画像を参照します。これは4次元テンソルです。タイプはhalf、bfloat16、float32、float64のいずれかです。次元の順序はdata_formatに従って設定され、デフォルトは[batch、 in_height、in_width、in_channels]。形状の具体的な意味は、[トレーニング中のバッチ内の画像の数、画像の高さ、画像の幅、画像チャネルの数]です。

フィルタ:
CNNの畳み込みカーネル同等です。テンソルが必要です。テンソルは入力タイプと同じである必要があります。[filter_height、filter_width、in_channels、out_channels]のような形状の場合、具体的な意味は[畳み込みカーネルの高さ、畳み込みカーネルの幅、画像チャネルの数、畳み込みカーネルの数]です。3番目の次元in_channelsは、パラメーター入力の4番目の次元であることに注意してください。

ストライド:
畳み込み中の画像の各次元におけるスライディングウィンドウステップサイズ。これは1次元のベクトルであり、次元の順序はdata_formatに従って設定され、デフォルトは[NHWC]、タイプはintまたはint list、長さは1、2、または4です。NとCはデフォルトで1に設定されており、一般的な形式は[1、stride [1]、stride [2]、1]です。ほとんどの場合、高さと幅のステップが同じに設定されているため、通常は[1、ストライド、ストライド、1]になります。

パディング:
文字列タイプの量。「SAME」または「VALID」のいずれかのみで、パディングが必要かどうかを示します。畳み込み後の出力サイズは一般に入力よりも小さいため、この時点で入力と同じサイズの出力を取得するためにパディングを使用できます。

 strides=[1, 1, 1, 1], padding="VALID"          strides=[1, 1, 1, 1], padding="SAME"

use_cudnn_on_gpu:
bool型、cudnnアクセラレーションを使用するかどうか、デフォルトはtrueです。

data_format
は、入力データと出力データの形式を指定します。オプションの文字列タイプ。「NHWC」または「NCHW」のいずれかで、デフォルトは「NHWC」です。デフォルトのフォーマット「NHWC」を使用する場合、データは次の順序で保存されます:[バッチ、高さ、幅、チャネル]。

dilations
各入力次元の膨張係数。これは1次元ベクトルであり、次元の順序はdata_formatに従って設定され、デフォルトは[NHWC]、タイプはintまたはintリスト、長さは1、2、または4で、値はによってすべて1に設定されます。デフォルト。単一の値が指定された場合、それはHとWにコピーされます。

入力テンソル[batch、in_height、in_width、in_channels]とフィルター/カーネルテンソル[filter_height、filter_width、in_channels、out_channels]を指定して、次の操作を実行します。

平坦化されたフィルターは、形状が[filter_height * filter_width * in_channels、output_channels]の2次元行列です。
フィルタサイズに従って入力から画像ブロックを抽出し、サイズ[batch、out_height、out_width、filter_height * filter_width * in_channels]の仮想テンソルを形成します。
画像ブロックごとに、右側のフィルター行列を乗算します。
計算式は次のとおりです。

いくつかの例:

[1,3,3,1]と入力すると、フィルターは[2,2,1,1]、padding = 'SAME'、充填方法は図のようになります。

[1,2,2,1]と入力すると、フィルターは[3,3,1,1]、padding = 'SAME'、充填方法は図のようになります。

マルチチャネルの場合、図に示すように、入力[1x3x3x2]は2チャネルの3x3画像、フィルターは[2x2x2x1]、ステップサイズは1、padding = VALID、出力は[1x2x2x1]です。

(2)tf.nn.max_pool

入力で最大プーリング演算を実行することは、特別な畳み込み演算と見なすことができます。

tf.nn.max_pool(
    value,
    ksize,
    strides,
    padding,
    data_format='NHWC',
    name=None,
    input=None
)


はプールされた入力である必要があります。通常、プールレイヤーの後に畳み込みレイヤーが続くため、入力は通常、[バッチ、高さ、幅、チャネル]のような形状のフィーチャマップです。

ksize
プーリングウィンドウのサイズは4次元のベクトルで、通常は[1、高さ、幅、1]です。これは、バッチとチャネルでプールしたくないため、これらの2つの次元が1に設定されているためです。

一例:

入力が2チャネルグラフ、つまりvalue = [1,4,4,2]であるとすると、プーリングウィンドウのサイズはksize = [1,2,2,1]であり、結果は次のようになります。

テストプログラム:

import tensorflow as tf
 
a = tf.constant([
    [[1.0, 2.0, 3.0, 4.0],
     [5.0, 6.0, 7.0, 8.0],
     [8.0, 7.0, 6.0, 5.0],
     [4.0, 3.0, 2.0, 1.0]],
    [[4.0, 3.0, 2.0, 1.0],
     [8.0, 7.0, 6.0, 5.0],
     [1.0, 2.0, 3.0, 4.0],
     [5.0, 6.0, 7.0, 8.0]]
])
 
a = tf.reshape(a, [1, 4, 4, 2])
 
pooling = tf.nn.max_pool(value=a, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
with tf.Session() as sess:
    print("image:")
    image = sess.run(a)
    print(image)
    print("reslut:")
    result = sess.run(pooling)
    print(result)

(3)tf.nn.dropout

一部のノードをランダムに0に設定します。トレーニング中に、ノードデータの一部をランダムに破棄して過剰適合を減らし、予測中にすべてのデータを保持して、より良い予測パフォーマンスを取得します。通常、完全に接続されたレイヤーで使用されます。

TensorFlowでは、そのパラメーターは次のように定義されています。

tf.nn.dropout(
    x,
    keep_prob=None,
    noise_shape=None,
    seed=None,
    name=None,
    rate=None
)

その中で、パラメーターxは、浮動小数点テンソルである入力を表します。keep_probはニューロンが選択される確率であり、rateはxの要素が破棄される確率です。明らかにkeep_prob = 1-rate(公式Webサイトではkeep_probが破棄されると記載されているため、代わりにrateを使用することをお勧めします)。Keep_probは初期化中のプレースホルダーです。keep_prob= tf.placeholder(tf.float32)、tensorflowは実行時にkeep_probの特定の値を設定します(例:keep_prob:0.5)。noise_shapeは、1次元のint32タイプのテンソルであり、「予約/破棄」フラグを使用してランダムに生成された形状を表します。seedは、整数である乱数seedです。

入力xの各要素について、確率レートで0を出力します。それ以外の場合、出力合計の期待値を変更しないようにするために、入力は1 /(1-レート)倍にスケールアップされます。

デフォルトでは、各要素は個別に保持または削除されます。noise_shapeが指定されている場合、noise_shape [i] == shape(x)[i]の次元のみが独立しています(noise_shapeの要素は1またはx.shapeの対応する要素のみです)。たとえば、shape(x)= [k、l、m、n]およびnoise_shape = [k、1、1、n]の場合、各バッチとチャネルは独立したままであり、各行または列はすべて予約されているか、すべてゼロ。

いくつかの例を以下に示します。

バッチ入力に2つの画像があるとすると、各画像は3×3のサイズのデュアルチャネル画像です。つまり、x = [2,3,3,2]; keep_prob = 0.5であり、結果は次のようになります。次のように。

テストプログラム:

import tensorflow as tf
 
b = tf.constant([
    [[1.0, 2.0, 3.0],
     [4.0, 5.0, 6.0],
     [7.0, 8.0, 9.0]],
    [[9.0, 8.0, 7.0],
     [6.0, 5.0, 4.0],
     [3.0, 2.0, 1.0]],
    [[1.0, 2.0, 3.0],
     [4.0, 5.0, 6.0],
     [7.0, 8.0, 9.0]],
    [[9.0, 8.0, 7.0],
     [6.0, 5.0, 4.0],
     [3.0, 2.0, 1.0]]
])
 
b = tf.reshape(b, [2, 3, 3, 2])
 
drop = tf.nn.dropout(x=b, keep_prob=0.5, noise_shape=[2, 3, 3, 2])
with tf.Session() as sess:
    print("image:")
    image = sess.run(b)
    print(image)
    print("result:")
    result = sess.run(drop)
    print(result)

noise_shape = [2,3,3,2]またはnoise_shape = Noneの場合、出力はランダムにゼロに設定され、その他の数値は1 /(1-0.5)=2倍に拡大されます。

noise_shape = [1,3,3,2]の場合、ゼロ化パターンは出力バッチ内の異なる画像間で同じです。

Noise_shape = [2,1,3,2]の場合、異なる出力ライン間のゼロ調整パターンは同じです。

noise_shape = [2,3,1,2]の場合、異なる出力列間のゼロ化パターンは同じです。

Noise_shape = [2,3,3,1]の場合、出力の異なるチャネル間のゼロ調整パターンは同じです。

3.2プログラムと結果
プログラムの実行バージョンは、python–> 3.7.3、tensorflow–> 1.13.1です。

重みは、標準偏差0.1の切断正規分布に従う乱数として初期化されます。ReLU関数が使用されるため、バイアスはデッドノードを回避するために0.1の定数値に初期化されます。

入力画像は28×28のグレースケール画像です。最初の畳み込み層畳み込みカーネルのサイズは5×5、1カラーチャネルに設定され、畳み込みカーネル(出力チャネル)の数は32(つまり、この畳み込み層が抽出する特徴のタイプの数)、幅に設定されます。とhighの移動ステップは両方とも1で、パディングが実行されます(padding = "SAME"、出力画像サイズは入力画像と同じです)。アクティブ化関数はReLU関数です。プーリング操作のウィンドウサイズは、 2×2、幅と高さはです。移動ステップは両方とも2です。

2番目の畳み込み層畳み込みカーネルのサイズは5×5に設定され、入力チャネルは32(つまり、前の層の出力チャネル)に設定され、畳み込みカーネルの数(出力チャネル)は64に設定され、他のパラメータは最初のボリュームのパラメータと同じです。ビルドアップは同じです。

ステップサイズが2×2の前の2つの最大プーリングの後、辺の長さは元の1/4になります。つまり、画像サイズは28×28から7×7に変更されます。2番目の畳み込み層の畳み込みカーネルの数は64であり、出力テンソルのサイズは7×7×64です。それを1次元ベクトルに変換してから、完全に接続されたレイヤーを接続します。隠れノードは1024で、ReLU活性化関数を使用します。

過剰適合を減らすために、ドロップアウトレイヤーが下で使用されます。Keep_probはトレーニング中に0.5に設定され、テスト中に1に設定されます。ドロップアウト層の出力は、最終的な確率出力を取得するためにソフトマックス層に接続されます。

損失関数はクロスエントロピーとして定義され、オプティマイザーはAdamを使用し、学習率は1e-4に設定されます。バッチサイズは50、トレーニング回数は20000です。手順は以下のとおりです。

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
 
# move warning
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
old_v = tf.logging.get_verbosity()
tf.logging.set_verbosity(tf.logging.ERROR)
 
 
# weight initialization
def weight_variable(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1))
 
 
# bias initialization
def bias_variable(shape):
    return tf.Variable(tf.constant(0.1, shape=shape))
 
 
# convolution operation
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="SAME")
 
 
# pooling operation
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
 
 
# Convolutional Neural Network
def cnn2(x):
    x_image = tf.reshape(x, [-1, 28, 28, 1])
 
    # Layer 1: convolutional layer
    W_conv1 = weight_variable([5, 5, 1, 32])
    b_conv1 = bias_variable([32])
    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
 
    # Layer 2: convolutional layer
    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = weight_variable([64])
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
 
    # Layer 3: full connection layer
    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])
    h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
 
    # dropout layer
    keep_prob = tf.placeholder("float")
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
 
    # output layer
    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])
    y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
 
    return y_conv, keep_prob
 
 
# read data
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
 
# input layer
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
 
# cnn
y_conv, keep_prob = cnn2(x)
 
# loss function & optimization algorithm
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv), reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
 
# new session
sess = tf.Session()
sess.run(tf.global_variables_initializer())
 
# train
losss = []
accurs = []
steps = []
correct_predict = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_predict, "float"))
for i in range(20000):
    batch = mnist.train.next_batch(50)
    sess.run(train_step, feed_dict={
    
    x: batch[0], y_: batch[1], keep_prob: 0.5})
 
    if i % 100 == 0:
        loss = sess.run(cross_entropy, {
    
    x: batch[0], y_: batch[1], keep_prob: 1.0})
        accur = sess.run(accuracy, feed_dict={
    
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})
        steps.append(i)
        losss.append(loss)
        accurs.append(accur)
        print('Steps: {} loss: {}'.format(i, loss))
        print('Steps: {} accuracy: {}'.format(i, accur))
 
# plot loss
plt.figure()
plt.plot(steps, losss)
plt.xlabel('Number of steps')
plt.ylabel('Loss')
 
plt.figure()
plt.plot(steps, accurs)
plt.hlines(1, 0, max(steps), colors='r', linestyles='dashed')
plt.xlabel('Number of steps')
plt.ylabel('Accuracy')
plt.show()
 
tf.logging.set_verbosity(old_v)

ロスとトレーニング回数によるテストセットの精度の曲線を下の図に示します。最終的な精度は約99.2%です。
ここに画像の説明を挿入します

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/qq_42293496/article/details/110005450