引言
前序学习进程中,已经掌握了多维卷积核自动计算的基本方法。
更早期的学习中,我们也曾了解到卷积原始矩阵扩充的基本原理。今天就把这两个知识点结合一下,探究原始矩阵扩充后的多维度卷积核计算效果。
初始代码
这先给出未扩充前的代码:
python
import torch
# 1. 定义原始输入(3通道5×5)和卷积核1(边缘检测核)
input_tensor = torch.tensor([
# 输入通道1(R):5×5
[
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]
],
# 输入通道2(G):5×5
[
[26, 27, 28, 29, 30],
[31, 32, 33, 34, 35],
[36, 37, 38, 39, 40],
[41, 42, 43, 44, 45],
[46, 47, 48, 49, 50]
],
# 输入通道3(B):5×5
[
[51, 52, 53, 54, 55],
[56, 57, 58, 59, 60],
[61, 62, 63, 64, 65],
[66, 67, 68, 69, 70],
[71, 72, 73, 74, 75]
]
], dtype=torch.float32) # 形状:(3,5,5)
# 输出原始通道的形状
input_tensor_channels,input_tensor_h,input_tensor_w=input_tensor.shape
print('input_tensor_channels=',input_tensor_channels)
print('input_tensor_h=',input_tensor_h)
print('input_tensor_w=',input_tensor_w)
# 卷积核1(边缘检测核):3个子核,每个3×3
kernel1 = torch.tensor([
[[1, 0, -1], [1, 0, -1], [1, 0, -1]], # 子核1(R通道)
[[1, 0, -1], [1, 0, -1], [1, 0, -1]], # 子核2(G通道)
[[1, 0, -1], [1, 0, -1], [1, 0, -1]] # 子核3(B通道)
], dtype=torch.float32) # 形状:(3,3,3)
# 输出卷积核的形状
kernel1_channels,kernel1_h,kernel1_w=kernel1.shape
print('kernel1_channels=',kernel1_channels)
print('kernel1_h=',kernel1_h)
print('kernel1_w=',kernel1_w)
# 通过循环自动计算卷积值
# 定义步长为1
stride=1
# 定义卷积运算后的输出矩阵大小
out_h=(input_tensor_h-kernel1_h)//stride+1
out_w=(input_tensor_w-kernel1_w)//stride+1
# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵
input_cal_tensor=input_tensor
# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵
output_cal_tensor=torch.zeros((out_h,out_w),dtype=torch.float32)
# 循环计算
for i in range(out_h):
for j in range(out_w):
# 对原始矩阵取块进行计算,这里定义了取块的起始行
in_h_start=i*stride
# 取块的末行
in_h_end=in_h_start+kernel1_h
# 取块的起始列
in_w_start=j*stride
# 取块的末列
in_w_end=in_w_start+kernel1_w
# 这是取到的块
input_patch=input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]
# 定义一个空列表output_cal存储数据
output_cal = []
for k in range(input_tensor_channels):
# k代表了通道数,因为每个通道都要进行卷积运算,
# 因此需要先得到单通道的计算效果,然后把它们储存在列表中
output_channel=(input_patch[k]*kernel1[k]).sum()
#print('input_patch[',k,']=',input_patch[k])
#print('output_patch[', k, ']=', kernel1[k])
# 计算结果储存在列表中
output_cal.append(output_channel)
# 把通道效果叠加后,保存到卷积输出矩阵
# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算
# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中
# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值
# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵
# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达
output_cal_tensor[i, j] = sum(output_cal)
#print('output_cal_tensor[', i, j, ']=', output_cal_tensor[i, j])
print('output=',output_cal_tensor)
扩充
对原始矩阵进行扩充,最简单最直接的做法就是手动往往矩阵外圈加数字。
python
# 扩充原始矩阵,周围补一圈0
padded_h=input_tensor_h+2*stride
padded_w=input_tensor_w+2*stride
padded_input_tensor = torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)
padded_input_tensor[:,stride:-stride,stride:-stride]=input_tensor
print(padded_input_tensor)
torch.zeros完成了扩充任务。
这里直接借用了stride来定义扩充大小,可以重新定义一个常量,改变扩充大小。
自动卷积计算
这一步相对简单,有了矩阵后就是机械地执行计算过程就可以:
python
# 对扩充后的矩阵卷积运算
# 计算卷积矩阵大小
padded_out_h=(padded_h-kernel1_h)//stride+1
padded_out_w=(padded_w-kernel1_w)//stride+1
# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵
padded_input_cal_tensor=padded_input_tensor
# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵
padded_output_cal_tensor=torch.zeros((padded_out_h,padded_out_w),dtype=torch.float32)
for i in range(padded_out_h):
for j in range(padded_out_w):
# 对原始矩阵取块进行计算,这里定义了取块的起始行
in_h_start=i*stride
# 取块的末行
in_h_end=in_h_start+kernel1_h
# 取块的起始列
in_w_start=j*stride
# 取块的末列
in_w_end=in_w_start+kernel1_w
# 这是取到的块
input_patch=padded_input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]
# 定义一个空列表output_cal存储数据
output_cal = []
for k in range(input_tensor_channels):
# k代表了通道数,因为每个通道都要进行卷积运算,
# 因此需要先得到单通道的计算效果,然后把它们储存在列表中
output_channel=(input_patch[k]*kernel1[k]).sum()
#print('input_patch[',k,']=',input_patch[k])
#print('output_patch[', k, ']=', kernel1[k])
# 计算结果储存在列表中
output_cal.append(output_channel)
# 把通道效果叠加后,保存到卷积输出矩阵
# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算
# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中
# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值
# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵
# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达
padded_output_cal_tensor[i, j] = sum(output_cal)
#print('padded_output_cal_tensor[', i, j, ']=', padded_output_cal_tensor[i, j])
print('padded_output=',padded_output_cal_tensor)
完整代码和效果
完整代码可以同时输出扩充前后的卷积效果:
python
import torch
# 1. 定义原始输入(3通道5×5)和卷积核1(边缘检测核)
input_tensor = torch.tensor([
# 输入通道1(R):5×5
[
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]
],
# 输入通道2(G):5×5
[
[26, 27, 28, 29, 30],
[31, 32, 33, 34, 35],
[36, 37, 38, 39, 40],
[41, 42, 43, 44, 45],
[46, 47, 48, 49, 50]
],
# 输入通道3(B):5×5
[
[51, 52, 53, 54, 55],
[56, 57, 58, 59, 60],
[61, 62, 63, 64, 65],
[66, 67, 68, 69, 70],
[71, 72, 73, 74, 75]
]
], dtype=torch.float32) # 形状:(3,5,5)
# 输出原始通道的形状
input_tensor_channels,input_tensor_h,input_tensor_w=input_tensor.shape
print('input_tensor_channels=',input_tensor_channels)
print('input_tensor_h=',input_tensor_h)
print('input_tensor_w=',input_tensor_w)
# 卷积核1(边缘检测核):3个子核,每个3×3
kernel1 = torch.tensor([
[[1, 0, -1], [1, 0, -1], [1, 0, -1]], # 子核1(R通道)
[[1, 0, -1], [1, 0, -1], [1, 0, -1]], # 子核2(G通道)
[[1, 0, -1], [1, 0, -1], [1, 0, -1]] # 子核3(B通道)
], dtype=torch.float32) # 形状:(3,3,3)
# 输出卷积核的形状
kernel1_channels,kernel1_h,kernel1_w=kernel1.shape
print('kernel1_channels=',kernel1_channels)
print('kernel1_h=',kernel1_h)
print('kernel1_w=',kernel1_w)
# 通过循环自动计算卷积值
# 定义步长为1
stride=1
# 定义卷积运算后的输出矩阵大小
out_h=(input_tensor_h-kernel1_h)//stride+1
out_w=(input_tensor_w-kernel1_w)//stride+1
# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵
input_cal_tensor=input_tensor
# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵
output_cal_tensor=torch.zeros((out_h,out_w),dtype=torch.float32)
# 循环计算
for i in range(out_h):
for j in range(out_w):
# 对原始矩阵取块进行计算,这里定义了取块的起始行
in_h_start=i*stride
# 取块的末行
in_h_end=in_h_start+kernel1_h
# 取块的起始列
in_w_start=j*stride
# 取块的末列
in_w_end=in_w_start+kernel1_w
# 这是取到的块
input_patch=input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]
# 定义一个空列表output_cal存储数据
output_cal = []
for k in range(input_tensor_channels):
# k代表了通道数,因为每个通道都要进行卷积运算,
# 因此需要先得到单通道的计算效果,然后把它们储存在列表中
output_channel=(input_patch[k]*kernel1[k]).sum()
#print('input_patch[',k,']=',input_patch[k])
#print('output_patch[', k, ']=', kernel1[k])
# 计算结果储存在列表中
output_cal.append(output_channel)
# 把通道效果叠加后,保存到卷积输出矩阵
# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算
# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中
# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值
# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵
# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达
output_cal_tensor[i, j] = sum(output_cal)
#print('output_cal_tensor[', i, j, ']=', output_cal_tensor[i, j])
print('output=',output_cal_tensor)
# 扩充原始矩阵,周围补一圈0
padded_h=input_tensor_h+2*stride
padded_w=input_tensor_w+2*stride
padded_input_tensor = torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)
padded_input_tensor[:,stride:-stride,stride:-stride]=input_tensor
print(padded_input_tensor)
# 对扩充后的矩阵卷积运算
# 计算卷积矩阵大小
padded_out_h=(padded_h-kernel1_h)//stride+1
padded_out_w=(padded_w-kernel1_w)//stride+1
# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵
padded_input_cal_tensor=padded_input_tensor
# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵
padded_output_cal_tensor=torch.zeros((padded_out_h,padded_out_w),dtype=torch.float32)
for i in range(padded_out_h):
for j in range(padded_out_w):
# 对原始矩阵取块进行计算,这里定义了取块的起始行
in_h_start=i*stride
# 取块的末行
in_h_end=in_h_start+kernel1_h
# 取块的起始列
in_w_start=j*stride
# 取块的末列
in_w_end=in_w_start+kernel1_w
# 这是取到的块
input_patch=padded_input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]
# 定义一个空列表output_cal存储数据
output_cal = []
for k in range(input_tensor_channels):
# k代表了通道数,因为每个通道都要进行卷积运算,
# 因此需要先得到单通道的计算效果,然后把它们储存在列表中
output_channel=(input_patch[k]*kernel1[k]).sum()
#print('input_patch[',k,']=',input_patch[k])
#print('output_patch[', k, ']=', kernel1[k])
# 计算结果储存在列表中
output_cal.append(output_channel)
# 把通道效果叠加后,保存到卷积输出矩阵
# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算
# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中
# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值
# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵
# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达
padded_output_cal_tensor[i, j] = sum(output_cal)
#print('padded_output_cal_tensor[', i, j, ']=', padded_output_cal_tensor[i, j])
print('padded_output=',padded_output_cal_tensor)
计算效果:
input_tensor_channels= 3
input_tensor_h= 5
input_tensor_w= 5
kernel1_channels= 3
kernel1_h= 3
kernel1_w= 3
output= tensor([[-18., -18., -18.],
-18., -18., -18.\], \[-18., -18., -18.\]\]) tensor(\[\[\[ 0., 0., 0., 0., 0., 0., 0.\], \[ 0., 1., 2., 3., 4., 5., 0.\], \[ 0., 6., 7., 8., 9., 10., 0.\], \[ 0., 11., 12., 13., 14., 15., 0.\], \[ 0., 16., 17., 18., 19., 20., 0.\], \[ 0., 21., 22., 23., 24., 25., 0.\], \[ 0., 0., 0., 0., 0., 0., 0.\]\],
[[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 26., 27., 28., 29., 30., 0.],
[ 0., 31., 32., 33., 34., 35., 0.],
[ 0., 36., 37., 38., 39., 40., 0.],
[ 0., 41., 42., 43., 44., 45., 0.],
[ 0., 46., 47., 48., 49., 50., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0., 0., 0.],
[ 0., 51., 52., 53., 54., 55., 0.],
[ 0., 56., 57., 58., 59., 60., 0.],
[ 0., 61., 62., 63., 64., 65., 0.],
[ 0., 66., 67., 68., 69., 70., 0.],
[ 0., 71., 72., 73., 74., 75., 0.],
[ 0., 0., 0., 0., 0., 0., 0.]]])
padded_output= tensor([[-177., -12., -12., -12., 189.],
-288., -18., -18., -18., 306.\], \[-333., -18., -18., -18., 351.\], \[-378., -18., -18., -18., 396.\], \[-267., -12., -12., -12., 279.\]\]) ## 总结 学习了原始矩阵扩充后的多维度卷积核计算方法。