39、深度卷积神经网络图像分类:原理与实践

深度卷积神经网络图像分类:原理与实践

1. 卷积神经网络基础

卷积神经网络(CNNs)通常由多个卷积层和下采样层组成,最后连接一个或多个全连接层。用卷积层替代传统的全连接多层感知机(MLP),能显著减少网络中的权重(参数)数量,同时提升捕捉显著特征的能力。在处理图像数据时,相邻像素通常比远距离像素更相关。

下采样层,通常称为池化层,没有可学习的参数,如权重或偏置单元。而卷积层和全连接层在训练过程中会优化权重和偏置。

2. 离散卷积操作

离散卷积是CNN中的基本操作,理解其工作原理至关重要。以下将从一维离散卷积开始介绍。

2.1 一维离散卷积

对于两个向量 $x$ 和 $w$,离散卷积表示为 $\mathbf{y} = \mathbf{x} * \mathbf{w}$,其中 $x$ 是输入(有时称为信号),$w$ 是滤波器或内核。数学定义如下:
$\mathbf{y} = \mathbf{x} * \mathbf{w} \rightarrow y[i] = \sum_{k = -\infty}^{+\infty} x[i - k] w[k]$

这里公式存在两个问题需要澄清:一是从 $-\infty$ 到 $+\infty$ 的索引,二是 $x$ 的负索引。在机器学习应用中,我们处理的是有限特征向量,因此为了正确计算上述求和,假设 $x$ 和 $w$ 用零填充,这一过程称为零填充或简单填充,填充的零的数量在每侧用 $p$ 表示。

假设原始输入 $x$ 有 $n$ 个元素,滤波器 $w$ 有 $m$ 个元素($m \leq n$),填充后的向量 $\mathbf{x} p$ 大小为 $n + 2p$,计算离散卷积的实用公式变为:
$\mathbf{y} = \mathbf{x} * \mathbf{w} \rightarrow y[i] = \sum
{k = 0}^{m - 1} \mathbf{x}_p[i + m - k] w[k]$

此外,卷积还有一个超参数——步长 $s$,它表示滤波器每次移动的单元格数。步长必须是小于输入向量大小的正整数。

以下是一个一维卷积的简单实现代码:

import numpy as np

def conv1d(x, w, p=0, s=1):
    w_rot = np.array(w[::-1])
    x_padded = np.array(x)
    if p > 0:
        zero_pad = np.zeros(shape=p)
        x_padded = np.concatenate([zero_pad, 
                                   x_padded, 
                                   zero_pad])
    res = []
    for i in range(0, int(len(x)/s), s):
        res.append(np.sum(x_padded[i:i+w_rot.shape[0]] * 
                          w_rot))
    return np.array(res)

## Testing:
x = [1, 3, 2, 4, 5, 6, 1, 3]
w = [1, 0, 3, 1, 2]
print('Conv1d Implementation:', 
      conv1d(x, w, p=2, s=1))
print('NumPy Results:', 
      np.convolve(x, w, mode='same'))
2.2 填充输入以控制输出特征图大小

填充可以使用任意 $p \geq 0$ 的值,不同的 $p$ 值会导致边界单元格与中间单元格的处理方式不同。常见的填充模式有三种:
- 全填充(Full) :填充参数 $p = m - 1$,会增加输出的维度,在CNN架构中很少使用。
- 相同填充(Same) :确保输出向量与输入向量大小相同,填充参数 $p$ 根据滤波器大小和输入输出大小相同的要求计算。
- 有效填充(Valid) :$p = 0$,即不进行填充。

在CNN中,最常用的填充模式是相同填充,它能保留向量大小或输入图像的高度和宽度,便于设计网络架构。而有效填充在多层神经网络中会使张量体积大幅减小,可能对网络性能产生不利影响。

2.3 确定卷积输出大小

卷积的输出大小由滤波器在输入向量上的移动次数决定。假设输入向量大小为 $n$,滤波器大小为 $m$,填充为 $p$,步长为 $s$,则输出大小 $o$ 由以下公式确定:
$o = \lfloor \frac{n + 2p - m}{s} \rfloor + 1$

例如:
- 输入向量大小为10,卷积核大小为5,填充为2,步长为1时,输出大小为:
$n = 10, m = 5, p = 2, s = 1 \rightarrow o = \lfloor \frac{10 + 2 \times 2 - 5}{1} \rfloor + 1 = 10$
- 输入向量相同,卷积核大小为3,步长为2时,输出大小为:
$n = 10, m = 3, p = 2, s = 2 \rightarrow o = \lfloor \frac{10 + 2 \times 2 - 3}{2} \rfloor + 1 = 6$

3. 二维离散卷积

一维卷积的概念很容易扩展到二维。当处理二维输入矩阵 $\mathbf{X} {n_1 \times n_2}$ 和滤波器矩阵 $\mathbf{W} {m_1 \times m_2}$($m_1 \leq n_1$ 且 $m_2 \leq n_2$)时,二维卷积结果 $\mathbf{Y} = \mathbf{X} * \mathbf{W}$ 的数学定义如下:
$\mathbf{Y} = \mathbf{X} * \mathbf{W} \rightarrow Y[i, j] = \sum_{k_1 = -\infty}^{+\infty} \sum_{k_2 = -\infty}^{+\infty} X[i - k_1, j - k_2] W[k_1, k_2]$

同样,之前提到的零填充、旋转滤波器矩阵和使用步长等技术也适用于二维卷积。

以下是一个二维卷积的简单实现代码:

import numpy as np
import scipy.signal

def conv2d(X, W, p=(0, 0), s=(1, 1)):
    W_rot = np.array(W)[::-1,::-1]
    X_orig = np.array(X)
    n1 = X_orig.shape[0] + 2*p[0]
    n2 = X_orig.shape[1] + 2*p[1]
    X_padded = np.zeros(shape=(n1, n2))
    X_padded[p[0]:p[0]+X_orig.shape[0], 
             p[1]:p[1]+X_orig.shape[1]] = X_orig
    res = []
    for i in range(0, int((X_padded.shape[0] - \
                           W_rot.shape[0])/s[0])+1, s[0]):
        res.append([])
        for j in range(0, int((X_padded.shape[1] - \
                               W_rot.shape[1])/s[1])+1, s[1]):
            X_sub = X_padded[i:i+W_rot.shape[0], 
                             j:j+W_rot.shape[1]]
            res[-1].append(np.sum(X_sub * W_rot))
    return(np.array(res))

X = [[1, 3, 2, 4], [5, 6, 1, 3], [1, 2, 0, 2], [3, 4, 3, 2]]
W = [[1, 0, 3], [1, 2, 1], [0, 1, 1]]
print('Conv2d Implementation:\n', 
      conv2d(X, W, p=(1, 1), s=(1, 1)))
print('SciPy Results:\n', 
      scipy.signal.convolve2d(X, W, mode='same'))

需要注意的是,上述实现是为了理解概念,在实际的神经网络应用中,由于内存需求和计算复杂度较高,并不适用。现代工具如TensorFlow通常使用更高效的算法,例如基于傅里叶变换的算法。

4. 下采样层(池化层)

下采样通常以两种池化操作的形式应用于CNN中:最大池化和平均池化。池化层通常表示为 $P_{n_1 \times n_2}$,下标表示执行最大或平均操作的邻域大小,称为池化大小。

池化的优点有两个方面:
- 引入局部不变性 :最大池化使得局部邻域内的小变化不会改变池化结果,有助于生成对输入数据中的噪声更鲁棒的特征。
- 减小特征大小 :降低特征数量,提高计算效率,同时可能减少过拟合程度。

传统上,池化被认为是非重叠的,即通过将步长参数设置为等于池化大小来实现。而当步长小于池化大小时,会出现重叠池化。

虽然池化仍然是许多CNN架构的重要组成部分,但也有一些CNN架构不使用池化层,而是使用步长为2的卷积层来减少特征大小。

以下是一个最大池化的示例,展示了两个不同输入矩阵经过最大池化后得到相同输出的情况:
$\mathbf{X}_1 =
\begin{bmatrix}
10 & 255 & 70 & 255 & 125 & 0 & 105 & 25 & 170 & 100 & 25 & 70 & 255 & 0 & 0 & 255 & 150 & 0 & 10 & 10 & 10 & 10 & 150 & 20 & 70 & 15 & 35 & 25 & 200 & 100 & 100 & 20 & 95 & 0 & 0 & 60
\end{bmatrix}$

$\mathbf{X}_2 =
\begin{bmatrix}
100 & 100 & 95 & 255 & 100 & 50 & 100 & 125 & 100 & 50 & 125 & 170 & 80 & 40 & 255 & 30 & 10 & 10 & 150 & 20 & 125 & 150 & 120 & 125 & 30 & 30 & 70 & 30 & 150 & 100 & 100 & 200 & 70 & 70 & 70 & 95
\end{bmatrix}$

经过 $P_{2 \times 2}$ 最大池化后得到:
$\begin{bmatrix}
255 & 125 & 170 & 255 & 150 & 150 & 70 & 200 & 95
\end{bmatrix}$

综上所述,我们介绍了卷积神经网络的基本组成部分,包括卷积层、池化层以及如何处理多输入通道等内容。这些概念是构建CNN的基础,后续我们将继续探讨如何将这些组件组合起来实现一个完整的CNN。

5. 多输入或颜色通道的处理

卷积层的输入可能包含一个或多个二维数组或矩阵,这些矩阵被称为通道。传统的卷积层实现期望输入是一个三维张量表示,例如 $\mathbf{X} {N_1 \times N_2 \times C {in}}$,其中 $C_{in}$ 是输入通道的数量。

例如,彩色图像使用RGB颜色模式时,$C_{in} = 3$(对应红、绿、蓝三个颜色通道);而灰度图像只有一个通道,$C_{in} = 1$。

5.1 读取图像数据

在处理图像时,我们可以使用不同的工具将图像读取为NumPy数组。以下是使用TensorFlow和imageio读取图像的示例:

# 使用TensorFlow读取图像
import tensorflow as tf
img_raw = tf.io.read_file('example-image.png')
img = tf.image.decode_image(img_raw)
print('Image shape:', img.shape)

# 使用imageio读取图像
import imageio
img = imageio.imread('example-image.png')
print('Image shape:', img.shape)
print('Number of channels:', img.shape[2])
print('Image data type:', img.dtype)
print(img[100:102, 100:102, :])
5.2 多通道卷积操作

对于多输入通道的卷积操作,我们对每个通道分别进行卷积,然后将结果相加。每个通道($c$)的卷积都有其自己的内核矩阵 $\mathbf{W}[:, :, c]$。总预激活结果的计算公式如下:

给定输入 $\mathbf{X} {n_1 \times n_2 \times C {in}}$、内核矩阵 $\mathbf{W} {m_1 \times m_2 \times C {in}}$ 和偏置值 $b$,则:
- 预激活:$\mathbf{Z} {conv} = \sum {c = 1}^{C_{in}} \mathbf{W}[:, :, c] * \mathbf{X}[:, :, c]$
- 特征图:$\mathbf{A} = \phi(\mathbf{Z})$,其中 $\phi$ 是激活函数

如果使用多个特征图,内核张量将变为四维:$\mathbf{W} {width \times height \times C {in} \times C_{out}}$,其中 $width \times height$ 是内核大小,$C_{in}$ 是输入通道数,$C_{out}$ 是输出特征图的数量。更新后的公式如下:

给定输入 $\mathbf{X} {n_1 \times n_2 \times C {in}}$、内核矩阵 $\mathbf{W} {m_1 \times m_2 \times C {in} \times C_{out}}$ 和偏置向量 $\mathbf{b} {C {out}}$,则:
- 预激活:$\mathbf{Z} {conv}[:, :, k] = \sum {c = 1}^{C_{in}} \mathbf{W}[:, :, c, k] * \mathbf{X}[:, :, c]$
- 预激活结果:$\mathbf{Z}[:, :, k] = \mathbf{Z}_{conv}[:, :, k] + b[k]$
- 特征图:$\mathbf{A}[:, :, k] = \phi(\mathbf{Z}[:, :, k])$

以下是一个包含三个输入通道、五个输出特征图的卷积层示例,展示了卷积层和池化层的组合:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A([输入图像 3通道]):::startend --> B(卷积层 5个特征图):::process
    B --> C(池化层):::process
    C --> D([输出 5个特征图]):::startend
5.3 可训练参数计算

在上述示例中,卷积层的内核是一个四维张量,因此与内核相关的参数数量为 $m_1 \times m_2 \times 3 \times 5$。此外,卷积层的每个输出特征图都有一个偏置向量,偏置向量的大小为5。池化层没有可训练参数,所以总的可训练参数数量为 $m_1 \times m_2 \times 3 \times 5 + 5$。

如果使用全连接层而不是卷积层,达到相同数量的输出单元所需的参数数量将大得多。对于全连接层,权重矩阵的参数数量为 $(n_1 \times n_2 \times 3) \times (n_1 \times n_2 \times 5) = (n_1 \times n_2)^2 \times 3 \times 5$,偏置向量的大小为 $n_1 \times n_2 \times 5$。由于 $m_1 < n_1$ 且 $m_2 < n_2$,可以看出卷积层的可训练参数数量明显少于全连接层。

6. 实现一个完整的CNN

到目前为止,我们已经学习了CNN的基本构建块,包括卷积层、池化层以及如何处理多输入通道。与传统的多层神经网络相比,CNN中的核心操作是卷积操作,而不是矩阵乘法。

在传统神经网络中,我们使用矩阵乘法计算预激活值,例如 $\mathbf{z} = \mathbf{W} \mathbf{x} + \mathbf{b}$,其中 $\mathbf{x}$ 是表示像素的列向量,$\mathbf{W}$ 是连接像素输入到每个隐藏单元的权重矩阵。

在CNN中,这个操作被卷积操作取代,即 $\mathbf{Z} = \mathbf{W} * \mathbf{X} + \mathbf{b}$,其中 $\mathbf{X}$ 是表示像素的矩阵。预激活值通过激活函数得到隐藏单元的激活值,即 $\mathbf{A} = \phi(\mathbf{Z})$。

以下是一个简单的CNN实现示例,结合了卷积层、池化层和全连接层:

import tensorflow as tf
from tensorflow.keras import layers, models

# 构建CNN模型
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))

# 编译模型
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

# 打印模型结构
model.summary()

在这个示例中,我们使用TensorFlow的Keras API构建了一个简单的CNN模型,包含三个卷积层、两个最大池化层和两个全连接层。通过编译模型并打印其结构,我们可以查看模型的详细信息。

总结

本文详细介绍了深度卷积神经网络(CNNs)的基本原理和实践方法。从卷积神经网络的基础结构开始,我们了解了卷积层、池化层和全连接层的作用和特点。通过一维和二维离散卷积的介绍,我们掌握了卷积操作的数学定义、填充模式和步长的影响,以及如何计算卷积输出的大小。

在处理多输入通道时,我们学会了如何读取图像数据,并对每个通道分别进行卷积操作,最后将结果相加得到特征图。通过对比卷积层和全连接层的可训练参数数量,我们认识到卷积层在减少参数数量方面的优势。

最后,我们使用TensorFlow的Keras API实现了一个简单的CNN模型,展示了如何将各个组件组合起来构建一个完整的神经网络。通过这些知识,我们可以更好地理解和应用CNN进行图像分类等任务,为进一步的深度学习研究和实践打下坚实的基础。

希望本文能够帮助读者深入理解深度卷积神经网络的核心概念和实现方法,在实际应用中能够灵活运用这些知识解决各种问题。同时,读者可以根据自己的需求对模型进行调整和优化,以获得更好的性能和效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值