深度学习笔记(二)
卷积神经网络
卷积神经网络
卷积神经网络(Convolutional Neural Network, CNN)是一种专为处理网格结构数据(如图像和时间序列)设计的深度学习模型。它是一类前馈神经网络,包含专门用于提取数据空间信息的卷积操作,其主要特点是通过局部连接和参数共享,有效地提取输入数据的局部特征,同时降低模型的参数量。
核心组成部分
输入层
- 接受原始输入数据,例如一张图像(RGB 图像是一个三维张量:宽度 × 高度 × 通道数)。
卷积层(Convolutional Layer)
- 核心功能是提取输入数据的局部特征,例如边缘、纹理等。通过卷积核(filters/kernels)在输入数据上滑动,计算内积,生成特征图(Feature Map)。
- 特点:
- 局部感受野:每个神经元只感知输入的一小部分(局部区域)。
- 参数共享:相同的卷积核在整个输入上应用,从而减少参数数量。
激活函数(Activation Function)
- 常用 ReLU(Rectified Linear Unit)作为非线性激活函数,增加模型的表达能力。
池化层(Pooling Layer)
- 用于下采样特征图,减少数据维度,同时保留关键特征,减小计算复杂度并降低过拟合风险。
- 常用方法:最大池化(Max Pooling) 和 平均池化(Average Pooling)。
全连接层(Fully Connected Layer)
- 将卷积和池化层输出的高维特征展平为一维向量,并连接到输出层。通过加权求和和激活函数计算最终输出。
输出层
- 根据任务要求输出结果,例如分类任务的概率分布(通过 softmax)或回归任务的实值。
特点
- 局部连接:每个神经元只关注输入的一部分(称为感受野),非常适合处理图像等具有局部相关性的结构化数据。
- 参数共享:卷积核在整个输入数据上重复使用,大幅降低参数量,提高计算效率。
- 空间不变性:通过卷积和池化操作,CNN 可以很好地捕捉输入数据的空间特征,例如边缘、纹理和形状等。
工作流程(以图像分类为例)
- 输入:输入一张图像,假设大小为 32×32×332 \times 32 \times 3(宽、长、RGB 通道)。
- 卷积:用多个卷积核扫描图像,提取低层特征(如边缘)。
- 激活:应用 ReLU 非线性激活函数。
- 池化:对卷积特征进行下采样,减少数据维度。
- 多次重复卷积和池化:提取更深层次特征(如纹理、形状等)。
- 全连接:将提取的高层特征展平,输入全连接层,用于最终的分类。
- 输出:通过 softmax 函数输出每个类别的概率。
优点
- 自动特征提取:无需手工设计特征,CNN 能自动学习有效的特征表示。
- 减少参数量:通过局部连接和参数共享,参数量远少于传统的全连接网络。
- 适用于图像处理:特别适合图像分类、目标检测、图像生成等任务。
应用场景
- 图像处理:图像分类、目标检测、图像分割(如识别猫狗图片)。
- 自然语言处理:文本分类、句法分析。
- 医疗影像分析:癌症检测、医学图像分割。
- 自动驾驶:目标检测(如行人识别、车道检测)。
- 其他领域:推荐系统、时间序列数据分析等。
CNN 是深度学习的核心技术之一,它在视觉领域取得了显著的成功。通过提取多层次的特征,CNN 能够从简单的边缘信息到复杂的全局信息,逐步构建对输入数据的理解。
C语言示例
要实现一个包含完整架构的卷积神经网络(输入层、卷积层、池化层、全连接层和输出层)的代码,虽然复杂度会有所增加,但我们可以通过简化网络结构,构建一个基本的 CNN 框架来实现。
以下是一个简单的例子,它会:
- 输入层:定义输入图像(一个 4x4 矩阵)。
- 卷积层:用一个 3x3 的卷积核计算特征图。
- 池化层:用最大池化(2x2)下采样特征图。
- 全连接层:将池化后的数据展平成向量并通过权重连接。
- 输出层:输出一个二维分类结果(假设为二分类任务)。
完整代码
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}
核心解释
卷积层:
- 使用 3x3 卷积核。
- 计算结果并通过 ReLU 激活函数进行非线性处理。
池化层:
- 采用 2x2 最大池化,减小特征图的大小。
全连接层:
- 展平池化层的输出(从 2D 转为 1D)。
- 使用简单的权重和偏置实现全连接操作。
输出层:
- 假设输出为二维(用于分类,比如二分类问题)。
输出示例
运行结果可能如下:
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