第二十七周:文献阅读笔记

第二十七周:文献阅读笔记

  • 摘要
  • Abstract
  • DenseNet 网络
    • 1. 文献摘要
    • 2. 引言
    • 3. ResNets
    • 4. Dense Block
    • 5. Pooling layers
    • 6. Implementation Details
    • 7. Experiments
    • 8. Feature Reuse
    • 9. 代码实现
  • 总结

摘要

DenseNet(密集连接网络)是一种深度学习神经网络架构,由Kaiming He等人在2017年提出。相较于传统的卷积神经网络(CNN),DenseNet具有更加密集的连接方式,每一层都与其前面所有层直接相连。这种结构有助于缓解梯度消失问题,并且可以促进信息和梯度的流动,有助于提升训练深度网络的效果。DenseNet在一些图像识别、物体检测和图像分割等领域取得了很好的效果。本文将详细介绍DenseNet网络,让我们来深入了解。

Abstract

DenseNet (Dense Connected Network) is a deep learning neural network architecture, proposed by Kaiming He et al. in 2017. Compared to traditional Convolutional Neural Networks (CNNs), DenseNet has a more densely connected approach, where each layer is directly connected to all its preceding layers. This structure helps to alleviate the problem of gradient vanishing and can facilitate the flow of information and gradients, which helps to improve the effect of training deep networks.DenseNet has achieved good results in some fields such as image recognition, object detection and image segmentation. In this article, we will introduce the DenseNet network in detail, so that we can get a deeper understanding.

DenseNet 网络

文献链接:Densely Connected Convolutional Networks

1. 文献摘要

目前的工作表明如果将接近输入和输出的层之间短接,卷积神经网络可以更深、精度更高且高效。在本篇论文中,我们利用到观察到的这个结果提出了密集卷积网络(DenseNet),它的每一层在前向反馈模式中都和后面的层有连接,与L层传统卷积神经网络有L个连接不同,DenseNet中每个层都和其之后的层有连接,因此L层的DenseNet有 L(L+1)/2 个连接关系。对于每一层,它的输入包括前一层的输出和该层之前所有层的输入。DenseNets有几个引入注目的优势:

  1. 缓解了梯度消失
  2. 加强了特征传播
  3. 增强了特征复用
  4. 减少了参数量

我们在四个目标识别的基准测试集(CIFAR-10、CIFAR-100、SVHN 和 ImageNet)上评估了我们的结构,可以发现DenseNet在减少计算量的同时取得了更好的表现。

2. 引言

当CNN网络的深度持续增加时,一个新的研究问题就会出现:输入部分的信息流或者梯度在经过很多层之后,当到达网络结束(或开始)的地方时会消失。关于这个问题的研究有很多,可以发现的是虽然在网络拓扑和训练时方法各异,但是它们都有一个关键的特征:即前层和后层之间有短接。
在这里插入图片描述
在这篇文章中,遵从上面的直觉并提出了一种简单的连接模式:可以最大化网络中各层之间的信息流动,我们将所有层之间都相互连接,为了保留前向的特征,每个层都会获得前层的额外输入并将本层的特征传递给后续的层。Figure 1展示了这个关系,与ResNets相比,我们没有对上一层的输入和上一层的输出进行特征融合后再送入下一层,而是将前面所有层的特征图它们进行拼接。因此,第l层有l个输入,它由前面卷积层的特征图构成,并且本层的特征图也会传递给后续的L-l个层,所以一个L层的网络总共会有 L(L+1)/2 个连接,而不是传统结构的L个。

  • 一个反直觉的事实就是相比传统卷积神经网络而言,DenseNet的参数量更少,这是因为它每一层的通道数都大大缩减了;
  • 另一个比较大的优势是提高了整个网络的信息和梯度流动,这使得网络更容易。每一层都可以将得到来自损失函数的梯度和原始的输入信号,这相当于深度监督的影响,对网络训练很有帮助;
  • 同时DenseNet还有正则化的影响,它使得面对小样本任务时过拟合的风险大大降低;

x0作为整个网络结构的输入,网络由L层组成,每一层的运算用一个非线性转换Hl()表示,这里的l表示第几层。Hl()可以看成由BN、ReLU、池化、卷积操作定义的复合运算。同时将第l层的输出定义为xl。

3. ResNets

传统的卷积神经网络中,第l层的输入为第l-1层的输出,第l层的输出为: x l = H l ( x l − 1 ) x_l=H_l(x_{l}-1) xl=Hl(xl1)ResNets添加了旁路支路: x l = H l ( x l − 1 ) + x l − 1 x_l=H_l(x_l-1)+x_{l-1} xl=Hl(xl1)+xl1ResNets的优势是梯度可以直接通过恒等映射从后面的层传到前层来,然而,恒等映射 和 Hl的输出通过叠加结合在一起,这一定程度上阻碍了网络中的信息流。

4. Dense Block

DenseBlock包含很多层,每个层的特征图大小相同(才可以在通道上进行连结),层与层之间采用密集连接方式。
在这里插入图片描述
在这里插入图片描述
上图是一个包含5层layer的Dense Block。可以看出Dense Block互相连接所有的层,具体来说就是每一层的输入都来自于它前面所有层的特征图,每一层的输出均会直接连接到它后面所有层的输入。所以对于一个L层的DenseBlock,共包含 L*(L+1)/2 个连接(等差数列求和公式),如果是ResNet的话则为(L-1)*2+1。从这里可以看出:相比ResNet,Dense Block采用密集连接。而且Dense Block是直接concat来自不同层的特征图,这可以实现特征重用(即对不同“级别”的特征——不同表征进行总体性地再探索),提升效率,这一特点是DenseNet与ResNet最主要的区别。

Note:k —— DenseNet中的growth rate(增长率),这是一个超参数。一般情况下使用较小的k(比如12),就可以得到较佳的性能。
假定输入层的特征图的通道数为k0,那么L层输入的channel数为 k0+k*(L-1),因此随着层数增加,尽管k设定得较小,DenseBlock中每一层输入依旧会越来越多。

另外一个特殊的点:DenseBlock中采用BN+ReLU+Conv的结构,平常我们常见的是Conv+BN+ReLU。这么做的原因是:卷积层的输入包含了它前面所有层的输出特征,它们来自不同层的输出,因此数值分布差异比较大,所以它们在输入到下一个卷积层时,必须先经过BN层将其数值进行标准化,然后再进行卷积操作。

5. Pooling layers

X l = H l ( [ x 0 , x 1 , . . . , x l − 1 ] ) X_l=H_l([x_0,x_1,...,x_l-1]) Xl=Hl([x0,x1,...,xl1])
[x0,x1,…,x(l-1)]是将第0、1、…、l-1层的feature map拼接在一起。当特征图的尺寸发生变化时,上式中的拼接操作是不可行的,然而,卷积网络的一个重要部分就是通过下采样改变特征图的尺寸。为了在我们的网络结构中做下采样,我们将网络划分为多个密集连接卷积网络块,如Figure 2所示,我们将块之间的层看作转换层,它是由BN、1x1卷积层、池化层构成,目的是做卷积和池化。

如果每一个Hl都产生k个feature map,那么第l个层就会有 k0 + k x (l - 1) 个输入的feature map,k0表示输入层的通道数,DenseNet和已经存在的网络结构中一个重要区别是DenseNet的通道数很窄,比如k=12,我们将超参数k定义为网络的growth rate,在后面的分析中我们会看到小的growth rate对于在测试集上获得很好的表现也是足够的。一个解释就是每一层都可以访问块中前面层,因此,可以理解为网络的“集体认识”,可以把网络的特征图看作是全局变量,每过一个层,就往全局变量中添加k个特征图。

尽管每个层的输出都只有k个通道,但是它的输入通道数很大。可以在 3x3 的卷积层之前使用 1x1 的瓶颈层来提高计算效率,我们发现这样的设计非常高效,一个 bottleneck层代指 BN-ReLU-Conv(1x1)-BN-ReLU-Conv(3x3),这样的网络结构称为 DenseNet-B。在我们的实验中,1x1 的卷积层的输出通道为 4k。

为了使模型更加紧密,我们通过transition层减少特征图的数量。如果一个dense block包含m个特征图,那么通过transition层会产生 theta * m(下取整)个输出feature map,在这里 0<theta<=1。当 theta=1时,输出通道数不会发生变化,我们将 theta<1 的DenseNet 记为 DenseNet-C,同时在我们的实验中,将theta设置为0.5.当bottleneck层和theta<1的transition层同时使用时,DenseNet可以称为 DenseNet-BC。

6. Implementation Details

在除了ImageNet之外的数据集上,实验中的DenseNet都用了三个dense block(ImageNet用了四个块),每个块总包含有相同数量的层。其他的具体细节可以参考论文第三节中的Implementation Details部分。

在ImageNet数据集上,我们使用了带有四个dense block的DenseNet-BC结构,输入图片尺寸为224x224。最开始的卷积层为2k个7x7x输入图片通道数的卷积核,步长为2;所有层的feature-maps的数量也都由k设置,对ImageNet使用的网络配置如table1所示:
在这里插入图片描述
DenseNet-121是指网络总共有121层:(6+12+24+16)*2 + 3(transition layer) + 1(7x7 Conv) + 1(Classification layer) = 121;

再详细说下bottleneck和transition layer操作。在每个Dense Block中都包含很多个子结构,以DenseNet-169的Dense Block(3)为例,包含32个11和33的卷积操作,也就是第32个子结构的输入是前面31层的输出结果,每层输出的channel是32(growth rate),那么如果不做bottleneck操作,第32层的33卷积操作的输入就是3132+(上一个Transition Layer的输出channel),近1000了。而加上11的卷积,代码中的11卷积的channel是growth rate4,也就是128,然后再作为33卷积的输入。这就大大减少了计算量,这就是bottleneck。至于transition layer,放在两个Dense Block中间,是因为每个Dense Block结束后的输出channel个数很多,需要用11的卷积核来降维。还是以DenseNet-169的Dense Block(3)为例,虽然第32层的33卷积输出channel只有32个(growth rate),但是紧接着还会像前面几层一样有通道的concat操作,即将第32层的输出和第32层的输入做concat,前面说过第32层的输入是1000左右的channel,所以最后每个Dense Block的输出也是1000多的channel。因此这个transition layer有个参数reduction(范围是0到1),表示将这些输出缩小到原来的多少倍,默认是0.5,这样传给下一个Dense Block的时候channel数量就会减少一半,这就是transition layer的作用。文中还用到dropout操作来随机减少分支,避免过拟合,毕竟这篇文章的连接确实多。

7. Experiments

我们设计实验在几个基准测试集上验证了DenseNet的有效性,并着重与ResNet 和它的几个变体做了比较。
在这里插入图片描述
训练的具体细节:

  • SGD训练网络;
  • 在CIFAR和SVHN上的batch size为64,epochs为300和40,初始学习率为0.1,在训练10%和75%的epochs之后衰减为原来的10%;
  • 在ImageNet上,我们训练网络时的batch size 为256,epochs为90,初始学习率为0.1,epoch为30和60时衰减到上次的10%;
  • 训练过程采用了和 ResNet 的文章完全相同的设定。但仍然存在一些技巧,例如因为多次 Concatenate 操作,同样的数据在网络中会存在多个复制,这里需要采用一些显存优化技术,使得训练时的显存占用可以随着层数线性增加,而非增加的更快,相关代码在链接中可以查看;
  • 权重衰减率为10**(-4),没有dampening的Nesterov momentum为0.9,dropout率为0.2;

在DenseNet和stochastic depth regularization之间存在着一种有趣的联系,在stochastic depth中,残差块中的层可以随意丢弃,这使得周围层之间可能直接相连。但是池化层从未丢弃,这看起来有些和DenseNet类似,尽管两者的方法不同,但我们可以从stochastic depth的角度理解DenseNet——引入了正则化的意味。

8. Feature Reuse

DenseNet允许本层访问之前所有层的feature maps,我们设计了一组实验来验证了这个想法,在C10+数据集上L=40、k=12,对于一个块内的l层,我们计算和它相连接的s层的权重平均值的绝对值,Figure 5展示了三个dense块的热力图,权重平均值绝对值展示了这一层对之前某一层特征的复用率。
在这里插入图片描述
可以发现:

  1. 在同一个块内所有层都经过了很多层传播它的权重,这表明了较早层提取的特征仍然会被较深层直接使用;
  2. 即便是transition层,也使用到 之前dense块内所有层的特征。
  3. 第二个和第三个dense块对之前transition层的复用率很低,这说明transition层的输出仍然有很多的冗余特征。这也为DenseNet提供了压缩必要性的证据支持。
  4. 尽管最后的位于最右面分类层也使用了dense块多层的特征信息,但是似乎它更倾向于使用最后几个层的feature-maps,说明在网络的最后几层产生了一些高级的特征。

9. 代码实现

BN-ReLu-Conv

class BN_Conv2d(nn.Module):
    """
    BN_CONV_RELU
    """

    def __init__(self, in_channels: object, out_channels: object, kernel_size: object, stride: object, padding: object,
                 dilation=1, groups=1, bias=False) -> object:
        super(BN_Conv2d, self).__init__()
        self.seq = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride,
                      padding=padding, dilation=dilation, groups=groups, bias=bias),
            nn.BatchNorm2d(out_channels)
        )

    def forward(self, x):
        return F.relu(self.seq(x))

Dense Block

class DenseBlock(nn.Module):

    def __init__(self, input_channels, num_layers, growth_rate):
        super(DenseBlock, self).__init__()
        self.num_layers = num_layers
        self.k0 = input_channels
        self.k = growth_rate
        self.layers = self.__make_layers()

    def __make_layers(self):
        layer_list = []
        for i in range(self.num_layers):
            layer_list.append(nn.Sequential(
                BN_Conv2d(self.k0+i*self.k, 4*self.k, 1, 1, 0),
                BN_Conv2d(4 * self.k, self.k, 3, 1, 1)
            ))
        return layer_list

    def forward(self, x):
        feature = self.layers[0](x)
        out = torch.cat((x, feature), 1)
        for i in range(1, len(self.layers)):
            feature = self.layers[i](out)
            out = torch.cat((feature, out), 1)
        return out

网络搭建并测试

class DenseNet(nn.Module):

    def __init__(self, layers: object, k, theta, num_classes) -> object:
        super(DenseNet, self).__init__()
        # params
        self.layers = layers
        self.k = k
        self.theta = theta
        # layers
        self.conv = BN_Conv2d(3, 2*k, 7, 2, 3)
        self.blocks, patches = self.__make_blocks(2*k)
        self.fc = nn.Linear(patches, num_classes)

    def __make_transition(self, in_chls):
        out_chls = int(self.theta*in_chls)
        return nn.Sequential(
            BN_Conv2d(in_chls, out_chls, 1, 1, 0),
            nn.AvgPool2d(2)
        ), out_chls

    def __make_blocks(self, k0):
        """
        make block-transition structures
        :param k0:
        :return:
        """
        layers_list = []
        patches = 0
        for i in range(len(self.layers)):
            layers_list.append(DenseBlock(k0, self.layers[i], self.k))
            patches = k0+self.layers[i]*self.k      # output feature patches from Dense Block
            if i != len(self.layers)-1:
                transition, k0 = self.__make_transition(patches)
                layers_list.append(transition)
        return nn.Sequential(*layers_list), patches

    def forward(self, x):
        out = self.conv(x)
        out = F.max_pool2d(out, 3, 2, 1)
        # print(out.shape)
        out = self.blocks(out)
        # print(out.shape)
        out = F.avg_pool2d(out, 7)
        # print(out.shape)
        out = out.view(out.size(0), -1)
        out = F.softmax(self.fc(out))
        return out

搭建网络并测试:

def densenet_121(num_classes=1000):
    return DenseNet([6, 12, 24, 16], k=32, theta=0.5, num_classes=num_classes)


def densenet_169(num_classes=1000):
    return DenseNet([6, 12, 32, 32], k=32, theta=0.5, num_classes=num_classes)


def densenet_201(num_classes=1000):
    return DenseNet([6, 12, 48, 32], k=32, theta=0.5, num_classes=num_classes)


def densenet_264(num_classes=1000):
    return DenseNet([6, 12, 64, 48], k=32, theta=0.5, num_classes=num_classes)


def test():
    net = densenet_264()
    summary(net, (3, 224, 224))
    x = torch.randn((2, 3, 224, 224))
    y = net(x)
    print(y.shape)

test()

当k=32,θ=0.5时,DenseNet_264网络的测试结果如下图,可以看到DenseNet的参数量确实比ResNet要少得多。
在这里插入图片描述

总结

本周看了 DenseNet 网络这篇经典论文,让我对DenseNet网络基本原理有了一定的了解,DenseNet作为一种新型神经网络架构,其密集连接方式和优秀的性能使其成为一种值得研究的模型,下周我将继续保持论文阅读的习惯,同时也进一步提升自己的代码能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/299482.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI 工具探索(二)

我参加了 奇想星球 与 Datawhale 举办的 【AI办公 X 财务】第一期&#xff0c;现在这是第二次打卡&#xff0c;也即自由探索&#xff0c;我选择 Modelscope 的 Agent 探索&#xff0c;并用gpts创作助理对比&#xff01; 最近想学学小红书的运营方法&#xff0c;选择了 小红书I…

图像分割-Grabcut法

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的C#版本请访问&#xff1a;图像分割-Grabcut法(C#)-CSDN博客 GrabCut是一种基于图像分割的技术&#xff0c;它可以用于将图像…

CnosDB容灾方案概述

本文主要介绍了跟容灾相关的关键技术以及技术整合后形成的几种具体方案&#xff0c;每种方案都在RTO、RPO、部署成本和维护成本等方面有自己的特点和区别&#xff0c;可以根据具体场景选择最合适的方案。 基本概念 RTO&#xff08;Recovery Time Objective&#xff09;&#x…

Qt基本认识

1. 基本认识 1.1 学习方法&#xff1a; &#xff08;1&#xff09;英语阅读能力要好一点 QT将一些类和方法进行了封装&#xff0c;一般是采用英语&#xff08;方法名、属性、子类、父类等等&#xff09;进行介绍 &#xff08;2&#xff09;学习QT reator 1&#xff09;多查帮助…

数据交互系列:认识 cookie

cookie的原理 http本身是一个无状态的请求&#xff0c;cookie最初的原始目的是为了维持状态而产生的。在首次访问网站时&#xff0c;浏览发送请求中并未携带cookie&#xff0c;即发送无状态请求服务器接受请求之后会在请求上的respond header上加入cookie相关信息并返回给浏览…

数字孪生在虚拟现实(VR)中的应用

数字孪生在虚拟现实&#xff08;VR&#xff09;中的应用为用户提供了更深入、沉浸式的体验&#xff0c;同时通过数字孪生技术模拟真实世界的物理实体。以下是数字孪生在VR中的一些应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发…

13年测试老鸟,性能测试-全链路压测总结,一文打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是全链路压…

二维和三维联合进行圆孔空间定位

0.任务描述 对空间圆孔进行三维空间的定位&#xff0c;方便后续的抓取或装配流程&#xff1a;使用二维图与opencv霍夫圆检测进行二维上的定位&#xff0c;再从深度图上查询深度信息&#xff0c;结合相机内参计算出相机坐标系下圆孔的三维坐标信息&#xff0c;并在点云上进行标…

自定义View之重写onMeasure

一、重写onMeasure()来修改已有的View的尺寸 步骤&#xff1a; 重写 onMeasure()&#xff0c;并调用 super.onMeasure() 触发原先的测量用 getMeasuredWidth() 和 getMeasuredHeight() 取到之前测得的尺寸&#xff0c;利用这两个尺寸来计算出最终尺寸使用 setMeasuredDimensio…

[通俗易懂]c语言中指针变量和数值之间的关系

一、指针变量的定义 在C语言中&#xff0c;指针变量是一种特殊类型的变量&#xff0c;它存储的是另一个变量的内存地址。指针变量可以用来间接访问和操作内存中的其他变量。指针变量的定义如下&#xff1a; 数据类型 *指针变量名&#xff1b;其中&#xff0c;数据类型可以是任…

SQL优化:执行计划

前面我们讲述了使用索引或分区表来进行存储层次的优化,也讲述了通过条件提升进行结果集的优化。这边文章我们来学习一下其中的细节,即查看数据库是怎么一步一步把数据拿给我们的。也就是执行计划。 语法 explain sql语句 练习 首先,我们来玩下简单的 explain select * …

HUAWEI WATCH 系列 eSIM 全新开通指南来了

HUAWEI WATCH 系列手表提供了eSIM硬件能力&#xff0c;致力为用户提供更便捷、高效的通信体验。但eSIM 业务是由运营商管理并提供服务的&#xff0c;当前运营商eSIM业务集中全面恢复&#xff0c;电信已经全面恢复&#xff0c;移动大部分省份已经全面放开和多号App开通方式&…

20240107移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下调通能上网

20240107移远的4G模块EC20在Firefly的AIO-3399J开发板的Android11下调通能上网 2024/1/7 11:17 开发板&#xff1a;Firefly的AIO-3399J【RK3399】SDK&#xff1a;rk3399-android-11-r20211216.tar.xz【Android11】 Android11.0.tar.bz2.aa【ToyBrick】 Android11.0.tar.bz2.ab …

Docker mysql 主从复制

目录 介绍&#xff1a;为什么需要进行mysql的主从复制 主从复制原理&#xff1a; ✨主从环境搭建 主从一般面试问题&#xff1a; 介绍&#xff1a;为什么需要进行mysql的主从复制 在实际的生产中&#xff0c;为了解决Mysql的单点故障已经提高MySQL的整体服务性能&#xff…

如何恢复Mac误删文件?

方法1. 使用撤消命令 当你在 Mac 上删除了错误的文件并立即注意到你的错误时&#xff0c;你可以使用撤消命令立即恢复它。顾名思义&#xff0c;此命令会反转上次完成的操作&#xff0c;并且有多种方法可以调用它。如果你已经采取了其他操作或退出了用于删除文件的应用程序&…

算法与数据结构之数组(Java)

目录 1、数组的定义 2、线性结构与非线性结构 3、数组的表现形式 3.1 一维数组 3.2 多维数组 4、重要特性&#xff1a;随机访问 5、ArrayList和数组 6、堆内存和栈内存 7、数组的增删查改 7.1 插入数据 7.2 删除一个数据 7.3 修改数组 7.4 查找数据 8、总结 什么…

蓝桥杯基础知识2 全排列 next_permutation(), prev_permutation()

蓝桥杯基础知识2 全排列 next_permutation()&#xff0c; prev_permutation() #include<bits/stdc.h> using namespace std;int a[10];int main(){for(int i 1; i < 4; i)a[i] i; //4*3*2*1 24bool tag true;while(tag){for(int i1; i < 4; i)cout << a[…

Fiddler工具 — 8.会话列表(Session List)

1、会话列表说明 Fiddler抓取到的每条HTTP请求&#xff08;每一条称为一个session&#xff09;。 主要包含了请求的ID编号、状态码、协议、主机名、URL、内容类型、body大小、进程信息、自定义备注等信息。如下图&#xff1a; 说明&#xff1a; 名称含义#抓取HTTP Request的顺…

电脑如何屏幕录制?轻松录制高清视频

在当今信息化的时代&#xff0c;电脑已经成为工作和生活的重要工具。无论是在进行演示、教学还是记录重要操作步骤时&#xff0c;屏幕录制都是非常有用的。可是电脑如何屏幕录制呢&#xff1f;本篇文章将介绍三种常见的电脑屏幕录制方法&#xff0c;通过学习这些方法&#xff0…

[C#]使用DlibDotNet人脸检测人脸68特征点识别人脸5特征点识别人脸对齐人脸比对FaceMesh

【官方框架地址】 https://github.com/takuya-takeuchi/DlibDotNet 【算法介绍】 DlibDotNet是一个开源的.NET库&#xff0c;用于实现机器学习和计算机视觉应用。它基于C库dlib&#xff0c;通过C/CLI封装了dlib的所有功能&#xff0c;为.NET开发者提供了简单易用的API。以下是…