深度可分离卷积是一种高效的卷积操作,它将传统的卷积操作分解为两个部分:深度卷积和逐点卷积。
深度卷积涉及对输入特征图的每个通道独立应用单个卷积核。这与传统卷积不同,后者在所有输入通道上应用卷积核。深度卷积的输出是一组分离的特征图,每个通道一个。
逐点卷积是一个 1x1 的卷积,用于将深度卷积的输出通道组合和混合。这可以看作是在深度卷积生成的每个像素点上应用一个全连接层。
深度可分离卷积的主要优势在于它大幅减少了模型的参数数量和计算需求。深度卷积独立处理每个输入通道,而逐点卷积负责组合这些通道的特征,这样的分解方式比标准卷积更加高效。
我们考虑一个 4x4x2 的输入特征图,进行深度卷积和逐点卷积。
输入特征图(两个通道):
通道 1 : [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ] , 通道 2 : [ 0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6 ] \text{通道 1}: \begin{bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \\ \end{bmatrix}, \text{通道 2}: \begin{bmatrix} 0 & 1 & 2 & 3 \\ 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 3 & 4 & 5 & 6 \\ \end{bmatrix} 通道 1: 15913261014371115481216 ,通道 2: 0123123423453456
卷积核(每个通道):
[ − 1 0 1 − 2 0 2 − 1 0 1 ] \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{bmatrix} −1−2−1000121
深度卷积是独立地在每个通道上应用卷积核。考虑通道 1,不使用填充,步长为 1,计算得到的输出特征图(只计算左上角的第一个元素)为:
( − 1 × 1 ) + ( 0 × 2 ) + ( 1 × 3 ) + ( − 2 × 5 ) + ( 0 × 6 ) + ( 2 × 7 ) + ( − 1 × 9 ) + ( 0 × 10 ) + ( 1 × 11 ) = − 1 + 3 − 10 + 14 − 9 + 11 = 8 \begin{aligned} & (-1 \times 1) + (0 \times 2) + (1 \times 3) \\ & + (-2 \times 5) + (0 \times 6) + (2 \times 7) \\ & + (-1 \times 9) + (0 \times 10) + (1 \times 11) \\ & = -1 + 3 - 10 + 14 - 9 + 11 = 8 \end{aligned} (−1×1)+(0×2)+(1×3)+(−2×5)+(0×6)+(2×7)+(−1×9)+(0×10)+(1×11)=−1+3−10+14−9+11=8
重复这个过程以计算其他元素,然后对通道 2 做相同的操作。
假设我们有两个 1x1 卷积核,用于逐点卷积。
逐点卷积的计算是在深度卷积的每个通道上应用 1x1 卷积核,并将结果相加。每个 1x1 卷积核应用于所有通道的同一位置,产生新的特征图。例如,使用第一个 1x1 卷积核计算左上角第一个元素的输出(假设通道 2 的相应输出也是 8):
( 1 × 通道 1 的左上角元素 ) + ( 1 × 通道 2 的左上角元素 ) = ( 1 × 8 ) + ( 1 × 8 ) = 8 + 8 = 16 \begin{aligned} & (1 \times \text{通道 1 的左上角元素}) + (1 \times \text{通道 2 的左上角元素}) \\ & = (1 \times 8) + (1 \times 8) \\ & = 8 + 8 = 16 \end{aligned} (1×通道 1 的左上角元素)+(1×通道 2 的左上角元素)=(1×8)+(1×8)=8+8=16
使用第二个 1x1 卷积核进行相同的计算:
( − 1 × 通道 1 的左上角元素 ) + ( − 1 × 通道 2 的左上角元素 ) = ( − 1 × 8 ) + ( − 1 × 8 ) = − 8 − 8 = − 16 \begin{aligned} & (-1 \times \text{通道 1 的左上角元素}) + (-1 \times \text{通道 2 的左上角元素}) \\ & = (-1 \times 8) + (-1 \times 8) \\ & = -8 - 8 = -16 \end{aligned} (−1×通道 1 的左上角元素)+(−1×通道 2 的左上角元素)=(−1×8)+(−1×8)=−8−8=−16
import numpy as np
def depthwise_conv2d(input, kernel):
"""深度卷积"""
kernel_height, kernel_width, channels = kernel.shape
input_height, input_width, _ = input.shape
# 计算输出特征图的尺寸
output_height = input_height - kernel_height + 1
output_width = input_width - kernel_width + 1
# 初始化输出特征图
output = np.zeros((output_height, output_width, channels))
# 深度卷积
for c in range(channels):
for i in range(output_height):
for j in range(output_width):
output[i, j, c] = np.sum(input[i:i+kernel_height, j:j+kernel_width, c] * kernel[:, :, c])
return output
def pointwise_conv2d(input, kernel):
"""逐点卷积"""
_, _, channels = input.shape
num_kernels = kernel.shape[0]
# 计算输出特征图的尺寸
output_height, output_width, _ = input.shape
# 初始化输出特征图
output = np.zeros((output_height, output_width, num_kernels))
# 逐点卷积
for n in range(num_kernels):
for c in range(channels):
output[:, :, n] += input[:, :, c] * kernel[n, c]
return output
# 输入特征图
input_feature_map = np.array([
[[1, 0], [2, 1], [3, 2], [4, 3]],
[[5, 1], [6, 2], [7, 3], [8, 4]],
[[9, 2], [10, 3], [11, 4], [12, 5]],
[[13, 3], [14, 4], [15, 5], [16, 6]]
])
# 深度卷积核
depthwise_kernel = np.array([
[[-1, -1], [0, 0], [1, 1]],
[[-2, -2], [0, 0], [2, 2]],
[[-1, -1], [0, 0], [1, 1]]
])
# 逐点卷积核
pointwise_kernel = np.array([
[1, 1],
[-1, -1]
])
# 深度卷积
depthwise_output = depthwise_conv2d(input_feature_map, depthwise_kernel)
print("深度卷积输出:\n", depthwise_output)
# 逐点卷积
pointwise_output = pointwise_conv2d(depthwise_output, pointwise_kernel)
print("逐点卷积输出:\n", pointwise_output)