上一篇我们介绍了卷积核的一些基本情况,现在来看一下它的计算机实现。
传送门:卷积核的基本概况
tf的卷积层
图片识别应用最多的是2D-卷积,我们着重介绍。
2D-卷积
2D-卷积与反卷积的tensorflow实现如下:
model = keras.Sequential(
[
keras.Input(shape=(Input[0], Input[1], Input[2])), #W*H*D
layers.Conv2D(8, (Kernel[0],Kernel[1]), strides=strides, padding=padding),
layers.Conv2DTranspose(Input[2], (Kernel[0],Kernel[1]), strides=strides, padding=padding),
])
首先是Conv2D的参数:
filters为过滤器数量,kernel_size是卷积核规模,padding分为“valid"和"same",是否进行补0策略。valid不填充,只进行有效的卷积,对不满足核卷积规模的边界数据不进行处理;same则进行填充,它会自动补充0,能够对边界进行有效卷积。
这里需要强调第一点是,Conv2D的“same"模式,tf是往右下进行填充0,利用公式可以计算:
Wou = (Win + p_w - Kernel_w) / strides + 1
Hou = (Hin + p_h - Kernel_h) / strides + 1
这里的padding是p_w和p_h,需要确定,tf的确定逻辑是这样的:
number_w = [i for i in range(Win)][::strides]
number_h = [i for i in range(Hin)][::strides]
if padding == 'valid':
p_w, p_h = 0, 0
else:
if number_w[-1] < Win:
p_w = Kernel_w - Win + number_w[-1]
else:
p_w = 0
if number_h[-1] < Hin:
p_h = Kernel_h - Hin + number_h[-1]
else:
p_h = 0
注:这里小编经过了上千次试验,确定tf是这样的。
与之相对的是反卷积过程,对于valid会去掉一些边界,因此反卷积后的规模可能小于原输入层的规模,对于same,则可能大于原输入层规模。下面的函数是小编编写的卷积后输出的规模和反卷积后得到的规模函数。
if padding == 'valid':
n_w = [0 if Kernel_w - strides < 0 else Kernel_w - strides][0]
n_h = [0 if Kernel_h - strides < 0 else Kernel_h - strides][0]
else:
n_w,n_h = 0,0
Restore_W = math.floor(Wou)*strides + n_w
Restore_H = math.floor(Hou)*strides + n_h
这里,如果卷积核边长小于步长,tf直接不还原,否则,在还原基础上加上其差值。
另外,反卷积层还有一个output_padding参数,它是指在反卷积后再补充一些层,其逻辑valid与same也不一致。最终小编把输入图像经过卷积和反卷积出来的维度编写为以下函数:
import math
def Output_Dimensions(Input,Kernel,strides=3,padding='valid',output_padding=None):
'''
:param Input: 输入图像,W*H*D
:param Kernel: 卷积核,w*h
:param strides: 步长
:param padding: tf的参数,'valid'或者'same'
:return:返回输出维度
'''
Win = Input[0]
Hin = Input[1]
Din = Input[2]
Kernel_w = Kernel[0]
Kernel_h = Kernel[1]
number_w = [i for i in range(Win)][::strides]
number_h = [i for i in range(Hin)][::strides]
if padding == 'valid':
p_w, p_h = 0, 0
else:
if number_w[-1] < Win:
p_w = Kernel_w - Win + number_w[-1]
else:
p_w = 0
if number_h[-1] < Hin:
p_h = Kernel_h - Hin + number_h[-1]
else:
p_h = 0
Wou = (Win + p_w - Kernel_w) / strides + 1
Hou = (Hin + p_h - Kernel_h) / strides + 1
print('output dimension(w*h) is {}*{}'.format(math.floor(Wou), math.floor(Hou)))
if padding == 'valid':
n_w = [0 if Kernel_w - strides < 0 else Kernel_w - strides][0]
n_h = [0 if Kernel_h - strides < 0 else Kernel_h - strides][0]
else:
n_w,n_h = 0,0
Restore_W = math.floor(Wou)*strides + n_w
Restore_H = math.floor(Hou)*strides + n_h
if output_padding is not None:
if padding == 'valid':
Restore_W = (math.floor(Wou) - 1)*strides + Kernel_w + output_padding[0]
Restore_H = (math.floor(Hou) - 1)*strides + Kernel_h + output_padding[1]
else:
Restore_W = (math.floor(Wou) - 1)*strides + Kernel_w - 2*math.floor(Kernel_w/2) + output_padding[0]
Restore_H = (math.floor(Hou) - 1)*strides + Kernel_h - 2*math.floor(Kernel_h/2) + output_padding[1]
print('deconvolution dimension(w*h) is {}*{}'.format(Restore_W,Restore_H))
return math.floor(Wou), math.floor(Hou),Restore_W,Restore_H
我们可以用实例做一个对比。对于输入层是28*28*3规模的tensor,卷积核为5,步长为5,不进行补0padding,则输出的规模和反卷积后规模为:
我们来看看tf包出来的结果:
model2 = keras.Sequential(
[
keras.Input(shape=(Input[0], Input[1], Input[2])), #W*H*D
layers.Conv2D(8, (Kernel[0],Kernel[1]), strides=strides, padding=padding),
])
input_shape = (1, Input[0], Input[1], Input[2])
x = tf.random.normal(input_shape)
out = model2(x)
out.shape #结果TensorShape([1, 5, 5, 8])
model2.add(layers.Conv2DTranspose(Input[2], (Kernel[0],Kernel[1]), strides=strides, padding=padding))
model2(x).shape #结果TensorShape([1, 25, 25, 3])
可见,手动计算结果与实际一致。再来看看padding进行填充的例子:
另外强调一点,输出给模型的x是带有样本个数的,它表示为第一维,上面例子只有1个样本。最后,我们给出卷积与反卷积的参数量公式:
1D-卷积
1D卷积常常用在文本处理上,它的tensorflow实现如下:
layers.Conv1D()
layers.Conv1DTranspose() #反卷积
1D-卷积的输入一般是【文本个数,单词个数,词向量维度】规模:
#[batch_size, seq_length, embedding_dim]
#文本个数,单词个数,词向量维度
input_shape = (4, 12, 20)
x = tf.random.normal(input_shape)
然后进行1D-卷积:
y = layers.Conv1D(filters=32, kernel_size=2,strides=2,padding='valid',input_shape=input_shape[1:])(x)
filters就是过滤器数量,也就是输出的维度;kernel_size仅输入一个参数,该参数表示高,宽与输入层的词向量维度一致;padding可选“valid”或“same”,valid表示不填充边缘,此时输出维度是:
比如上面例子,每一个文本输出应该是(6,32),一共4个文本,所以最终y是(4,6,32):
same表示使得输出维度(非词向量维度)与输入维度相同(单词个数),但只有当步长为1时生效,输出维度是:
举个例子:
input_shape = (4, 12, 128)
x = tf.random.normal(input_shape)
y = layers.Conv1D(32,2,strides=1,padding='same',input_shape=input_shape[1:])(x)
如果步长不为1,则结果与valid一致。
1D的反卷积过程也很简单,只要设置filters数为原来输入的embedding维度即可:
input_shape = (4, 10, 128)
x = tf.random.normal(input_shape)
y = layers.Conv1D(32,2,strides=2,padding='same',input_shape=input_shape[1:])(x)
x_t = layers.Conv1DTranspose(128,2,strides=2,padding='same',input_shape=input_shape[1:])(y)
x_t.shape
3D-卷积这里不再展开,只给出实现接口:
layers.Conv3D()
layers.Conv3DTranspose()
pytorch的卷积层
pytorch的卷积与反卷积也对应三种:
from torch.nn import Conv1d, ConvTranspose1d
from torch.nn import Conv2d, ConvTranspose2d
from torch.nn import Conv3d, ConvTranspose3d
一个例子就能说明白2D的情况:
model = torch.nn.Sequential(
Conv2d(in_channels=3, out_channels=64, kernel_size=5,stride=2),
ConvTranspose2d(in_channels=64,out_channels=3,kernel_size=5,stride=2)
)
x = torch.rand(6,3,32,32) #B,C,W,H
model(x)
模型传入的维度是B*C*W*H,分别代表batch(小批量样本量),通道数,宽度,高度.然后是conv2d的参数:
in_channels——输入信号的通道
out_channels——卷积产生的通道
kernel_size——卷积核的尺寸
stride——卷积步长
padding——每一条边补充0的层数
dilation——空洞卷积的卷积核元素的间距
bisas——默认为True,添加偏置项
它卷积和反卷积出来的规模是下面的公式: