深度学习笔记(二)
卷积神经网络

卷积神经网络

卷积神经网络(Convolutional Neural Network, CNN)是一种专为处理网格结构数据(如图像和时间序列)设计的深度学习模型。它是一类前馈神经网络,包含专门用于提取数据空间信息的卷积操作,其主要特点是通过局部连接和参数共享,有效地提取输入数据的局部特征,同时降低模型的参数量。


核心组成部分

  1. 输入层

    • 接受原始输入数据,例如一张图像(RGB 图像是一个三维张量:宽度 × 高度 × 通道数)。
  2. 卷积层(Convolutional Layer)

    • 核心功能是提取输入数据的局部特征,例如边缘、纹理等。通过卷积核(filters/kernels)在输入数据上滑动,计算内积,生成特征图(Feature Map)。
    • 特点:
      • 局部感受野:每个神经元只感知输入的一小部分(局部区域)。
      • 参数共享:相同的卷积核在整个输入上应用,从而减少参数数量。
  3. 激活函数(Activation Function)

    • 常用 ReLU(Rectified Linear Unit)作为非线性激活函数,增加模型的表达能力。
  4. 池化层(Pooling Layer)

    • 用于下采样特征图,减少数据维度,同时保留关键特征,减小计算复杂度并降低过拟合风险。
    • 常用方法:最大池化(Max Pooling)平均池化(Average Pooling)
  5. 全连接层(Fully Connected Layer)

    • 将卷积和池化层输出的高维特征展平为一维向量,并连接到输出层。通过加权求和和激活函数计算最终输出。
  6. 输出层

    • 根据任务要求输出结果,例如分类任务的概率分布(通过 softmax)或回归任务的实值。

特点

  1. 局部连接:每个神经元只关注输入的一部分(称为感受野),非常适合处理图像等具有局部相关性的结构化数据。
  2. 参数共享:卷积核在整个输入数据上重复使用,大幅降低参数量,提高计算效率。
  3. 空间不变性:通过卷积和池化操作,CNN 可以很好地捕捉输入数据的空间特征,例如边缘、纹理和形状等。

工作流程(以图像分类为例)

  1. 输入:输入一张图像,假设大小为 32×32×332 \times 32 \times 3(宽、长、RGB 通道)。
  2. 卷积:用多个卷积核扫描图像,提取低层特征(如边缘)。
  3. 激活:应用 ReLU 非线性激活函数。
  4. 池化:对卷积特征进行下采样,减少数据维度。
  5. 多次重复卷积和池化:提取更深层次特征(如纹理、形状等)。
  6. 全连接:将提取的高层特征展平,输入全连接层,用于最终的分类。
  7. 输出:通过 softmax 函数输出每个类别的概率。

优点

  1. 自动特征提取:无需手工设计特征,CNN 能自动学习有效的特征表示。
  2. 减少参数量:通过局部连接和参数共享,参数量远少于传统的全连接网络。
  3. 适用于图像处理:特别适合图像分类、目标检测、图像生成等任务。

应用场景

  1. 图像处理:图像分类、目标检测、图像分割(如识别猫狗图片)。
  2. 自然语言处理:文本分类、句法分析。
  3. 医疗影像分析:癌症检测、医学图像分割。
  4. 自动驾驶:目标检测(如行人识别、车道检测)。
  5. 其他领域:推荐系统、时间序列数据分析等。

CNN 是深度学习的核心技术之一,它在视觉领域取得了显著的成功。通过提取多层次的特征,CNN 能够从简单的边缘信息到复杂的全局信息,逐步构建对输入数据的理解。

C语言示例

要实现一个包含完整架构的卷积神经网络(输入层、卷积层、池化层、全连接层和输出层)的代码,虽然复杂度会有所增加,但我们可以通过简化网络结构,构建一个基本的 CNN 框架来实现。

以下是一个简单的例子,它会:

  1. 输入层:定义输入图像(一个 4x4 矩阵)。
  2. 卷积层:用一个 3x3 的卷积核计算特征图。
  3. 池化层:用最大池化(2x2)下采样特征图。
  4. 全连接层:将池化后的数据展平成向量并通过权重连接。
  5. 输出层:输出一个二维分类结果(假设为二分类任务)。

完整代码

  1#include <stdio.h>
  2#include <math.h>
  3
  4#define INPUT_SIZE 4
  5#define KERNEL_SIZE 3
  6#define CONV_OUTPUT_SIZE (INPUT_SIZE - KERNEL_SIZE + 1)
  7#define POOL_SIZE 2
  8#define POOL_OUTPUT_SIZE (CONV_OUTPUT_SIZE / POOL_SIZE)
  9#define FC_INPUT_SIZE (POOL_OUTPUT_SIZE * POOL_OUTPUT_SIZE)
 10#define FC_OUTPUT_SIZE 2 // 假设输出为2类
 11
 12// 激活函数(ReLU)
 13float relu(float x) {
 14    return x > 0 ? x : 0;
 15}
 16
 17// 打印一维矩阵
 18void printVector(float vector[], int size) {
 19    for (int i = 0; i < size; i++) {
 20        printf("%.2f ", vector[i]);
 21    }
 22    printf("\n");
 23}
 24
 25// 打印二维矩阵
 26void printMatrix(float matrix[][CONV_OUTPUT_SIZE], int rows, int cols) {
 27    for (int i = 0; i < rows; i++) {
 28        for (int j = 0; j < cols; j++) {
 29            printf("%.2f ", matrix[i][j]);
 30        }
 31        printf("\n");
 32    }
 33}
 34
 35// 卷积层操作
 36void convolution2D(float input[INPUT_SIZE][INPUT_SIZE], 
 37                   float kernel[KERNEL_SIZE][KERNEL_SIZE], 
 38                   float output[CONV_OUTPUT_SIZE][CONV_OUTPUT_SIZE]) {
 39    for (int i = 0; i < CONV_OUTPUT_SIZE; i++) {
 40        for (int j = 0; j < CONV_OUTPUT_SIZE; j++) {
 41            float sum = 0.0;
 42            for (int ki = 0; ki < KERNEL_SIZE; ki++) {
 43                for (int kj = 0; kj < KERNEL_SIZE; kj++) {
 44                    sum += input[i + ki][j + kj] * kernel[ki][kj];
 45                }
 46            }
 47            output[i][j] = relu(sum); // 应用 ReLU 激活
 48        }
 49    }
 50}
 51
 52// 最大池化层操作
 53void maxPooling2D(float input[CONV_OUTPUT_SIZE][CONV_OUTPUT_SIZE], 
 54                  float output[POOL_OUTPUT_SIZE][POOL_OUTPUT_SIZE]) {
 55    for (int i = 0; i < POOL_OUTPUT_SIZE; i++) {
 56        for (int j = 0; j < POOL_OUTPUT_SIZE; j++) {
 57            float maxVal = -INFINITY;
 58            for (int pi = 0; pi < POOL_SIZE; pi++) {
 59                for (int pj = 0; pj < POOL_SIZE; pj++) {
 60                    float val = input[i * POOL_SIZE + pi][j * POOL_SIZE + pj];
 61                    if (val > maxVal) {
 62                        maxVal = val;
 63                    }
 64                }
 65            }
 66            output[i][j] = maxVal;
 67        }
 68    }
 69}
 70
 71// 全连接层操作
 72void fullyConnected(float input[], float weights[][FC_OUTPUT_SIZE], float biases[], float output[]) {
 73    for (int i = 0; i < FC_OUTPUT_SIZE; i++) {
 74        float sum = 0.0;
 75        for (int j = 0; j < FC_INPUT_SIZE; j++) {
 76            sum += input[j] * weights[j][i];
 77        }
 78        output[i] = relu(sum + biases[i]); // 应用 ReLU 激活
 79    }
 80}
 81
 82int main() {
 83    // 输入层(4x4 图像)
 84    float input[INPUT_SIZE][INPUT_SIZE] = {
 85        {1, 2, 3, 0},
 86        {4, 5, 6, 1},
 87        {7, 8, 9, 2},
 88        {0, 1, 2, 3}
 89    };
 90
 91    // 卷积核(3x3)
 92    float kernel[KERNEL_SIZE][KERNEL_SIZE] = {
 93        {1, 0, -1},
 94        {1, 0, -1},
 95        {1, 0, -1}
 96    };
 97
 98    // 卷积层输出
 99    float convOutput[CONV_OUTPUT_SIZE][CONV_OUTPUT_SIZE] = {0};
100    convolution2D(input, kernel, convOutput);
101
102    printf("Convolution Output:\n");
103    printMatrix(convOutput, CONV_OUTPUT_SIZE, CONV_OUTPUT_SIZE);
104
105    // 池化层输出
106    float poolOutput[POOL_OUTPUT_SIZE][POOL_OUTPUT_SIZE] = {0};
107    maxPooling2D(convOutput, poolOutput);
108
109    printf("\nPooling Output:\n");
110    printMatrix(poolOutput, POOL_OUTPUT_SIZE, POOL_OUTPUT_SIZE);
111
112    // 展平池化层输出
113    float flatInput[FC_INPUT_SIZE] = {0};
114    int index = 0;
115    for (int i = 0; i < POOL_OUTPUT_SIZE; i++) {
116        for (int j = 0; j < POOL_OUTPUT_SIZE; j++) {
117            flatInput[index++] = poolOutput[i][j];
118        }
119    }
120
121    // 全连接层权重和偏置
122    float fcWeights[FC_INPUT_SIZE][FC_OUTPUT_SIZE] = {
123        {0.1, 0.2}, {0.3, 0.4}, {0.5, 0.6}, {0.7, 0.8},
124        {0.9, 1.0}, {1.1, 1.2}, {1.3, 1.4}, {1.5, 1.6},
125        {1.7, 1.8}
126    };
127    float fcBiases[FC_OUTPUT_SIZE] = {0.5, -0.5};
128
129    // 全连接层输出
130    float fcOutput[FC_OUTPUT_SIZE] = {0};
131    fullyConnected(flatInput, fcWeights, fcBiases, fcOutput);
132
133    printf("\nFully Connected Output:\n");
134    printVector(fcOutput, FC_OUTPUT_SIZE);
135
136    return 0;
137}

核心解释

  1. 卷积层

    • 使用 3x3 卷积核。
    • 计算结果并通过 ReLU 激活函数进行非线性处理。
  2. 池化层

    • 采用 2x2 最大池化,减小特征图的大小。
  3. 全连接层

    • 展平池化层的输出(从 2D 转为 1D)。
    • 使用简单的权重和偏置实现全连接操作。
  4. 输出层

    • 假设输出为二维(用于分类,比如二分类问题)。

输出示例

运行结果可能如下:

Convolution Output:
12.00 15.00 
6.00 6.00 

Pooling Output:
15.00 

Fully Connected Output:
33.00 34.00 

这段代码实现了最基本的 CNN 结构,适合初学者理解 CNN 的各个模块如何协作。如果需要进一步扩展,可以加入更多卷积层、池化层或使用优化方法(如反向传播)进行训练。


最后修改于 2025-01-26 16:56