一、卷积神经网络背景

1、神经网络

       神经网络(Neural Network)是一种模仿人类神经系统结构和功能的计算模型。它由大量简单的处理单元(称为神经元)组成,这些神经元通过连接(称为权重)相互通信,并共同完成复杂的信息处理任务。

神经网络的基本构成包括以下几个要素:

神经元(Neuron):神经网络的基本单元,模拟生物神经元的功能。每个神经元接收一组输入,通过激活函数处理输入并产生输出。激活函数可以是线性或非线性的,常见的激活函数有Sigmoid、ReLU、Tanh等。

权重(Weight):神经元之间的连接具有权重,用于调整输入信号的重要性。权重决定了某个神经元对输入的敏感程度,通过不断调整权重,神经网络可以学习到合适的参数从而提高性能。

层(Layer):神经网络由多个层组成,每一层包含多个神经元。常见的神经网络层包括输入层、隐藏层和输出层。输入层接收原始数据,输出层产生最终的预测结果,而隐藏层用于中间信息的处理和特征提取。

前向传播(Forward Propagation):神经网络通过前向传播将输入信号从输入层经过各个隐藏层传递到输出层。每个神经元根据输入和权重计算输出,并将其发送给下一层的神经元,这样逐层传递,直到达到输出层。

反向传播(Backpropagation):神经网络通过反向传播算法来更新权重,以最小化预测输出与真实标签之间的误差。通过计算损失函数的梯度,反向传播算法将误差从输出层向后传递,逐层更新权重。

     神经网络在机器学习和人工智能领域有广泛的应用,包括图像识别、语音识别、自然语言处理、推荐系统等。通过大规模的训练数据和适当的网络结构设计,神经网络可以学习到复杂的模式和特征,并对未知数据进行预测和分类。  

2、什么是卷积神经网络

         卷积神经网络(Convolutional Neural Networks,简称CNN)是一种具有局部连接、权值共享等特点的深层前馈神经网络(Feedforward Neural Networks),是深度学习(deep learning)的代表算法之一,擅长处理图像特别是图像识别等相关机器学习问题,比如图像分类、目标检测、图像分割等各种视觉任务中都有显著的提升效果,是目前应用最广泛的模型之一。

        卷积神经网络具有表征学习(representation learning)能力,能够按其阶层结构对输入信息进行平移不变分类(shift-invariant classification),可以进行监督学习和非监督学习,其隐含层内的卷积核参数共享和层间连接的稀疏性使得卷积神经网络能够以较小的计算量对格点化(grid-like topology)特征,例如像素和音频进行学习、有稳定的效果且对数据没有额外的特征工程(feature engineering)要求,并被大量应用于计算机视觉、自然语言处理等领域。

3、为什么要用卷积神经网络

使用传统神经网络处理机器视觉面临的一个挑战是数据的输入可能会非常大。例如一张1000x1000x3的图片,神经网络输入层的维度将高达三百万,使得网络权重 非常庞大。这样会造成两个后果:

  • 神经网络结构复杂,数据量相对较少,容易出现过拟合。
  • 所需内存和计算量巨大。

因此,一般的神经网络很难处理海量图像数据。解决这一问题的方法就是使用特殊结构的神经网络:卷积神经网络(Convolutional Neural Network, CNN)

4、了解图像原理

图像在计算机中是一堆按顺序排列的数字,数值为0到255。0表示最暗,255表示最亮。 如下图:

上图是只有黑白颜色的灰度图,而更普遍的图片表达方式是RGB颜色模型,即红、绿、蓝三原色的色光以不同的比例相加,以产生多种多样的色光。RGB颜色模型中,单个矩阵就扩展成了有序排列的三个矩阵,也可以用三维张量去理解。

二、卷积神经网络的网络结构

1、数据输入层

该层要做的处理主要是对原始图像数据进行预处理,其中包括:

  • 去均值:把输入数据各个维度都中心化为0,如下图所示,其目的就是把样本的中心拉回到坐标系原点上。
    import numpy as np
    
    # 假设有一个数据集 X,每行表示一个样本
    X = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])
    
    # 计算每列的均值
    mean = np.mean(X, axis=0)
    
    # 对每个样本减去均值
    X_mean_subtracted = X - mean
    
    print("去均值后的数据集:")
    print(X_mean_subtracted)
  • 归一化:幅度归一化到同样的范围,如下所示,即减少各维度数据取值范围的差异而带来的干扰,比如,我们有两个维度的特征A和B,A范围是0到10,而B范围是0到10000,如果直接使用这两个特征是有问题的,好的做法就是归一化,即A和B的数据都变为0到1的范围。
    # 假设有一个数据集 X,每行表示一个样本
    X = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])
    
    # 计算每列的最小值和最大值
    min_vals = np.min(X, axis=0)
    max_vals = np.max(X, axis=0)
    
    # 对每个样本进行归一化
    X_normalized = (X - min_vals) / (max_vals - min_vals)
    
    print("归一化后的数据集:")
    print(X_normalized)
  • PCA/白化:用PCA降维;白化是对数据各个特征轴上的幅度归一化
    from sklearn.decomposition import PCA
    import numpy as np
    
    # 假设有一个数据集 X,每行表示一个样本
    X = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])
    
    # 创建PCA对象,并指定要保留的主成分个数
    pca = PCA(n_components=2)
    
    # 对数据集进行PCA降维
    X_pca = pca.fit_transform(X)
    
    print("PCA降维后的数据集:")
    print(X_pca)

去均值与归一化效果图:

去相关与白化效果图:

2、卷积计算层

1)边缘检测

如下图所示,以人脸识别为例,神经网络由浅层到深层,分别可以检测出图片的边缘特征 、局部特征(例如眼睛、鼻子等)、整体面部轮廓等。

垂直边缘检测和水平边缘检测

图片的边缘检测可以通过与相应滤波器进行卷积来实现。以垂直边缘检测为例,原始图片尺寸为 6x6,中间的矩阵被称作滤波器(filter),尺寸为3x3  ,卷积后得到的图片尺寸为4x4  ,得到结果如下(数值表示灰度,以左上角和右下角的值为例):(卷积运算的求解过程是从左到右,由上到下,每次在原始图片矩阵中取与滤波器同等大小的一部分,每一部分中的值与滤波器中的值对应相乘后求和,将结果组成一个矩阵)

卷积运算

垂直边缘检测滤波器水平边缘检测滤波器如下所示,其他常用的滤波器还有Sobel滤波器Scharr滤波器,它们增加了中间行的权重,以提高结果的稳健性。

   边缘检测示例代码
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import paddle
from paddle.nn import Conv2D
from paddle.nn.initializer import Assign
img = Image.open('path')#图片路径

# 设置卷积核参数
w = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]], dtype='float32')/8
w = w.reshape([1, 1, 3, 3])
# 由于输入通道数是3,将卷积核的形状从[1,1,3,3]调整为[1,3,3,3]
w = np.repeat(w, 3, axis=1)
# 创建卷积算子,输出通道数为1,卷积核大小为3x3,
# 并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2D(in_channels=3, out_channels=1, kernel_size=[3, 3], 
            weight_attr=paddle.ParamAttr(
              initializer=Assign(value=w)))
    
# 将读入的图片转化为float32类型的numpy.ndarray
x = np.array(img).astype('float32')
# 图片读入成ndarry时,形状是[H, W, 3],
# 将通道这一维度调整到最前面
x = np.transpose(x, (2,0,1))
# 将数据形状调整为[N, C, H, W]格式
x = x.reshape(1, 3, img.height, img.width)
x = paddle.to_tensor(x)
y = conv(x)
out = y.numpy()
plt.figure(figsize=(20, 10))
f = plt.subplot(121)
f.set_title('input image', fontsize=15)
plt.imshow(img)
f = plt.subplot(122)
f.set_title('output feature map', fontsize=15)
plt.imshow(out.squeeze(), cmap='gray')
plt.show()

2)填充padding

们假设输入图片的大小为 nxn,而滤波器的大小为 fxf(注意 一般为奇数),则卷积后的输出图片大小为 (n-f+1)x(n-f+1)

这样的处理会带来两个问题:

  • 每次卷积运算后,输出图片的尺寸缩小。
  • 原始图片的角落、边缘区像素点在输出中采用较少,输出图片丢失边缘位置的很多信息。

为了解决这些问题,可以在进行卷积操作前,对原始图片在边界上进行填充(Padding),以增加矩阵的大小,保证输出的尺寸以及边缘位置信息的有效利用。我们通常用 0 进行填充。 

设每个方向扩展像素点数量为p,则填充后原始图片的大小为 (n+2p)x(n+2p) ,滤波器大小保持fxf不变,则输出图片大小为(n+2p-f+1)x(n+2p-f+1)

有两种对于p的设置是最常见的:

  • Valid卷积:不进行填充,直接卷积。结果大小为(n-f+1)x(n-f+1) 
  • Same卷积:进行填充,并保证卷积后结果大小与输入一致,此时 

在计算机视觉领域,f通常为奇数。原因包括 Same 卷积中 能得到自然数结果,并且滤波器有一个便于表示其所在位置的中心点。

3)卷积步长

卷积过程中,有时需要通过填充来避免信息损失,有时也需要通过设置步长(Stride)来压缩一部分信息。

步长表示滤波器在原始图片的水平方向和垂直方向上每次移动的距离。之前,步长被默认为 1。而如果我们设置步长为2,则卷积过程如下图所示:

设步长stride为s ,padding填充大小为p  ,输入图片大小为nxn  ,滤波器大小为fxf,则卷积后图片的尺寸为:

上述公式中有一个向下取整的符号,用于处理商不为整数的情况。

4)卷积计算

 这里的蓝色矩阵就是输入的图像,粉色矩阵就是卷积层的神经元,这里表示了有两个神经元(w0,w1)。绿色矩阵就是经过卷积运算后的输出矩阵,这里的步长设置为2。

蓝色的矩阵(输入图像)对粉色的矩阵(filter)进行矩阵内积计算并将三个内积运算的结果与偏置值b相加(比如上面图的计算:2+(-2+1-2)+(1-2-2) + 1= 2 - 3 - 3 + 1 = -3),计算后的值就是绿框矩阵的一个元素。

3、激活层

   激活函数就是在神经元网络中加入非线性操作,这样神经元通过激活函数就可以应用在非线性函数中了。因为卷积对输入图像进行的操作是线性的,但输入的图像的信息不都是线性可分的,所以通过激活函数来进行非线性操作,能够更好的映射特征去除数据中的冗余,以增强卷积神经网络的表达能力。在卷积神经网络中经常用到的激活函数有:sigmoid 激活函数、tanh 激活函数、ReLU 激活函数以及 Leaky ReLU 函数等。

神经网络激活函数

(1) sigmoid 函数

        Sigmoid函数是一个在生物学中常见的S型函数,也称为S型生长曲线。在信息科学中,由于其单增以及反函数单增等性质,Sigmoid函数常被用作神经网络的阈值函数,将变量映射到0,1之间。公式和图像如下:

  • 优点:取值范围(0,1)、比较简单且容易理解;
  • 缺点:容易出现饱和以及终止梯度传递,即死神经元,同时函数输出没有0中心化。
 (2)Tanh函数

      Tanh是双曲函数中的一个,Tanh()为双曲正切。在数学中,双曲正切“Tanh”是由基本双曲函数双曲正弦和双曲余弦推导而来。公式和图像如下:

  • 优点:取值范围(-1,1),容易理解且函数输出由0中心化;
  • 缺点:容易出现饱和以及终止梯度传递,即死神经元。

 卷积神经网络激活函数

(1)ReLU激活函数

Relu激活函数(The Rectified Linear Unit),用于隐层神经元输出。公式和图像如下:

  • 优点:与上述两个函数相比提升了收敛速度,且梯度求解公式简单,不会产生梯度消失和梯度爆炸。pic_center
  • 缺点:没有边界,可以使用变种ReLU:min(max(0,x),6);且比较脆弱,容易陷入死神经元,可以通过较小学习率解决。

 其他激活函数

总结

①不要用sigmoid!不要用sigmoid!不要用sigmoid!
② 首先试RELU,因为快,但要小心点
③ 如果2失效,请用Leaky ReLU或者Maxout
④ 某些情况下tanh倒是有不错的结果,但是很少

4、池化层

池化层夹在连续的卷积层中间, 用于压缩数据和参数的量,减小过拟合。
简而言之,如果输入是图像的话,那么池化层的最主要作用就是压缩图像。

作用:

  1. 特征不变性,也就是我们在图像处理中经常提到的特征的尺度不变性,池化操作就是图像的resize,平时一张狗的图像被缩小了一倍我们还能认出这是一张狗的照片,这说明这张图像中仍保留着狗最重要的特征,我们一看就能判断图像中画的是一只狗,图像压缩时去掉的信息只是一些无关紧要的信息,而留下的信息则是具有尺度不变性的特征,是最能表达图像的特征。
  2. 特征降维,我们知道一幅图像含有的信息是很大的,特征也很多,但是有些信息对于我们做图像任务时没有太多用途或者有重复,我们可以把这类冗余信息去除,把最重要的特征抽取出来,这也是池化操作的一大作用。
  3. 在一定程度上防止过拟合,更方便优化。

1)最大池化

将输入拆分成不同的区域,输出的每个元素都是对应区域中元素的最大值,如下图所示:

对最大池化的一种直观解释是,元素值较大可能意味着池化过程之前的卷积过程提取到了某些特定的特征,池化过程中的最大化操作使得只要在一个区域内提取到某个特征,它都会保留在最大池化的输出中。

2)平均池化

3)池化层总结 

池化过程的特点之一是,它有一组超参数,但是并没有参数需要学习。池化过程的超参数包括滤波器的大小 f、步长 s ,以及选用最大池化还是平均池化。而填充p  则很少用到。

池化过程的输入维度为: 

输出维度为:

5、全连接层 

全连接层就是将最后一层卷积得到的特征图(矩阵)展开成一维向量,并为分类器提供输入。

 

全连接层则起到将学到的特征表示映射到样本的标记空间的作用。换句话说,就是把特征整合到一起(高度提纯特征),方便交给最后的分类器或者回归。

三、CNN的基本特征

(1)局部感知

         卷积层解决这类问题的一种简单方法是对隐含单元和输入单元间的连接加以限制:每个隐含单元仅仅只能连接输入单元的一部分。例如,每个隐含单元仅仅连接输入图像的一小片相邻区域。

(2)参数共享

        在卷积层中每个神经元连接数据窗的权重是固定的,每个神经元只关注一个特性。神经元就是图像处理中的滤波器,比如边缘检测专用的Sobel滤波器,即卷积层的每个滤波器都会有自己所关注一个图像特征,比如垂直边缘,水平边缘,颜色,纹理等等,这些所有神经元加起来就好比就是整张图像的特征提取器集合。

        权值共享使得我们能更有效的进行特征抽取,因为它极大的减少了需要学习的自由变量的个数。通过控制模型的规模,卷积网络对视觉问题可以具有很好的泛化能力。

例如一组固定的权重和不同窗口内数据做内积: 

 四、CNN说明及实例

1. 训练算法

1.同一般机器学习算法,先定义Loss function,衡量和实际结果之间差距。
2.找到最小化损失函数的W和b, CNN中用的算法是SGD(随机梯度下降)。

2.优缺点

(1)优点
  •共享卷积核,对高维数据处理无压力
  •无需手动选取特征,训练好权重,即得特征分类效果好
(2)缺点
  •需要调参,需要大样本量,训练最好要GPU
  •物理含义不明确(也就说,我们并不知道没个卷积层到底提取到的是什么特征,而且神经网络本身就是一种难以解释的“黑箱模型”)

3. 典型CNN

  • LeNet,这是最早用于数字识别的CNN
  • AlexNet, 2012 ILSVRC比赛远超第2名的CNN,比
  • LeNet更深,用多层小卷积层叠加替换单大卷积层。
  • ZF Net, 2013 ILSVRC比赛冠军
  • GoogLeNet, 2014 ILSVRC比赛冠军
  • VGGNet, 2014 ILSVRC比赛中的模型,图像识别略差于GoogLeNet,但是在很多图像转化学习问题(比如object detection)上效果奇好

4、 CNN应用案例

手写数字识别案例

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms
from torchvision.datasets import MNIST
from torch.utils.data import DataLoader

# 下载并标准化数据集
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_dataset = MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = MNIST(root='./data', train=False, transform=transform)

# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64)

# 定义 LeNet 网络结构
class LeNet(nn.Module):
    def __init__(self, num_classes=10):
        super(LeNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 6, kernel_size=5),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(6, 16, kernel_size=5),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(16 * 4 * 4, 120),
            nn.ReLU(inplace=True),
            nn.Linear(120, 84),
            nn.ReLU(inplace=True),
            nn.Linear(84, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

# 初始化 LeNet 模型
lenet = LeNet()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(lenet.parameters())

# 训练模型
def train_model(model, criterion, optimizer, train_loader, num_epochs=5):
    for epoch in range(num_epochs):
        running_loss = 0.0
        for i, data in enumerate(train_loader, 0):
            inputs, labels = data
            optimizer.zero_grad()

            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            running_loss += loss.item()
            if i % 100 == 99:
                print('[%d, %5d] loss: %.3f' %
                      (epoch + 1, i + 1, running_loss / 100))
                running_loss = 0.0

# 测试模型
def test_model(model, test_loader):
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    print('Accuracy of the network on the 10000 test images: %d %%' % (
            100 * correct / total))

# 训练模型
train_model(lenet, criterion, optimizer, train_loader)

# 测试模型
test_model(lenet, test_loader)

五、卷积神经网络总结

        卷积网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式,只要用已知的模式对卷积网络加以训练,网络就具有输入输出对之间的映射能力。

        CNN一个非常重要的特点就是头重脚轻(越往输入权值越小,越往输出权值越多),呈现出一个倒三角的形态,这就很好地避免了BP神经网络中反向传播的时候梯度损失得太快。

        卷积神经网络CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。由于CNN的特征检测层通过训练数据进行学习,所以在使用CNN时,避免了显式的特征抽取,而隐式地从训练数据中进行学习;再者由于同一特征映射面上的神经元权值相同,所以网络可以并行学习,这也是卷积网络相对于神经元彼此相连网络的一大优势。卷积神经网络以其局部权值共享的特殊结构在语音识别和图像处理方面有着独特的优越性,其布局更接近于实际的生物神经网络,权值共享降低了网络的复杂性,特别是多维输入向量的图像可以直接输入网络这一特点避免了特征提取和分类过程中数据重建的复杂度。

本文仅作为学习笔记使用,并无商业用途

 参考系列教程 (showmeai.tech)

 机器学习算法之——卷积神经网络(CNN)原理讲解 - 知乎 (zhihu.com)

【深度学习】一文搞懂卷积神经网络(CNN)的原理(超详细)_AI_dataloads的博客-CSDN博客

【CNN】深入浅出讲解卷积神经网络(介绍、结构、原理)_cnn网络结构及原理_程序遇上智能星空的博客-CSDN博客

《CNN笔记总结系列之三》激励层-CSDN博客CNN基础知识——全连接层(Fully Connected Layer) - 知乎 (zhihu.com)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐