在前面的文章中,我们已经掌握了MindSpore的基础知识,包括Tensor、nn.Cell、数据处理等。从本篇开始,我们将正式进入激动人心的模型构建部分,首先要学习的就是在计算机视觉(CV)领域大放异彩的——卷积神经网络(Convolutional Neural Network, CNN)。
1. CNN是什么
把CNN想象成一个拥有“火眼金睛”的图像识别专家。它不像我们之前接触的简单网络那样“一视同仁”地看待所有像素,而是通过模仿人类视觉系统的方式,逐层地从图像中提取特征,从最基础的边缘、颜色,到更复杂的纹理、形状,最终识别出图像的内容。
本篇文章将作为您进入CNN世界的第一站,详细拆解构成CNN的几个最核心的“零件”——即MindSpore中的关键神经网络层。
2. CNN的核心“零件”
在MindSpore中,构建一个CNN网络就像搭乐高积木一样,我们需要从mindspore.nn模块中拿出各种功能的“积木块”(神经网络层),然后将它们有序地拼接起来。下面,我们就来逐一认识这些核心“积木块”。
2.1 卷积层 (nn.Conv2d):特征提取器
卷积层是CNN的灵魂,它负责从输入图像中提取特征。
工作原理:想象你有一个“滤镜”(称为卷积核或滤波器),这个滤镜非常小,比如
3x3大小。你将这个滤镜覆盖在输入图像的左上角,计算滤镜覆盖区域的像素与滤镜自身值的加权和,得到一个输出值。然后,你将滤镜向右移动一个“步长”(stride),重复计算,直到扫过整行。接着,下移一个步长,继续扫描,最终生成一张新的、尺寸更小的图像,这张图就叫做特征图(Feature Map)。这个过程就模拟了大脑识别物体边缘、角落等局部特征的方式。关键参数:
in_channels(int): 输入通道数。对于RGB彩色图像,就是3。out_channels(int): 输出通道数,也等于卷积核的数量。每个卷积核可以学习提取一种不同的特征,所以输出通道数越多,提取的特征就越丰富。kernel_size(int or tuple): 卷积核的大小。常用的有3(代表3x3) 或(3, 5)。stride(int): 卷积核移动的步长。默认为1。pad_mode(str): 填充模式。'same'模式会在图像周围自动填充0,使得输出特征图的尺寸与输入大致相同;'valid'模式则不填充,输出尺寸会变小。
代码示例:
importmindsporefrommindsporeimportnn,Tensorimportnumpyasnp# 假设我们有一个 1x1x5x5 的单通道图像 (N, C, H, W)# N: 批量大小, C: 通道数, H: 高度, W: 宽度input_image=Tensor(np.ones([1,1,5,5]),mindspore.float32)# 定义一个卷积层:输入1通道,输出2通道,卷积核3x3,步长1# pad_mode='valid'表示不填充conv_layer=nn.Conv2d(in_channels=1,out_channels=2,kernel_size=3,stride=1,pad_mode='valid')# 将图像输入卷积层output_feature_map=conv_layer(input_image)print("输入图像尺寸:",input_image.shape)print("输出特征图尺寸:",output_feature_map.shape)# (5-3)/1 + 1 = 3, 所以输出尺寸是 1x2x3x32.2 激活函数 (nn.ReLU):引入非线性
如果只有卷积层,无论叠加多少层,其效果都等同于一个线性变换,这无法让网络学习复杂的数据模式。因此,我们需要激活函数来引入非线性。
工作原理:
ReLU(Rectified Linear Unit) 是目前最常用的激活函数之一。它的规则极其简单:对于输入的每个值,如果大于0,则保持不变;如果小于或等于0,则直接变为0。这个简单的操作却能极大地提升网络的表达能力。代码示例:
# 定义一个ReLU激活函数层relu_layer=nn.ReLU()# 假设有一个包含正数和负数的Tensorinput_tensor=Tensor(np.array([[-1.0,4.0,-8.0],[2.0,-5.0,9.0]]),mindspore.float32)# 应用ReLUoutput_tensor=relu_layer(input_tensor)print("应用ReLU前:",input_tensor)print("应用ReLU后:",output_tensor)2.3 池化层 (nn.MaxPool2d):信息浓缩与降维
池化层通常紧跟在卷积层和激活层之后,它有两个主要作用:
- 降维:显著减小特征图的尺寸,从而减少网络后续的参数数量和计算量。
- 保持特征不变性:通过取一个区域内的最大值(Max Pooling)或平均值(Avg Pooling),使得网络对特征的微小位移不那么敏感,增强了模型的鲁棒性。
工作原理:
MaxPool2d(最大池化)与卷积类似,也是用一个窗口在特征图上滑动,但它不进行加权计算,而是简单地取窗口内的最大值作为输出。关键参数:
kernel_size(int): 池化窗口的大小。stride(int): 窗口移动的步长。
代码示例:
# 假设我们有一个 1x2x4x4 的特征图feature_map=Tensor(np.arange(1*2*4*4).reshape(1,2,4,4),mindspore.float32)# 定义一个最大池化层:窗口2x2,步长2maxpool_layer=nn.MaxPool2d(kernel_size=2,stride=2)# 应用池化output_pooled=maxpool_layer(feature_map)print("池化前尺寸:",feature_map.shape)print("池化后尺寸:",output_pooled.shape)# (4-2)/2 + 1 = 2, 所以输出尺寸是 1x2x2x22.4 展平层 (nn.Flatten):从三维到一维的“压平”
在经过多轮“卷积-激活-池化”操作后,我们得到了一系列高度浓缩的特征图。但在将这些特征用于最终分类之前,需要将它们“压平”,变成一个一维的向量。这就是nn.Flatten层的工作。
工作原理:它会保留
batch_size(N),然后将后面的所有维度(C, H, W)的数值全部拉伸成一个长长的一维向量。代码示例:
# 假设我们有一个 1x2x2x2 的池化后特征pooled_map=Tensor(np.ones([1,2,2,2]),mindspore.float32)# 定义一个展平层flatten_layer=nn.Flatten()# 应用展平output_vector=flatten_layer(pooled_map)print("展平前尺寸:",pooled_map.shape)print("展平后尺寸:",output_vector.shape)# 2 * 2 * 2 = 8, 所以输出尺寸是 1x82.5 全连接层 (nn.Dense):最终分类器
全连接层(在MindSpore中称为Dense)通常位于CNN的末端。在特征被展平后,这个一维向量会被送入一个或多个全连接层,进行最终的分类或回归。
工作原理:它的每一个神经元都与前一层的所有神经元相连接,通过学习到的权重对特征进行加权求和,最终映射到指定的输出维度(例如,在10分类任务中,输出维度就是10)。
关键参数:
in_channels(int): 输入神经元的数量(即展平后向量的长度)。out_channels(int): 输出神经元的数量(即分类的类别数)。
代码示例:
# 假设我们有一个长度为8的展平向量flatten_vector=Tensor(np.ones([1,8]),mindspore.float32)# 定义一个全连接层:输入8个特征,输出到10个类别dense_layer=nn.Dense(in_channels=8,out_channels=10)# 应用全连接层output_logits=dense_layer(flatten_vector)print("全连接层输入尺寸:",flatten_vector.shape)print("全连接层输出尺寸:",output_logits.shape)3. 组装一个简单的CNN
现在我们已经认识了所有的“零件”,让我们把它们组装起来,构建一个简单的CNN模型。这个模型将遵循经典的卷积 -> 激活 -> 池化 -> 展平 -> 全连接的结构。
importmindsporefrommindsporeimportnnclassSimpleCNN(nn.Cell):def__init__(self,num_classes=10):super(SimpleCNN,self).__init__()# 定义第一组卷积、激活、池化self.conv1=nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,pad_mode='valid')self.relu1=nn.ReLU()self.pool1=nn.MaxPool2d(kernel_size=2,stride=2)# 定义第二组卷积、激活、池化self.conv2=nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,pad_mode='valid')self.relu2=nn.ReLU()self.pool2=nn.MaxPool2d(kernel_size=2,stride=2)# 定义展平层和全连接层self.flatten=nn.Flatten()# 假设输入是32x32的图像,经过两轮卷积池化后,尺寸需要计算# 这里我们先假设一个值,后续实战中会精确计算self.fc1=nn.Dense(in_channels=16*5*5,out_channels=120)self.relu3=nn.ReLU()self.fc2=nn.Dense(in_channels=120,out_channels=84)self.relu4=nn.ReLU()self.fc3=nn.Dense(in_channels=84,out_channels=num_classes)defconstruct(self,x):# 按照顺序将输入x通过各个层x=self.conv1(x)x=self.relu1(x)x=self.pool1(x)x=self.conv2(x)x=self.relu2(x)x=self.pool2(x)x=self.flatten(x)x=self.fc1(x)x=self.relu3(x)x=self.fc2(x)x=self.relu4(x)x=self.fc3(x)returnx# 实例化网络net=SimpleCNN()print(net)注意:上述代码中
fc1的in_channels是一个估算值。在实际项目中,你需要根据输入图像的尺寸和卷积/池化层的参数精确计算出展平后的向量长度。我们将在后续的LeNet-5实战文章中详细演示这个计算过程。
4. 总结
在本篇文章中,我们详细学习了构建卷积神经网络(CNN)所需的几个核心层:
nn.Conv2d:用于提取局部特征。nn.ReLU:用于引入非线性,增强模型表达力。nn.MaxPool2d:用于降低维度,减少计算量。nn.Flatten:用于将多维特征“压平”成一维向量。nn.Dense:用于根据提取的特征进行最终分类。
通过将这些“零件”有序地组合,我们成功搭建了一个简单的CNN模型。这为您后续学习更复杂的网络(如LeNet-5、ResNet等)并进行端到端实战打下了坚实的基础。
在下一篇文章中,我们将学习如何构建循环神经网络(RNN),敬请期待!