tensorflowでtf.nn.conv2dは何をしますか?


135

tf.nn.conv2d ここでテンソルフローのドキュメントを見ていました。しかし、私はそれが何をするのか、それが何を達成しようとしているのか理解できません。それはドキュメントで言う、

#1:フィルターを形状のある2次元行列に平坦化する

[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)

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

編集:それで、私ははるかに単純なコードを実装しました。しかし、私は何が起こっているのか理解できません。結果はこんな感じです。どのプロセスがこの出力を生成するかを誰かに教えてもらえれば、非常に役立ちます。

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]]]]

実際にcudnnはのGPUではデフォルトで有効になっているtf.nn.conv2d()ため、use_cudnn_on_gpu=False明示的に指定されていない限り、GPUサポートでTFを使用する場合、問題のメソッドはまったく使用されません。
gkcn '19年

回答:


59

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


最も基本的な例では、パディングはなく、ストライド= 1です。あなたinputと仮定してみましょうkernelここに画像の説明を入力してください

カーネルを使用すると、次の出力ここに画像の説明を入力してくださいが表示されます。これは、次の方法で計算されます。

  • 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)

そして、手動で計算したものと同等になります。


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


良い例ですが、一部のリンクが壊れています。
silgon 2017年

1
@silgonは残念なことに、SOが最初に作成して宣伝したドキュメント機能をサポートしないことに決めたためです。
サルバドールダリ

161

これは、すべてを説明する最も簡単な方法についてだと思います。


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

この単純なケースの場合、結果の2x2、1チャネルの画像(サイズ1x2x2x1、画像の数x高さx幅xxチャネル)は、フィルター値に画像の各ピクセルを乗算した結果です。


次に、より多くのチャネルを試してみましょう。

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')

ここで、3x3画像と1x1フィルターにはそれぞれ5つのチャネルがあります。結果の画像は、1チャネル(サイズ1x3x3x1)の3x3になります。各ピクセルの値は、入力画像の対応するピクセルを含むフィルターのチャネル間のドット積です。


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チャネル(サイズ1x1x1x1)の1x1画像を取得します。値は、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')

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

各出力は、入力画像の9つの中央ピクセルの1つにフィルターを中央揃えして作成されるため、どのフィルターもはみ出しません。x以下の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')

これにより、5x5の出力イメージ(サイズ1x5x5x1)が得られます。これは、画像の各位置でフィルターを中央に配置することによって行われます。

フィルターが画像の端を超えてはみ出す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チャネルですが、3x3(サイズ1x3x3x7)しかありません。

これは、フィルターを画像上のすべての点に中央揃えするのではなく、画像上の他のすべての点にx中央揃えし、幅2のステップ(ストライド)を取るためです。以下のは、各出力ピクセルのフィルター中央を表します。入力画像。

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画像のスタックを提供します(サイズ10x3x3x7)


@ZijunLostいいえ、ドキュメントでは最初と最後の要素は1でなければならないと述べていますMust have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].
JohnAllen

このテプリッツ行列ベースの畳み込みの実装ですか?
gkcn 2016年

これに関して:「これでも5x5の出力画像が得られますが、7チャネル(サイズ1x5x5x7)です。各チャネルは、セット内のフィルターの1つによって生成されます。」、まだ7チャネルの出所がわかりません。「セット内のフィルター」とはどういう意味ですか?ありがとう。
derek 2017

@mdaoustこんにちは、の2番目の例に関してthe 3x3 image and the 1x1 filter each have 5 channels、手動で計算した内積と結果が異なることがわかりました。
Tgn Yang

1
@derek同じ質問があります。「output_channel」は「フィルターの数」と同じですか??? もしそうなら、それらはテンソルフローのドキュメントで「output_channel」と名付けられているのですか?

11

他の答えに追加するために、あなたはパラメータを考える必要があります

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

私は(勉強のために)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でチェックすると、非常に近い結果が得られました(ただし、この実装は低速です)。これがお役に立てば幸いです。


0

他の回答に加えて、conv2d操作は、特定の方法でデータを平坦化および再形成し、gemmBLASまたはcuBLAS(cuda)行列乗算を使用する必要があるgpuマシンのc ++(cpu)またはcudaで動作しています。


したがって、メモリでは、畳み込みは実際には行列乗算として実行されます。これは、大きな画像が実行されると必ずしも計算時間が長くなるのではなく、OOM(メモリ不足)エラーが発生する可能性が高いことを説明しています。3Dたたみ込みが2Dたたみ込みに比べてメモリ効率が悪い/効率的である理由を説明していただけますか?たとえば、[B * C、H、W、D]での2D変換と比較して、[B、H、W、D、C]での3D変換を行います。確かに、計算コストは​​同じですか?
SomePhysicsStudent
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.