tf.nn.conv2d ทำอะไรใน tensorflow?


138

ฉันถูกมองที่เอกสารของ tensorflow เกี่ยวกับที่นี่tf.nn.conv2d แต่ฉันไม่เข้าใจว่ามันทำอะไรหรือพยายามทำอะไร มันบอกในเอกสาร

# 1: แผ่ฟิลเตอร์เป็นเมทริกซ์ 2 มิติที่มีรูปร่าง

[filter_height * filter_width * in_channels, output_channels].

ตอนนี้ทำอะไร? นั่นคือการคูณอย่างชาญฉลาดขององค์ประกอบหรือการคูณเมทริกซ์ธรรมดา? ฉันยังไม่เข้าใจอีกสองประเด็นที่กล่าวถึงในเอกสาร ฉันได้เขียนไว้ด้านล่าง:

# 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()ดังนั้นจึงไม่มีการใช้วิธีการที่เป็นปัญหาเลยเมื่อเราใช้ TF ที่รองรับ GPU เว้นแต่use_cudnn_on_gpu=Falseจะระบุไว้อย่างชัดเจน
gkcn

คำตอบ:


65

2D Convolution คำนวณในลักษณะที่คล้ายกันซึ่งจะคำนวณ1D Convolution : คุณเลื่อนเคอร์เนลของคุณไปที่อินพุตคำนวณการคูณที่ชาญฉลาดขององค์ประกอบและสรุปรวมเข้าด้วยกัน แต่แทนที่เคอร์เนล / อินพุตของคุณจะเป็นอาร์เรย์นี่คือเมทริกซ์


ในตัวอย่างพื้นฐานที่สุดไม่มีช่องว่างภายในและก้าว = 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

ฟังก์ชันConv2dของ TF จะคำนวณ Convolutions เป็นแบทช์และใช้รูปแบบที่แตกต่างกันเล็กน้อย สำหรับการป้อนข้อมูลมันเป็นสำหรับเคอร์เนลมันเป็น[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')

หลังจากนั้น Convolution จะคำนวณด้วย:

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)

และจะเทียบเท่ากับที่เราคำนวณด้วยมือ


สำหรับตัวอย่างกับ padding / ความก้าวหน้าให้ดูที่นี่


เป็นตัวอย่างที่ดีอย่างไรก็ตามบางลิงก์เสีย
silgon

1
@silgon น่าเศร้าที่เป็นเพราะ SO ตัดสินใจที่จะไม่สนับสนุนคุณสมบัติเอกสารที่พวกเขาสร้างและโฆษณาในตอนแรก
Salvador Dali

160

โอเคฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุดในการอธิบายทั้งหมด


ตัวอย่างของคุณคือภาพ 1 ภาพขนาด 2x2 พร้อมช่อง 1 ช่อง คุณมี 1 ตัวกรองขนาด 1x1 และ 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 ช่อง ภาพที่ได้จะมีขนาด 3x3 พร้อมช่อง 1 ช่อง (ขนาด 1x3x3x1) โดยค่าของแต่ละพิกเซลคือผลิตภัณฑ์จุดข้ามช่องของฟิลเตอร์ที่มีพิกเซลที่สอดคล้องกันในภาพอินพุต


ตอนนี้มีฟิลเตอร์ 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')

ที่นี่เราได้ภาพ 1x1 1 ช่อง (ขนาด 1x1x1x1) ค่านี้คือผลรวมของผลิตภัณฑ์จุด 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 พิกเซลกลางของภาพอินพุตเพื่อไม่ให้ฟิลเตอร์ใดหลุดออกมา xs ด้านล่างแทนศูนย์กรองสำหรับแต่ละพิกเซลเอาท์พุท

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

เนื่องจากแทนที่จะวางฟิลเตอร์ไว้ที่จุดใดจุดหนึ่งของรูปภาพฟิลเตอร์จะอยู่กึ่งกลางที่จุดอื่น ๆ ของรูปภาพโดยทำตามขั้นตอน (การก้าว) ของความกว้าง 2 xด้านล่างแสดงถึงศูนย์ฟิลเตอร์สำหรับพิกเซลเอาต์พุตแต่ละพิกเซลบน ภาพอินพุต

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 No เอกสารระบุว่าองค์ประกอบแรกและองค์ประกอบสุดท้ายต้องเป็น 1Must have strides[0] = strides[3] = 1. For the most common case of the same horizontal and vertices strides, strides = [1, stride, stride, 1].
JohnAllen

เมทริกซ์ Toeplitzนี้เป็นไปตามการดำเนินการ Convolution หรือไม่?
gkcn

เกี่ยวกับเรื่องนี้: "สิ่งนี้ยังคงให้ภาพเอาต์พุต 5x5 แต่มี 7 ช่องสัญญาณ (ขนาด 1x5x5x7) แต่ละช่องผลิตโดยตัวกรองตัวใดตัวหนึ่งในชุด" ฉันยังมีปัญหาในการทำความเข้าใจว่าช่อง 7 มาจากไหน? คุณหมายถึงอะไร "ตัวกรองในชุด"? ขอบคุณ.
derek

@mdaoust สวัสดีเกี่ยวกับตัวอย่างที่สองของคุณซึ่งthe 3x3 image and the 1x1 filter each have 5 channelsฉันพบว่าผลลัพธ์แตกต่างจากผลิตภัณฑ์ดอทที่คำนวณด้วยตนเอง
Tgn Yang

1
@derek ฉันมีคำถามเดียวกัน "output_channel" เหมือนกับ "จำนวนตัวกรอง" หรือไม่ ??? ถ้าเป็นเช่นนั้นเหตุใดจึงตั้งชื่อว่า "output_channel" ในเอกสารเทนเซอร์โฟลว์
Wei

12

หากต้องการเพิ่มคำตอบอื่น ๆ คุณควรคิดถึงพารามิเตอร์ใน

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

เป็น '5' ที่สอดคล้องกับจำนวนช่องในแต่ละตัวกรอง ฟิลเตอร์แต่ละตัวเป็นลูกบาศก์ 3 มิติโดยมีความลึก 5 ความลึกของฟิลเตอร์ของคุณต้องสอดคล้องกับความลึกของภาพที่คุณป้อน พารามิเตอร์สุดท้ายคือ 7 ควรคำนึงถึงจำนวนตัวกรองในชุดงาน อย่าลืมว่านี่คือ 4D และลองจินตนาการว่าคุณมีชุดหรือชุดฟิลเตอร์ 7 ชุด สิ่งที่คุณทำคือสร้างคิวบ์ตัวกรอง 7 ตัวที่มีขนาด (3,3,5)

การมองเห็นภาพในโดเมนฟูริเยร์นั้นง่ายกว่ามากเนื่องจากการแปลงกลายเป็นการคูณแบบชี้จุด สำหรับภาพอินพุตที่มีขนาด (100,100,3) คุณสามารถเขียนขนาดตัวกรองใหม่เป็น

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

เพื่อให้ได้หนึ่งในแผนที่คุณลักษณะเอาต์พุต 7 รายการเราเพียงแค่ทำการคูณอย่างชาญฉลาดของคิวบ์ตัวกรองด้วยคิวบ์รูปภาพจากนั้นเราจะรวมผลลัพธ์ในช่อง / มิติความลึก (ในที่นี้คือ 3) โดยยุบเป็น 2d (100,100) แผนที่คุณลักษณะ ทำสิ่งนี้กับคิวบ์ตัวกรองแต่ละอันและคุณจะได้รับแผนที่คุณลักษณะ 7 มิติ


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 ยังทำงานใน c ++ (cpu) หรือ cuda สำหรับเครื่อง gpu ที่ต้องการทำให้ข้อมูลแบนและจัดรูปแบบใหม่ด้วยวิธีใดวิธีหนึ่งและใช้การคูณเมทริกซ์ gemmBLAS หรือ cuBLAS (cuda)


ดังนั้นในหน่วยความจำคอนโวลูชั่นจึงถูกดำเนินการโดยการคูณเมทริกซ์ซึ่งอธิบายได้ว่าทำไมภาพขนาดใหญ่จึงไม่จำเป็นต้องใช้เวลาในการคำนวณที่มากขึ้น แต่มีแนวโน้มที่จะพบข้อผิดพลาด OOM (หน่วยความจำไม่เพียงพอ) แทน คุณช่วยอธิบายให้ฉันฟังได้ไหมว่าทำไมคอนโวลูชั่น 3 มิติจึงไม่มีประสิทธิภาพ / มีประสิทธิภาพมากกว่าเมื่อเทียบกับคอนโวลูชั่น 2 มิติ ตัวอย่างเช่นการสร้าง Conv. 3 มิติบน [B, H, W, D, C] เทียบกับ Conv. 2 มิติใน [B * C, H, W, D] แน่นอนว่าค่าใช้จ่ายในการคำนวณเท่ากัน?
SomePhysicsStudent

0

มันกำลังดำเนินการชักผ่านภาพเมื่อคุณพยายามยกตัวอย่างเช่นฟังก์ชันการจำแนกประเภทรูปภาพ thuis มีพารามิเตอร์ทั้งหมดที่จำเป็นในการทำเช่นนั้น

โดยพื้นฐานแล้วคุณสามารถเลือกมิติตัวกรองได้ ก้าว การขยายความ. ก่อนที่จะใช้มันจำเป็นต้องเปิดเผยแนวคิดของการสนทนา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.