卷积
机器学习中的"局部感知"艺术,图像处理与深度学习的魔法滤镜
- 引言
- 什么是卷积?
- 一维卷积:从简单开始
- 二维卷积:图像处理的核心
- 卷积的数学原理
- 卷积的重要性质
- 填充(Padding)和步长(Stride)
- 卷积在深度学习中的应用
- 卷积的计算复杂度
- 不同类型的卷积
- 卷积的应用场景
- 总结:卷积的核心思想
引言
想象一下,你正在用PS给照片加滤镜:点击一个按钮,照片瞬间变得更清晰、更有艺术感,或者边缘更加突出。这背后的魔法,其实就是卷积在默默工作!
卷积是信号处理、图像处理和深度学习中最重要的操作之一。它看似复杂,但本质上就是一种**“滑动窗口"的模式匹配游戏**。
什么是卷积?
卷积(Convolution)是一种数学运算,它将两个函数结合起来产生第三个函数。在图像处理中,卷积就是用一个**小矩阵(卷积核/滤波器)在大矩阵(图像)**上滑动,进行局部计算的过程。
生活中的卷积类比
用印章盖章 🖨️
- 印章 = 卷积核
- 纸张 = 原始图像
- 在纸上移动印章,每个位置都盖一下 = 卷积操作
- 最终的图案 = 卷积结果
擦窗户 🪟
- 抹布 = 卷积核
- 窗户 = 原始图像
- 用抹布在窗户上按固定方式擦拭 = 卷积操作
- 擦干净的窗户 = 处理后的图像
调制饮料 🥤
- 调料包 = 卷积核
- 原料 = 输入信号
- 按比例混合 = 卷积运算
- 最终饮料 = 输出结果
一维卷积:从简单开始
让我们从最简单的一维卷积开始理解:
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
def conv1d_manual(signal, kernel):
"""手动实现一维卷积"""
signal_len = len(signal)
kernel_len = len(kernel)
output_len = signal_len - kernel_len + 1
result = []
for i in range(output_len):
# 提取信号片段
segment = signal[i:i + kernel_len]
# 元素相乘再求和
conv_value = np.sum(segment * kernel)
result.append(conv_value)
return np.array(result)
# 示例:信号平滑
np.random.seed(42)
# 原始信号(带噪声)
t = np.linspace(0, 10, 100)
clean_signal = np.sin(t) + 0.5 * np.sin(3*t)
noise = np.random.normal(0, 0.3, len(t))
noisy_signal = clean_signal + noise
# 平滑卷积核(移动平均)
smooth_kernel = np.ones(5) / 5 # 5点平均
# 应用卷积
smoothed_signal = conv1d_manual(noisy_signal, smooth_kernel)
# 可视化
plt.figure(figsize=(15, 10))
# 原始信号
plt.subplot(3, 2, 1)
plt.plot(t, clean_signal, 'g-', label='纯净信号', linewidth=2)
plt.plot(t, noisy_signal, 'b-', alpha=0.7, label='噪声信号')
plt.title('原始信号对比')
plt.legend()
plt.grid(True, alpha=0.3)
# 卷积核
plt.subplot(3, 2, 2)
plt.stem(range(len(smooth_kernel)), smooth_kernel, basefmt=' ')
plt.title('平滑卷积核(5点平均)')
plt.xlabel('索引')
plt.ylabel('权重')
plt.grid(True, alpha=0.3)
# 卷积过程演示
plt.subplot(3, 2, 3)
# 显示卷积操作的一个具体步骤
pos = 10 # 选择一个位置进行演示
segment = noisy_signal[pos:pos+5]
plt.stem(range(pos, pos+5), segment, basefmt=' ', label='信号片段')
plt.stem(range(pos, pos+5), smooth_kernel * max(segment), basefmt=' ', label='卷积核×max')
plt.title(f'卷积操作演示(位置{pos})')
plt.legend()
plt.grid(True, alpha=0.3)
# 卷积结果
plt.subplot(3, 2, 4)
result_t = t[2:-2] # 调整时间轴(卷积后长度变短)
plt.plot(t, noisy_signal, 'b-', alpha=0.5, label='原始噪声信号')
plt.plot(result_t, smoothed_signal, 'r-', linewidth=2, label='卷积平滑后')
plt.plot(t, clean_signal, 'g--', alpha=0.7, label='理想信号')
plt.title('卷积平滑效果')
plt.legend()
plt.grid(True, alpha=0.3)
# 边缘检测卷积核
edge_kernel = np.array([-1, 0, 1]) # 简单边缘检测
edges = conv1d_manual(noisy_signal, edge_kernel)
plt.subplot(3, 2, 5)
plt.stem(range(len(edge_kernel)), edge_kernel, basefmt=' ')
plt.title('边缘检测卷积核')
plt.grid(True, alpha=0.3)
plt.subplot(3, 2, 6)
edge_t = t[1:-1]
plt.plot(edge_t, edges, 'purple', linewidth=2, label='边缘检测结果')
plt.title('边缘检测效果')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("一维卷积示例:")
print(f"原始信号长度: {len(noisy_signal)}")
print(f"卷积核长度: {len(smooth_kernel)}")
print(f"卷积结果长度: {len(smoothed_signal)}")
print(f"长度变化: {len(noisy_signal)} - {len(smooth_kernel)} + 1 = {len(smoothed_signal)}")
二维卷积:图像处理的核心
二维卷积是图像处理的核心操作:
def conv2d_manual(image, kernel):
"""手动实现二维卷积"""
img_h, img_w = image.shape
ker_h, ker_w = kernel.shape
# 输出尺寸
out_h = img_h - ker_h + 1
out_w = img_w - ker_w + 1
result = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
# 提取图像块
patch = image[i:i+ker_h, j:j+ker_w]
# 卷积运算
result[i, j] = np.sum(patch * kernel)
return result
# 创建示例图像
def create_test_image():
"""创建测试图像"""
img = np.zeros((50, 50))
# 添加一些形状
img[10:15, 10:40] = 1 # 水平线
img[10:40, 10:15] = 1 # 垂直线
img[25:35, 25:35] = 1 # 正方形
# 添加噪声
noise = np.random.normal(0, 0.1, img.shape)
img = img + noise
return img
# 定义各种卷积核
kernels = {
'恒等': np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]]),
'模糊': np.ones((3, 3)) / 9,
'边缘检测': np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]]),
'水平边缘': np.array([[-1, -1, -1],
[ 0, 0, 0],
[ 1, 1, 1]]),
'垂直边缘': np.array([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]]),
'锐化': np.array([[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]]),
}
# 创建测试图像
test_image = create_test_image()
# 应用不同的卷积核
fig, axes = plt.subplots(3, 3, figsize=(15, 15))
axes = axes.flatten()
# 显示原始图像
axes[0].imshow(test_image, cmap='gray')
axes[0].set_title('原始图像')
axes[0].axis('off')
# 应用各种卷积核
for i, (name, kernel) in enumerate(kernels.items(), 1):
result = conv2d_manual(test_image, kernel)
axes[i].imshow(result, cmap='gray')
axes[i].set_title(f'{name}卷积结果')
axes[i].axis('off')
# 在子图下方显示卷积核
if i < len(axes):
print(f"{name}卷积核:")
print(kernel)
print()
# 隐藏多余的子图
for i in range(len(kernels) + 1, len(axes)):
axes[i].axis('off')
plt.tight_layout()
plt.show()
卷积的数学原理
卷积的数学定义
对于连续函数:
(f * g)(t) = ∫ f(τ)g(t-τ)dτ
对于离散信号:
(f * g)[n] = Σ f[m]g[n-m]
图像卷积的具体计算
def demonstrate_convolution_step_by_step():
"""逐步演示卷积计算过程"""
# 简单的3x3图像
image = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# 3x3卷积核
kernel = np.array([
[1, 0, -1],
[1, 0, -1],
[1, 0, -1]
])
print("图像:")
print(image)
print("\n卷积核:")
print(kernel)
# 由于3x3图像和3x3卷积核,结果是1x1
result = 0
calculation_steps = []
print("\n逐步计算过程:")
print("位置 | 图像值 | 核值 | 乘积")
print("-" * 30)
for i in range(3):
for j in range(3):
img_val = image[i, j]
ker_val = kernel[i, j]
product = img_val * ker_val
result += product
calculation_steps.append(f"({i},{j}) | {img_val} | {ker_val} | {product}")
print(f"({i},{j}) | {img_val} | {ker_val} | {product}")
print(f"\n最终结果: {result}")
# 可视化计算过程
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
# 原始图像
im1 = axes[0].imshow(image, cmap='Blues')
axes[0].set_title('原始图像')
for i in range(3):
for j in range(3):
axes[0].text(j, i, str(image[i, j]), ha='center', va='center', fontsize=12, fontweight='bold')
# 卷积核
im2 = axes[1].imshow(kernel, cmap='Reds')
axes[1].set_title('卷积核')
for i in range(3):
for j in range(3):
axes[1].text(j, i, str(kernel[i, j]), ha='center', va='center', fontsize=12, fontweight='bold')
# 元素相乘
product_matrix = image * kernel
im3 = axes[2].imshow(product_matrix, cmap='Greens')
axes[2].set_title('元素相乘')
for i in range(3):
for j in range(3):
axes[2].text(j, i, str(product_matrix[i, j]), ha='center', va='center', fontsize=12, fontweight='bold')
# 最终结果
result_matrix = np.array([[result]])
im4 = axes[3].imshow(result_matrix, cmap='Purples')
axes[3].set_title('求和结果')
axes[3].text(0, 0, str(result), ha='center', va='center', fontsize=16, fontweight='bold')
for ax in axes:
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.show()
demonstrate_convolution_step_by_step()
卷积的重要性质
1. 交换律
def demonstrate_convolution_properties():
"""演示卷积的数学性质"""
# 创建测试数据
signal1 = np.array([1, 2, 3, 4, 5])
signal2 = np.array([1, 1, 1])
# 交换律: f * g = g * f
conv1 = np.convolve(signal1, signal2, mode='valid')
conv2 = np.convolve(signal2, signal1, mode='valid')
print("卷积的交换律演示:")
print(f"signal1 * signal2 = {conv1}")
print(f"signal2 * signal1 = {conv2}")
print(f"结果相等: {np.array_equal(conv1, conv2)}")
# 结合律演示
signal3 = np.array([0.5, 0.5])
# (f * g) * h
temp1 = np.convolve(signal1, signal2, mode='full')
result1 = np.convolve(temp1, signal3, mode='valid')
# f * (g * h)
temp2 = np.convolve(signal2, signal3, mode='full')
result2 = np.convolve(signal1, temp2, mode='valid')
print(f"\n结合律演示:")
print(f"(f * g) * h = {result1}")
print(f"f * (g * h) = {result2}")
print(f"结果相等: {np.allclose(result1, result2)}")
# 分配律演示
signal4 = np.array([1, 0, -1])
# f * (g + h)
sum_signals = signal2 + signal4
result3 = np.convolve(signal1, sum_signals, mode='valid')
# f * g + f * h
conv_g = np.convolve(signal1, signal2, mode='valid')
conv_h = np.convolve(signal1, signal4, mode='valid')
result4 = conv_g + conv_h
print(f"\n分配律演示:")
print(f"f * (g + h) = {result3}")
print(f"f * g + f * h = {result4}")
print(f"结果相等: {np.array_equal(result3, result4)}")
demonstrate_convolution_properties()
填充(Padding)和步长(Stride)
填充的作用
def demonstrate_padding_and_stride():
"""演示填充和步长的效果"""
# 创建测试图像
image = np.random.rand(6, 6)
kernel = np.ones((3, 3)) / 9 # 3x3平均池化核
def conv2d_with_padding_stride(img, ker, padding=0, stride=1):
"""带填充和步长的二维卷积"""
# 添加填充
if padding > 0:
img_padded = np.pad(img, padding, mode='constant', constant_values=0)
else:
img_padded = img
img_h, img_w = img_padded.shape
ker_h, ker_w = ker.shape
# 计算输出尺寸
out_h = (img_h - ker_h) // stride + 1
out_w = (img_w - ker_w) // stride + 1
result = np.zeros((out_h, out_w))
for i in range(0, out_h * stride, stride):
for j in range(0, out_w * stride, stride):
if i + ker_h <= img_h and j + ker_w <= img_w:
patch = img_padded[i:i+ker_h, j:j+ker_w]
result[i//stride, j//stride] = np.sum(patch * ker)
return result, img_padded
# 不同参数的卷积
configs = [
(0, 1, "无填充,步长1"),
(1, 1, "填充1,步长1"),
(0, 2, "无填充,步长2"),
(1, 2, "填充1,步长2")
]
fig, axes = plt.subplots(2, 5, figsize=(20, 8))
# 原始图像
axes[0, 0].imshow(image, cmap='viridis')
axes[0, 0].set_title('原始图像 (6×6)')
axes[0, 0].axis('off')
axes[1, 0].imshow(kernel, cmap='Reds')
axes[1, 0].set_title('卷积核 (3×3)')
axes[1, 0].axis('off')
for i, (padding, stride, title) in enumerate(configs, 1):
result, padded_img = conv2d_with_padding_stride(image, kernel, padding, stride)
# 显示填充后的图像
axes[0, i].imshow(padded_img, cmap='viridis')
axes[0, i].set_title(f'填充后图像 ({padded_img.shape[0]}×{padded_img.shape[1]})')
axes[0, i].axis('off')
# 显示卷积结果
axes[1, i].imshow(result, cmap='plasma')
axes[1, i].set_title(f'{title}\n输出: {result.shape[0]}×{result.shape[1]}')
axes[1, i].axis('off')
print(f"{title}:")
print(f" 输入: {image.shape} -> 填充后: {padded_img.shape} -> 输出: {result.shape}")
# 计算输出尺寸公式验证
expected_h = (padded_img.shape[0] - kernel.shape[0]) // stride + 1
expected_w = (padded_img.shape[1] - kernel.shape[1]) // stride + 1
print(f" 公式计算: ({padded_img.shape[0]} - {kernel.shape[0]}) // {stride} + 1 = {expected_h}")
print(f" 实际输出: {result.shape}")
print()
plt.tight_layout()
plt.show()
demonstrate_padding_and_stride()
卷积在深度学习中的应用
1. 卷积神经网络基础
def demonstrate_cnn_basics():
"""演示CNN中卷积的应用"""
# 模拟RGB图像
np.random.seed(42)
rgb_image = np.random.rand(32, 32, 3) # 32x32x3的RGB图像
# 定义多个卷积核(特征检测器)
kernels = {
'水平边缘': np.array([[[1, 1, 1],
[0, 0, 0],
[-1, -1, -1]]]),
'垂直边缘': np.array([[[1, 0, -1],
[1, 0, -1],
[1, 0, -1]]]),
'对角边缘': np.array([[[1, 1, 0],
[1, 0, -1],
[0, -1, -1]]]),
}
def apply_3d_convolution(image, kernel):
"""应用3D卷积(多通道)"""
h, w, c = image.shape
kh, kw = kernel.shape[1], kernel.shape[2]
output_h = h - kh + 1
output_w = w - kw + 1
result = np.zeros((output_h, output_w))
for i in range(output_h):
for j in range(output_w):
# 对所有通道求和
conv_sum = 0
for ch in range(c):
patch = image[i:i+kh, j:j+kw, ch]
conv_sum += np.sum(patch * kernel[0]) # 假设所有通道用同一个核
result[i, j] = conv_sum
return result
# 可视化
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
# 显示原始图像的各个通道
for i in range(3):
axes[0, i].imshow(rgb_image[:, :, i], cmap='gray')
axes[0, i].set_title(f'通道{i+1}')
axes[0, i].axis('off')
axes[0, 3].imshow(rgb_image)
axes[0, 3].set_title('RGB图像')
axes[0, 3].axis('off')
# 应用不同的卷积核
for i, (name, kernel) in enumerate(kernels.items()):
result = apply_3d_convolution(rgb_image, kernel)
axes[1, i].imshow(result, cmap='gray')
axes[1, i].set_title(f'{name}检测')
axes[1, i].axis('off')
axes[1, 3].axis('off')
plt.tight_layout()
plt.show()
print("CNN中的卷积特点:")
print("1. 多通道输入(RGB图像有3个通道)")
print("2. 多个卷积核(检测不同特征)")
print("3. 参数共享(同一个核在整个图像上滑动)")
print("4. 局部连接(每个神经元只看局部区域)")
demonstrate_cnn_basics()
2. 特征图可视化
def visualize_feature_maps():
"""可视化卷积操作产生的特征图"""
# 创建一个更复杂的测试图像
def create_complex_image():
img = np.zeros((64, 64))
# 添加各种形状
# 水平线
img[15:17, 10:50] = 1
# 垂直线
img[10:50, 15:17] = 1
# 对角线
for i in range(20):
img[40+i, 10+i] = 1
# 圆形
center = (45, 45)
for i in range(64):
for j in range(64):
if (i-center[0])**2 + (j-center[1])**2 <= 36:
img[i, j] = 0.7
return img
image = create_complex_image()
# 定义更多类型的卷积核
feature_detectors = {
'水平边缘': np.array([[ 1, 2, 1],
[ 0, 0, 0],
[-1, -2, -1]]),
'垂直边缘': np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]]),
'左对角': np.array([[ 0, 1, 2],
[-1, 0, 1],
[-2, -1, 0]]),
'右对角': np.array([[ 2, 1, 0],
[ 1, 0, -1],
[ 0, -1, -2]]),
'模糊': np.ones((5, 5)) / 25,
'锐化': np.array([[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]]),
}
# 应用所有卷积核
feature_maps = {}
for name, kernel in feature_detectors.items():
feature_maps[name] = conv2d_manual(image, kernel)
# 可视化结果
fig, axes = plt.subplots(3, 3, figsize=(15, 15))
axes = axes.flatten()
# 原始图像
axes[0].imshow(image, cmap='gray')
axes[0].set_title('原始图像', fontsize=14)
axes[0].axis('off')
# 特征图
for i, (name, feature_map) in enumerate(feature_maps.items(), 1):
axes[i].imshow(feature_map, cmap='gray')
axes[i].set_title(f'{name}特征图', fontsize=14)
axes[i].axis('off')
# 隐藏多余的子图
for i in range(len(feature_maps) + 1, len(axes)):
axes[i].axis('off')
plt.tight_layout()
plt.show()
# 分析特征图的统计信息
print("特征图分析:")
print("-" * 50)
for name, feature_map in feature_maps.items():
print(f"{name}:")
print(f" 形状: {feature_map.shape}")
print(f" 最大值: {feature_map.max():.3f}")
print(f" 最小值: {feature_map.min():.3f}")
print(f" 平均值: {feature_map.mean():.3f}")
print(f" 标准差: {feature_map.std():.3f}")
print()
visualize_feature_maps()
卷积的计算复杂度
复杂度分析
def analyze_convolution_complexity():
"""分析卷积操作的计算复杂度"""
def calculate_operations(input_shape, kernel_shape, stride=1, padding=0):
"""计算卷积操作的乘法次数"""
if len(input_shape) == 2: # 2D卷积
h_in, w_in = input_shape
h_ker, w_ker = kernel_shape
# 输出尺寸
h_out = (h_in + 2*padding - h_ker) // stride + 1
w_out = (w_in + 2*padding - w_ker) // stride + 1
# 总操作次数
operations = h_out * w_out * h_ker * w_ker
elif len(input_shape) == 3: # 3D卷积(多通道)
h_in, w_in, c_in = input_shape
h_ker, w_ker = kernel_shape
h_out = (h_in + 2*padding - h_ker) // stride + 1
w_out = (w_in + 2*padding - w_ker) // stride + 1
operations = h_out * w_out * h_ker * w_ker * c_in
return operations, (h_out, w_out)
# 分析不同尺寸的复杂度
test_cases = [
((28, 28), (3, 3), "小图像+小核"),
((224, 224), (3, 3), "中等图像+小核"),
((224, 224), (7, 7), "中等图像+大核"),
((224, 224, 3), (3, 3), "RGB图像+小核"),
((224, 224, 64), (3, 3), "深层特征图+小核"),
]
print("卷积计算复杂度分析:")
print("=" * 70)
print(f"{'输入形状':<15} {'卷积核':<8} {'输出形状':<12} {'操作次数':<12} {'描述'}")
print("-" * 70)
for input_shape, kernel_shape, description in test_cases:
ops, output_shape = calculate_operations(input_shape, kernel_shape)
if len(input_shape) == 2:
output_str = f"{output_shape[0]}×{output_shape[1]}"
else:
output_str = f"{output_shape[0]}×{output_shape[1]}×1"
print(f"{str(input_shape):<15} {str(kernel_shape):<8} {output_str:<12} {ops:<12,} {description}")
# 比较不同优化策略的效果
print(f"\n优化策略对比(以224×224×64输入为例):")
print("-" * 50)
base_input = (224, 224, 64)
base_kernel = (3, 3)
base_ops, _ = calculate_operations(base_input, base_kernel)
# 策略1:增加步长
stride2_ops, stride2_out = calculate_operations(base_input, base_kernel, stride=2)
# 策略2:使用1×1卷积降维
conv1x1_ops, _ = calculate_operations(base_input, (1, 1)) # 降维到16通道
reduced_input = (224, 224, 16)
conv3x3_ops, _ = calculate_operations(reduced_input, base_kernel)
total_ops = conv1x1_ops * 16 + conv3x3_ops # 假设降到16通道
print(f"基础卷积: {base_ops:,} 次操作")
print(f"步长为2: {stride2_ops:,} 次操作 (减少 {(1-stride2_ops/base_ops)*100:.1f}%)")
print(f"1×1+3×3: {total_ops:,} 次操作 (减少 {(1-total_ops/base_ops)*100:.1f}%)")
# 可视化复杂度增长
sizes = [32, 64, 128, 224, 512]
operations = []
for size in sizes:
ops, _ = calculate_operations((size, size, 3), (3, 3))
operations.append(ops)
plt.figure(figsize=(10, 6))
plt.plot(sizes, operations, 'bo-', linewidth=2, markersize=8)
plt.xlabel('图像尺寸')
plt.ylabel('操作次数')
plt.title('卷积计算复杂度随图像尺寸变化')
plt.yscale('log')
plt.grid(True, alpha=0.3)
# 添加数据标签
for x, y in zip(sizes, operations):
plt.annotate(f'{y:,}', (x, y), textcoords="offset points",
xytext=(0,10), ha='center')
plt.tight_layout()
plt.show()
analyze_convolution_complexity()
不同类型的卷积
转置卷积(反卷积)
def demonstrate_transpose_convolution():
"""演示转置卷积(上采样)"""
def transpose_conv2d(input_matrix, kernel, stride=1):
"""简单的转置卷积实现"""
input_h, input_w = input_matrix.shape
kernel_h, kernel_w = kernel.shape
# 输出尺寸计算
output_h = (input_h - 1) * stride + kernel_h
output_w = (input_w - 1) * stride + kernel_w
# 初始化输出
output = np.zeros((output_h, output_w))
# 对输入的每个元素
for i in range(input_h):
for j in range(input_w):
# 计算在输出中的位置
start_i = i * stride
start_j = j * stride
end_i = start_i + kernel_h
end_j = start_j + kernel_w
# 累加贡献
output[start_i:end_i, start_j:end_j] += input_matrix[i, j] * kernel
return output
# 创建小的输入特征图
small_input = np.array([[1, 2],
[3, 4]])
# 定义卷积核
kernel = np.array([[1, 0.5],
[0.5, 0.25]])
# 应用转置卷积
upsampled = transpose_conv2d(small_input, kernel)
print("转置卷积演示:")
print("输入 (2×2):")
print(small_input)
print("\n卷积核 (2×2):")
print(kernel)
print("\n输出 (3×3):")
print(upsampled)
# 可视化过程
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
# 输入
im1 = axes[0].imshow(small_input, cmap='Blues')
axes[0].set_title('输入特征图 (2×2)')
for i in range(2):
for j in range(2):
axes[0].text(j, i, f'{small_input[i,j]}', ha='center', va='center',
fontsize=14, fontweight='bold')
# 卷积核
im2 = axes[1].imshow(kernel, cmap='Reds')
axes[1].set_title('卷积核 (2×2)')
for i in range(2):
for j in range(2):
axes[1].text(j, i, f'{kernel[i,j]}', ha='center', va='center',
fontsize=14, fontweight='bold')
# 输出
im3 = axes[2].imshow(upsampled, cmap='Greens')
axes[2].set_title('转置卷积输出 (3×3)')
for i in range(3):
for j in range(3):
axes[2].text(j, i, f'{upsampled[i,j]:.2f}', ha='center', va='center',
fontsize=12, fontweight='bold')
for ax in axes:
ax.set_xticks([])
ax.set_yticks([])
plt.tight_layout()
plt.show()
print(f"\n转置卷积的作用: 从 {small_input.shape} 上采样到 {upsampled.shape}")
demonstrate_transpose_convolution()
卷积的应用场景
1. 图像分类
def convolution_for_classification():
"""演示卷积在图像分类中的作用"""
# 创建模拟的手写数字
def create_digit_7():
digit = np.zeros((20, 20))
digit[2, 2:18] = 1 # 顶部横线
digit[3:10, 15:17] = 1 # 右上斜线
digit[8:15, 8:10] = 1 # 左下斜线
return digit
def create_digit_1():
digit = np.zeros((20, 20))
digit[2:18, 9:11] = 1 # 垂直线
digit[2:4, 7:9] = 1 # 顶部小帽
return digit
# 创建数字图像
digit_7 = create_digit_7()
digit_1 = create_digit_1()
# 设计特征检测器
detectors = {
'水平线检测': np.array([[ 1, 1, 1],
[ 0, 0, 0],
[-1, -1, -1]]),
'垂直线检测': np.array([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]]),
'斜线检测': np.array([[ 1, 0, -1],
[ 0, 0, 0],
[-1, 0, 1]]),
}
# 分析两个数字的特征
digits = {'数字7': digit_7, '数字1': digit_1}
fig, axes = plt.subplots(3, 4, figsize=(16, 12))
for col, (digit_name, digit_img) in enumerate(digits.items()):
# 显示原始数字
axes[0, col*2].imshow(digit_img, cmap='gray')
axes[0, col*2].set_title(f'{digit_name}')
axes[0, col*2].axis('off')
# 应用特征检测器
for row, (detector_name, detector) in enumerate(detectors.items()):
feature_map = conv2d_manual(digit_img, detector)
axes[row, col*2 + 1].imshow(feature_map, cmap='gray')
axes[row, col*2 + 1].set_title(f'{digit_name} - {detector_name}')
axes[row, col*2 + 1].axis('off')
# 计算特征强度(用于分类)
feature_strength = np.sum(np.abs(feature_map))
print(f"{digit_name} - {detector_name}: 特征强度 = {feature_strength:.2f}")
# 隐藏多余的子图
axes[0, 1].axis('off')
axes[0, 3].axis('off')
plt.tight_layout()
plt.show()
print("\n分类思路:")
print("通过不同特征检测器的响应强度,可以区分不同的数字")
print("数字7在水平线和斜线检测器上响应强,数字1在垂直线检测器上响应强")
convolution_for_classification()
总结:卷积的核心思想
卷积就像是一个智能的图像分析师:
🎯 核心概念
- 滑动窗口:用小窗口扫描大图像
- 模式匹配:检测特定的图像模式
- 特征提取:从原始数据中提取有用信息
- 参数共享:同一个检测器在整个图像上使用
🔍 工作原理
- 卷积核:定义要检测的模式
- 滑动计算:在输入上逐位置计算
- 特征图:保存检测结果
- 非线性:通过激活函数增加表达能力
💪 优势特点
- 平移不变性:无论特征在哪里,都能检测到
- 局部连接:只关注局部区域,减少参数
- 层次特征:从简单到复杂逐层提取
- 计算高效:参数共享大大减少计算量
🎪 应用领域
- 图像处理:边缘检测、模糊、锐化
- 计算机视觉:目标检测、图像分类
- 信号处理:滤波、降噪
- 深度学习:CNN的核心操作
🧠 记忆口诀
“小窗扫大图,模式来匹配,特征层层提,智能自学习”
卷积不仅仅是一个数学操作,更是让机器"看懂"世界的关键技术。从Instagram滤镜到自动驾驶,卷积无处不在,默默地让我们的数字世界变得更加智能!
作者: meimeitou