web-dev-qa-db-ja.com

tf.nn.conv2dはテンソルフローで何をしますか?

私はtf.nn.conv2dここ についてのtensorflowのドキュメントを見ていました。しかし、それが何をしているのか、何を達成しようとしているのか理解できません。それは文書に書いている、

#1:フィルタを形のある2-D行列に平坦化する

[filter_height * filter_width * in_channels, output_channels]

今それは何をしますか?それは要素ごとの乗算なのか、それとも単なる行列乗算なのか。私はまた、文書に記載されている他の2つの点を理解できませんでした。私はそれらを以下に書きました:

#2:入力テンソルから画像パッチを抽出して形状の仮想テンソルを形成する

[batch, out_height, out_width, filter_height * filter_width * in_channels]

#3:パッチごとに、フィルタ行列と画像パッチベクトルを右乗算します。

誰かが例、コード片(非常に役立つ)を与え、そこで何が起こっているのか、そしてなぜ操作がこのようなものであるのかを説明できるならば、それは本当に役に立つでしょう。

小さな部分をコーディングして、操作の形を印刷してみました。それでも、理解できません。

私はこのようなことを試しました:

op = tf.shape(tf.nn.conv2d(tf.random_normal([1,10,10,10]), 
              tf.random_normal([2,10,10,10]), 
              strides=[1, 2, 2, 1], padding='SAME'))

with tf.Session() as sess:
    result = sess.run(op)
    print(result)

私は畳み込みニューラルネットワークの断片を理解しています。私はそれらを勉強しました ここ 。しかし、テンソルフローの実装は私が期待したものではありません。それでそれは質問をしました。

EDIT:だから、私はもっと単純なコードを実装しました。しかし、私は何が起こっているのかわからない。結果がどのようになっているのかということです。誰が私にどのプロセスがこのアウトプットをもたらすかを私に言うことができればそれは非常に役に立ちます。

input = tf.Variable(tf.random_normal([1,2,2,1]))
filter = tf.Variable(tf.random_normal([1,1,1,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)

    print("input")
    print(input.eval())
    print("filter")
    print(filter.eval())
    print("result")
    result = sess.run(op)
    print(result)

出力

input
[[[[ 1.60314465]
   [-0.55022103]]

  [[ 0.00595062]
   [-0.69889867]]]]
filter
[[[[-0.59594476]]]]
result
[[[[-0.95538563]
   [ 0.32790133]]

  [[-0.00354624]
   [ 0.41650501]]]]
120
Shubhashis

2D畳み込みは、 1D畳み込み を計算するのと同じ方法で計算されます。入力に対してカーネルをスライドさせ、要素ごとの乗算を計算してそれらを合計します。しかし、あなたのカーネル/入力が配列である代わりに、ここでそれらは行列です。


最も基本的な例では、パディングはなく、stride = 1です。 inputkernelが次のように仮定しましょう。 enter image description here

カーネルを使用すると、以下のような出力が得られます。 enter image description here 、これは次のように計算されます。

  • 14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1
  • 6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1
  • 6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1
  • 12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

TFの conv2d 関数は畳み込みをバッチで計算し、わずかに異なるフォーマットを使用します。入力の場合は[batch, in_height, in_width, in_channels]、カーネルの場合は[filter_height, filter_width, in_channels, out_channels]です。そのため、正しい形式でデータを提供する必要があります。

import tensorflow as tf
k = tf.constant([
    [1, 0, 1],
    [2, 1, 0],
    [0, 0, 1]
], dtype=tf.float32, name='k')
i = tf.constant([
    [4, 3, 1, 0],
    [2, 1, 0, 1],
    [1, 2, 4, 1],
    [3, 1, 0, 2]
], dtype=tf.float32, name='i')
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image  = tf.reshape(i, [1, 4, 4, 1], name='image')

その後、たたみ込みは次のように計算されます。

res = tf.squeeze(tf.nn.conv2d(image, kernel, [1, 1, 1, 1], "VALID"))
# VALID means no padding
with tf.Session() as sess:
   print sess.run(res)

そして私達が手で計算したものと同等になります。


パディング/ストライドのある例は、こちらをご覧ください

44
Salvador Dali

わかりましたこれがすべてを説明する最も簡単な方法についてであると思います。


あなたの例は1チャンネル、1画像、サイズ2x2です。サイズが1×1、サイズが1×1のフィルタが1つあります(サイズは高さ×幅×チャンネル×フィルタの数です)。

この単純なケースでは、結果として得られる2×2、1チャンネルの画像(サイズ1×2×2×1、画像数×高さ×幅×xチャネル)は、フィルタ値に画像の各ピクセルを乗算した結果です。


それでは、もっとチャンネルを試してみましょう。

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([1,1,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

ここでは、3×3の画像と1×1のフィルタはそれぞれ5つのチャンネルを持っています。結果として得られる画像は、1チャンネルで3×3(サイズ1×3×3×1)になります。各ピクセルの値は、入力画像内の対応するピクセルとフィルタのチャンネル間の内積です。


今3x3フィルターを使って

input = tf.Variable(tf.random_normal([1,3,3,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

ここでは、1チャンネル(サイズ1×1×1×1)の1×1の画像が得られます。値は9、5要素の内積の合計です。しかし、これを45要素の内積と呼ぶこともできます。


大きな画像で

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='VALID')

出力は3 x 3の1チャンネル画像(サイズ1 x 3 x 3 x 1)です。これらの各値は、9個の5要素ドット積の合計です。

各出力は、フィルタを入力画像の9つの中央のピクセルの1つにセンタリングすることによって作成されるので、フィルタはどれもはみ出しません。以下のxname__sは、各出力ピクセルのフィルタ中心を表します。

.....
.xxx.
.xxx.
.xxx.
.....

"SAME"パディングを使って

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,1]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

これにより、5 x 5の出力イメージ(サイズ1 x 5 x 5 x 1)が得られます。これは、フィルタを画像上の各位置にセンタリングすることによって行われます。

フィルタが画像の端を超えて突き出ている5要素の内積はすべてゼロの値になります。

そのため、角は4、5要素の内積の合計にすぎません。


今複数のフィルターを使って。

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 1, 1, 1], padding='SAME')

これはまだ5x5の出力画像を与えますが、7チャンネル(サイズ1x5x5x7)を持ちます。各チャンネルがセット内のいずれかのフィルタによって生成される場所。


今ストライド2,2で:

input = tf.Variable(tf.random_normal([1,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

結果はまだ7つのチャンネルを持っていますが、たった3 x 3(サイズ1 x 3 x 3 x 7)です。

これは、フィルタを画像上のすべての点でセンタリングするのではなく、幅2のステップ(ストライド)をとって、フィルタが画像上の他のすべての点でセンタリングされるためです。以下のxname__は、各出力ピクセルのフィルタ中心を表します。入力画像.

x.x.x
.....
x.x.x
.....
x.x.x

そしてもちろん、入力の最初の次元は画像の数ですので、10個の画像のバッチに適用することができます。例えば:

input = tf.Variable(tf.random_normal([10,5,5,5]))
filter = tf.Variable(tf.random_normal([3,3,5,7]))

op = tf.nn.conv2d(input, filter, strides=[1, 2, 2, 1], padding='SAME')

これは、各画像に対して独立して同じ操作を実行し、結果として10画像のスタックを生成します(サイズ10 x 3 x 3 x 7)。

154
mdaoust

私はconv2dを実装しようとしました(勉強のために)。まあ、私はそれを書いた:

def conv(ix, w):
   # filter shape: [filter_height, filter_width, in_channels, out_channels]
   # flatten filters
   filter_height = int(w.shape[0])
   filter_width = int(w.shape[1])
   in_channels = int(w.shape[2])
   out_channels = int(w.shape[3])
   ix_height = int(ix.shape[1])
   ix_width = int(ix.shape[2])
   ix_channels = int(ix.shape[3])
   filter_shape = [filter_height, filter_width, in_channels, out_channels]
   flat_w = tf.reshape(w, [filter_height * filter_width * in_channels, out_channels])
   patches = tf.extract_image_patches(
       ix,
       ksizes=[1, filter_height, filter_width, 1],
       strides=[1, 1, 1, 1],
       rates=[1, 1, 1, 1],
       padding='SAME'
   )
   patches_reshaped = tf.reshape(patches, [-1, ix_height, ix_width, filter_height * filter_width * ix_channels])
   feature_maps = []
   for i in range(out_channels):
       feature_map = tf.reduce_sum(tf.multiply(flat_w[:, i], patches_reshaped), axis=3, keep_dims=True)
       feature_maps.append(feature_map)
   features = tf.concat(feature_maps, axis=3)
   return features

うまくやったことを願っています。 MNISTで確認しましたが、非常に近い結果が得られました(ただし、この実装は遅い)これがお役に立てば幸いです。

8
Artem Yaschenko

他の答えに追加するために、あなたはのパラメータを考えるべきです。

filter = tf.Variable(tf.random_normal([3,3,5,7]))

各フィルタのチャンネル数に対応する '5'として。各フィルタは深さ5の3D立方体です。フィルタの深さは入力画像の深さに対応している必要があります。最後のパラメータ7は、バッチ内のフィルタ数と見なす必要があります。これが4Dであることを忘れるだけで、代わりに7つのフィルタのセットまたはバッチがあると想像してください。あなたがすることは次元(3,3,5)の7つのフィルター立方体を作成することです。

畳み込みは点単位の乗算になるので、フーリエ領域で視覚化するのははるかに簡単です。寸法(100、100、3)の入力画像の場合、フィルタの寸法を次のように書き換えることができます。

filter = tf.Variable(tf.random_normal([100,100,3,7]))

7つの出力特徴マップのうちの1つを得るために、我々は単純にフィルタ立方体と画像立方体の点ごとの乗算を実行して、それからチャンネル/深さ次元(ここではそれは3)にわたる結果を合計して2dに折りたたみます(100,100)機能マップ各フィルタキューブでこれを行うと、7つの2Dフィーチャーマップが得られます。

8
Val9265

他の答えに加えて、conv2d操作はc ++(cpu)またはcudaで動作していて、ある方法でデータを平らにし、形を変え、gemmBLASまたはcuBLAS(cuda)行列乗算を使用する必要があります。

0
karaspd