前言
本文不讲述如泛化,前向后向传播,过拟合等基础概念。
本文图片来源于网络,图片所有者可以随时联系笔者删除。
本文提供代码不代表该神经网络的全部实现,只是为了方便展示此模型的关键结构。
CNN,常用于计算机视觉,是计算机视觉方面常见的基础模型,后面发展的有很多其他变种,也被用于文字处理等非计算机视觉领域。概念是由AI领域著名大佬LeCun等人在上世纪90年代提出。CNN之所以在计算机视觉领域(CNN、)表现出色,是因为它们能够自动并有效地捕捉到图像中的空间层次结构,这一点对于理解像素组成的图像至关重要。通过利用这种层次结构,CNN能够识别和分类从简单到复杂的对象和场景,无论它们的大小、位置或者是姿态如何变化。CNN的变种和改进模型层出不穷:AlexNet、VGG、GoogLeNet到ResNet和DenseNet等等等等。
正文
新概念之 梯度消失、梯度爆炸、数据标准化、批量归一化
梯度消失
在深度神经网络中,当梯度的值在经过多层传播后变得非常小,以至于权重几乎不更新,从而导致网络难以学习,这种现象称为梯度消失。
梯度爆炸
与梯度消失相反,梯度爆炸是指梯度的值变得异常大,导致权重更新过度,使模型无法收敛。
数据标准化
据标准化是将数据按比例缩放,使之落入一个小的特定区间。
- 未经标准化的数据可能会有很大的方差,导致某些特征在梯度下降过程中占据主导地位,标准化后的数据有助于确保所有特征在学习过程中以相似的尺度参与,从而加速收敛。
- 同时某些激活函数(如sigmoid或tanh)在接收高值或低值输入时,可能会导致梯度消失问题,标准化有助于减轻这一问题。
一种常见的方法是特征缩放,使得数据的平均值为0,方差为1。
import numpy as np
X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
mean = np.mean(X, axis=0)
std = np.std(X, axis=0)
X_normalized = (X - mean) / std
print("Normalized Data:\n", X_normalized)
批量归一化
在深度网络训练过程中,每层输入的分布会因为前一层参数的更新而变化,这种现象称为内部协变量偏移。
- 批量归一化通过规范化层输入,有助于稳定分布,从而提高训练效率和稳定性。
- 未经归一化处理的数据可能导致模型训练时梯度消失或爆炸,批量归一化也有助于减轻这一问题。
具体原理是通过调整网络中间层的输入,使其均值为0,方差为1,实际我们不需要管这些,直接调用已有API。如下为谷歌Tensorflow框架示例
import tensorflow as tf
from tensorflow.keras.layers import BatchNormalization
x = tf.random.normal(shape=(10, 100))
bn_layer = BatchNormalization()
output = bn_layer(x, training=True)
print("Batch Normalized Output:\n", output)
大名鼎鼎ResNet 残差连接
随着网络深度的增加,理论上模型的表示能力应该变得更强,能够更准确地拟合复杂的数据模式。然而过深的网络容易遭受梯度消失或梯度爆炸的问题,导致训练变得异常困难。
此外,还存在一种直观上似乎矛盾的现象:虽然增加网络层理论上不应降低模型的性能(因为新模型解的空间包含了原模型解的空间,如果额外层仅执行恒等映射,新旧模型性能应相当)。
但在实际操作中,添加过多层后,模型的训练误差往往不降反升。
何恺明等人在2015年提出了一种创新的网络架构:残差网络(ResNet)。残差网络通过引入“跳过连接”(残差连接),允许数据直接跳过某些层。在这种设计下,新增加的层不需要学习出一个完整的变换,而是学习与恒等映射的残差。
这一简单而强大的操作极大地缓解了深层网络中的梯度消失问题,使得训练深达数百甚至数千层的网络成为可能,而不损害模型的训练性能。
这非常深刻影响了后续深度神经网络的设计思路。
残差块(ResNet核心组件)图例如下:
代码如下:
import tensorflow as tf
from tensorflow.keras import layers, models
def residual_block(x, filters, kernel_size=3, stride=1):
"""构建一个残差块"""
y = layers.Conv2D(filters, kernel_size=kernel_size, strides=(stride, stride), padding='same')(x)
y = layers.BatchNormalization()(y)
y = layers.ReLU()(y)
y = layers.Conv2D(filters, kernel_size=kernel_size, strides=(1, 1), padding='same')(y)
y = layers.BatchNormalization()(y)
# 当输入和输出维度不一致时,使用1x1卷积调整维度
if x.shape[-1] != filters or stride != 1:
x = layers.Conv2D(filters, kernel_size=1, strides=(stride, stride), padding='same')(x)
x = layers.BatchNormalization()(x)
out = layers.Add()([x, y])
out = layers.ReLU()(out)
return out
接下来就可以堆叠残差块来构建一个简单的ResNet模型。
当然,还可以包含更多的残差块和更复杂的结构,如ResNet-50以及ResNet-101。
def build_resnet(input_shape, num_classes):
inputs = layers.Input(shape=input_shape)
x = layers.Conv2D(64, (7, 7), strides=(2, 2), padding='same')(inputs)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)
# 堆叠残差块
x = residual_block(x, 64)
x = residual_block(x, 128, stride=2)
x = residual_block(x, 256, stride=2)
x = residual_block(x, 512, stride=2)
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense(num_classes, activation='softmax')(x)
model = models.Model(inputs=inputs, outputs=outputs)
return model
其他的经典CNN神经网络
DenseNet
DenseNet是一种网络架构,其核心思想是通过将每层直接与前面所有层相连,来促进信息的流动和重用。这种设计导致网络在深度增加的同时,参数的数量却相对较少,因为它避免了重复学习相似的特征。
在DenseNet中,第i
层接收前面所有0...i-1
层的特征图作为输入。这种密集连接机制增强了特征的传递,使得网络可以更深。由于每层都可以访问前面层的特征,这使得网络在特征学习上更为高效,能够用更少的参数达到更好的性能。
DenseNet通过引入Transition Layers
(过渡层)控制特征图的维度,其中过渡层由卷积层和池化层组成,用于连接不同的Dense Block(密集连接块)
。
MobileNet
MobileNet系列专为移动和嵌入式视觉应用设计,它对模型大小和计算速度进行了优化,以适应计算能力有限的设备。
MobileNet的核心是深度可分离卷积,这一技术将传统的卷积操作分解为深度卷积(每个输入通道单独卷积)和逐点卷积(1x1卷积用于组合深度卷积的输出)。这种分解大大减少了模型的参数和计算量。
MobileNet引入了两个超参数,宽度乘数(width multiplier)和分辨率乘数(resolution multiplier),允许开发者根据具体需求调整模型的大小和速度。
大概就是如下这张图,把深度卷积和逐点卷积分开操作,大大减少了模型参数。
更多
还有着更多经典的神经网络,不过本系列文章不再详细介绍,CNN篇完结,后续会写CNN实战CV任务的文章,欢迎继续阅读。