写这个的原因:一来好像没怎么搜到别人手动实现,作为补充;二来巩固一下基础。
先从一张示意图说起,卷积基础概念和操作步骤就不啰嗦了,只讲这张图,大意就是,有in-channel,有out-channel,你需要把in-channel都做卷积操作,然后产出out-channel,所以这个w是要层层拆解,w分拆成w0和w1,以对应2个out-channel。w0分拆成3个矩阵w0[:,:,0]、w0[:,:,1]、w0[:,:,2],以对应3个in-channel,因为输入的3通道终究要合二为一的(进入一个核):y0=w0*x+b0、y1=w1*x+b1,所以w0只有一个b0对应,而不是三个。又因为卷积操作中,一个核要计算的目标是一个sum总值(绿图中的一个点),而不是9个值,所以3*3的矩阵只要对应1*1的b就够了。
图讲清楚了,下面开始实现。
1.完成conv2d自动卷积,拿结果做参考,拿指定的相同的weights给第二步。
2.自己写循环,进行相应操作。
3.做对比。
第一步:
使用conv2d的代码如下:
batch没什么用,设1。3通道输入,原图尺寸5*5,卷积核3*3,2通道输出,输出尺寸3*3,输出数值27(三个卷积核结果相加:9+9+9),注意,这个conv2d接口默认是不带bias的!一会如果手动卷积结果想要一模一样,bias我会设0。
import tensorflow as tf # [batch, in_height, in_width, in_channels] input_arg = tf.Variable(tf.ones([1, 5, 5, 3])) # [filter_height, filter_width, in_channels, out_channels] filter_arg = tf.Variable(tf.ones([3 ,3 , 3 ,2])) conv2d_output = tf.nn.conv2d(input_arg, filter_arg, strides=[1,1,1,1], use_cudnn_on_gpu=False, padding='VALID') with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print('input:\n', sess.run(input_arg)) print('filter:\n', sess.run(filter_arg)) print('output:\n', sess.run(conv2d_output))
第一步结果:
input: [[[[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]] [[1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.] [1. 1. 1.]]]] filter: [[[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]] [[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]] [[[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]] [[1. 1.] [1. 1.] [1. 1.]]]] output: [[[[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]]]]
因为卷积操作中,channel是最后一维,所以如果最后不是一维,打印之后看起来会有那么点别扭。但是这个形状是没错的。
第二步:构思循环
padding分valid和same两种,主要用简单一些的valid,也就是不填充,这个不填充没什么争议,same的话,为了达到效果,他会分别从后边或者从前后进行0的填充,因为是封装的,规律不是很清晰,不太好比较。
如果是valid,那么在一个维度上,卷积循环次数和输出size大概是i-j+1,i是输入的size,j是核大小。
Tensor的元素赋值操作比较麻烦,先做一个numpy版(可以先忽略tf相关变量),旨在写出等效计算过程:
围绕输出通道做大循环,每个输出通道都是对所有输入通道做卷积操作的结果的和,加偏置。注意加偏置的时机:if input == input_channel - 1,一个out_channel对应一个偏置,所以在最后一个input_channel下加biases。
每一个卷积核的窗口操作就是窗口中元素和卷积核元素按位相乘,求和。
import tensorflow as tf import numpy as np batch_size = 1 input_height = 5 input_width = 5 filter_height = 3 filter_width = 3 output_height =input_height - filter_height + 1 output_width = input_width - filter_width + 1 input_channel = 3 output_channel = 2 # [batch, in_height, in_width, in_channels] np_input_arg = np.ones([batch_size, input_height, input_width, input_channel]) # [filter_height, filter_width, in_channels, out_channels] np_filter_arg = np.ones([filter_height, filter_width, input_channel, output_channel]) np_biases = np.ones([batch_size,1,1,output_channel]) np_final_output = np.zeros([batch_size, output_height, output_width, output_channel]) # manual convolution for batch in range(batch_size): for output in range(output_channel): for input in range(input_channel): for i in range(output_height): for j in range(output_width): # a filter window filter_sum = 0 # convolution operation: [i,i+1,i+2] * [j,j+1,j+2] [3] * [3] = [9] for m in range(filter_height): for n in range(filter_width): np_final_output[batch][i][j][output] += np_input_arg[batch][i + m][j + n][input] * \ np_filter_arg[m][n][input][output] if input == input_channel - 1: np_final_output[batch][i][j][output] += np_biases[batch][0][0][output] # print('np_final_output[{0}][{1}][{2}][{3}]:{4}'.format(batch,i,j,output, np_final_output[batch][i][j][output])) print('np_final_output:', np_final_output)
如果np_biases用0,结果与tf.nn.conv2d结果一致:
np_final_output: [[[[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]] [[27. 27.] [27. 27.] [27. 27.]]]]
如果np_biases(偏置)用1,结果如下:
np_final_output: [[[[28. 28.] [28. 28.] [28. 28.]] [[28. 28.] [28. 28.] [28. 28.]] [[28. 28.] [28. 28.] [28. 28.]]]]
todo:Tensor版本的卷积操作:
遇到问题:Tensorflow要写计算图,Tensor不能像普通list那样随意按元素操作
TypeError: 'Tensor' object does not support item assignment
AttributeError: 'Tensor' object has no attribute 'assign_add'