热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

零基础入门深度学习(七):图像分类任务之VGG、GoogLeNet和ResNet

授课讲师|孙高峰百度深度学习技术平台部资深研发工程师授课时间|每周二、周四晚20:00-21:00编辑整理|孙高峰内容来源|百度飞桨深度学习集训营01导读本课程是百度官方开设的零基

授课讲师 | 孙高峰 百度深度学习技术平台部资深研发工程师

授课时间 | 每周二、周四晚20:00-21:00

编辑整理 | 孙高峰

内容来源 | 百度飞桨深度学习集训营

01

导读

本课程是百度官方开设的零基础入门深度学习课程,主要面向没有深度学习技术基础或者基础薄弱的同学,帮助大家在深度学习领域实现从0到1+的跨越。从本课程中,你将学习到:

  1. 深度学习基础知识

  2. numpy实现神经网络构建和梯度下降算法

  3. 计算机视觉领域主要方向的原理、实践

  4. 自然语言处理领域主要方向的原理、实践

  5. 个性化推荐算法的原理、实践

本周为开讲第四周,百度深度学习技术平台部资深研发工程师孙高峰,开始讲解计算机视觉中图像分类任务。在上一节课中,我们为大家介绍了经典的LeNet和AlexNet神经网络结构在眼疾识别任务中的应用,本节将继续为大家带来更多精彩内容。

02

VGG

VGG是当前最流行的CNN模型之一,2014年由Simonyan和Zisserman提出,其命名来源于论文作者所在的实验室Visual Geometry Group。AlexNet模型通过构造多层网络,取得了较好的效果,但是并没有给出深度神经网络设计的方向。VGG通过使用一系列大小为3x3的小尺寸卷积核和pooling层构造深度卷积神经网络,并取得了较好的效果。VGG模型因为结构简单、应用性极强而广受研究者欢迎,尤其是它的网络结构设计方法,为构建深度神经网络提供了方向。

图3 是VGG-16的网络结构示意图,有13层卷积和3层全连接层。VGG网络的设计严格使用的卷积层和池化层来提取特征,并在网络的最后面使用三层全连接层,将最后一层全连接层的输出作为分类的预测。在VGG中每层卷积将使用ReLU作为激活函数,在全连接层之后添加dropout来抑制过拟合。使用小的卷积核能够有效地减少参数的个数,使得训练和测试变得更加有效。比如使用两层卷积层,可以得到感受野为5的特征图,而比使用的卷积层需要更少的参数。由于卷积核比较小,可以堆叠更多的卷积层,加深网络的深度,这对于图像分类任务来说是有利的。VGG模型的成功证明了增加网络的深度,可以更好的学习图像中的特征模式。


图3:VGG模型网络结构示意图

VGG在眼疾识别数据集iChallenge-PM上的具体实现如下代码所示:

# -*- coding:utf-8 -*-
# VGG模型代码import numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.layer_helper import LayerHelperfrom paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, FCfrom paddle.fluid.dygraph.base import to_variable
# 定义vgg块,包含多层卷积和1层2x2的最大池化层class vgg_block(fluid.dygraph.Layer): def __init__(self, name_scope, num_convs, num_channels): """ num_convs, 卷积层的数目 num_channels, 卷积层的输出通道数,在同一个Incepition块内,卷积层输出通道数是一样的 """ super(vgg_block, self).__init__(name_scope) self.conv_list = [] for i in range(num_convs): conv_layer = self.add_sublayer('conv_' + str(i), Conv2D(self.full_name(), num_filters=num_channels, filter_size=3, padding=1, act='relu')) self.conv_list.append(conv_layer) self.pool = Pool2D(self.full_name(), pool_stride=2, pool_size = 2, pool_type='max') def forward(self, x): for item in self.conv_list: x = item(x) return self.pool(x)
class VGG(fluid.dygraph.Layer): def __init__(self, name_scope, conv_arch=((2, 64), (2, 128), (3, 256), (3, 512), (3, 512))): super(VGG, self).__init__(name_scope) self.vgg_blocks=[] iter_id = 0 # 添加vgg_block # 这里一共5个vgg_block,每个block里面的卷积层数目和输出通道数由conv_arch指定 for (num_convs, num_channels) in conv_arch: block = self.add_sublayer('block_' + str(iter_id), vgg_block(self.full_name(), num_convs, num_channels)) self.vgg_blocks.append(block) iter_id += 1 self.fc1 = FC(self.full_name(), size=4096, act='relu') self.drop1_ratio = 0.5 self.fc2= FC(self.full_name(), size=4096, act='relu') self.drop2_ratio = 0.5 self.fc3 = FC(self.full_name(), size=1, ) def forward(self, x): for item in self.vgg_blocks: x = item(x) x = fluid.layers.dropout(self.fc1(x), self.drop1_ratio) x = fluid.layers.dropout(self.fc2(x), self.drop2_ratio) x = self.fc3(x) return x

with fluid.dygraph.guard(): model = VGG("VGG")
train(model)

通过运行结果可以发现,在眼疾筛查数据集iChallenge-PM上使用VGG,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到94%左右。

03

GoogLeNet

GoogLeNet是2014年ImageNet比赛的冠军,它的主要特点是网络不仅有深度,还在横向上具有“宽度”。由于图像信息在空间尺寸上的巨大差异,如何选择合适的卷积核大小来提取特征就显得比较困难了。空间分布范围更广的图像信息适合用较大的卷积核来提取其特征,而空间分布范围较小的图像信息则适合用较小的卷积核来提取其特征。为了解决这个问题,GoogLeNet提出了一种被称为Inception模块的方案。如 图4 所示:


说明:

  • Google的研究人员为了向LeNet致敬,特地将模型命名为GoogLeNet

  • Inception一词来源于电影《盗梦空间》(Inception)



图4:Inception模块结构示意图


图4(a)是Inception模块的设计思想,使用3个不同大小的卷积核对输入图片进行卷积操作,并附加最大池化,将这4个操作的输出沿着通道这一维度进行拼接,构成的输出特征图将会包含经过不同大小的卷积核提取出来的特征。Inception模块采用多通路(multi-path)的设计形式,每个支路使用不同大小的卷积核,最终输出特征图的通道数是每个支路输出通道数的总和,这将会导致输出通道数变得很大,尤其是使用多个Inception模块串联操作的时候,模型参数量会变得非常巨大。为了减小参数量,Inception模块使用了图(b)中的设计方式,在每个3x3和5x5的卷积层之前,增加1x1的卷积层来控制输出通道数;在最大池化层后面增加1x1卷积层减小输出通道数。基于这一设计思想,形成了上图(b)中所示的结构。下面这段程序是Inception块的具体实现方式,可以对照图(b)和代码一起阅读。


提示:

可能有读者会问,经过3x3的最大池化之后图像尺寸不会减小吗,为什么还能跟另外3个卷积输出的特征图进行拼接?这是因为池化操作可以指定窗口大小,pool_stride=1和pool_padding=1,输出特征图尺寸可以保持不变。


Inception模块的具体实现如下代码所示:

class Inception(fluid.dygraph.Layer):def __init__(self, name_scope, c1, c2, c3, c4, **kwargs):'''Inception模块的实现代码,name_scope, 模块名称,数据类型为stringc1, 图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数c2,图(b)中第二条支路卷积的输出通道数,数据类型是tuple或list, 其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3c3,图(b)中第三条支路卷积的输出通道数,数据类型是tuple或list, 其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3c4, 图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数'''super(Inception, self).__init__(name_scope)# 依次创建Inception块每条支路上使用到的操作self.p1_1 = Conv2D(self.full_name(), num_filters=c1, filter_size=1, act='relu')self.p2_1 = Conv2D(self.full_name(), num_filters=c2[0], filter_size=1, act='relu')self.p2_2 = Conv2D(self.full_name(), num_filters=c2[1], filter_size=3, padding=1, act='relu')self.p3_1 = Conv2D(self.full_name(), num_filters=c3[0], filter_size=1, act='relu')self.p3_2 = Conv2D(self.full_name(), num_filters=c3[1], filter_size=5, padding=2, act='relu')self.p4_1 = Pool2D(self.full_name(), pool_size=3, pool_stride=1, pool_padding=1, pool_type='max')self.p4_2 = Conv2D(self.full_name(), num_filters=c4, filter_size=1, act='relu')def forward(self, x):# 支路1只包含一个1x1卷积p1 = self.p1_1(x)# 支路2包含 1x1卷积 + 3x3卷积p2 = self.p2_2(self.p2_1(x))# 支路3包含 1x1卷积 + 5x5卷积p3 = self.p3_2(self.p3_1(x))# 支路4包含 最大池化和1x1卷积p4 = self.p4_2(self.p4_1(x))# 将每个支路的输出特征图拼接在一起作为最终的输出结果return fluid.layers.concat([p1, p2, p3, p4], axis=1)

GoogLeNet的架构如 图5 所示,在主体卷积部分中使用5个模块(block),每个模块之间使用步幅为2的3 ×3最大池化层来减小输出高宽。

  • 第一模块使用一个64通道的7 × 7卷积层。

  • 第二模块使用2个卷积层:首先是64通道的1 × 1卷积层,然后是将通道增大3倍的3 × 3卷积层。

  • 第三模块串联2个完整的Inception块。

  • 第四模块串联了5个Inception块。

  • 第五模块串联了2 个Inception块。

  • 第五模块的后面紧跟输出层,使用全局平均池化 层来将每个通道的高和宽变成1,最后接上一个输出个数为标签类别数的全连接层。


说明:在原作者的论文中添加了图中所示的softmax1和softmax2两个辅助分类器,如下图所示,训练时将三个分类器的损失函数进行加权求和,以缓解梯度消失现象。这里的程序作了简化,没有加入辅助分类器。



图5:GoogLeNet模型网络结构示意图

GoogLeNet的具体实现如下代码所示:

# -*- coding:utf-8 -*-
# GoogLeNet模型代码import numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.layer_helper import LayerHelperfrom paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, FCfrom paddle.fluid.dygraph.base import to_variable
# 定义Inception块class Inception(fluid.dygraph.Layer): def __init__(self, name_scope, c1, c2, c3, c4, **kwargs): ''' Inception模块的实现代码, name_scope, 模块名称,数据类型为string c1, 图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数 c2,图(b)中第二条支路卷积的输出通道数,数据类型是tuple或list, 其中c2[0]是1x1卷积的输出通道数,c2[1]是3x3 c3,图(b)中第三条支路卷积的输出通道数,数据类型是tuple或list, 其中c3[0]是1x1卷积的输出通道数,c3[1]是3x3 c4, 图(b)中第一条支路1x1卷积的输出通道数,数据类型是整数 ''' super(Inception, self).__init__(name_scope) # 依次创建Inception块每条支路上使用到的操作 self.p1_1 = Conv2D(self.full_name(), num_filters=c1, filter_size=1, act='relu') self.p2_1 = Conv2D(self.full_name(), num_filters=c2[0], filter_size=1, act='relu') self.p2_2 = Conv2D(self.full_name(), num_filters=c2[1], filter_size=3, padding=1, act='relu') self.p3_1 = Conv2D(self.full_name(), num_filters=c3[0], filter_size=1, act='relu') self.p3_2 = Conv2D(self.full_name(), num_filters=c3[1], filter_size=5, padding=2, act='relu') self.p4_1 = Pool2D(self.full_name(), pool_size=3, pool_stride=1, pool_padding=1, pool_type='max') self.p4_2 = Conv2D(self.full_name(), num_filters=c4, filter_size=1, act='relu')def forward(self, x): # 支路1只包含一个1x1卷积 p1 = self.p1_1(x) # 支路2包含 1x1卷积 + 3x3卷积 p2 = self.p2_2(self.p2_1(x)) # 支路3包含 1x1卷积 + 5x5卷积 p3 = self.p3_2(self.p3_1(x)) # 支路4包含 最大池化和1x1卷积 p4 = self.p4_2(self.p4_1(x)) # 将每个支路的输出特征图拼接在一起作为最终的输出结果 return fluid.layers.concat([p1, p2, p3, p4], axis=1)
class GoogLeNet(fluid.dygraph.Layer): def __init__(self, name_scope): super(GoogLeNet, self).__init__(name_scope) # GoogLeNet包含五个模块,每个模块后面紧跟一个池化层 # 第一个模块包含1个卷积层 self.conv1 = Conv2D(self.full_name(), num_filters=64, filter_size=7, padding=3, act='relu') # 3x3最大池化 self.pool1 = Pool2D(self.full_name(), pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') # 第二个模块包含2个卷积层 self.conv2_1 = Conv2D(self.full_name(), num_filters=64, filter_size=1, act='relu') self.conv2_2 = Conv2D(self.full_name(), num_filters=192, filter_size=3, padding=1, act='relu') # 3x3最大池化 self.pool2 = Pool2D(self.full_name(), pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') # 第三个模块包含2个Inception块 self.block3_1 = Inception(self.full_name(), 64, (96, 128), (16, 32), 32) self.block3_2 = Inception(self.full_name(), 128, (128, 192), (32, 96), 64) # 3x3最大池化 self.pool3 = Pool2D(self.full_name(), pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') # 第四个模块包含5个Inception块 self.block4_1 = Inception(self.full_name(), 192, (96, 208), (16, 48), 64) self.block4_2 = Inception(self.full_name(), 160, (112, 224), (24, 64), 64) self.block4_3 = Inception(self.full_name(), 128, (128, 256), (24, 64), 64) self.block4_4 = Inception(self.full_name(), 112, (144, 288), (32, 64), 64) self.block4_5 = Inception(self.full_name(), 256, (160, 320), (32, 128), 128) # 3x3最大池化 self.pool4 = Pool2D(self.full_name(), pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') # 第五个模块包含2个Inception块 self.block5_1 = Inception(self.full_name(), 256, (160, 320), (32, 128), 128) self.block5_2 = Inception(self.full_name(), 384, (192, 384), (48, 128), 128) # 全局池化,尺寸用的是global_pooling,pool_stride不起作用 self.pool5 = Pool2D(self.full_name(), pool_stride=1, global_pooling=True, pool_type='avg') self.fc = FC(self.full_name(), size=1)def forward(self, x): x = self.pool1(self.conv1(x)) x = self.pool2(self.conv2_2(self.conv2_1(x))) x = self.pool3(self.block3_2(self.block3_1(x))) x = self.block4_3(self.block4_2(self.block4_1(x))) x = self.pool4(self.block4_5(self.block4_4(x))) x = self.pool5(self.block5_2(self.block5_1(x))) x = self.fc(x) return x

with fluid.dygraph.guard(): model = GoogLeNet("GoogLeNet")
train(model)

通过运行结果可以发现,使用GoogLeNet在眼疾筛查数据集iChallenge-PM上,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到95%左右。

04

ResNet

ResNet是2015年ImageNet比赛的冠军,将识别错误率降低到了3.6%,这个结果甚至超出了正常人眼识别的精度。

通过前面几个经典模型学习,我们可以发现随着深度学习的不断发展,模型的层数越来越多,网络结构也越来越复杂。那么是否加深网络结构,就一定会得到更好的效果呢?从理论上来说,假设新增加的层都是恒等映射,只要原有的层学出跟原模型一样的参数,那么深模型结构就能达到原模型结构的效果。换句话说,原模型的解只是新模型的解的子空间,在新模型解的空间里应该能找到比原模型解对应的子空间更好的结果。但是实践表明,增加网络的层数之后,训练误差往往不降反升。

Kaiming He等人提出了残差网络ResNet来解决上述问题,其基本思想如 图6所示。

  • 图6(a):表示增加网络的时候,将x映射成输出。

  • 图6(b):对图6(a)作了改进,输出。这时不是直接学习输出特征y的表示,而是学习。

    • 如果想学习出原模型的表示,只需将F(x)的参数全部设置为0,则是恒等映射。

    • 也叫做残差项,如果的映射接近恒等映射,图6(b)中通过学习残差项也比图6(a)学习完整映射形式更加容易。


图6:残差块设计思想

图6(b)的结构是残差网络的基础,这种结构也叫做残差块(residual block)。输入x通过跨层连接,能更快的向前传播数据,或者向后传播梯度。残差块的具体设计方案如 7 所示,这种设计方案也成称作瓶颈结构(BottleNeck)。


图7:残差块结构示意图

下图表示出了ResNet-50的结构,一共包含49层卷积和1层全连接,所以被称为ResNet-50。


图8:ResNet-50模型网络结构示意图

ResNet-50的具体实现如下代码所示:

# -*- coding:utf-8 -*-# ResNet模型代码import numpy as npimport paddleimport paddle.fluid as fluidfrom paddle.fluid.layer_helper import LayerHelperfrom paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, FCfrom paddle.fluid.dygraph.base import to_variable# ResNet中使用了BatchNorm层,在卷积层的后面加上BatchNorm以提升数值稳定性# 定义卷积批归一化块class ConvBNLayer(fluid.dygraph.Layer): def __init__(self, name_scope, num_channels, num_filters, filter_size, stride=1, groups=1, act=None): """ name_scope, 模块的名字 num_channels, 卷积层的输入通道数 num_filters, 卷积层的输出通道数 stride, 卷积层的步幅 groups, 分组卷积的组数,默认groups=1不使用分组卷积 act, 激活函数类型,默认act=None不使用激活函数 """ super(ConvBNLayer, self).__init__(name_scope) # 创建卷积层 self._conv = Conv2D( self.full_name(), num_filters=num_filters, filter_size=filter_size, stride=stride, padding=(filter_size - 1) // 2, groups=groups, act=None, bias_attr=False) # 创建BatchNorm层 self._batch_norm = BatchNorm(self.full_name(), num_filters, act=act) def forward(self, inputs): y = self._conv(inputs) y = self._batch_norm(y) return y# 定义残差块# 每个残差块会对输入图片做三次卷积,然后跟输入图片进行短接# 如果残差块中第三次卷积输出特征图的形状与输入不一致,则对输入图片做1x1卷积,将其输出形状调整成一致class BottleneckBlock(fluid.dygraph.Layer): def __init__(self, name_scope, num_channels, num_filters, stride, shortcut=True): super(BottleneckBlock, self).__init__(name_scope) # 创建第一个卷积层 1x1 self.conv0 = ConvBNLayer( self.full_name(), num_channels=num_channels, num_filters=num_filters, filter_size=1, act='relu') # 创建第二个卷积层 3x3 self.conv1 = ConvBNLayer( self.full_name(), num_channels=num_filters, num_filters=num_filters, filter_size=3, stride=stride, act='relu') # 创建第三个卷积 1x1,但输出通道数乘以4 self.conv2 = ConvBNLayer( self.full_name(), num_channels=num_filters, num_filters=num_filters * 4, filter_size=1, act=None) # 如果conv2的输出跟此残差块的输入数据形状一致,则shortcut=True # 否则shortcut = False,添加1个1x1的卷积作用在输入数据上,使其形状变成跟conv2一致 if not shortcut: self.short = ConvBNLayer( self.full_name(), num_channels=num_channels, num_filters=num_filters * 4, filter_size=1, stride=stride) self.shortcut = shortcut self._num_channels_out = num_filters * 4 def forward(self, inputs): y = self.conv0(inputs) conv1 = self.conv1(y) conv2 = self.conv2(conv1) # 如果shortcut=True,直接将inputs跟conv2的输出相加 # 否则需要对inputs进行一次卷积,将形状调整成跟conv2输出一致 if self.shortcut: short = inputs else: short = self.short(inputs) y = fluid.layers.elementwise_add(x=short, y=conv2) layer_helper = LayerHelper(self.full_name(), act='relu') return layer_helper.append_activation(y)# 定义ResNet模型class ResNet(fluid.dygraph.Layer): def __init__(self, name_scope, layers=50, class_dim=1): """ name_scope,模块名称 layers, 网络层数,可以是50, 101或者152 class_dim,分类标签的类别数 """ super(ResNet, self).__init__(name_scope) self.layers = layers supported_layers = [50, 101, 152] assert layers in supported_layers, \ "supported layers are {} but input layer is {}".format(supported_layers, layers) if layers == 50: #ResNet50包含多个模块,其中第2到第5个模块分别包含3、4、6、3个残差块 depth = [3, 4, 6, 3] elif layers == 101: #ResNet101包含多个模块,其中第2到第5个模块分别包含3、4、23、3个残差块 depth = [3, 4, 23, 3] elif layers == 152: #ResNet50包含多个模块,其中第2到第5个模块分别包含3、8、36、3个残差块 depth = [3, 8, 36, 3] # 残差块中使用到的卷积的输出通道数 num_filters = [64, 128, 256, 512] # ResNet的第一个模块,包含1个7x7卷积,后面跟着1个最大池化层 self.conv = ConvBNLayer( self.full_name(), num_channels=3, num_filters=64, filter_size=7, stride=2, act='relu') self.pool2d_max = Pool2D( self.full_name(), pool_size=3, pool_stride=2, pool_padding=1, pool_type='max') # ResNet的第二到第五个模块c2、c3、c4、c5 self.bottleneck_block_list = [] num_channels = 64 for block in range(len(depth)): shortcut = False for i in range(depth[block]): bottleneck_block = self.add_sublayer( 'bb_%d_%d' % (block, i), BottleneckBlock( self.full_name(), num_channels=num_channels, num_filters=num_filters[block], stride=2 if i == 0 and block != 0 else 1, # c3、c4、c5将会在第一个残差块使用stride=2;其余所有残差块stride=1 shortcut=shortcut)) num_channels = bottleneck_block._num_channels_out self.bottleneck_block_list.append(bottleneck_block) shortcut = True # 在c5的输出特征图上使用全局池化 self.pool2d_avg = Pool2D( self.full_name(), pool_size=7, pool_type='avg', global_pooling=True) # stdv用来作为全连接层随机初始化参数的方差 import math stdv = 1.0 / math.sqrt(2048 * 1.0) # 创建全连接层,输出大小为类别数目 self.out = FC(self.full_name(), size=class_dim, param_attr=fluid.param_attr.ParamAttr( initializer=fluid.initializer.Uniform(-stdv, stdv))) def forward(self, inputs): y = self.conv(inputs) y = self.pool2d_max(y) for bottleneck_block in self.bottleneck_block_list: y = bottleneck_block(y) y = self.pool2d_avg(y) y = self.out(y) return y

with fluid.dygraph.guard(): model = ResNet("ResNet")
train(model)

通过运行结果可以发现,使用ResNet在眼疾筛查数据集iChallenge-PM上,loss能有效的下降,经过5个epoch的训练,在验证集上的准确率可以达到95%左右。

05

总结

本周课程中孙老师主要为大家讲解了计算机视觉中分类任务的主要内容,以眼疾识别任务为例,讲解了经典卷积神经网络VGG、GoogLeNet和ResNet。在后期课程中,将继续为大家带来内容更丰富的课程,帮助学员快速掌握深度学习方法。

【如何学习】

  1. 如何观看配套视频?如何代码实践?

视频+代码已经发布在AI Studio实践平台上,视频支持PC端/手机端同步观看,也鼓励大家亲手体验运行代码哦。打开以下链接:

https://aistudio.baidu.com/aistudio/course/introduce/888

  1. 学习过程中,有疑问怎么办?

加入深度学习集训营QQ群:726887660,班主任与飞桨研发会在群里进行答疑与学习资料发放。

  1. 如何学习更多内容?

百度飞桨将通过飞桨深度学习集训营的形式,继续更新《零基础入门深度学习》课程,由百度深度学习高级研发工程师亲自授课,每周二、每周四8:00-9:00不见不散,采用直播+录播+实践+答疑的形式,欢迎关注~

请搜索AI Studio,点击课程-百度架构师手把手教深度学习,或者点击文末「阅读原文」收看。


推荐阅读
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 2019年后蚂蚁集团与拼多多面试经验详述与深度剖析
    2019年后蚂蚁集团与拼多多面试经验详述与深度剖析 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • 深入解析十大经典排序算法:动画演示、原理分析与代码实现
    本文深入探讨了十种经典的排序算法,不仅通过动画直观展示了每种算法的运行过程,还详细解析了其背后的原理与机制,并提供了相应的代码实现,帮助读者全面理解和掌握这些算法的核心要点。 ... [详细]
  • 利用ViewComponents在Asp.Net Core中构建高效分页组件
    通过运用 ViewComponents 技术,在 Asp.Net Core 中实现了高效的分页组件开发。本文详细介绍了如何通过创建 `PaginationViewComponent` 类并利用 `HelloWorld.DataContext` 上下文,实现对分页参数的定义与管理,从而提升 Web 应用程序的性能和用户体验。 ... [详细]
  • 如何判断一个度序列能否构成简单图——哈维尔-哈基米算法的应用与解析 ... [详细]
  • 深入解析经典卷积神经网络及其实现代码
    深入解析经典卷积神经网络及其实现代码 ... [详细]
  • 进程(Process)是指计算机中程序对特定数据集的一次运行活动,是系统资源分配与调度的核心单元,构成了操作系统架构的基础。在早期以进程为中心的计算机体系结构中,进程被视为程序的执行实例,其状态和控制信息通过任务描述符(task_struct)进行管理和维护。本文将深入探讨进程的概念及其关键数据结构task_struct,解析其在操作系统中的作用和实现机制。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • Jedis接口分类详解与应用指南
    本文详细解析了Jedis接口的分类及其应用指南,重点介绍了字符串数据类型(String)的接口功能。作为Redis中最基本的数据存储形式,字符串类型支持多种操作,如设置、获取和更新键值对等,适用于广泛的应用场景。 ... [详细]
  • Go语言中的高效排序与搜索算法解析
    在探讨Go语言中高效的排序与搜索算法时,本文深入分析了Go语言提供的内置排序功能及其优化策略。通过实例代码,详细讲解了如何利用Go语言的标准库实现快速、高效的排序和搜索操作,为开发者提供了实用的编程指导。 ... [详细]
  • 在探讨 AS3 中的数据深度复制技术时,本文详细介绍了实现数据深度克隆的有效方法。通过对比多种方案,最终确定了一种高效且可靠的实现方式,所有代码均来源于公开资源,确保了方法的实用性和可操作性。 ... [详细]
  • 在斯坦福大学的公开课中,详细探讨了多变量线性回归在机器学习中的应用与解析。课程内容涵盖了多维特征的处理方法,以及如何通过多变量梯度下降算法优化模型参数,为理解和实现复杂的回归问题提供了坚实的理论基础和实践指导。 ... [详细]
  • Go语言中Goroutine与通道机制及其异常处理深入解析
    在Go语言中,Goroutine可视为一种轻量级的并发执行单元,其资源消耗远低于传统线程,初始栈大小仅为2KB,而普通线程则通常需要几MB。此外,Goroutine的调度由Go运行时自动管理,能够高效地支持成千上万个并发任务。本文深入探讨了Goroutine的工作原理及其与通道(channel)的配合使用,特别是在异常处理方面的最佳实践,为开发者提供了一套完整的解决方案,以确保程序的稳定性和可靠性。 ... [详细]
  • 本文介绍了实现链表数据结构的方法与技巧,通过定义一个 `MyLinkedList` 类来管理链表节点。该类包含三个主要属性:`first` 用于指向链表的第一个节点,`last` 用于指向链表的最后一个节点,以及 `size` 用于记录链表中节点的数量。此外,还详细探讨了如何通过这些属性高效地进行链表的操作,如插入、删除和查找等。 ... [详细]
author-avatar
刘小宝0201
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有