从认识AI开始-----卷积神经网络(CNN)

article/2025/6/26 2:11:07

前言

在上一篇文章里,我们手写了多层感知机,细心地小伙伴们可能会发现一个问题,对于MLP,有两个突出的问题,尤其是处理图像任务时:

  • 参数太多:例如一个28*28的图像扁平化为784维后,连接一个256个神经元的隐藏层就需要784*256=200704个权重参数。
  • 空间结构丢失:MLP无法感知像素之间的空间布局,图像各像素之间的绝对位置信息与相对位置信息完全被打乱。

此时,急切需要一种更聪明的模型出现,那就是今天我要讲的卷积神经网络(CNN)。它通过局部连接参数共享来有效减少参数的数量,同时,也保留了图像的空间结构信息,相比MLP而言,实现了更强大的学习能力。


一、卷积神经网络的两个动机:局部性与平移不变性

1. 局部性

我们知道,在图像中,一个像素点的类别往往由其附近的像素共同决定,比如一张小狗图像,其中眼睛部分是有多个像素点共同决定的。因此,我们无需全连接每一个像素与神经元,而是让神经元只感知图像中的一个局部区域,例如4*4 区域,这就是我们常说的“感受野”的概念

  • 感受野的存在让网络能够首先捕捉局部特征,然后再逐层组合形成全局特征。

2. 平移不变性(参数共享)

所谓的平移不变性其实很好理解,就是我们在图像中使用相同的卷积核在图像上滑动,相当于使用同一组权重探测图像的所有位置,这同时也是“卷积”的概念

input(image_{x,y})=\sum _{i,j}W_{i,j}*image_{x+i,y+j} + b_{i,j}

通过上面的公式我们可以看出,相比于全连接,卷积操作大大降低了参数数量,一个卷积核可能只有3*3=9个参数,同时,卷积操作在移动的过程中是用的同一个卷积核,也带来了平移不变性:即使不同图片里的相同特征出现在不同的位置,特征仍可被捕捉,例如一只鸟出现在图像的左上角,那么使用同一个卷积核,即使该鸟出现在图片的右下角,该鸟所具有的特征仍能被捕捉到。


二、卷积神经网络的基本结构:

对于一个典型的CNN来说,需要包括以下几个结构:

1. 卷积层:

所谓的卷积层,就是:用一个卷积核(小窗口)在图像上进行滑动,与区域内像素做加权和。具体的公式可以写成这样:

设输入图像为 X\in\mathbb{R}^{H*W},卷积核 K\in\mathbb{R}^{k*k},输出 y\in\mathbb{R}^{(H-k+1)*(W-k+1)} :

y(i,j)=\sum_{u=0}^{k-1}\sum_{v=0}^{k-1}X(i+u,j+v)*K(u,v)

其计算过程如下图所示:

接下来,我将手写一下具体的卷积操作的代码流程,对具体操作感兴趣的小伙伴们可以看一下:

import torch
import numpy as npclass conv2d(nn.Module):def __init__(self, kernel_size):super(conv2d, self).__init__()self.kernel_size = kernel_sizeself.w = nn.Parameter(torch.rand(kernel_size))self.b = nn.Parameter(torch.zeros(1))def conv(self, X, K):H, W = K.shapey = torch.zeros(X.shape[0]-H+1, X.shape[1]-W+1)for i in range(y.shape[0]):for j in range(y.shape[1]):y[i,j] = (X[i:i+H, j:j+W] * K).sum()return ydef forward(self, x):return self.conv(x, self.w) + self.b# 示例
mycnn = conv2d(kernel_size=(3,3))
x = torch.arange(25, dtype=torch.float32).reshape(5,5)output = mycnn(x)
print(output, output.shape)

2. 填充(Padding)

无填充时,图像边缘信息会被逐层压缩掉,Padding的目的是保留边缘特征,同时,经过padding后,能控制输出的尺寸。填充减小的输出大小与层数具有线性相关性。

常见填充有两种:

  • padding = 0:无填充时,尺寸缩小
  • padding= 1:即上下左右同时填充行和一列,能够使输入输出尺寸一致
  • padding通常取核大小减1

让我们通过下面的图片来看具体的padding过程:

 接下来,我将手写一下具体的pddding操作的代码流程,在conv中添加padding:

import torch
import torch.nn as nn
import torch.nn.functional as Fclass conv2d(nn.Module):def __init__(self, kernel_size, padding=0):super(conv2d, self).__init__()self.kernel_size = kernel_sizeself.padding = paddingself.w = nn.Parameter(torch.rand(kernel_size))self.b = nn.Parameter(torch.zeros(1))def conv(self, X, K):H, W = K.shapeif self.padding > 0:X = F.pad(X, (self.padding, self.padding, self.padding, self.padding), mode='constant', value=0)y = torch.zeros(X.shape[0]-H+1, X.shape[1]-W+1)for i in range(y.shape[0]):for j in range(y.shape[1]):y[i,j] = (X[i:i+H, j:j+W] * K).sum()return ydef forward(self, x):return self.conv(x, self.w) + self.b

3. 步幅(stride)

步幅的作用也很重要,具体来说:控制卷积核的滑动速度,从而影响下采样。

步幅越大,输出的尺寸越小,计算越快,但信息更稀疏。步幅减小的输出大小与层数的指数相关:

  • stride = 1:默认滑动一步,最大限度的保留特征
  • stride = 2:相当于下采样

接下来,我将手写一下具体的Stride操作的代码流程,在conv中添加Stride:

import torch
import torch.nn as nn
import torch.nn.functional as Fclass conv2d(nn.Module):def __init__(self, kernel_size, padding=0, stride=1):super(conv2d, self).__init__()self.kernel_size = kernel_sizeself.padding = paddingself.stride = strideself.w = nn.Parameter(torch.rand(kernel_size))self.b = nn.Parameter(torch.zeros(1))def conv(self, X, K):H, W = K.shapeif self.padding > 0:X = F.pad(X, (self.padding, self.padding, self.padding, self.padding), mode='constant', value=0)y = torch.zeros((X.shape[0]-H)//self.stride + 1, (X.shape[1]-W)//self.stride + 1)for i in range(y.shape[0]):for j in range(y.shape[1]):xi = i * self.stridexj = j * self.stridey[i,j] = (X[i:xi+H, j:xj+W] * K).sum()return ydef forward(self, x):return self.conv(x, self.w) + self.b

4. 池化层(Pooling)

之所以会设计池化层,主要是因为卷积层对于图像的位置信息很敏感

  • 降低维度
  • 保留关键信息(如边缘、纹理)
  • 增加一定程度的平移不变性,从而降低对位置的敏感性

接下来,我将手写一下具体的Pooling操作的代码流程,以最大池化为例:

import torch
import torch.nn as nnclass maxpool2d(nn.Module):def __init__(self, kernel_size, stride=None):super(maxpool2d, self,).__init__()self.kernel_size = kernel_sizeself.stride = stride or kernel_sizedef forward(self, x):H, W = x.shapeKH, KW = self.kernel_size, self.kernel_sizeSH, SW = self.stride, self.stridey = torch.zeros((H-KW)//SH + 1,(W-KW)//SW + 1)for i in range(y.shape[0]):for j in range(y.shape[1]):xi = i * SHxj = j * SWy[i,j] = x[xi:xi+KH, xj:xj+KW].max()return y

 5. 输出尺寸与输入尺寸、卷积核、填充、步幅的关系

输出尺寸与输入大小、卷积核大小、填充、步幅具有下述关系:

O=\frac {N+2P-K}{S}+1

其中,N 为输入尺寸,P 为padding,K 为卷积核尺寸,S 为步幅大小


三、多通道输入

在现实世界中,大多数图片都是RGB三通道,比如,对于大小为32*32的RGB图像,其形状就是[3, 32, 32],有三个输入通道,这三个通道分别包含不同的颜色信息,缺少任何一个通道都会导致图像特征信息丢失。具体做法如下:

通常,在卷积层中,我们需要为每个输入通道分配一个卷积核,每个卷积核会在输入通道上滑动,从不同视角捕捉局部结构信息(比如边缘、纹理等),多个输入通道意味着模型可以并行提取多种不同类型的特征,提升表达能力。

具体表达形式如下:

y^{out}=\sum_{c=1}^{C_{in}}X_c^{in} * W_c + b

其中,X_c^{in} 为第c个输入通道,W_c 对应第 c 哥输入通道的卷积核, b 为偏置

接下来,我将手写具体的多通道输入代码,各位感兴趣的小伙伴们可以了解一下:

import torch
import torch.nn as nn
import torch.nn.functional as Fclass Conv2D(nn.Module):def __init__(self, in_channels, out_channels, kernel_size, padding=0, stride=1):super(Conv2D, self).__init__()if isinstance(kernel_size, int):kernel_size = (kernel_size, kernel_size)self.kernel_size = kernel_sizeself.padding = paddingself.stride = strideself.in_channels = in_channelsself.out_channels = out_channels# 权重形状: [out_channels, in_channels, kH, kW]self.w = nn.Parameter(torch.rand(out_channels, in_channels, *kernel_size))self.b = nn.Parameter(torch.zeros(out_channels))def conv(self, X, K):# X: [in_channels, H, W]in_channels, H, W = X.shapekH, kW = K.shape[-2], K.shape[-1]# 进行 paddingif self.padding > 0:X = F.pad(X, (self.padding, self.padding, self.padding, self.padding), mode='constant', value=0)H_out = (X.shape[1] - kH) // self.stride + 1W_out = (X.shape[2] - kW) // self.stride + 1Y = torch.zeros((self.out_channels, H_out, W_out))# 多输出通道for oc in range(self.out_channels):for i in range(H_out):for j in range(W_out):region_sum = 0.0for ic in range(self.in_channels):h_start = i * self.strideh_end = h_start + kHw_start = j * self.stridew_end = w_start + kWregion = X[ic, h_start:h_end, w_start:w_end]region_sum += (region * K[oc, ic]).sum()Y[oc, i, j] = region_sum + self.b[oc]return Ydef forward(self, x):# x: [in_channels, H, W]return self.conv(x, self.w)
# 示例 3通道输入,32x32图像
x = torch.randn(3, 32, 32)  
conv = Conv2D(in_channels=3, out_channels=2, kernel_size=3, padding=1, stride=1)
y = conv(x)
# torch.Size([2, 32, 32])
print(y.shape)  

四、使用Pytorch内置函数构建基础CNN模型

接下来,我将使用Pytorch内置的卷积层来构建一个基础的CNN网络,进行手写数字识别(MINST):

import torch
import torch.nn as nn
import torch.nn.functional as Fclass cnn(nn.Module):def __init__(self, ):super(cnn, self).__init__()self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1)self.pool = nn.MaxPool2d(kernel_size=3, stride=2)self.conv2 = nn.Conv2d(16,32,3,padding=1)self.fc1 = nn.Linear(32*7*7, 128)self.fc2 = nn.Linear(128, 10)def forward(self, x):x = F.relu(self.conv1(x))x = self.pool(x)x = F.relu(self.conv2(x))x = self.pool(x)    x = x.view(x.size(0), -1)x = self.fc1(x)x = F.relu(x)x = self.fc2(x)return x
# 示例
model = cnn()
# [batch, channel, H, W]
x = torch.randn(4,1,28,28)
output = model(x)
print(output.shape)

总结

以上就是本文的全部内容,相信小伙伴们在看完本篇之后会对卷积神经网络有更深刻的理解:CNN作为深度学习中处理图像、语音、视频等具有空间数据的模型,其设计核心在于:局部连接模拟生物视觉感知,关注局部区域特征;参数共享能显著减少模型参数;平移不变性与池化操作能降低CNN对位置的敏感性。相较于传统的MLP,CNN的具有低参数,并且保留了数据的空间信息。


如果小伙伴们觉得本文对各位有帮助,欢迎:👍点赞 | ⭐ 收藏 |  🔔 关注。我将持续在专栏《人工智能》中更新人工智能知识,帮助各位小伙伴们打好扎实的理论与操作基础,欢迎🔔订阅本专栏,向AI工程师进阶!


http://www.hkcw.cn/article/fVnXXSLsCL.shtml

相关文章

从“固定“到“流动“:移动充电如何重塑用户体验?

在传统充电模式中,"固定"不仅是技术的特征,更成为用户行为的枷锁——人们需要规划行程、寻找插座、等待电量填满,这种被动适配正在被移动充电技术颠覆。当充电设备从墙面解放,化身可携带的能源胶囊,甚至嵌入…

π0基于自己的数据集微调,convert_libero_data_to_lerobot.py 各种报错的解决方法

π0 github地址 在做:π0基于自己的数据集微调 Fine-Tuning Base Models on Your Own Data 的时候遇到各种报错,记录一下。 推荐教程:π0的微调——如何基于各种开源数据集、以及私有数据集微调通用VLA π0(含我司七月的微调实践及在机械臂上…

在哈佛演讲25岁中国女生发声 签证不确定性影响未来规划

5月30日,哈佛大学毕业典礼上,中国学生蒋雨融身穿中国传统服饰发表了一段动情演讲。25岁的蒋雨融来自中国青岛,获得了哈佛大学国际发展方向的公共行政硕士学位。她提到,特朗普政府威胁遣返学生签证持有者,这给她的未来规划带来了极大的不确定性。蒋雨融表示自己也开始考虑去…

半导体B2B分销中台如何与ERP集成

在半导体B2B分销领域,高效的销售链管理和精准的数据流转是企业竞争力的核心。随着市场需求的快速变化和业务规模的持续扩张,传统的人工下单销售模式已难以满足实时协同、动态定价、库存优化等关键需求。如何将半导体B2B分销中台与企业核心ERP系统深度集成…

普通遥控器竟牵出10亿元大案 27名嫌疑人落网

涉及河南、四川、浙江等16省市,涉案交易金额达10.3亿元。随着最后一名境外逃犯投案,内蒙古自治区鄂尔多斯市杭锦旗这起利用流量计调节流量、篡改数据非法牟利的合同诈骗案成功告破,27名犯罪嫌疑人全部落网。2024年3月,一封匿名举报信打破了平静:某石油工程有限公司通过更改…

CCleaner:高效免费系统清理软件指南

本文还有配套的精品资源,点击获取 简介:免费系统清理软件是旨在帮助用户清除计算机中的无用文件以优化性能和释放硬盘空间的应用程序。它们通常包含临时文件删除、注册表清理和无用程序卸载等功能。本文将探讨CCleaner等系统清理工具如何通过清理临时…

C语言—扫雷项目

一、扫雷游戏分析和设计 (1.1)扫雷游戏功能说明 • 使⽤控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷 • 可以排查雷 ◦ 如果位置不是雷,就显⽰周围有⼏个雷 ◦ 如…

[蓝桥杯] 挖矿(CC++双语版)

题目链接 P10904 [蓝桥杯 2024 省 C] 挖矿 - 洛谷 题目理解 我们可以将这道题中矿洞的位置理解成为一个坐标轴,以题目样例绘出坐标轴: 样例: 输入的5为矿洞数量,4为可走的步数。第二行输入是5个矿洞的坐标。输出结果为在要求步数…

【C语言】 —— 预处理详解(下)

【C语言】 —— 预处理详解(下) 前言七、# 和 \##7.1 # 运算符7.2 ## 运算符 八、命名约定九、# u n d e f undef undef十、命令行定义十一、条件编译11.1、单分支的条件编译11.2、多分支的条件编译11.3、判断是否被定义11.4、嵌套指令 十二、头文件的包…

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

一、引言:理解内存管理的核心价值 在系统级编程领域,内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言,赋予开发者直接操作内存的能力,却也要求开发者深入理解内存布局与生命周期管理。本文将从内…

VS Studio2022安装教程(保姆级教程)

1.下载 官网下载: Visual Studio 2022 IDE - 适用于软件开发人员的编程工具 (microsoft.com)https://visualstudio.microsoft.com/zh-hans/vs/ 1.点击下载Community2022(社区版),等待下载完成之后,运行安装包(VisualstudioSetup.exe) 2.等待…

【CSAPP】【attacklab】实验三 Attack lab 详解

前置知识 call 指令 ret 指令 寄存器rip 栈 小端模式 栈向下生长,栈顶在低地址,栈底在高地址 指令寄存器(RIP)包含下一条将要被执行的指令的逻辑地址。 通常情况下,每取出一条指令后,RIP会自增指向下一…

【C/C++】字符函数和字符串函数

文章目录 前言字符函数和字符串函数1.字符分类函数2.字符转换函数3.strlen的使用和模拟实现3.1 代码演示3.2 strlen返回值3.3 strlen的模拟实现 4.strcpy的使用和模拟实现4.1 代码演示4.2 模拟实现 5.strcat的使用和模拟实现5.1 代码演示5.2 模拟实现 6.strcmp的使用和模拟实现…

C/C++之内存管理

1. 内存分布 我们定义的变量对于电脑来说也叫数据,同时电脑也会把这些数据分为不同的类型,分别是局部数据,静态数据,全局数据,常量数据和动态申请数据。 在 C 中,各类数据存储位置如下: • 局…

C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比

C从入门到实战(十一)详细讲解C/C语言中内存分布与C与C内存管理对比 前言一、C/C语言中内存分布1.内核空间2.栈3.堆4.数据段5.代码段 二、例题带练巩固C/C语言中内存分布的知识题目讲解题目答案 三、C语言动态内存分配(知识回顾)3.…

Educational Codeforces Round 175 (C.二分 D.树形结构、dp)

文章目录 2025.3.3C. Limited Repainting(二分)题意思路代码 D. Tree Jumps(树形结构、dp)题意思路代码 2025.3.3 Educational Codeforces Round 175 (Rated for Div. 2) C. Limited Repainting(二分) 题意 给出一个字符串a由“R”B“组成,不同位置对应一个惩罚…

老太突然倒地吓坏路人民警紧急相救 家属感激救命之恩

“谢谢你们帮我父亲‘捡’回一条命,再晚一会儿后果不堪设想!”5月21日,市民刘女士接到民警电话时再次表达了对紧急救援的感激之情。她的父亲76岁,患有阿尔兹海默病,下午扛着锄头出门后一直未归,家人找了3个多小时都没找到。5月19日傍晚6时许,大冶市公安局还地桥派出所接…

53岁男子诱骗侵害幼女被判刑 深挖彻查揭露更多罪行

5月29日,江苏省人民检察院召开新闻发布会,介绍了近年来加强未成年人网络司法保护的工作情况及典型案例。如皋市检察院副检察长卢海琴介绍了一起典型案例,通过深挖彻查,案件从1名被告人追到3名被告人、从1个罪名查到5个罪名、从1起强奸事实挖到19起犯罪事实、从1名被害人增加…

谷歌DeepMind最强手语翻译模型登场 打破沟通障碍

谷歌DeepMind团队于5月27日宣布推出SignGemma,这是其迄今为止最强大的手语翻译模型,能够将手语转化为口语文本。该开源模型计划在今年晚些时候加入Gemma模型家族。SignGemma支持多语言功能,但目前主要针对美国手语(ASL)和英语进行了深度优化,开发者可以自由使用并改进它。…

【C++】STL详解-----(二)vetor的使用

文章目录 vector的介绍vector的使用:元素访问empty vector的增删查改push_back和pop_backinsert和erase vector迭代器失效问题迭代器失效解决方法 vector的介绍 vector是可变大小数组的容器vector采用连续空间存储的方式,同时也表示可以采用下标访问vec…