//2022.2.27日下午18:33开始学习笔记

7.在ImageNet上训练VGGNet

在本章中,我们将学习如何在ImageNet数据集上从零开始训练VGG16网络架构。卷积神经网络的VGG家族最早是由Simonyan和Zisserman在他们2014年的论文《用于大规模图像识别的V极深卷积网络[17]》中提出的。

该网络的特点是简单,只使用3 × 3的卷积层叠加在一起,并不断增加深度。减少卷的空间维度是通过使用最大池来实现的。两个完全连接的层,每个层有4096个节点(中间有dropout),然后是一个softmax分类器。

如今,VGG经常被用于迁移学习,因为该网络在泛化数据集方面的能力高于平均水平(与其他网络类型如googlet和ResNet相比)。如果你正在阅读一份应用迁移学习的出版物或实验室期刊,它很可能使用VGG作为基础模型。

不幸的是,至少可以说,从头开始训练VGG是一件痛苦的事情。网络的训练速度非常慢,而且网络体系结构本身的权重非常大(超过500MB)。这是ImageNet Bundle中唯一一个我建议你不要训练的网络,如果你没有访问至少四个gpu的话。由于网络的深度以及全连接层,反向传播阶段非常缓慢。

在我的例子中,在8个gpu上训练VGG花了大约10天——如果少于4个gpu,从头开始训练VGG可能会花费非常长的时间(除非您非常有耐心)。也就是说,作为一个深度学习实践者,了解深度学习的历史是很重要的,特别是了解pre-training的概念,以及我们后来如何通过优化初始权值函数来避免这种昂贵的操作。

同样,这一章包含在ImageNet Bundle中,因为VGG网络家族是深度学习的一个关键方面;但是,请不要觉得有必要从头开始训练这个网络——我在本章中包含了从我的实验中获得的weights文件,以便您在自己的应用程序中使用。使用本章作为一个教育参考,这样当您将VGG应用到您自己的项目时,您就可以从它中学到一些东西。特别是,本章将重点介绍如何正确使用PReLU激活函数和MSRA初始化。

7.1 实现VGGNet

在实现VGG时,Simonyan和Zisserman尝试了深度增加的VGG变体。为了突出他们的实验,下面的图7.1中包含了他们发表的表1。特别是,我们最感兴趣的是配置A、B、D和E。在本书的前面部分,您已经使用了配置D和E——它们是VGG16和VGG19体系结构。看看这些架构,你会注意到两个模式:

首先,网络只使用3 × 3的滤波器。第二,随着网络深度的增加,学习到的过滤器的数量也会增加——准确地说,每次使用最大池来减小容量时,过滤器的数量都会翻倍。在深度学习文献中,每当你降低空间维度时,过滤器的数量就会翻倍,这一概念在历史上具有重要意义,甚至是你今天将看到的模式。

我们将在下面的内容中实现VGG16;

我们这样做的原因是为了确保没有任何一个层块比其他的更有偏倚。网络架构中较早的层有较少的过滤器,但它们的空间体积也大得多,这意味着有“更多(空间)数据”可供学习。

然而,我们知道应用max pooling操作会减少我们的空间输入量。如果我们在不增加滤波器数量的情况下减少空间体积,那么我们的层就会变得不平衡并可能有偏差,这意味着网络中较早的层可能比网络中更深的层更能影响我们的输出分类。为了克服这种不平衡,我们记住了体积大小与过滤器数量的比例。如果我们将输入体积大小减少50-75%,那么我们将在下一组CONV层中增加一倍的滤波器数量以保持平衡。

训练这种深度架构的问题在于,Simonyan和Zisserman发现,由于VGG16和VGG19的深度,训练非常具有挑战性。如果这些架构是随机初始化和从头开始训练,他们经常会努力学习和获得任何初始“牵引力”——网络只是太深了,不适合基本的随机初始化。因此,为了训练更深层次的VGG变体,Simonyan和Zisserman提出了一个名为“预训练”的聪明概念。

预训练是先训练具有更少权值层的网络架构的较小版本,然后使用这些融合的网络权值作为更大、更深层次网络的初始化。在VGG的情况下,作者首先训练配置A, VGG11。VGG11能够收敛到相当低的损耗水平,但无法达到最先进的精度。

然后,VGG11的权重被用作配置B (VGG13)的初始化。VGG13中的conv3-64和conv3-128层(图7.1中用粗体显示)被随机初始化,而其余的层则从预先训练的VGG11网络中简单复制过来。使用初始化,Simonyan和Zisserman成功地训练了VGG13,但仍然没有达到最先进的准确性。

这种训练前的模式继续进行配置D,也就是我们通常所说的VGG16。这一次,三个新图层被随机初始化,而其他的图层从VGG13复制过来。然后使用这些“预热的预训练”层对网络进行训练,从而允许随机初始化的层收敛并学习识别模式。最终,VGG16能够在ImageNet分类挑战中表现得非常好。

作为最后的实验,Simonyan和Zisserman再次将pre-training应用于配置E, VGG19。这个非常深的架构复制了预先训练的VGG16架构的权值,然后添加了另外三个卷积层。经过训练,实验发现VGG19的分类准确率最高;然而,模型的大小(574MB)和训练和评估网络所花费的时间(这一切都是为了微薄的收益)使得它对深度学习实践者的吸引力降低。

如果赛前训练听起来像是一个痛苦而乏味的过程,那是因为它确实是。训练网络架构的较小变化,然后使用聚合权值作为网络深层版本的初始化,这是一个聪明的技巧;然而,它需要训练和调整N个独立网络的超参数,其中N是最终的网络架构,以及获得最终模型所需的之前的(较小的)网络数量。执行这个过程非常耗时,特别是对于具有许多全连接层(如VGG)的更深层网络。

好消息是,在训练非常深度的卷积神经网络时,我们不再执行预训练——相反,我们依赖于一个良好的初始化函数。我们现在使用Xavier/Glorot[18]或MSRA(也称为He等人的初始化),而不是纯粹的随机权值初始化。通过和米什金的工作放在在他们一份2015年的论文,所有你需要的是一个很好的初始化[19],他等人的深入钻研整流器:超越人类表现ImageNet分类[20],我们发现,我们完全可以跳过训练的阶段,直接跳转到更深层次的网络体系结构的变化。

在这些论文发表后,Simonyan和Zisserman重新评估了他们的实验,发现这些“更智能的”初始化方案和激活函数能够复制他们之前的表现,而不需要使用冗长的前训练。

因此,当你训练VGG16或VGG19时,请确保:

  1. 将所有ReLUs替换为prelu。
  2. 使用MSRA(也称为“He et al. initialization”)初始化网络中的权重层。

我还建议在网络中的激活函数之后使用批量归一化。在最初的Simonyan和Zisserman的论文中没有讨论应用批处理归一化,但是正如其他章节所讨论的,批处理归一化可以稳定你的训练,并减少获得一个合理执行模型所需的epoch总数。

现在我们已经了解了VGG16的架构,让我们继续实现来自Simonyan和Zisserman的配置D,也就是重要的VGG16架构:

你会从上面的项目结构中注意到,我已经在pyimagesearch的mxconv子模块中创建了一个名为mxvggnet.py的文件——这个文件是我们VGG16的实现将驻留的地方。打开mxvggnet.py,我们将开始实现网络:

第2行导入了我们的mxnet库,而第4行定义了MxVGGNet类。与本书中实现的所有其他cnn一样,我们将在第6行创建一个构建方法,该方法负责构建实际的网络架构。这个方法只接受一个参数,即我们的网络应该区别对待的类的数量。然后,第8行初始化所有重要的数据变量,即CNN的实际输入数据。

看看上面的图7.1,我们可以看到我们的第一个层块应该包含(CONV => RELU) * 2 => POOL。现在让我们来定义这个图层块:

这个块中的CONV层学习64个滤波器,每个滤波器的大小为3 × 3。一个有漏洞的ReLU变体,PReLU(在Starter Bundle的第10章中有介绍)被用作我们的激活函数。在每一个PReLU之后,我们应用一个批处理归一化层(在原始文章中没有包含,但在稳定VGG训练时很有用)。在层块的末端使用池化层,使用2 × 2核和2 × 2 stride减小卷的空间维度,减小卷的大小。我们还应用了一个小百分比(25%)的dropout来帮助对抗过拟合。

VGG16中设置的第二层也适用(CONV => RELU) * 2 =< POOL,只是这次学习了128个过滤器(同样,每个都是3 × 3)。由于我们通过最大池操作减少了空间输入体积,所以过滤器的数量翻了一倍:

该层块中的每一层CONV层负责学习128个滤波器。同样,在每个CONV层之后应用PReLU激活函数,然后是批处理归一化层。使用Max pooling减小体积的空间维度,然后使用少量的drop - out来减小过拟合的影响。

VGG的第三层块增加了一个额外的CONV层,意味着在最大池之前总共有三个CONV操作:

在这里,每个CONV层学习的滤波器数量跳到256个,但滤波器的大小仍然保持在3 × 3。同样使用了prelu和批处理标准化的模式。

VGG16的第四个块也应用了三个相互叠加的CONV层,但这次过滤器的总数再次翻倍至512:

有趣的是,VGG中的最后一组CONV层并没有增加过滤器的数量,仍然保持在512。至于为什么这个数字没有翻倍,我也不完全确定,但我的最佳猜测是:

  1. 从512个过滤器跳到1024个过滤器,引入了太多的网络参数,导致VGG16过拟合。
  2. 在最终的CONV块中使用1024个过滤器训练网络的计算开销太大。

无论如何,VGG16中最后一组CONV层也学习了512个过滤器,每个3 × 3:

我们的第一组FC层包括4,096个节点,也遵循了应用PReLU和批处理规范化的模式:

Dropout在FC层中应用的概率更大,为50%,因为这里的连接更密集,容易过度拟合。同样,我们添加另一个FC层,设置方式如下:

VGG16中最后的代码块为总类数构造FC层,然后应用softmax分类器:

虽然实现VGG16肯定需要更多的代码(与AlexNet相比),但这一切都相当简单。此外,使用上面图7.1所示的蓝图可以大大简化这个过程。当你实现像这样的顺序网络架构时,我建议你写第一组CONV层,然后复制和粘贴它们到下一个块,确保你:

  1. 适当地更改每个层的输入(即数据参数)。
  2. 根据你的蓝图增加CONV层的过滤器数量。

这样做将有助于减少由于手工编写每个层定义而可能注入到代码中的任何错误。

7.2 训练VGGNet

现在我们已经实现了VGG16,我们可以在ImageNet数据集上训练它。但首先,让我们定义一下我们的项目结构:

项目结构基本上与前一章中的AlexNet结构相同。我们需要一个名为train_vggnet.py的脚本来实际训练网络。然后,test_vggnet.py脚本将负责评估ImageNet上的VGG16。最后,imagenet_vggnet_config.py文件包含我们的实验配置。

在定义这个项目结构时,我简单地复制了整个mx_imagenet_alexnet目录,然后将文件重命名为vggnet而不是alexnet。比较imagenet_vggnet_config.py和imagenet_alexnet_config.py,你会注意到配置是相同的(因为我们的ImageNet数据集文件的路径没有改变),只有一个例外:

这里我将BATCH_SIZE从128减少到32。由于VGG16的深度和大小(因此它在我们的GPU上消耗的内存数量),我们将不能在同一时间通过网络传递相同数量的图像批。为了在我的GPU (Titan X, 12GB)上适合VGG16,批处理大小必须减少到32。如果你选择在自己的GPU上从头开始训练VGG16,如果你没有那么多的GPU内存,你可能需要进一步减少BATCH_SIZE。

其次,我还将用于训练VGGNet的gpu数量更新为8个。正如我在本章开头提到的,VGG16是ImageNet Bundle中唯一一个我不建议从头开始训练的章节,除非你有4到8个gpu。即使有这么多的gpu,培训网络也需要10-20天的时间。

现在我们已经更新了配置文件,让我们也更新train_vggnet.py。再回想一下,在AlexNet的第6章中,我们对train_alexnet.py进行了编码,这样在ImageNet上训练一个新的网络架构时,只需做最小的改变。作为完整性的问题,我将回顾整个文件,同时突出显示我们已做的更新;但是,要更详尽地了解培训脚本,请参阅第6章,其中详细地介绍了整个模板。

在第2行,我们从我们的项目结构中导入imagenet_vggnet_config文件——这个配置文件是从上一章的imagenet_alexnet_config导入中更改的。我们还将导入MxVGGNet,这是VGG16架构的实现。这是我们的导入部分所需要的惟一两个更改。

接下来,让我们解析我们的命令行参数,并创建我们的日志文件,这样mxnet就可以记录培训进度:

第27行加载RGB意味着,因此我们可以应用平均值减法数据归一化,而第28行根据我们用来训练VGG16的设备总数计算batchSize。接下来,我们需要构造训练数据迭代器:

以及验证数据迭代器:

现在我们准备初始化我们的优化器:

在Simonyan和Zisserman的论文之后,我们将使用SGD优化器,初始学习率为1e−2,动量项为0.9,L2权值衰减为0.0005。需要特别注意的是,根据我们的批量大小重新缩放梯度。

从那里,我们可以构造到我们的checkpointsPath目录的路径,其中模型权重将在每个epoch之后被序列化:

接下来,我们可以确定我们是(1)从第一个epoch开始训练我们的模型,还是(2)从一个特定的epoch重新开始训练:

第66-69行处理如果我们在没有预先检查点的情况下训练VGG16。在本例中,我们在第69行实例化了MxVGGNet类。否则,第72-81行假设我们正在从磁盘加载一个特定的检查点并重新开始训练(假设是在根据SGD优化器调整学习速率之后)。

最后,我们可以编译模型:

我们的模型将使用NUM_DEVICES gpu的ctx进行训练-您应该根据系统上使用的gpu数量更改这一行。另外,要特别注意第87行,在那里我们定义了初始化器——我们将使用MSRAPrelu作为我们的初始化方法,这与He等人提出的训练深度神经网络的方法完全相同。如果没有这种初始化方法,VGG16在训练时会难以收敛。

接下来,让我们定义我们的回调和评估指标集:

由于VGG16是一个非常慢的网络训练,我希望看到更多的训练更新记录到文件;因此,我减少了批量速度计更新的数量(以前AlexNet是500个),以便更频繁地将训练更新记录到日志中。然后我们将监测1级准确度、5级准确度和分类交叉熵。

我们最终的代码块处理培训VGG16:

正如您从train_vggnet.py的实现中看到的,这里详细介绍的代码与上一章中的trian_alexnet.py之间的差异非常小——这种相似性正是我在ImageNet上训练自己的cnn时使用相同模板的原因。我可以简单地复制文件,调整几个import语句,实例化我的网络,然后有选择地调整我的优化器和初始化器参数。这样做几次之后,你就可以在不到十分钟的时间内启动一个新的ImageNet项目。

7.3 评价VGGNet

//截止到P88页,2022.2.27日晚上21:38

//2022.2.28日下午12:55开始阅读

为了评估VGGNet,我们将使用在上面的项目结构中提到的test_vggnet.py脚本。请注意,这个脚本与前一章中的test_alexnet.py相同。没有对脚本做任何修改,因为本章中的test_*.py脚本是可以应用和重新应用于任何经过ImageNet训练的CNN的模板。

因为代码是相同的,所以我不会在这里检查test_vggnet.py。请参阅test_alexnet.py上的第6章,以全面了解代码。此外,您可以使用本书的代码下载部分来检查项目并查看test_vggnet.py的内容。同样,这些文件的内容是相同的,因为它们是我们在ImageNet上训练和评估cnn的框架的一部分。

7.4 VGGNet实验

在训练VGG16时,我考虑了Simonyan和Zisserman[17]、He等人[21,22]和Mishkin等人[19]进行的实验,这是至关重要的。通过这些工作,我能够避免进行额外的、昂贵的实验,并应用以下指导方针:

  1. 跳过预训练以获得更好的初始化方法。
  2. 使用MSRA/He等初始化。
  3. 使用PReLU激活函数。

因为我遵循了这些指导,我只需要用VGG16做一个实验来复制Simonyan和Zisserman的结果——这个单一的实验几乎完全复制了他们的结果。在本次实验中,我使用SGD优化器训练VGG16,初始学习率为1e−2,动量项为0.9,L2权值正则化为0.0005。为了加快训练速度,我使用了一个带有8个gpu的Amazon EC2实例。我不建议在少于4个gpu的机器上训练VGG,除非您非常有耐心。

我使用以下命令开始了VGG训练过程:

我允许网络一直训练到第50期,在这个时期,训练和验证的准确性似乎都停滞不前(图7.2,左上角)。然后按ctrl + c退出train_vggnet.py脚本,将学习速率从1e−2降低到1e−3:

然后使用下列命令恢复训练:

在图7.2(右上)中,您可以看到在20个时期内降低学习速率的结果。你马上就能看到训练和验证准确性的巨大飞跃。训练和验证损失也随着学习速率的更新而下降,这与在大型数据集(如ImageNet)上训练的深度卷积神经网络(deep Convolutional Neural Networks)上执行数量级变化时的准确性/损失饱和时的情况一样常见。

在过去的70年代,我再次注意到验证损失/准确性停滞,而训练损失继续下降——这个指标表明过度拟合开始发生。事实上,我们可以看到,在精度图中,这种发散在60年代后开始出现:训练精度继续攀升,而验证精度保持不变。

为了充分利用VGG的每一点性能,我再次将学习速率从1e−3降低到1e−4,并在第70纪元重新开始训练:

然后,我允许网络继续训练10个时期,直到纪元80,在那里我应用了早期停止标准(图7.2,底部)。此时,验证准确性/损失已经停滞不前,而训练损失和验证损失之间的差距开始出现严重分歧,这表明我们有轻微过拟合,进一步训练只会损害模型的泛化能力。在80年代末,VGG16的rank-1和rank-5验证准确率分别为68.77%和88.78%。然后,我使用以下命令计算测试集上的第80个epoch:

从我的输出结果可以看出,VGG16达到了71.42%的rank-1和90.03%的rank-5准确率,这与Simonyan和Zisserman的原始VGGNet论文几乎相同。为了完整起见,我在表7.1中列出了我的学习速度计划,以供希望复制这些结果的读者参考。

VGG16最大的缺点(除了训练所需的时间)是最终模型的大小,重量超过533MB。如果你正在构建一个深度学习应用程序,并打算将模型大小的应用程序与你的应用程序一起发布,那么你已经有了一个500MB的包>来分发。此外,所有软件都会在某个时候更新,这需要您重新打包和重新分发一个500MB的大包,很可能是通过网络连接。对于高速宽带连接,这种大模型尺寸可能不是问题。但对于资源有限的设备,如嵌入式设备、手机,甚至自动驾驶汽车,500MB的模型大小可能是一个巨大的负担。在这种情况下,我们喜欢非常小的模型尺寸。

幸运的是,我们将在此包中讨论的所有剩余模型都比VGGNet小得多。我们高度精确的ResNet模型的权重为102MB。谷歌网络甚至更小,只有28MB。而超小、高效的SqueezeNet模型尺寸只有4.9MB,这使得它非常适合任何类型的资源受限的深度学习。

7.5 总结

在本章中,我们使用mxnet库实现了VGG16架构,并在ImageNet数据集上从头开始训练它。我们没有使用冗长、耗时的预训练过程来训练我们网络架构的较小版本,然后使用这些预训练的权值作为我们更深层次架构的初始化,而是跳过了这一步,依靠He等人和Mishkin等人的工作:

  1. 我们用PReLUs取代了标准的ReLU激活。
  2. 我们将gloot /Xavier权值初始化替换为MSRA/He等人的初始化。

这个过程使我们能够在一个单一的实验中复制Simonyan和Zisserman的工作。无论何时从头开始训练类似vgg的架构,只要可能,一定要考虑使用prelu和MSRA初始化。在某些网络体系结构中,当使用PReLU + MSRA时,您不会注意到对性能的影响,但对于VGG,这种影响是巨大的。

总的来说,我们的VGG16版本在ImageNet测试集中获得了71.42%的排名1和90.03%的排名5的准确性,这是我们目前为止在这个bundle中看到的最高的准确性。此外,VGG体系结构已经证明自己非常适合于泛化任务。在下一章中,我们将更详细地探索微架构,包括GoogLeNet模型,它将为ResNet和SqueezeNet等更专业的微架构奠定基础。

上述实验代码详见本人github仓库:

TheWangYang/Code_For_Deep_Learning_for_Computer_Vision_with_Python: A code repository for Deep Learning for Computer Vision with Python. (github.com)

《Deep Learning for Computer Vision with Python》阅读笔记-ImageNetBundle(第7章)-在ImageNet上训练VGGNet相关推荐

  1. 《Deep Learning for Computer Vision withPython》阅读笔记-StarterBundle(第6 - 7章)

    6.配置您的开发环境 当涉及到学习新技术(尤其是深度学习)时,配置开发环境往往是成功的一半.在不同的操作系统.不同的依赖版本以及实际的库本身之间,配置您自己的深度学习开发环境可能是相当令人头痛的事情. ...

  2. 《Deep Learning for Computer Vision withPython》阅读笔记-PractitionerBundle(第9 - 11章)

    9.使用HDF5和大数据集 到目前为止,在本书中,我们只使用了能够装入机器主存储器的数据集.对于小数据集来说,这是一个合理的假设--我们只需加载每一个单独的图像,对其进行预处理,并允许其通过我们的网络 ...

  3. 《Deep Learning for Computer Vision withPython》阅读笔记-StarterBundle(第4 - 5章)

    4.图像分类基础 这句格言在我们的生活中已经听过无数次了.它只是意味着一个复杂的想法可以在一个单一的图像中传达.无论是查看我们股票投资组合的折线图,查看即将到来的足球比赛的传播,还是简单地学习绘画大师 ...

  4. 《Deep Learning for Computer Vision withPython》阅读笔记-StarterBundle(第18 - 23章)

    18.检查点模型 截止到P265页 //2022.1.18日22:14开始学习 在第13章中,我们讨论了如何在培训完成后将模型保存和序列化到磁盘上.在上一章中,我们学习了如何在发生欠拟合和过拟合时发现 ...

  5. 《Deep Learning for Computer Vision with Python》StarterBundle-总结概述

    <Deep Learning for Computer Vision withPython> StarterBundle总结概述 //2022.2.4日下午16:18开始总结 第2章-什么 ...

  6. Python视觉深度学习系列教程 第三卷 第5章 在ImageNet上训练VGGNet

            第三卷 第五章 在ImageNet上训练VGGNet 在本章中,我们将从头开始学习如何在 ImageNet 数据集上训练 VGG16 网络架构. 该网络的特点是简单,仅使用3*3 卷积 ...

  7. 《Deep Learning for Computer Vision with Python》阅读笔记-ImageNetBundle(第5章)-ImageNet数据集的准备

    5.准备ImageNet数据集 一旦你下载了ImageNet数据集,你可能会有点不知所措.你现在有超过120万的图片驻留在磁盘上,没有一个人有"人类可读"文件的名字,没有一个明显的 ...

  8. Python视觉深度学习系列教程 第三卷 第8章 在ImageNet上训练SqueezeNet

            第三卷 第八章 在ImageNet上训练SqueezeNet         关于在ImageNet大规模视觉识别挑战 (ILSVRC) 上训练深度神经网络的最后一章中,将讨论Sque ...

  9. 阅读笔记:What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision?

    阅读笔记:What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision? 1.介绍 2.相关工作 2.1 贝叶 ...

最新文章

  1. treeview托拽和动态添加节点以及treeview和xml的交互的实现
  2. Properties作为Map集合的使用
  3. c语言中异或指令,C语言总结之异或运算的一些特性及巧妙应用
  4. swing中模态对话框(setModal(true))和显示对话框(setVisible(true))的编写顺序
  5. 自己编写一个前端精确打印控件
  6. Android原生系统API自带dp、px、sp单位转换
  7. springboot怎么返回404_Spring Boot2 系列教程(十三)Spring Boot 中的全局异常处理
  8. NLP+语义分析(四)︱中文语义分析研究现状(CIPS2016、角色标注、篇章分析)
  9. 【DCVRP】基于matlab蚁群算法求解带容量+距离的车辆路径规划问题【含Matlab源码 1038期】
  10. 【Docker】拉取Oracle 11g镜像配置
  11. vue require图片_前后端分离当下,后端同学总结的手动构建vue项目
  12. hive窗口函数和hive基础使用
  13. python 更新pip镜像源
  14. matlab宝典pdf,《MATLAB 宝典(第4版)》---- 优化.pdf
  15. Qt使用flowlayout,使控件两端间距始终固定,垂直和水平间距相等
  16. python 身份证校验
  17. 计算机如何与光猫连接网络,路由器怎么连接猫和电脑 路由器连接详解【图文】...
  18. HTTP流量复制引流工具(web压测及线上问题复现利器)--Gor(GoReplay)
  19. java保留小数点后7位,不够补0,去小数点存库,带小数点展示
  20. 你非要躺平,我也没办法啊,逼着学吗

热门文章

  1. 解析阿里巴巴为什么选择赴美上市
  2. AR+餐饮创意应用亮相微信公开课
  3. 如何设计一张高品位高水准的海报?
  4. QGIS 3.17 编译
  5. 数据可视化之信息图表
  6. Linux用ls和grep统计文件个数
  7. 图片格式有哪些?区别是什么
  8. java doxygen_Doxygen 使用总结
  9. mathtype 7.4中文版如何嵌入到word2016中
  10. xcode编译工程时遇到 Permission denied的解决办法