Tf.nn.conv2d làm gì trong tenorflow?


135

Tôi đã xem xét các tài liệu của tenorflow về tf.nn.conv2d đây . Nhưng tôi không thể hiểu những gì nó làm hoặc những gì nó đang cố gắng để đạt được. Nó nói trên các tài liệu,

# 1: Làm phẳng bộ lọc thành ma trận 2 chiều có hình dạng

[filter_height * filter_width * in_channels, output_channels].

Bây giờ nó làm gì? Đó là phép nhân phần tử khôn ngoan hay chỉ là phép nhân ma trận đơn giản? Tôi cũng không thể hiểu hai điểm khác được đề cập trong các tài liệu. Tôi đã viết chúng dưới đây:

# 2: Trích xuất các bản vá hình ảnh từ tenxơ đầu vào để tạo thành một tenor ảo có hình dạng

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

# 3: Đối với mỗi bản vá, nhân phải ma trận bộ lọc và vectơ bản vá hình ảnh.

Nó sẽ thực sự hữu ích nếu bất cứ ai có thể đưa ra một ví dụ, một đoạn mã (cực kỳ hữu ích) có thể và giải thích những gì đang diễn ra ở đó và tại sao hoạt động lại như thế này.

Tôi đã thử mã hóa một phần nhỏ và in ra hình dạng của hoạt động. Tuy nhiên, tôi không thể hiểu.

Tôi đã thử một cái gì đó như thế này:

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)

Tôi hiểu các bit và các mảnh của mạng nơ ron tích chập. Tôi đã nghiên cứu chúng ở đây . Nhưng việc thực hiện trên tenorflow không như tôi mong đợi. Vì vậy, nó đưa ra câu hỏi.

EDIT : Vì vậy, tôi đã triển khai một mã đơn giản hơn nhiều. Nhưng tôi không thể hiểu chuyện gì đang xảy ra. Tôi có nghĩa là làm thế nào kết quả là như thế này. Sẽ rất hữu ích nếu bất cứ ai có thể cho tôi biết quá trình nào mang lại đầu ra này.

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)

đầu ra

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

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

  [[-0.00354624]
   [ 0.41650501]]]]

Trên thực tế, cudnn được bật theo mặc định trên GPU tf.nn.conv2d(), vì vậy phương pháp được đề cập hoàn toàn không được sử dụng khi chúng tôi sử dụng TF với hỗ trợ GPU, trừ khi use_cudnn_on_gpu=Falseđược chỉ định rõ ràng.
gkcn

Câu trả lời:


59

Tích chập 2D được tính theo cách tương tự người ta sẽ tính tích chập 1D : bạn trượt hạt nhân của bạn qua đầu vào, tính toán các phần tử khôn ngoan của phần tử và tính tổng chúng. Nhưng thay vì kernel / input của bạn là một mảng, ở đây chúng là ma trận.


Trong ví dụ cơ bản nhất không có phần đệm và stride = 1. Hãy giả sử bạn inputkernellà: nhập mô tả hình ảnh ở đây

Khi bạn sử dụng kernel, bạn sẽ nhận được đầu ra sau : nhập mô tả hình ảnh ở đây, được tính theo cách sau:

  • 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

Hàm conv2d của TF tính toán các giá trị theo đợt và sử dụng định dạng hơi khác. Đối với một đầu vào, nó là [batch, in_height, in_width, in_channels]cho kernel [filter_height, filter_width, in_channels, out_channels]. Vì vậy, chúng tôi cần cung cấp dữ liệu theo đúng định dạng:

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

Sau đó, tích chập được tính toán với:

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)

Và sẽ tương đương với cái mà chúng ta tính toán bằng tay.


dụ với phần đệm / sải chân, hãy xem ở đây .


Ví dụ đẹp, tuy nhiên một số liên kết bị hỏng.
silgon

1
@silgon đáng buồn là vì SO quyết định không hỗ trợ tính năng tài liệu mà họ đã tạo và quảng cáo lúc đầu.
Salvador Dali

161

Ok tôi nghĩ rằng đây là cách đơn giản nhất để giải thích tất cả.


Ví dụ của bạn là 1 hình ảnh, kích thước 2x2, với 1 kênh. Bạn có 1 bộ lọc, với kích thước 1x1 và 1 kênh (kích thước là chiều cao x chiều rộng x kênh x số lượng bộ lọc).

Trong trường hợp đơn giản này, kết quả hình ảnh 2x2, 1 kênh (kích thước 1x2x2x1, số lượng hình ảnh x chiều cao x chiều rộng xx kênh) là kết quả của việc nhân giá trị bộ lọc với mỗi pixel của hình ảnh.


Bây giờ hãy thử nhiều kênh hơn:

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

Ở đây, hình ảnh 3x3 và bộ lọc 1x1 mỗi bộ có 5 kênh. Hình ảnh thu được sẽ là 3x3 với 1 kênh (kích thước 1x3x3x1), trong đó giá trị của mỗi pixel là sản phẩm chấm trên các kênh của bộ lọc có pixel tương ứng trong hình ảnh đầu vào.


Bây giờ với bộ lọc 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')

Ở đây chúng tôi nhận được một hình ảnh 1x1, với 1 kênh (kích thước 1x1x1x1). Giá trị là tổng của các sản phẩm chấm 9, 5 phần tử. Nhưng bạn chỉ có thể gọi đây là một sản phẩm chấm 45 yếu tố.


Bây giờ với một hình ảnh lớn hơn

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

Đầu ra là hình ảnh 1 kênh 3x3 (kích thước 1x3x3x1). Mỗi giá trị này là tổng của 9, 5 sản phẩm chấm.

Mỗi đầu ra được thực hiện bằng cách căn giữa bộ lọc trên một trong 9 pixel trung tâm của hình ảnh đầu vào, sao cho không có bộ lọc nào ló ra. Các xs dưới đây đại diện cho các trung tâm bộ lọc cho từng pixel đầu ra.

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

Bây giờ với phần đệm "CÙNG":

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

Điều này cho hình ảnh đầu ra 5x5 (kích thước 1x5x5x1). Điều này được thực hiện bằng cách căn giữa bộ lọc tại mỗi vị trí trên ảnh.

Bất kỳ sản phẩm chấm 5 phần tử nào trong đó bộ lọc nhô ra khỏi cạnh của hình ảnh đều có giá trị bằng 0.

Vì vậy, các góc chỉ là tổng của các sản phẩm chấm 4, 5 yếu tố.


Bây giờ với nhiều bộ lọc.

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

Điều này vẫn cho hình ảnh đầu ra 5x5, nhưng với 7 kênh (kích thước 1x5x5x7). Trong đó mỗi kênh được tạo bởi một trong các bộ lọc trong bộ.


Bây giờ với những bước tiến 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')

Bây giờ kết quả vẫn có 7 kênh, nhưng chỉ là 3x3 (kích thước 1x3x3x7).

Điều này là do thay vì căn giữa các bộ lọc tại mọi điểm trên ảnh, các bộ lọc được căn giữa ở mọi điểm khác trên ảnh, thực hiện các bước (sải chân) có chiều rộng 2. Dưới xđây biểu thị trung tâm bộ lọc cho từng pixel đầu ra, trên hình ảnh đầu vào.

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

Và tất nhiên, kích thước đầu tiên của đầu vào là số lượng hình ảnh để bạn có thể áp dụng nó trong một loạt 10 hình ảnh, ví dụ:

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

Điều này thực hiện cùng một hoạt động, đối với từng hình ảnh một cách độc lập, tạo ra một chồng 10 hình ảnh (kích thước 10x3x3x7)


@ZijunLost Không, tài liệu nói rằng phần tử đầu tiên và cuối cùng phải là 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].
John ALLen

Đây có phải là ma trận Toeplitz dựa trên cơ sở thực hiện tích chập không?
gkcn

Về điều này: "Điều này vẫn cho hình ảnh đầu ra 5x5, nhưng với 7 kênh (kích thước 1x5x5x7). Mỗi kênh được sản xuất bởi một trong các bộ lọc trong bộ.", Tôi vẫn gặp khó khăn khi hiểu 7 kênh đó đến từ đâu? "Bộ lọc trong bộ" nghĩa là gì? Cảm ơn.
derek

@mdaoust Xin chào, về ví dụ thứ hai của bạn the 3x3 image and the 1x1 filter each have 5 channels, tôi thấy kết quả khác với sản phẩm chấm được tính toán thủ công.
Tgn Yang

1
@derek Tôi có cùng một câu hỏi, "output_channel" có giống "số lượng bộ lọc" không ??? nếu vậy tại sao chúng được đặt tên là "output_channel" trong các tài liệu tenorflow?
Ngụy

11

Chỉ cần thêm vào các câu trả lời khác, bạn nên nghĩ về các tham số trong

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

là '5' tương ứng với số lượng kênh trong mỗi bộ lọc. Mỗi bộ lọc là một khối lập phương 3d, có độ sâu 5. Độ sâu bộ lọc của bạn phải tương ứng với độ sâu của hình ảnh đầu vào của bạn. Tham số cuối cùng, 7, nên được coi là số lượng bộ lọc trong lô. Chỉ cần quên điều này là 4D, và thay vào đó hãy tưởng tượng rằng bạn có một bộ hoặc một loạt 7 bộ lọc. Những gì bạn làm là tạo 7 khối lọc với kích thước (3,3,5).

Việc hình dung trong miền Fourier sẽ dễ dàng hơn rất nhiều vì phép tích chập trở thành phép nhân điểm. Đối với hình ảnh đầu vào có kích thước (100.100,3), bạn có thể viết lại kích thước bộ lọc dưới dạng

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

Để có được một trong 7 bản đồ tính năng đầu ra, chúng tôi chỉ cần thực hiện phép nhân điểm của khối bộ lọc với khối hình ảnh, sau đó chúng tôi tổng hợp kết quả trên các chiều / chiều sâu (ở đây là 3), thu gọn thành 2d (100.100) bản đồ tính năng. Làm điều này với mỗi khối bộ lọc và bạn nhận được 7 bản đồ tính năng 2D.


8

Tôi đã cố gắng thực hiện conv2d (cho việc học của tôi). Vâng, tôi đã viết rằng:

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

Hy vọng tôi đã làm đúng. Đã kiểm tra trên MNIST, đã có kết quả rất gần (nhưng việc thực hiện này chậm hơn). Tôi hy vọng cái này sẽ giúp bạn.


0

Ngoài các câu trả lời khác, hoạt động của conv2d đang hoạt động trong c ++ (cpu) hoặc cuda cho các máy gpu yêu cầu làm phẳng và định hình lại dữ liệu theo cách nhất định và sử dụng phép nhân ma trận gemmBLAS hoặc cuBLAS (cuda).


Vì vậy, trong bộ nhớ, tích chập thực sự được thực hiện dưới dạng phép nhân ma trận, điều này giải thích tại sao các hình ảnh lớn hơn không nhất thiết phải chạy trong thời gian tính toán lớn hơn mà thay vào đó có nhiều khả năng gặp phải lỗi OOM (hết bộ nhớ). Bạn có thể giải thích cho tôi tại sao tích chập 3D lại kém hiệu quả / hiệu quả hơn so với tích chập 2D không? Ví dụ: thực hiện đối lưu 3D trên [B, H, W, D, C] so với đối lưu 2D trên [B * C, H, W, D]. Chắc chắn, họ tính toán như nhau?
Một số môn vật lý học
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.