秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转
💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡
专栏目录 :《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有80+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进——点击即可跳转
卷积神经网络通常在固定的资源预算下开发,如果资源更充足,则会进行扩展以提高准确性。在EfficientNet的论文中,作者系统地研究了模型扩展,并发现仔细平衡网络的深度、宽度和分辨率可以带来更好的性能。基于这一观察,提出了一种新的扩展方法,该方法使用简单但高效的复合系数统一扩展深度/宽度/分辨率的各个维度。证明了将此方法应用于扩展MobileNets和ResNet的有效性。为了更进一步,我们使用神经架构搜索来设计一个新的基准网络,并对其进行扩展以获得一系列模型,称为EfficientNets,这些模型的准确性和效率都优于先前的ConvNets。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
专栏地址:YOLOv8改进——更新各种有效涨点方法——点击即可跳转 订阅学习不迷路
目录
1. 原理
2. 将EfficientNet添加到YOLOv8中
2.1 EfficientNet的代码实现
2.2 更改init.py文件
2.3 添加yaml文件
2.4 在task.py中进行注册
2.5 执行程序
3. 完整代码分享
4. GFLOPs
5. 进阶
6. 总结
1. 原理
论文地址:EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks——点击即可跳转
官方代码:官方代码仓库——点击即可跳转
EfficientNet是一种针对卷积神经网络(CNN)模型的高效缩放方法,其核心思想在于通过复合缩放策略,平衡地调整网络的深度(depth)、宽度(width)和分辨率(resolution),从而在有限资源下最大化模型的准确性和效率。
1. 复合缩放方法
传统的CNN模型通常通过增加深度、宽度或者输入图像的分辨率来提升性能,但这些方法往往只针对单一维度进行缩放,导致效果不理想。EfficientNet提出了一个新的方法:复合缩放,即同时以固定比例缩放网络的深度、宽度和分辨率。这种方法背后的直觉是:输入图像越大,网络需要更多的层次来扩大感受野,更多的通道来捕捉更细粒度的特征。
具体来说,假设你有2N倍的计算资源,那么你可以按如下方式进行缩放:
-
深度按比例αN增加
-
宽度按比例βN增加
-
分辨率按比例γN增加
这三者的比例(α, β, γ)通过在小模型上的网格搜索确定,并应用到大模型上。
2. 神经架构搜索 (NAS)
EfficientNet不仅仅是提出了复合缩放方法,还使用神经架构搜索(Neural Architecture Search, NAS)设计了一个高效的基础网络结构,这个基础网络经过复合缩放后可以生成一系列高性能的模型(EfficientNet-B0到B7)。这些模型在ImageNet等基准数据集上展示了卓越的性能。例如,EfficientNet-B7达到了84.3%的ImageNet top-1准确率,同时参数量比之前的最佳模型小8.4倍,推理速度快6.1倍。
3. 效果与性能
通过复合缩放方法生成的EfficientNet系列模型显著超过了之前的各种卷积网络,不仅在ImageNet数据集上取得了极高的准确率,还在多个迁移学习任务上表现出色。此外,EfficientNet系列的模型在参数量和计算量方面表现得非常高效,适合在资源受限的环境中使用。
总结
EfficientNet通过提出复合缩放方法,以及结合神经架构搜索,成功设计出了一系列在准确性和效率上都领先的卷积神经网络模型。这种方法的核心在于找到一个合理的平衡点,统一缩放网络的各个维度,从而充分利用计算资源,提升模型性能.
2. 将EfficientNet添加到YOLOv8中
2.1 EfficientNet的代码实现
关键步骤一: 在/ultralytics/ultralytics/nn/modules/下面新建efficeient.py,将下面代码粘贴到文件内
import torch.nn as nn
import torch
# SE-Net Adaptive avg pooling --> fc --> fc --> Sigmoid
class SeBlock(nn.Module):
def __init__(self, in_channel, reduction=4):
super().__init__()
self.Squeeze = nn.AdaptiveAvgPool2d(1)
self.Excitation = nn.Sequential()
self.Excitation.add_module('FC1', nn.Conv2d(in_channel, in_channel // reduction, kernel_size=1)) # 1*1卷积与此效果相同
self.Excitation.add_module('ReLU', nn.ReLU())
self.Excitation.add_module('FC2', nn.Conv2d(in_channel // reduction, in_channel, kernel_size=1))
self.Excitation.add_module('Sigmoid', nn.Sigmoid())
def forward(self, x):
y = self.Squeeze(x)
ouput = self.Excitation(y)
return x*(ouput.expand_as(x))
# EfficientNetLite
class drop_connect:
def __init__(self, drop_connect_rate):
self.drop_connect_rate = drop_connect_rate
def forward(self, x, training):
if not training:
return x
keep_prob = 1.0 - self.drop_connect_rate
batch_size = x.shape[0]
random_tensor = keep_prob
random_tensor += torch.rand([batch_size, 1, 1, 1], dtype=x.dtype, device=x.device)
binary_mask = torch.floor(random_tensor) # 1
x = (x / keep_prob) * binary_mask
return x
class stem(nn.Module):
def __init__(self, c1, c2, act='ReLU6'):
super().__init__()
self.conv = nn.Conv2d(c1, c2, kernel_size=3, stride=2, padding=1, bias=False)
self.bn = nn.BatchNorm2d(num_features=c2)
if act == 'ReLU6':
self.act = nn.ReLU6(inplace=True)
def forward(self, x):
return self.act(self.bn(self.conv(x)))
class MBConvBlock(nn.Module):
def __init__(self, inp, final_oup, k, s, expand_ratio, drop_connect_rate, has_se=False):
super(MBConvBlock, self).__init__()
self._momentum = 0.01
self._epsilon = 1e-3
self.input_filters = inp
self.output_filters = final_oup
self.stride = s
self.expand_ratio = expand_ratio
self.has_se = has_se
self.id_skip = True # skip connection and drop connect
se_ratio = 0.25
# Expansion phase
oup = inp * expand_ratio # number of output channels
if expand_ratio != 1:
self._expand_conv = nn.Conv2d(in_channels=inp, out_channels=oup, kernel_size=1, bias=False)
self._bn0 = nn.BatchNorm2d(num_features=oup, momentum=self._momentum, eps=self._epsilon)
# Depthwise convolution phase
self._depthwise_conv = nn.Conv2d(
in_channels=oup, out_channels=oup, groups=oup, # groups makes it depthwise
kernel_size=k, padding=(k - 1) // 2, stride=s, bias=False)
self._bn1 = nn.BatchNorm2d(num_features=oup, momentum=self._momentum, eps=self._epsilon)
# Squeeze and Excitation layer, if desired
if self.has_se:
num_squeezed_channels = max(1, int(inp * se_ratio))
self.se = SeBlock(oup, 4)
# Output phase
self._project_conv = nn.Conv2d(in_channels=oup, out_channels=final_oup, kernel_size=1, bias=False)
self._bn2 = nn.BatchNorm2d(num_features=final_oup, momentum=self._momentum, eps=self._epsilon)
self._relu = nn.ReLU6(inplace=True)
self.drop_connect = drop_connect(drop_connect_rate)
def forward(self, x, drop_connect_rate=None):
"""
:param x: input tensor
:param drop_connect_rate: drop connect rate (float, between 0 and 1)
:return: output of block
"""
# Expansion and Depthwise Convolution
identity = x
if self.expand_ratio != 1:
x = self._relu(self._bn0(self._expand_conv(x)))
x = self._relu(self._bn1(self._depthwise_conv(x)))
# Squeeze and Excitation
if self.has_se:
x = self.se(x)
x = self._bn2(self._project_conv(x))
# Skip connection and drop connect
if self.id_skip and self.stride == 1 and self.input_filters == self.output_filters:
if drop_connect_rate:
x = self.drop_connect(x, training=self.training)
x += identity # skip connection
return x
EfficientNet处理图像的主要流程可以分为以下几个关键步骤:
1. 输入图像的预处理
首先,输入图像会被标准化处理,这通常包括缩放到特定的分辨率以及归一化。EfficientNet的复合缩放策略决定了不同版本的EfficientNet模型(如B0到B7)会使用不同的输入分辨率。通过对分辨率的缩放,EfficientNet能够在不同大小的输入图像上保持性能与效率的平衡。
2. 卷积操作和特征提取
图像经过预处理后,会被输入到EfficientNet的网络结构中。EfficientNet的网络结构基于MobileNetV2中的Inverted Residual Blocks,但通过神经架构搜索(NAS)进一步优化,使其在性能和效率之间取得更好的平衡。
在这些卷积层中,EfficientNet会依次进行以下操作:
-
标准卷积:用于提取低级特征。
-
深度可分离卷积:这是一个轻量级的卷积层,由标准卷积分解而来,分为深度卷积(针对每个通道的空间卷积)和点卷积(对通道进行线性组合)。这种卷积方式既高效又节省计算资源。
-
Inverted Residual Block:它是EfficientNet的基础构建块,这些块使用跳跃连接(skip connections)和扩展因子来捕捉不同层次的特征。
3. 复合缩放策略的应用
在整个网络结构中,EfficientNet通过复合缩放策略来调整网络的宽度(通道数)、深度(层数)和分辨率(输入图像大小)。这种方法确保网络可以在增加计算资源时,合理地扩展各个维度,从而提高模型的表达能力和分类准确性。
4. 特征聚合与全连接层
在卷积层完成特征提取后,EfficientNet会通过全局平均池化层将所有提取到的特征聚合成一个固定大小的向量。然后,这个向量会被传递到全连接层进行分类预测。全连接层的输出维度与分类任务的类别数量一致(例如,ImageNet有1000个类,因此输出层维度为1000)。
5. 输出分类结果
最终,全连接层的输出会经过softmax激活函数,将结果转化为概率分布,每个类的概率代表该图像属于这个类的可能性。模型输出最高概率的类别作为预测结果。
总结
EfficientNet处理图像的流程包括:对输入图像进行预处理,通过优化的卷积层和Inverted Residual Blocks提取多层次特征,应用复合缩放策略调整网络结构,最后通过全局池化和全连接层进行分类。整个过程体现了EfficientNet在保持高效计算的同时,充分利用网络深度、宽度和输入分辨率的优化策略。
2.2 更改init.py文件
关键步骤二:修改modules文件夹下的__init__.py文件,先导入函数
然后在下面的__all__中声明函数
2.3 添加yaml文件
关键步骤三:在/ultralytics/ultralytics/cfg/models/v8下面新建文件yolov8_EfficientNet.yaml文件,粘贴下面的内容
- 目标检测yaml文件
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, stem, [32, 'ReLU6']] # 0-P1/2
- [-1, 1, MBConvBlock, [16, 3, 1, 1, 0]] # 1
- [-1, 1, MBConvBlock, [24, 3, 2, 6, 0.028, True]] # 2-P2/4
- [-1, 1, MBConvBlock, [24, 3, 1, 6, 0.057]]
- [-1, 1, MBConvBlock, [40, 5, 2, 6, 0.085]] # 4-P3/8
- [-1, 1, MBConvBlock, [40, 5, 1, 6, 0.114]]
- [-1, 1, MBConvBlock, [80, 3, 2, 6, 0.142]] # 6-P4/16
- [-1, 1, MBConvBlock, [80, 3, 1, 6, 0.171]]
- [-1, 1, MBConvBlock, [80, 3, 1, 6, 0.200]]
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.228]] # 9
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.257]]
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.285]]
- [-1, 1, MBConvBlock, [192, 5, 2, 6, 0.314]] # 12-P5/32
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.342]]
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.371]]
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.400]]
- [-1, 1, MBConvBlock, [320, 3, 1, 6, 0.428]] # 16
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 11], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 19
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 5], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 22 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 19], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 25 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 16], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 28 (P5/32-large)
- [[22, 25, 28], 1, Detect, [nc]] # Detect(P3, P4, P5)
- 语义分割yaml文件
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, stem, [32, 'ReLU6']] # 0-P1/2
- [-1, 1, MBConvBlock, [16, 3, 1, 1, 0]] # 1
- [-1, 1, MBConvBlock, [24, 3, 2, 6, 0.028, True]] # 2-P2/4
- [-1, 1, MBConvBlock, [24, 3, 1, 6, 0.057]]
- [-1, 1, MBConvBlock, [40, 5, 2, 6, 0.085]] # 4-P3/8
- [-1, 1, MBConvBlock, [40, 5, 1, 6, 0.114]]
- [-1, 1, MBConvBlock, [80, 3, 2, 6, 0.142]] # 6-P4/16
- [-1, 1, MBConvBlock, [80, 3, 1, 6, 0.171]]
- [-1, 1, MBConvBlock, [80, 3, 1, 6, 0.200]]
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.228]] # 9
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.257]]
- [-1, 1, MBConvBlock, [112, 5, 1, 6, 0.285]]
- [-1, 1, MBConvBlock, [192, 5, 2, 6, 0.314]] # 12-P5/32
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.342]]
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.371]]
- [-1, 1, MBConvBlock, [192, 5, 1, 6, 0.400]]
- [-1, 1, MBConvBlock, [320, 3, 1, 6, 0.428]] # 16
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 11], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 19
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 5], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 22 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 19], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 25 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 16], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 28 (P5/32-large)
- [[22, 25, 28], 1, Segment, [nc, 32, 256]] # Segment(P3, P4, P5)
温馨提示:因为本文只是对yolov8基础上添加模块,如果要对yolov8n/l/m/x进行添加则只需要指定对应的depth_multiple 和 width_multiple。不明白的同学可以看这篇文章: yolov8yaml文件解读——点击即可跳转
# YOLOv8n
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.25 # layer channel multiple
max_channels: 1024 # max_channels
# YOLOv8s
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
max_channels: 1024 # max_channels
# YOLOv8l
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
max_channels: 512 # max_channels
# YOLOv8m
depth_multiple: 0.67 # model depth multiple
width_multiple: 0.75 # layer channel multiple
max_channels: 768 # max_channels
# YOLOv8x
depth_multiple: 1.33 # model depth multiple
width_multiple: 1.25 # layer channel multiple
max_channels: 512 # max_channels
2.4 在task.py中进行注册
关键步骤四:在task.py的parse_model函数中进行注册,
elif m in [stem, MBConvBlock]:
c1, c2 = ch[f], args[0]
if c2 != nc:
c2 = make_divisible(min(c2, max_channels) * width, 8)
args = [c1, c2, *args[1:]]
2.5 执行程序
在train.py中,将model的参数路径设置为yolov8_EfficientNet.yaml的路径
建议大家写绝对路径,确保一定能找到
from ultralytics import YOLO
# Load a model
# model = YOLO('yolov8n.yaml') # build a new model from YAML
# model = YOLO('yolov8n.pt') # load a pretrained model (recommended for training)
model = YOLO(r'/projects/ultralytics/ultralytics/cfg/models/v8/yolov8_EfficientNet.yaml') # build from YAML and transfer weights
# Train the model
model.train(batch=16)
🚀运行程序,如果出现下面的内容则说明添加成功🚀
from n params module arguments
0 -1 1 232 ultralytics.nn.modules.EfficientNet.stem [3, 8, 'ReLU6']
1 -1 1 168 ultralytics.nn.modules.EfficientNet.MBConvBlock[8, 8, 3, 1, 1, 0]
2 -1 1 2620 ultralytics.nn.modules.EfficientNet.MBConvBlock[8, 8, 3, 2, 6, 0.028, True]
3 -1 1 1408 ultralytics.nn.modules.EfficientNet.MBConvBlock[8, 8, 3, 1, 6, 0.057]
4 -1 1 2576 ultralytics.nn.modules.EfficientNet.MBConvBlock[8, 16, 5, 2, 6, 0.085]
5 -1 1 5888 ultralytics.nn.modules.EfficientNet.MBConvBlock[16, 16, 5, 1, 6, 0.114]
6 -1 1 10624 ultralytics.nn.modules.EfficientNet.MBConvBlock[16, 80, 3, 2, 6, 0.142]
7 -1 1 83200 ultralytics.nn.modules.EfficientNet.MBConvBlock[80, 80, 3, 1, 6, 0.171]
8 -1 1 83200 ultralytics.nn.modules.EfficientNet.MBConvBlock[80, 80, 3, 1, 6, 0.2]
9 -1 1 67744 ultralytics.nn.modules.EfficientNet.MBConvBlock[80, 32, 5, 1, 6, 0.228]
10 -1 1 17920 ultralytics.nn.modules.EfficientNet.MBConvBlock[32, 32, 5, 1, 6, 0.257]
11 -1 1 17920 ultralytics.nn.modules.EfficientNet.MBConvBlock[32, 32, 5, 1, 6, 0.285]
12 -1 1 21024 ultralytics.nn.modules.EfficientNet.MBConvBlock[32, 48, 5, 2, 6, 0.314]
13 -1 1 36096 ultralytics.nn.modules.EfficientNet.MBConvBlock[48, 48, 5, 1, 6, 0.342]
14 -1 1 36096 ultralytics.nn.modules.EfficientNet.MBConvBlock[48, 48, 5, 1, 6, 0.371]
15 -1 1 36096 ultralytics.nn.modules.EfficientNet.MBConvBlock[48, 48, 5, 1, 6, 0.4]
16 -1 1 40768 ultralytics.nn.modules.EfficientNet.MBConvBlock[48, 80, 3, 1, 6, 0.428]
17 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
18 [-1, 11] 1 0 ultralytics.nn.modules.conv.Concat [1]
19 -1 1 113408 ultralytics.nn.modules.block.C2f [112, 128, 1]
20 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
21 [-1, 5] 1 0 ultralytics.nn.modules.conv.Concat [1]
22 -1 1 34176 ultralytics.nn.modules.block.C2f [144, 64, 1]
23 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2]
24 [-1, 19] 1 0 ultralytics.nn.modules.conv.Concat [1]
25 -1 1 123648 ultralytics.nn.modules.block.C2f [192, 128, 1]
26 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2]
27 [-1, 16] 1 0 ultralytics.nn.modules.conv.Concat [1]
28 -1 1 448000 ultralytics.nn.modules.block.C2f [208, 256, 1]
29 [22, 25, 28] 1 897664 ultralytics.nn.modules.head.Detect [80, [64, 128, 256]]
YOLOv8_efficeinet summary: 265 layers, 2265180 parameters, 2265164 gradients
3. 完整代码分享
https://pan.baidu.com/s/1vqFzDmSDLP85P8laBxkYmA?pwd=i5ck
提取码: i5ck
4. GFLOPs
关于GFLOPs的计算方式可以查看:百面算法工程师 | 卷积基础知识——Convolution
未改进的YOLOv8nGFLOPs
改进后的GFLOPs
现在手上没有卡了,等过段时候有卡了把这补上,需要的同学自己测一下
5. 进阶
可以与其他的注意力机制或者损失函数等结合,进一步提升检测效果
6. 总结
EfficientNet的主要原理在于提出了一种复合缩放策略,通过统一调整卷积神经网络的深度、宽度和输入分辨率,以实现性能和计算效率的最佳平衡。传统的模型通常只在单一维度上进行缩放(如增加网络的深度或宽度),然而这种方法往往效果有限,难以充分利用额外的计算资源。EfficientNet创新性地提出同时按固定比例缩放这三个维度,从而更好地适应大规模计算需求。在此基础上,研究人员还通过神经架构搜索(NAS)设计了一个高效的基础网络,并通过复合缩放生成了一系列模型。这些模型在多个基准数据集(如ImageNet)上展现了显著的性能提升,同时显著减少了参数量和计算开销,证明了这种方法在准确性和效率方面的优越性。