CNN计算|矩阵扩充方法变化和卷积核移动步长变化

引言

前序学习进程中,已经对原始矩阵扩充后的多维度卷积核计算进行了初步探索。

不过在这里使用了比较"偷懒"地做法,把卷积核移动步长stride参数直接借用来扩充矩阵。

为了更加明朗地理解代码,首先需要定义单独的参数来完成矩阵扩充的任务;然后尝试修改卷积核步长,获取新的卷积计算效果。

定义矩阵扩充参数padding

定义矩阵扩充参数padding相对简单,定义一个常量即可:

python 复制代码
padding=1
padded_h=input_tensor_h+2*padding
padded_w=input_tensor_w+2*padding
padded_input_tensor = torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)
padded_input_tensor[:,padding:-padding,padding:-padding]=input_tensor
print(padded_input_tensor)

这里就是最简单的定义方法,仅需新增一个参数padding,此时代码运行效果和单纯使用stride一样。

对应的完整代码为:

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
padding=1
padded_h=input_tensor_h+2*padding
padded_w=input_tensor_w+2*padding
padded_input_tensor = torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)
padded_input_tensor[:,padding:-padding,padding:-padding]=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)

更新卷积核步长参数stride

卷积核步长参数stride代表了卷积核的作用范围,毕竟移动两步相对移动一步,至少有一列数据的计算次数会变少,为此我们直接看一下计算效果。

首先是移动一步,完整代码与上一节一致。

计算效果为:

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

原始矩阵5X5,扩充后7X7,卷积核3X3,算下来卷积运算一共5步,所以计算结果是5X5。

如果设置stride=2,此时的卷积运算只会有3步,计算结果应该是3X3。

完整代码为:

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)

# 通过循环自动计算卷积值
# 定义步长为2
stride=2
# 定义卷积运算后的输出矩阵大小
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
padding=1
padded_h=input_tensor_h+2*padding
padded_w=input_tensor_w+2*padding
padded_input_tensor = torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)
padded_input_tensor[:,padding:-padding,padding:-padding]=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)

对应的计算效果为:

padded_output= tensor([[-177., -12., 189.],

-333., -18., 351.\], \[-267., -12., 279.\]\])

如果想改为其它参数,大家可以随意尝试。

总结

对CNN计算中,矩阵扩充方法变化和卷积核移动步长变化进行了测试。

相关推荐
Sui_Network2 小时前
BitGo 通过 LayerZero 将原生 WBTC 引入 Sui
大数据·人工智能·科技·去中心化·区块链
IT_陈寒2 小时前
Redis性能提升50%的7个关键配置:从慢查询优化到内存碎片整理实战指南
前端·人工智能·后端
汽车仪器仪表相关领域2 小时前
SCG-1 增压 + 空燃比二合一仪表:涡轮改装的 “空间杀手” 与 “安全保镖”
大数据·服务器·人工智能·功能测试·安全·汽车·可用性测试
生成论实验室2 小时前
生成论入门十讲 · 第九讲生成的创造——设计学与人工智能
人工智能·科技·神经网络·信息与通信·几何学
程序员岳焱2 小时前
Java 调用 DeepSeek API 的 8 个高频坑
java·人工智能·后端
热点速递2 小时前
机器人“极限挑战”登陆香港——四足机器狗夺冠,展示具身智能新突破!
人工智能·机器人·业界资讯
饕餮怪程序猿2 小时前
寻找数据中的“真相”:零基础入门关键特征筛选方法
人工智能·特征工程
oil欧哟3 小时前
产品图质感提升指南:光影、材质、构图的底层逻辑与 AI 优化方案
人工智能·ai作画·材质·opencreator
ARM+FPGA+AI工业主板定制专家3 小时前
基于JETSON/RK3588+FPGA+AI商用自动割草机器人方案
人工智能·目标检测·计算机视觉·fpga开发·机器人