深度学习中卷积与互相关
1. 互相关运算 (cross-correlation)
- 输出位置
(m, n)
的值:
Y[m,n]=∑i=0Hk−1∑j=0Wk−1X[m+i,n+j]⋅K[i,j]Y[m,n] = \sum_{i=0}^{H_k-1} \sum_{j=0}^{W_k-1} X[m+i, n+j] \cdot K[i,j]Y[m,n]=i=0∑Hk−1j=0∑Wk−1X[m+i,n+j]⋅K[i,j] - 输出尺寸公式:
output_height=Hx−Hk+1\text{output\_height} = H_x - H_k + 1output_height=Hx−Hk+1
output_width=Wx−Wk+1\text{output\_width} = W_x - W_k + 1output_width=Wx−Wk+1
(其中 Hx,WxH_x, W_xHx,Wx 是输入尺寸,Hk,WkH_k, W_kHk,Wk 是卷积核尺寸) - $ X[m+i, n+j] $ 是输入图像的局部区域。
- $ K[i,j] $ 是核的对应位置元素。
2. 卷积运算 (convolution)
- 数学定义要求先翻转卷积核:
Kflipped[i,j]=K[Hk−1−i,Wk−1−j]K_{\text{flipped}}[i,j] = K[H_k-1-i, W_k-1-j]Kflipped[i,j]=K[Hk−1−i,Wk−1−j] - 卷积输出:
Y[m,n]=∑i=0Hk−1∑j=0Wk−1X[m+i,n+j]⋅Kflipped[i,j]Y[m,n] = \sum_{i=0}^{H_k-1} \sum_{j=0}^{W_k-1} X[m+i, n+j] \cdot K_{\text{flipped}}[i,j]Y[m,n]=i=0∑Hk−1j=0∑Wk−1X[m+i,n+j]⋅Kflipped[i,j] - 等价形式(直接使用原始核):
Y[m,n]=∑i=0Hk−1∑j=0Wk−1X[m+i,n+j]⋅K[Hk−1−i,Wk−1−j]Y[m,n] = \sum_{i=0}^{H_k-1} \sum_{j=0}^{W_k-1} X[m+i, n+j] \cdot K[H_k-1-i, W_k-1-j]Y[m,n]=i=0∑Hk−1j=0∑Wk−1X[m+i,n+j]⋅K[Hk−1−i,Wk−1−j]
3. 工作流程图示
卷积计算详细过程(结合公式)
输入数据:
输入 X: 卷积核 K: 翻转核 K_flipped:
[1, 2, 3] [0, 1] [3, 2]
[4, 5, 6] [2, 3] [1, 0]
[7, 8, 9]
卷积公式:
数学定义的卷积运算:
Y[m,n]=∑i=0Hk−1∑j=0Wk−1X[m+i,n+j]⋅Kflipped[i,j]Y[m,n] = \sum_{i=0}^{H_k-1} \sum_{j=0}^{W_k-1} X[m+i, n+j] \cdot K_{\text{flipped}}[i,j]Y[m,n]=i=0∑Hk−1j=0∑Wk−1X[m+i,n+j]⋅Kflipped[i,j]
其中:
- Hk=2H_k = 2Hk=2(核高度)
- Wk=2W_k = 2Wk=2(核宽度)
- 输出尺寸:(3−2+1)×(3−2+1)=2×2(3-2+1) \times (3-2+1) = 2 \times 2(3−2+1)×(3−2+1)=2×2
计算步骤:
-
位置 (0,0) 的计算:
- 输入窗口:
[1, 2] [4, 5]
- 翻转核:
[3, 2] [1, 0]
- 计算过程:
1*3 = 3 2*2 = 4 4*1 = 4 5*0 = 0 --------- 总和 = 3 + 4 + 4 + 0 = 11
- 公式表示:
Y[0,0]=∑i=01∑j=01X[i,j]⋅Kflipped[i,j]Y[0,0] = \sum_{i=0}^{1}\sum_{j=0}^{1} X[i,j] \cdot K_{\text{flipped}}[i,j]Y[0,0]=i=0∑1j=0∑1X[i,j]⋅Kflipped[i,j]
- 输入窗口:
-
位置 (0,1) 的计算:
- 输入窗口:
[2, 3] [5, 6]
- 翻转核:
[3, 2] [1, 0]
- 计算过程:
2*3 = 6 3*2 = 6 5*1 = 5 6*0 = 0 --------- 总和 = 6 + 6 + 5 + 0 = 17
- 输入窗口:
-
位置 (1,0) 的计算:
- 输入窗口:
[4, 5] [7, 8]
- 翻转核:
[3, 2] [1, 0]
- 计算过程:
4*3 = 12 5*2 = 10 7*1 = 7 8*0 = 0 --------- 总和 = 12 + 10 + 7 + 0 = 29
- 输入窗口:
-
位置 (1,1) 的计算:
- 输入窗口:
[5, 6] [8, 9]
- 翻转核:
[3, 2] [1, 0]
- 计算过程:
5*3 = 15 6*2 = 12 8*1 = 8 9*0 = 0 --------- 总和 = 15 + 12 + 8 + 0 = 35
- 输入窗口:
最终输出:
Y = [[11, 17],[29, 35]]
图解计算过程:
输入 X 和滑动窗口:
[1, 2, 3] [1,2] [2,3]
[4, 5, 6] → [4,5] [5,6] → 输出 Y[0,0] 和 Y[0,1]
[7, 8, 9] [4,5] [5,6] [7,8] [8,9] → 输出 Y[1,0] 和 Y[1,1]每个窗口与翻转核进行元素乘加:
[1,2] ⊙ [3,2] = 1*3 + 2*2 + 4*1 + 5*0 = 11
[4,5] [1,0]
关键点:数学卷积需要先翻转卷积核(180°旋转),然后执行互相关运算(滑动点积)。深度学习框架通常省略翻转步骤,直接进行互相关操作。
code
import torch
import numpy as np# 手动实现的卷积函数
def flip_kernel1(K):"""180° 翻转卷积核"""# 上下翻转K_ud = K[::-1]# 左右翻转K_flipped = [row[::-1] for row in K_ud]return K_flippeddef flip_kernel(K):"""180° 翻转卷积核(完全基础实现)"""# 1. 获取卷积核尺寸height = len(K) # 总行数width = len(K[0]) # 总列数# 2. 创建空的结果矩阵(尺寸相同)K_flipped = []# 3. 逐行处理(从最后一行到第一行)for row_index in range(height):# 创建新行new_row = []# 4. 逐列处理(从最后一列到第一列)for col_index in range(width):# 计算翻转后的位置flipped_row = height - 1 - row_indexflipped_col = width - 1 - col_index# 5. 获取原始核对应位置的元素original_value = K[flipped_row][flipped_col]# 6. 添加到新行new_row.append(original_value)# 7. 将完整新行添加到结果矩阵K_flipped.append(new_row)return K_flippeddef corr2d(X, K):h = len(K) # 核的高度 H_kw = len(K[0]) # 核的宽度 W_k# 输出矩阵的尺寸output_height = len(X) - h + 1output_width = len(X[0]) - w + 1Y = [[0 for _ in range(output_width)] for _ in range(output_height)]for m in range(output_height): # 外部循环:输出位置 mfor n in range(output_width): # 外部循环:输出位置 ncurrent_sum = 0.0for i in range(h): # 内部循环:卷积核行索引 ifor j in range(w): # 内部循环:卷积核列索引 jx_val = X[m + i][n + j] # 输入矩阵的对应位置 X[m+i, n+j]k_val = K[i][j] # 卷积核的对应位置 K[i, j]current_sum += x_val * k_val # 乘积累加Y[m][n] = current_sum # 存储结果 Y[m,n]return Ydef conv2d(X, K):"""二维卷积运算(先翻转核,再调用互相关)"""K_flipped = flip_kernel(K)return corr2d(X, K_flipped)# 构造输入数据和卷积核
X =[[0, 0, 0, 0, 0],[0, 1, 0, 2, 0],[0, 0, 0, 0, 0],[0, 3, 0, 4, 0],[0, 0, 0, 0, 0]]K = [[8, 7],[6, 5]]
# 手动实现的互相关结果
manual_result = corr2d(X, K)
manual_conv_result = conv2d(X, K)# 转换为 PyTorch 张量
X_tensor = torch.tensor(X, dtype=torch.float32).unsqueeze(0).unsqueeze(0) # [1, 1, 5, 5]
K_tensor = torch.tensor(K, dtype=torch.float32).unsqueeze(0).unsqueeze(0) # [1, 1, 3, 3]# 使用 PyTorch 的 Conv2d 进行卷积
conv_layer = torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(3, 3), stride=1, padding=0, bias=False)
conv_layer.weight.data = K_tensorwith torch.no_grad():pytorch_result = conv_layer(X_tensor).squeeze().tolist()# 打印结果对比
print("手动实现卷积结果:")
for row in manual_conv_result:print(row)print("手动实现互相关结果:")
for row in manual_result:print(row)print("\nPyTorch 卷积结果:")
for row in pytorch_result:print(row)# 计算误差
manual_flat = [item for row in manual_result for item in row]
pytorch_flat = [item for row in pytorch_result for item in row]error = [abs(m - p) for m, p in zip(manual_flat, pytorch_flat)]
max_error = max(error)
mean_error = sum(error) / len(error)print("\n误差分析:")
print(f"最大误差:{max_error:.6f}")
print(f"平均误差:{mean_error:.6f}")
结果:
手动实现卷积结果:
[8.0, 7.0, 16.0, 14.0]
[6.0, 5.0, 12.0, 10.0]
[24.0, 21.0, 32.0, 28.0]
[18.0, 15.0, 24.0, 20.0]
手动实现互相关结果:
[5.0, 6.0, 10.0, 12.0]
[7.0, 8.0, 14.0, 16.0]
[15.0, 18.0, 20.0, 24.0]
[21.0, 24.0, 28.0, 32.0]PyTorch 卷积结果:
[5.0, 6.0, 10.0, 12.0]
[7.0, 8.0, 14.0, 16.0]
[15.0, 18.0, 20.0, 24.0]
[21.0, 24.0, 28.0, 32.0]误差分析:
最大误差:0.000000
平均误差:0.000000