微信扫码
添加专属顾问
我要投稿
从传统特征工程到CNN革命,一文读懂计算机视觉的进化之路。 核心内容: 1. 计算机视觉从手工特征到深度学习的范式转变 2. CNN核心原理与架构设计的数学可视化解析 3. ImageNet竞赛中经典模型的性能突破与历史意义
阿里妹导读
本文系统回顾了计算机视觉的发展历程,从早期基于手工特征的传统方法,到深度学习的崛起与卷积神经网络(CNN)的广泛应用,并通过数学原理、代码示例与可视化手段,全面解析了卷积操作的本质与CNN的架构设计。
【系列文章】
沿着 AI 的发展脉络,本系列文章从Seq2Seq到RNN,再到Transformer,直至今日强大的GPT模型,我们将带你一步步深入了解这些关键技术背后的原理与实现细节。无论你是初学者还是有经验的开发者,相信读完这个系列文章后,不仅能掌握Transformer的核心概念,还能对其在整个NLP领域中的位置有一个全面而深刻的认识。那就让我们一起开始这段学习之旅吧!
计算机视觉的发展
计算机视觉是人工智能的核心分支,目标是赋予计算机像人类一样理解视觉世界的能力——从图像、视频等视觉数据中提取有价值的信息,完成分类、检测、分割、跟踪、理解场景语义等任务。
在早期,计算机视觉依赖手工设计特征,如 SIFT、HOG 等算法,结合传统机器学习模型(如 SVM、随机森林)处理视觉任务,但这种方式受限于特征设计的经验性和数据规模,识别的准确率有限。
说起计算机视觉就不得不提到 ImageNet,ImageNet 大规模视觉识别挑战赛由斯坦福大学李飞飞团队于 2010 年发起的,其核心目标是推动图像分类、目标检测、场景解析等任务的算法创新。前两年 ImageNet 竞赛的排行榜由传统机器学习方法垄断,2010 年的冠军 SIFT+FVS(错误率 28.2%)、2011 年的冠军稀疏编码(错误率 25.7%),这和人类平均错误率 5.1% 相距甚远,无法达到工业落地标准。
既然传统机器学习不行,那么深度学习是不是可以解决问题?然而传统深度学习处理图像时存在两个问题:
1.参数爆炸:假设输入是 100x100 像素的图像,全连接层的第一个隐藏层若有 1 万个神经元,参数数量就达到 100×100×10000 = 1亿,计算效率极低。
2.忽略空间结构:图像中相邻像素往往具有强相关性(如边缘、纹理),人类视觉系统正是通过局部特征逐级组合来识别物体的,但传统神经网络无法利用这些信息。
但 2012 年发生了历史性转折——使用 CNN 架构的 AlexNet 在 ImageNet 竞赛中以 15.3% 的错误率碾压传统方法(第二名 26.2%),这也直接导致传统特征工程研究的式微,学术界开始转向 CNN 架构探索。2014 年的 VGGNet 通过堆叠 16-19 层卷积层,将错误率降至 7.3%;2015 年的 ResNet 引入残差连接,错误率进一步降至 3.57%,首次超越人类水平。
如今 CNN(Convolutional Neural Network)在图像分类、目标检测(自动驾驶中的行人检测、车辆检测)、图像分割(医学图像中的器官区域)、人脸识别等领域都有广泛的应用。
什么是卷积
在数学中,卷积(Convolutional)是一种描述两个函数如何通过 “相互作用” 生成新函数的运算,其核心思想是 “翻转、平移、叠加”。对于连续函数其数学公式如下:
核心分三步步骤
1.翻转:将关于原点翻转得到
;
2.平移:将翻转后的函数平移 t 个单位,得到;
3.相乘并积分:将与平移后的
相乘,再对所有
积分,得到在位置t
处的卷积值;
耗时 6 小时,顺便也复(yu)习了部分导数、微积分知识,终于理解了这个公式的含义,也知道了其对接下来 CNN 图像处理学习影响不大,知道 CNN 的卷积有一定的数学依据就行
对于离散数列和
卷积定义为:
无需积分后公式变得比较好理解,举个例子,若,
,计算其卷积。因为索引越界其值为 0(如
),因为k取值 0~2 即可,其计算步骤如下
1.计算每个n的值(结果是两数列长度和减一,也就是 3 + 2 - 1 = 4,从 0 开始计数为 3)
2.最终
用 Numpy 计算非常简单:
import numpy as np
f = np.array([1,2,3])
g = np.array([4,5])
print(np.convolve(f,g))
卷积的现实意义
卷积不仅在数学理论上刁难大家的作用,在实际中也有许多有趣且直观的例子。其在信号处理、图像处理、概率论、微分方程等领域有非常多应用,本质上体现了系统对输入信号的 “加权叠加响应”。
掷骰子
当掷两个六面骰子时,两个骰子的点数和的概率分布是通过卷积运算得到的。每个骰子的点数可以看作一个离散的概率分布,两个骰子的和就是这两个概率分布的卷积。
一个六面骰子的每个点数都有一定概率(),如果没出老千应该概率相同,都是 1/6。
两个独立随机变量的和的概率分布,是它们各自概率分布的卷积。因此两个骰子的点数和的概率分布可以通过对单个骰子的概率分布进行卷积得到。
import numpy as np
import matplotlib.pyplot as plt
# 定义单个骰子的概率分布
# 索引0表示点数1,索引1表示点数2,依此类推
dice = np.array([1/6] * 6)
# 计算两个骰子的和的概率分布
sum_dice = np.convolve(dice, dice)
# 定义和的可能取值
sum_values = np.arange(2, 13) # 两个骰子的和从2到12
# 输出结果
print("和\t概率")
for value, prob in zip(sum_values, sum_dice):
print(f"{value}\t{prob:.4f}")
# 绘制概率分布图
plt.bar(sum_values, sum_dice, color='skyblue', edgecolor='black')
plt.xlabel('点数和')
plt.ylabel('概率')
plt.title('两个六面骰子点数和的概率分布')
plt.xticks(sum_values)
plt.show()
可见正常情况下买大买小概率是一样的,7 的话庄家通吃,但只要稍微操作一下庄家就能稳赚了。
import numpy as np
import matplotlib.pyplot as plt
# 均匀骰子(正常骰子)
dice_uniform = np.array([1/6] * 6)
# 灌铅骰子A(6点概率50%)
dice_A = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.5]) # 点数1-6的概率
# 灌铅骰子B(1点概率50%)
dice_B = np.array([0.5, 0.1, 0.1, 0.1, 0.1, 0.1]) # 点数1-6的概率
# 正常骰子的和:dice_uniform * dice_uniform
sum_normal = np.convolve(dice_uniform, dice_uniform)
# 灌铅骰子的和:dice_A * dice_B
sum_loaded = np.convolve(dice_A, dice_B)
sum_values = np.arange(2, 13)
# 绘制对比图
plt.figure(figsize=(12, 6))
plt.rcParams['font.family'] = 'SimSong' # 设置中文字体
# 正常骰子分布
plt.subplot(1, 2, 1)
plt.bar(sum_values, sum_normal, color='lightblue', edgecolor='black', label='均匀分布')
plt.xlabel('点数和')
plt.ylabel('概率')
plt.title('正常骰子的概率分布')
plt.xticks(sum_values)
plt.legend()
# 灌铅骰子分布
plt.subplot(1, 2, 2)
plt.bar(sum_values, sum_loaded, color='coral', edgecolor='black', label='灌铅分布')
plt.xlabel('点数和')
plt.ylabel('概率')
plt.title('灌铅骰子的概率分布')
plt.xticks(sum_values)
plt.legend()
plt.tight_layout()
plt.show()
信号处理
无线通信中的电信号本应是平滑的波浪信息,但现实世界充满了电磁干扰、设备噪声或环境扰动,导致波形失真,收音机的杂音、摄像头的雪花屏等都是信号被噪声污染的表现。
如何从夹杂噪声的信号中提取出 “干净” 的有效信息?一种经典且高效的方法是使用移动平均滤波器(Moving Average Filter)。它的核心思想是通过对信号的局部邻域进行加权平均,抑制高频噪声的干扰,让信号重新回归平滑,而这一过程的数学本质正是卷积运算。
比如一个 3 点移动平均滤波器会将信号中每个点的值替换为该点及其前后各一点的平均值,从而 “熨平” 突变的噪声尖峰。
import numpy as np
import matplotlib.pyplot as plt
# 生成原始信号(一个简单的正弦波加噪声)
x = np.linspace(0, 2 * np.pi, 100)
original_signal = np.sin(x)
noise = np.random.normal(0, 0.5, original_signal.shape)
noisy_signal = original_signal + noise
# 一个长度为 5 的平均滤波器
filter_size = 5
filter_kernel = np.ones(filter_size) / filter_size
# 对信号进行卷积
filtered_signal = np.convolve(noisy_signal, filter_kernel, mode='same') # 保持信号长度不变
# 绘制信号对比图
plt.figure(figsize=(12, 6))
plt.rcParams['font.family'] = 'SimSong' # 设置中文字体
plt.plot(x, original_signal, label='原始信号 (正弦波)', linewidth=2)
plt.plot(x, noisy_signal, label='含噪声信号', alpha=0.7)
plt.plot(x, filtered_signal, label='滤波后信号', linewidth=2)
plt.xlabel('时间')
plt.ylabel('幅度')
plt.title('移动平均滤波器')
plt.legend()
plt.show()
可以观察到,滤波后的信号相比含噪声信号更加平滑,噪声被有效抑制。
CNN 的卷积
CNN 中也利用了类似信号处理中的滤波器(称之为卷积核 Kernel)对图像进行特征提取,卷积核本质上是一个小的权重矩阵,通过在输入图像上进行滑动,执行卷积操作,从而提取图像中的特征。每一个卷积核都可以检测特定类型的特征,像边缘、纹理等。
假设输入是一张二维图像,卷积核在图像上从左到右、从上到下滑动,每次滑动时,卷积核与它覆盖的图像区域对应元素相乘,然后将结果相加,得到一个输出值。这个过程持续进行,直到卷积核遍历完整个图像,最终得到一个特征图。
看个简单的例子,使用不同的卷积核来强化棋盘图像的垂直边缘和水平边缘。
cv2 是 OpenCV 库在 Python 中的接口,它是一个广泛应用于计算机视觉任务的开源库,提供了丰富的图像处理和计算机视觉算法。
代码中卷积核使用的是 Sobel 核,Sobel 核是一种广泛应用于图像处理和计算机视觉中的边缘检测滤波器。旨在突出图像中的边缘特征,帮助识别图像中物体的边界和形状。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 定义垂直边缘检测卷积核
vertical_kernel = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]], dtype=np.float32)
# 定义水平边缘检测卷积核
horizontal_kernel = np.array([[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]], dtype=np.float32)
# 加载图像
image = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
# 进行卷积操作
vertical_output = cv2.filter2D(image, -1, vertical_kernel)
horizontal_output = cv2.filter2D(image, -1, horizontal_kernel)
# 显示灰度图像和处理后的图像
plt.rcParams['font.family'] = 'SimSong' # 设置中文字体
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(image, cmap='gray')
axes[0].set_title('灰度图像')
axes[1].imshow(vertical_output, cmap='gray')
axes[1].set_title('垂直纹理增强')
axes[2].imshow(horizontal_output, cmap='gray')
axes[2].set_title('水平纹理增强')
plt.tight_layout()
plt.show()
垂直边缘检测卷积核的设计是基于图像中垂直边缘处灰度值的变化特点。在垂直边缘处,图像的一侧灰度值较高,而另一侧灰度值较低。卷积核中的数值能够捕捉到这种灰度值的变化,当卷积核与图像中的垂直边缘对齐时,加权求和的结果会比较大,从而在输出图像中突出显示垂直边缘。
卷积操作本质上是对两个函数进行的一种数学运算,在图像处理中,图像和卷积核可以看作一个二元离散函数,卷积操作可以理解为对图像函数和卷积核函数
进行卷积。通过卷积操作,我们可以得到一个新的函数(新的图像数组),它是原图像函数和卷积核函数相互作用的结果。
CNN 架构
终于可以看看 CNN 的架构了,CNN 由多个功能层堆叠而成,核心层包括:
1.输入层:负责接收原始数据,如图像的像素值。对于彩色图像,通常有红、绿、蓝三个通道,而灰度图像则只有一个通道。
2.卷积层:图像特征的 “过滤器”,通过卷积核在输入图像上滑动,计算局部像素的加权和,提取局部特征(如边缘、纹理、颜色)。
3.激活函数层:通常在卷积层之后使用,为模型引入非线性因素,使模型能够学习到更复杂的函数关系。常见的激活函数有 ReLU、Sigmoid、Tanh 等。
4.池化层:特征的 “压缩器”,对卷积层输出的特征图进行降维,保留关键信息,减少计算量,同时增强特征的平移不变性(如物体位置稍有移动,特征仍不变)。
5.全连接层:特征的 “分类器”,将池化层输出的特征图 “拉平” 成一维向量,通过全连接网络进行分类(如判断图像是猫还是狗)
6.输出层:根据具体的任务输出结果。例如在分类任务中,输出各个类别的概率;在回归任务中,输出连续的数值。常用的输出层激活函数有 Softmax(用于多分类任务,将输出转化为各个类别的概率分布)、Sigmoid(用于二分类任务,输出 0 到 1 之间的概率值)等。
在实际的 CNN 架构中,通常会包含多个卷积层、激活函数层和池化层的组合,形成一个深度网络,以逐步提取更高级、更抽象的特征。不同的 CNN 模型在层数、卷积核大小、步长、池化方式等方面会有所不同,以适应不同的任务和数据特点。
CNN 如何解决参数爆炸问题
前面提到了传统深度神经网络在处理图像时候的两大难题在 CNN 架构下都有了很好的解决。
参数爆炸
传统神经网络在处理图片时候由于网络层数加深、神经元数量增多,导致需要学习的参数数量急剧增长,进而引发计算量过大、训练时间过长以及过拟合等一系列问题。CNN 架构的特性可以解决参数爆炸问题:
1.局部连接:在卷积层一个卷积核仅在图像的一个小区域(感受野)上进行卷积操作,并非和整个图像的所有像素连接。比如处理一张大小为 28x28 像素的图像,若采用全连接层且有 100 个神经元,参数数量是 28*28*100 = 78400个;若使用 5x5 的卷积核进行卷积操作,每个卷积核的参数数量仅为 5*5 = 25 个。
2.参数共享:在 CNN 中,同一个卷积核在整个输入数据上滑动进行卷积操作,即卷积核的参数在不同位置是共享的。这意味着无论输入数据的大小怎样,只需学习一组卷积核的参数。在一个卷积层使用 10 个 5x5 的卷积核,不管输入图像大小是 28x28 还是更大,需要学习的参数数量始终是10*5*5 = 250 个,不会随输入数据增大而无限增加。
3.池化操作:池化层一般紧跟在卷积层之后,用于对卷积层的输出进行下采样,也就是减少特征图的尺寸。对一个 28*28 的特征图进行 2*2 的最大池化操作后,特征图尺寸变为 14*14,相应地后续层的参数数量也会减少。
丢失空间结构信息
图像、视频等数据具有天然的空间结构信息,如像素之间的邻接关系、物体的空间布局等。传统的全连接网络会破坏这种空间结构,CNN 架构模型卷积核在输入数据上进行滑动卷积操作时,每个神经元只关注输入图像的一个小区域,有效捕捉图像中的局部特征,如边缘和角点,这样保留了像素之间的局部空间关系。
使用 CNN 实现手写数字识别
我们定义一个简单的 CNN,由两个卷积层、两个池化层、一个全连接层和一个输出层组成。
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# 第一个卷积层:灰度图输入通道数=1,输出通道数=32,卷积核大小=3x3
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
# 第二个卷积层:输入通道数=32,输出通道数=64,卷积核大小=3x3
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
# 最大池化层:2x2
self.pool = nn.MaxPool2d(2, 2)
# 全连接层:输入=64 * 7 * 7(在两次池化后,图像尺寸变为7x7),输出=128
self.fc1 = nn.Linear(64 * 7 * 7, 128)
# 输出层:输入=128,输出=10(对应0-9十个数字)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
# 卷积 -> ReLU -> 池化
x = self.pool(F.relu(self.conv1(x)))
# 卷积 -> ReLU -> 池化
x = self.pool(F.relu(self.conv2(x)))
# 展平,将多维张量展平成一维向量
x = x.view(-1, 64 * 7 * 7)
# 全连接 -> ReLU
x = F.relu(self.fc1(x))
# 输出层
x = self.fc2(x)
return x
# 实例化模型
model = CNN()
卷积层
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
输入通道数:对于灰度图像,通道数为 1;对于彩色图像,通常通道数为 3(RGB)。
输出通道数:每个卷积层会使用多个卷积核进行卷积操作,输出通道数就等于卷积核的数量。代码中self.conv1 使用 32 个卷积核,self.conv2 使用 64 个卷积核,增加输出通道数可以让模型学习到更多的特征。
kernel_size:卷积核的大小,这里设置为 3x3,这是一种常用的卷积核大小,能够在捕捉局部特征的同时,控制参数数量。
padding:填充的大小,设置为 1 表示在图像的边界周围填充一圈 0。填充的目的是在卷积操作后保持图像的尺寸不变。
在卷积过程中,卷积核从输入数据的左上角开始,按照一定的步长(stride)在输入数据上滑动进行计算。当卷积核滑动到输入数据的边缘时,如果再继续滑动就会超出输入数据的范围,此时卷积操作就会停止。这样会导致输出特征图的尺寸变小,而且边缘信息会因为卷积核无法完全覆盖而丢失较多,使得边缘部分的特征提取不够充分。
当不进行填充时,卷积操作后输出特征图的尺寸通常会小于输入特征图的尺寸。具体的尺寸变化可以通过以下公式计算:
例如,输入特征图的尺寸是 28x28,卷积核大小为 3x3,步长为 1,当 padding = 0 时,输出特征图的尺寸为 (28 - 3 + 1)*(28 - 3 + 1)=26*26。
池化层
self.pool = nn.MaxPool2d(2, 2)
池化窗口的大小:这里设置为 2,表示池化窗口是 2x2 的。
池化操作的步长:这里也设置为 2。步长为 2 意味着池化窗口每次移动 2 个像素。
MaxPool 会对每个2x2的区域取最大值,经过这样设置的池化操作后,图像的尺寸会缩小一半,28x28 的输入通过一个 2x2 池化层后变为 14x14。
全连接层
self.fc1 = nn.Linear(64 * 7 * 7, 128)
输入特征的数:在 self.fc1 中,输入特征的数量是 64*7*7,这是因为经过两次 2x2 的最大池化操作后,图像的尺寸从 28x28 变为 7x7,而 self.conv2 的输出通道数为 64,所以特征图的总特征数量为 64*7*7。
输出特征数:全连接层的输出特征数决定了下一层的维度。128 一个中等大小的隐藏层神经元数量的经验值,足以学习到复杂的特征表示,同时避免过多的参数导致过拟合。
输出层
self.fc2 = nn.Linear(128, 10)
输入特征数:self.fc1 的输出特征数量设置为 128,做为下一层的 self.fc2 的输入层特征数是 128。
输出特征数:因为手写数字识别任务有 0-9 这十个类别,所以输出层需要输出 10 个值,分别表示图像属于每个类别的概率。
前向传播路径
Pytorch nn.Module 类的forward()
方法定义了输入数据在神经网络模型中前向传播的具体计算流程。前向传播指的是输入数据从输入层开始,依次经过神经网络的各个层(如卷积层、池化层、全连接层等),最终得到输出结果的过程。
大部分代码很好理解,简单看下卷积层->激活层->池化层x = self.pool(F.relu(self.conv1(x)))
self.conv1(x): 对输入进行第一层卷积操作,输出 32 个特征图,每个特征图的大小为 28x28。
F.relu(...): 应用 ReLU 激活函数,引入非线性,使模型能够学习更复杂的特征。
self.pool(...): 进行一次 2x2 最大池化,将特征图尺寸从 28x28 缩小到 14x14。
为了更好地阅读代码。最好了解一下 PyTorch 的基本使用:
https://www.yuque.com/sunluyong/survivor/hr49pkt86xae7g92
完整代码
import torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torchvision import datasets, transformsimport matplotlib.pyplot as pltimport numpy as np# 检查是否有GPU可用device = torch.device("cuda"if torch.cuda.is_available() else"cpu")# 定义CNN模型class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() # 第一个卷积层 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # 第二个卷积层 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 池化层 self.pool = nn.MaxPool2d(2, 2) # 全连接层 self.fc1 = nn.Linear(64 * 7 * 7, 128) # 输出层 self.fc2 = nn.Linear(128, 10) def forward(self, x): # 卷积 -> ReLU -> 池化 x = self.pool(F.relu(self.conv1(x))) # 卷积 -> ReLU -> 池化 x = self.pool(F.relu(self.conv2(x))) # 展平 x = x.view(-1, 64 * 7 * 7) # 全连接 -> ReLU x = F.relu(self.fc1(x)) # 输出层 x = self.fc2(x) return x# 实例化模型并移动到设备model = CNN().to(device)# 定义损失函数和优化器criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001)# 定义数据预处理步骤transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])# 下载并加载训练集train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)# 下载并加载测试集test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=1000, shuffle=False)# 定义训练函数def train(model, device, train_loader, optimizer, epoch): model.train() running_loss = 0.0 for batch_idx, (data, target) in enumerate(train_loader): # 将数据移动到设备上 data, target = data.to(device), target.to(device) # 零化参数梯度 optimizer.zero_grad() # 前向传播 outputs = model(data) # 计算损失 loss = criterion(outputs, target) # 反向传播 loss.backward() # 更新权重 optimizer.step() running_loss += loss.item() if batch_idx % 100 == 99: print(f"Epoch [{epoch}], Batch [{batch_idx + 1}], Loss: {running_loss / 100:.4f}") running_loss = 0.0# 训练模型epochs = 5for epoch in range(1, epochs + 1): train(model, device, train_loader, optimizer, epoch)# 定义测试函数def test(model, device, test_loader): model.eval() correct = 0 total = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) outputs = model(data) _, predicted = torch.max(outputs.data, 1) total += target.size(0) correct += (predicted == target).sum().item() print(f"测试集准确率: {100 * correct / total:.2f}%")test(model, device, test_loader)# 可视化部分预测结果dataiter = iter(test_loader)images, labels = next(dataiter)# 将数据移动到设备上images, labels = images.to(device), labels.to(device)# 获取预测结果outputs = model(images)_, preds = torch.max(outputs, 1)# 移动数据到CPU并转换为NumPy数组images = images.cpu().numpy()preds = preds.cpu().numpy()labels = labels.cpu().numpy()# 可视化前 10 个样本plt.rcParams['font.family'] = 'SimSong' # 设置中文字体fig, axes = plt.subplots(2, 5, figsize=(12, 6))axes = axes.flatten()for idx in range(10): img = images[idx].squeeze() axes[idx].imshow(img, cmap='gray') axes[idx].set_title(f"真实: {labels[idx]}, 预测: {preds[idx]}") axes[idx].axis('off')plt.tight_layout()plt.show()
模型输出
Epoch [1], Batch [500], Loss: 0.9679Epoch [2], Batch [500], Loss: 0.2308Epoch [3], Batch [500], Loss: 0.1450Epoch [4], Batch [500], Loss: 0.0980Epoch [5], Batch [500], Loss: 0.0804测试集准确率: 99.12%
测试集准确率达到了 99.12%,这是一个相当高的准确率,表明模型具有很强的泛化能力,能够准确地识别出各种不同风格、不同书写习惯的手写数字。
CNN 家族模型与应用
常见模型
CNN 凭借强大的特征提取能力和对图像数据的高效处理,成为计算机视觉任务的核心技术。从早期探索到技术突破,众多 CNN 模型不断推动着领域发展
LeNet-5:
早期经典的卷积神经网络,专为手写数字识别设计。
LeNet-5 通过层级化的卷积和池化操作提取图像特征,结合全连接层进行分类。其简洁的架构适用于简单的图像识别任务,如手写数字和字符识别,是现代深度学习模型的奠基石。
AlexNet:
突破性的深度卷积网络,显著提升图像分类性能并引领深度学习热潮。
AlexNet 采用更深的网络结构和ReLU激活函数,加上数据增强和Dropout正则化,大幅提升了大规模图像分类的准确率。它在2012年ImageNet竞赛中取得显著成绩,适用于复杂的图像识别和计算机视觉任务,如图像分类和对象识别。
VGGNet:
以均匀的3×3卷积核堆叠构建的深度网络,强调简洁和深度。
VGGNet 通过连续堆叠小卷积核增加网络深度,提升特征表达能力。其模块化设计便于扩展和迁移学习,广泛应用于图像分类、对象检测和分割等领域,尤其在需要高精度特征提取的任务中表现优异。
GoogLeNet (Inception):
引入Inception模块的高效深度网络,优化计算资源利用。
GoogLeNet 通过 Inception 模块在同一层处理多尺度特征,减少参数数量并提高计算效率。它采用深层网络架构,适用于资源受限环境下的大规模图像识别和检测任务,在保持高准确率的同时显著降低了计算成本。
ResNet:
采用残差学习的深层网络,解决深度网络训练中的退化问题。
ResNet 引入残差连接,使信息在网络中更易传播,允许构建超过100层的深度网络而不会出现梯度消失。通过跳跃连接,ResNet有效缓解了深度模型的退化问题,广泛应用于图像分类、目标检测和语义分割,显著提升了模型的表现和训练效率。
EfficientNet:
通过复合缩放优化网络结构与深度的高效卷积网络。
EfficientNet 系列通过均衡地缩放网络的深度、宽度和分辨率,达到更高的性能与效率。它采用复合缩放方法,优化模型复杂度和精度,适用于各种图像分类和视觉任务,提供了在相同计算资源下更优的准确率表现,广泛应用于工业和研究领域。
YOLO (You Only Look Once):
实时目标检测模型,快速准确地在图像中定位和分类对象。
YOLO 将目标检测视为单一回归问题,通过一个网络直接预测边界框和类别概率,实现高速度和实时检测。它在处理实时视频流和需要快速响应的应用场景(如自动驾驶、监控系统)中表现优异,能够同时检测和分类多个对象,极大提高了目标检测的效率和实用性。
MTCNN (Multi-task Cascaded Convolutional Networks):
多任务级联卷积网络,用于高效的人脸检测与关键点定位。
MTCNN 通过三级级联网络同时执行人脸检测和关键点定位,提升了检测的精度和鲁棒性。其层级化结构首先生成候选区域,然后细化边界框,最后定位关键点(如眼睛、鼻子等)。MTCNN在各种复杂环境下(如不同光照、表情变化)表现出色,广泛应用于人脸识别、面部表情分析和增强现实等需要精确人脸检测的场景中。
目标识别
使用 YOLO 可以快速在图像、视频中定位和分类对象。
from ultralytics import YOLO
import cv2
# 加载预训练的 YOLOv8 模型,yolov8n 比较轻量
model = YOLO('yolov8n.pt')
image_path = 'yolotest.jpg'
# 进行目标检测
results = model(image_path)
# 遍历检测结果
for result in results:
# 获取原始图像
img = result.orig_img
# 获取检测到的边界框信息
boxes = result.boxes
for box in boxes:
# 获取边界框的坐标,格式为 (x1, y1, x2, y2)
bbox = box.xyxy[0].cpu().numpy().astype(int)
# 获取类别索引
cls = int(box.cls[0])
# 获取置信度
conf = float(box.conf[0])
# 定义类别名称列表,YOLOv8 预训练模型默认支持 80 个类别
class_names = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
'hair drier', 'toothbrush']
# 获取类别名称
class_name = class_names[cls]
# 在图像上绘制边界框
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 0, 255), 2)
# 在图像上添加类别名称和置信度信息
cv2.putText(img, f'{class_name}: {conf:.2f}', (bbox[0], bbox[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 0, 255), 2)
# 显示处理后的图像
cv2.imshow('YOLOv8 Object Detection', img)
# 等待按键事件,按任意键关闭窗口
cv2.waitKey(0)
# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()
人脸识别
使用 MTCNN 可以轻松做到人脸识别,别说,看人真准。
import torch
from facenet_pytorch import MTCNN
import cv2
import matplotlib.pyplot as plt
from PIL import Image
# 检查是否有可用的 GPU,如果有则使用 GPU 进行计算
device = torch.device('cuda:0'if torch.cuda.is_available() else'cpu')
print(f'Running on device: {device}')
# 初始化 MTCNN 模型
mtcnn = MTCNN(keep_all=True, device=device)
# 读取照片
image_path = 'jackma.jpg'
image = Image.open(image_path)
# 使用 MTCNN 进行人脸检测
boxes, _ = mtcnn.detect(image)
# 将 PIL 图像转换为 OpenCV 格式
image_cv = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)
# 如果检测到人脸
if boxes is not None:
for box in boxes:
# 提取边界框坐标
x1, y1, x2, y2 = [int(coord) for coord in box]
# 在图像上绘制边界框
cv2.rectangle(image_cv, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示带有检测框的图像
plt.imshow(image_cv)
plt.axis('off')
plt.show()
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-08-02
大模型时代的AI Infra内容浅析与趋势思考
2025-08-02
阿里Qwen-MT翻译模型发布: 挑战GPT-4.1,专业术语、领域风格精准拿捏!
2025-08-02
AI开发者必看:深度解析MCP,打造高效LLM应用的秘密武器!
2025-08-02
【深度】企业 AI 落地实践(四):如何构建端到端的 AI 应用观测体系
2025-08-02
Ollama vs vLLM:哪个框架更适合推理?(第二部分)
2025-08-02
刚刚,Anthropic切断OpenAI对Claude的访问权限
2025-08-02
金融大模型的“垂直突围”:蚂蚁数科打造更懂金融的行业大脑
2025-08-02
一个人干掉整个技术部
2025-05-29
2025-05-23
2025-06-01
2025-05-07
2025-05-07
2025-05-07
2025-06-07
2025-06-21
2025-06-12
2025-05-20
2025-08-02
2025-08-02
2025-07-31
2025-07-31
2025-07-31
2025-07-30
2025-07-30
2025-07-30