深度学习原理篇 第七章:Deformable DETR

简介: 简要介绍Deformable DETR的原理和代码实现。

参考教程:

论文:https://arxiv.org/pdf/2010.04159.pdf

源码:https://github.com/fundamentalvision/Deformable-DETR

Deformable Conv

首先来简单的介绍一下可变形卷积。

代码链接: torchvison.DeformConv2d

deformable conv 可变形卷积认为CNN固定的几何结构不能适应图像中复杂的物体。普通的卷积是在固定的、规则的网络点上进行数据采样,感受野的形状是受限的,网络对几何形变的适应力也是受限制的。

所以可变形卷积就给卷积核的每个采样点增加一个可学习的偏移量,让采样点不再受限于规则的网格上。
image.png

普通的卷积网络在处理一张图的不同位置时,感受野大小都是相同的,这不太合适。对编码了位置信息的神经网络来说,不同位置对应的应该是不同的元素或形变的物体。它不应该是固定的一个框。

在上图中举了几个可变形卷积的例子。第一个是我们标准的方方正正的普通卷积。第二个是deformed 卷积,可以看到它的采样点都有着不同方向和距离的偏移。第三个图比较规整,它更像是我们常说的空洞卷积。第四个图是角度的旋转。
image.png

上面这个图是比较直白的deformable convolution的原理图。regular的卷积是某个位置上的值是通过输入的对应位置和卷积核的加权结合得到的。在deformable convolution中,它给每个位置$p_n$增加了一个偏移量$\Delta p_n$。而这个偏移量则是由另一个卷积得到的。

也就是说deformable convolution其实是由两个卷积组成的,一个走正常的卷积,一个用于计算偏移量。

Deformable DETR

DETR作为一个省去了很多比如NMS这些处理工作的目标检测方法,不仅可以实现端到端的检测,检测的效果还非常好。

但是它也是有一些问题的。

  1. 首先它的收敛速度非常慢。这个问题之前VIT也有说过,算是基于self-attention训练的常见问题。它相当于自己学习感受野和元素间的关系,更加flexible,所以收敛时间也就更长。
  2. 它检测小物体的效果比较差。因为他是基于backbone得到的feature map做的检测,所以也受到feature map的resolution的限制。假如你想用high-resolution的featuremap来做预测,那么使用DETR的计算复杂度也太高了,是不可接受的。

作者在论文中说,上面这两个问题归根到底都是transformer自身的缺陷。在初始化的时候,这个attention module对feature map上的每一个像素施加了均匀的注意力权重,所以需要长时间的寻来你来关注到稀疏的有意义的位置。

deformable convolution呢,则是一个非常有效的关注稀疏空间位置的方法,它天然地能避免上面这个问题,但是它呢缺乏元素相对关系的建模,而这又是transformer能给它的。所以才会想把两者结合起来。

计算量

之前我们有计算过MSA的计算量,这里来复习一下。

  1. C代表token的维度,假设我们的输入大小是$A^{h*w,C}$。在进行Q\K\V计算时唯独没有发生变化,那么进行三次矩阵乘法$A^{h*w,C}*W^{C,C}$,每次计算量是$hwC^2$,计算了三次,总的计算量是$3hwC^2$。
  2. 然后计算$Q^{h*w,C}$和$K^{h*w,C}$的内积,计算结果为$X^{h*w,h*w}$,内积计算需要点对点进行计算,所以计算量是$h^2w^2C$。
  3. 再下一步计算$X^{h*w,h*w}*V^{h*w,C}$,计算量为$h^2w^2C$。
  4. 因为是多头自注意力,所以还需要增加一步$B^{h*w,C}*W^{C,C}$,计算量为$hwC^2$
  5. 加起来的总的计算量是:
    $$ 4hwC^2 + 2h^2w^2C $$

假设我们以backbone的最后一层featuremap为输出,送入encoder中进行MSA。假如你的输入图像大小为480*480*3,那么经过backbone后得到的featuremap大小为15*15*2048,2048的通道数还需要经过1*1卷积降维变成C,直接代入数字:
$$ \begin{align} &4hwC^2 + 2h^2w^2C \\ = &4\times15\times15\times C^2+ 2\times15^2\times15^2\times C\\ =&900C^2 + 101250C \end{align}$$

你想要用多尺度、多层featuremap,比如你使用倒数第二层,那么这时得到的featuremap大小时30*30*1024。也就是h和w都翻倍了,这带来的计算量的提升是很恐怖的。

所以说不太可能使用比较低层的featuremap来作transformer的输入。

Method

image.png

Deformable Attention Module

transformer的一个问题是对于一个输入的feature map,它会看一遍所有的点。所以作者提出了deformable attention module,只关注目标点附近的一小部分点集,而不是整个feature map。

原本的MSA公式如下:
$$ MultiHeadAttn(z_q,x) = \sum^M_{m=1}W_m[\sum_{k\in\Omega_k}A_{mqk}*W'_mx_k] $$
其中m表示第m个head。head的个数是M个。
$x_k$是输入特征,也就是通俗的讲的token。一共有N个输入。
$W'_mx_k$可以理解成从输入的token转变成MSA中的Value的过程。
$A{mqk}$是求得的attention matrix。表示第m个head上的第q个Query和第k个Key之间的attention。

而作者提出的deformable attention的公式如下:
$$ DeformAttn(z_q,p_q,x) = \sum^M_{m=1}W_m[\sum^K_{k=1}A{mqk}*W'_mx(p_q+\Delta p_{mqk}] $$
两者相比有明显的差别:

  1. 在MSA中k是从全局取的,而在DeformAttn中k是有限制的。K是取样的总数。每个Query只和K个Key进行注意力的计算。
  2. 在DeformAttn中使用了一个$\Delta p_{mqk}$来代表采样的偏移量。

此外,通过前面的module流程图,也能看到在deformAttn中,attention matrix不再是Query和Key进行内积得到的,而是通过linear的线性变化得到的。
image.png

在实际实验中,feature $z_q$会通过一个线性映射来得到通道数为3MK的结果,其中前2MK是用来代表偏移量$\Delta p_{mqk}$的,最后一个MK会送入softmax操作来获得我们的attention matrix。

DeformAttn计算量

理一下这个过程,并算一下计算量。

假设输入大小为$H*W,C$,M代表head的个数,K代表选取的样本点的个数。

以下指的是encoder的过程,在decoder中没有HW的概念,直接使用的是object query的数量。

  1. 计算attention matrix需要$H*W*C*MK$
  2. 计算偏移量需要$2*H*W*C*MK$
  3. 进行Value值的计算有两种方法:
    1. 一种是采样前进行计算,需要$H*W*C^2$
    2. 一种是采样后计算,需要$H*W*K*C^2$
  4. 计算输出的时候需要$H*W*C*K$
  5. 用的是多头,还需要增加一步$B^{H*W,C}*W^{C,C}$,计算量为$HWC^2$
  6. 在偏移量计算的时候,要用到插值的方式,带来了比较多的计算量,计为$4*H*W*C*K$

所以总的计算量是:
$$ 3HWCMK + 5HWCK + HWC^2 + min{HWC^2, HWKC^2} $$

在decoder中因为输入的token个数不是HW,所以要进行一些替换。N代表object query的数量,在decoder中的计算量如下。
$$ 3NCMK + 5NCK + NC^2 + min{HWC^2, NKC^2} $$

Multi-scale Deformable Attention Module

为了便于在多尺度多层次的feature map上应用,作者又提出了DeformAttn的扩展:Multi-scale deformable attention。

$$ MSDeformAttn(z_q,\hat{p}_q,\{x^l\}^L_{l=1}) = \sum^M_{m=1}W_m[\sum^L_{l=1}\sum^K_{k=1}A{mlqk}*W'_mx^l(\phi(\hat{p}_q)+\Delta p_{mlqk})] $$
m还代表第几个head,l代表第几个feature level。
$\Delta p_{mlqk}$和$A_{mlqk}$代表了第m个head上第l个feature level下第k个采样点的偏移量和attention。并且满足不同层不同head的权重和为1。

对于多尺度的feature map,要求是将它们的通道数映射成一致。如下图:所有feature map的通道数都被映射为256。

image.png

假如你想要的feature map数量比backbone给的要多怎么办呢,就要使用stride = 2的3*3卷积来获得更小的feature map,同时通道数还要保持一致。

Deformable Transformer

image.png

看一下整体结构。

在encoder部分,所有的attention模块都用的是multi-scale deformable attention。并且用的也是多尺度的输入和输出。

在decoder中,它的cross-attention的部分被换成了multi-scale deformable attention,self-attention的部分用的还是普通的MSA。

代码实现

源码链接:
https://github.com/fundamentalvision/Deformable-DETR

我们用和DETR一样的顺序来看Deformable DETR的代码。

Deformable DETR的代码和DETR代码整体上其实大差不大,没有很大的区别。

DeformableDETR

我们首先来看DeformableDETR这个类。

init()

class DeformableDETR(nn.Module):
    """ This is the Deformable DETR module that performs object detection """
    def __init__(self, backbone, transformer, num_classes, num_queries, num_feature_levels,
                 aux_loss=True, with_box_refine=False, two_stage=False):

它的传入参数和DETR的也很一致。

  1. backbone:你打算使用的backbone
  2. transformer:构造好的transformer
  3. num_classes:数据集中物体种类的数量。
  4. num_feature_levels 你想要使用的feature层数
  5. num_queries:object queries的数量,也就代表了每张图中能预测的物体的最大数量。
  6. aux_loss:是否要使用aux_loss。

在此基础上增加了with_box_refine和two-stage,这是deformable DETR做的扩展内容,我们先忽略。

它的构造函数部分,我们忽略掉扩展内容后,看看剩下的代码。

self.num_queries = num_queries # object query的数量
self.transformer = transformer # 你的transformer
hidden_dim = transformer.d_model
self.class_embed = nn.Linear(hidden_dim, num_classes) # 分类预测头
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3) # bbox预测头
self.backbone = backbone # 你的backbone
self.aux_loss = aux_loss # 是否使用aux_loss
self.num_feature_levels = num_feature_levels

if not two_stage:
    self.query_embed = nn.Embedding(num_queries, hidden_dim*2)

self.class_embed = nn.ModuleList([self.class_embed for _ in range(num_pred)])
self.bbox_embed = nn.ModuleList([self.bbox_embed for _ in range(num_pred)])

前几行和DETR中一样。多了个num_feature_levels来控制是否使用多尺度。

假如说你打算使用多尺度,因为不同尺度下的feature map的通道数不一样,所以要将它们映射到一样的通道数。

假如你想要的feature map数量比num_backbone_outs要多,那么就需要用stride=2的3*3卷积自己再做出来一点。

下面的代码就是实现了这个功能。

假如num_feature_levels = 1,就直接将最后一层映射过去就行。
假如num_feature_levels>1,对于num_backbone_outs范围内的部分,使用1*1卷积进行降维,超过的部分,就用3*3卷积创造新的feature map。

if num_feature_levels > 1:
    num_backbone_outs = len(backbone.strides)
    input_proj_list = []
    for _ in range(num_backbone_outs):
        in_channels = backbone.num_channels[_]
        input_proj_list.append(nn.Sequential(
            nn.Conv2d(in_channels, hidden_dim, kernel_size=1),
            nn.GroupNorm(32, hidden_dim),
        ))
    for _ in range(num_feature_levels - num_backbone_outs):
        input_proj_list.append(nn.Sequential(
            nn.Conv2d(in_channels, hidden_dim, kernel_size=3, stride=2, padding=1),
            nn.GroupNorm(32, hidden_dim),
        ))
        in_channels = hidden_dim
    self.input_proj = nn.ModuleList(input_proj_list)
else:
    self.input_proj = nn.ModuleList([
        nn.Sequential(
            nn.Conv2d(backbone.num_channels[0], hidden_dim, kernel_size=1),
            nn.GroupNorm(32, hidden_dim),
        )])

这一部分也就是对应着DETR中的。

nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size=1)

forward()

在DETR中这部分代码也是比较简单的,在DeformableDETR中变长了不少。

因为这里是考虑了multi-scale feature map的。在DETR的backbone中拿出的features,直接经过projection后就送进transformer了。在这里还需要按顺序每个都projection一下。

features, pos = self.backbone(samples)
srcs = []
masks = []
for l, feat in enumerate(features):
    src, mask = feat.decompose()
    srcs.append(self.input_proj[l](src))
    masks.append(mask)
    assert mask is not None

假如你想要的featuremap比backbone给的多,这时候还要多处理一下。多出来的第一个featuremap还是在backbone的输出上进行卷积,后面的都要在前一个的基础上进行卷积。
并且也要专门生成position embedding。

if self.num_feature_levels > len(srcs):
     _len_srcs = len(srcs)
     for l in range(_len_srcs, self.num_feature_levels):
         if l == _len_srcs:
             src = self.input_proj[l](features[-1].tensors)
         else:
             src = self.input_proj[l](srcs[-1])
         m = samples.mask
         mask = F.interpolate(m[None].float(), size=src.shape[-2:]).to(torch.bool)[0]
         pos_l = self.backbone[1](NestedTensor(src, mask)).to(src.dtype)
         srcs.append(src)
         masks.append(mask)
         pos.append(pos_l)

然后这些东西,才一股脑地放进transformer里面去。

 hs, init_reference, inter_references, enc_outputs_class, enc_outputs_coord_unact = self.transformer(srcs, masks, pos, query_embeds)

之前DETR中的transformer的输入是tensor,这里变成了list,说明transformer中肯定也有比较大的改动。

backbone 和 position encoding

和DETR中没有什么区别,仍然是使用了IntermediateLayerGetter来获取不同层的输出。

在backbone的部分增加了一些attributes。主要是因为在DeformDETR的forward()中会用到。

 if return_interm_layers:
      # return_layers = {"layer1": "0", "layer2": "1", "layer3": "2", "layer4": "3"}
      return_layers = {
   
   "layer2": "0", "layer3": "1", "layer4": "2"}
      self.strides = [8, 16, 32]
      self.num_channels = [512, 1024, 2048]
  else:
      return_layers = {
   
   'layer4': "0"}
      self.strides = [32]
      self.num_channels = [2048]

backbone里面的len(self.strides)也就代表了你输出的featuremap的个数。
在刚刚DeformDETR的forward()中,当num_feature_level = 1的时候,它的projection用的是nn.Conv2d(backbone.num_channels[0],,我解释说这代表输入是最后一层的通道数,其实就是我们的backbone的num_channels数量只有1,所以[0]也是最后一层。

transformer

这一部分变复杂了好多啊。

DeformableTransformer的整体结构仍是由n个encoderlayer组成的一个Encoder和n个decoderlayer组成的一个Decoder拼成的。

一个区别是这里还增加了一个level embedding 还有一个reference_points。

self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model))

self.reference_points = nn.Linear(d_model, 2)

然后我们来看一下forward()的部分。

bs, c, h, w = src.shape
src = src.flatten(2).permute(2, 0, 1)
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)
mask = mask.flatten(1)

这里的src在flatten()后使用permute(2,0,1)是因为nn.MultiheadAttention中默认的batch_size不是在最前面的。

在DETR中只有一层feature map,所以都是直接算的。但是在这里有多个,所以要遍历list后每一个都处理一下。

for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
     bs, c, h, w = src.shape
     spatial_shape = (h, w)
     spatial_shapes.append(spatial_shape)
     src = src.flatten(2).transpose(1, 2)
     mask = mask.flatten(1)
     pos_embed = pos_embed.flatten(2).transpose(1, 2)
     lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
     lvl_pos_embed_flatten.append(lvl_pos_embed)
     src_flatten.append(src)
     mask_flatten.append(mask)

单个比较来看,src, mask的处理和之前没有区别,pos_embedding上额外加上了level_embed,进行了level位置的区分。

然后这些东西就被一股脑地送进encoder中去。

memory = self.encoder(src_flatten, spatial_shapes, level_start_index, valid_ratios, lvl_pos_embed_flatten, mask_flatten)

假如说你用的不是two-stage的扩展的话。

query_embed, tgt = torch.split(query_embed, c, dim=1)
            query_embed = query_embed.unsqueeze(0).expand(bs, -1, -1)
            tgt = tgt.unsqueeze(0).expand(bs, -1, -1)
            reference_points = self.reference_points(query_embed).sigmoid()
            init_reference_out = reference_points

获取我们的decoder的输入还有reference_points。然后将这些东西和encoder的输出一起,一股脑送进decoder里。

hs, inter_references = self.decoder(tgt, reference_points, memory,                                          spatial_shapes, level_start_index, valid_ratios, query_embed, mask_flatten)

MSDeformAttn

forward部分比较复杂,大致看一下流程。细节不管了。

init()

相比于一般的attention的输入d_model, nhead,这里增加了一个n_levels和n_points。
n_levels就是我们之前公式里的L,你想要在多少个feature level上进行attention。
n_points就是我们之前公式里的K,采样点的数量。

class MSDeformAttn(nn.Module):
    def __init__(self, d_model=256, n_levels=4, n_heads=8, n_points=4):
       super().__init__()
       self.im2col_step = 64
       self.d_model = d_model
       self.n_levels = n_levels
       self.n_heads = n_heads
       self.n_points = n_points

       self.sampling_offsets = nn.Linear(d_model, n_heads * n_levels * n_points * 2) 
       self.attention_weights = nn.Linear(d_model, n_heads * n_levels * n_points)
       self.value_proj = nn.Linear(d_model, d_model)
       self.output_proj = nn.Linear(d_model, d_model)

       self._reset_parameters()

偏移量和attention matrix都通过线性映射得到。

偏移量的维度是$2*M*K*L$, attention matrix的维度是$M*K*L$

forward()

forward()部分的输入有点多。

配合着transformer的输入看了一下之后,大致了解了这部分都是什么。

def forward(self, query, reference_points, input_flatten, input_spatial_shapes, input_level_start_index, input_padding_mask=None):
  1. query: 我们输入的带位置编码的query。
  2. input_flatten:在encoder的输入部分其实就是不带位置编码的query,原始的flatten的feature。在decoder的cross_attn部分就是encoder的输出。
  3. 剩下的先不管,记住这两个就可以。

我们的value是对input_flatten进行projection得到的,这个在整体流程图中也是可以看到的。

value = self.value_proj(input_flatten)

而我们的偏移量和attention weight都是基于query计算的。

sampling_offsets = self.sampling_offsets(query).view(N, Len_q, self.n_heads, self.n_levels, self.n_points, 2)
attention_weights = self.attention_weights(query).view(N, Len_q, self.n_heads, self.n_levels * self.n_points)
attention_weights = F.softmax(attention_weights, -1).view(N, Len_q, self.n_heads, self.n_levels, self.n_points)

这里的attention weights要保证和为1,所以也要做softmax。

然后这些会经过一个MSDeformAttnFunction的处理,这里面也包括了前向和后向的计算。源码位置:ms_deform_attn_func.py

最后的输出还要经过projection:

output = self.output_proj(output)

Encoder和Decoder

如果忽略MSA到DeformAttn的变化,encoder和decoder中的改动其实挺小的。

我们直接来看一下encoder layer的forward():

def forward(self, src, pos, reference_points, spatial_shapes, level_start_index, padding_mask=None):
        # self attention
        src2 = self.self_attn(self.with_pos_embed(src, pos), reference_points, src, spatial_shapes, level_start_index, padding_mask)
        src = src + self.dropout1(src2)
        src = self.norm1(src)

        # ffn
        src = self.forward_ffn(src)

        return src

简洁明了:

  1. 加pos_embedding
  2. self-attention得到src2
  3. src2和src残差和 然后norm
  4. 经过MLP(这里面已经包括了残差和+norm)

再来看一下decoder的forward()

首先进行decoder的自注意力计算,这里用的还是普通的MSA,因为这一部分没有涉及到feature。

self.self_attn = nn.MultiheadAttention(d_model, n_heads, dropout=dropout)

这里的q,k,tgt进行transpose也是因为默认batch_first=False的原因。

q = k = self.with_pos_embed(tgt, query_pos)
tgt2 = self.self_attn(q.transpose(0, 1), k.transpose(0, 1), tgt.transpose(0, 1))[0].transpose(0, 1)
tgt = tgt + self.dropout2(tgt2)
tgt = self.norm2(tgt)

然后再用自注意力的结果和encoder的输出做cross_attn,这里用的就是DeformAttn了。

tgt2 = self.cross_attn(self.with_pos_embed(tgt, query_pos),
                               reference_points,
                               src, src_spatial_shapes, level_start_index, src_padding_mask)
tgt = tgt + self.dropout1(tgt2)
tgt = self.norm1(tgt)

# ffn
tgt = self.forward_ffn(tgt)
相关文章
|
5月前
|
机器学习/深度学习 数据采集 自然语言处理
OneFlow深度学习框原理、用法、案例和注意事项
OneFlow深度学习框原理、用法、案例和注意事项
56 0
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
62 11
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的自适应神经网络:原理与应用
【8月更文挑战第14天】在深度学习领域,自适应神经网络作为一种新兴技术,正逐渐改变我们处理数据和解决问题的方式。这种网络通过动态调整其结构和参数来适应输入数据的分布和特征,从而在无需人工干预的情况下实现最优性能。本文将深入探讨自适应神经网络的工作原理、关键技术及其在多个领域的实际应用,旨在为读者提供一个全面的视角,理解这一技术如何推动深度学习向更高效、更智能的方向发展。
|
11天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
41 1
|
5月前
|
机器学习/深度学习 算法 TensorFlow
深度学习基础:神经网络原理与构建
**摘要:** 本文介绍了深度学习中的神经网络基础,包括神经元模型、前向传播和反向传播。通过TensorFlow的Keras API,展示了如何构建并训练一个简单的神经网络,以对鸢尾花数据集进行分类。从数据预处理到模型构建、训练和评估,文章详细阐述了深度学习的基本流程,为读者提供了一个深度学习入门的起点。虽然深度学习领域广阔,涉及更多复杂技术和网络结构,但本文为后续学习奠定了基础。
106 5
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络背后的原理与实践
【9月更文挑战第29天】本文将带你深入理解深度学习的核心概念,从基础理论到实际应用,逐步揭示其神秘面纱。我们将探讨神经网络的工作原理,并通过实际代码示例,展示如何构建和训练一个简单的深度学习模型。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技能。
38 2
|
2月前
|
机器学习/深度学习 人工智能 监控
深度学习中的图像识别:原理与实践
【9月更文挑战第21天】本文将深入浅出地探讨深度学习在图像识别领域的应用。我们将从基础的神经网络概念出发,逐步深入到卷积神经网络(CNN)的工作机制,最后通过一个实际的代码示例来展示如何利用深度学习进行图像识别。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能。
60 1
|
2月前
|
机器学习/深度学习 自然语言处理 自动驾驶
深度学习的奥秘:从基本原理到实际应用
在这篇文章中,我们将探索深度学习的神秘世界。首先,我们将介绍深度学习的基本概念和原理,然后深入探讨其在不同领域的应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和思考方式。让我们一起揭开深度学习的面纱,探索其无限可能!
|
3月前
|
机器学习/深度学习 人工智能 TensorFlow
利用深度学习进行图像识别的基本原理与实践
【8月更文挑战第27天】在这篇文章中,我们将探索图像识别技术的核心原理,并借助深度学习框架实现一个基本的图像识别模型。通过简洁的代码示例和直观的解释,我们旨在向读者展示如何从零开始构建自己的图像识别系统,以及这一过程中可能遇到的挑战和解决方案。无论你是AI领域的初学者还是有一定基础的开发者,这篇文章都将为你提供有价值的见解和指导。
|
3月前
|
机器学习/深度学习 算法 PyTorch
【深度学习】深度学习基本概念、工作原理及实际应用案例
深度学习是一种机器学习方法,它试图模拟人脑中的神经网络结构,以解决复杂的问题。深度学习的核心在于构建多层非线性处理单元(即神经元)的网络结构,这些网络可以从原始数据中自动提取特征并进行学习。
375 1

热门文章

最新文章