深度学习原理篇 第五章:YOLOv8

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

找工作也太难了吧根本找不到工作我哭死。


参考教程:
https://mmyolo.readthedocs.io/en/latest/recommended_topics/algorithm_descriptions/yolov8_description.html

https://zhuanlan.zhihu.com/p/599761847【这个写的挺不错】

https://zhuanlan.zhihu.com/p/633094573【正负样本匹配策略】
@[TOC]

版本回顾

目标检测模型可以分为两种,一种是one-stage模型,它的优点是速度快、适合做实时的检测。另一种是two-stage模型,它的速度通常比较慢,但是效果会相对好一点。

yolo系列就是one-stage模型的代表之作,我们首先来看一下在yolo系列在从v1到v7的更新发展中,都有哪些比较重要的改动。

yolov1

对于一个大小为224x224的输入图像,经过32倍下采样后吗,得到大小为7x7的feature map,并在这个feature map上进行目标检测的预测。

使用两个全连接层进行线性回归,最终得到的输出大小为$7\times7\times(2\times(4+1)+20)$。其中4代表了xywh,1代表了置信度,20代表了物体的类别。2代表的则是anchor的数量。

这里anchor=2,是考虑到物体的bbox可能有不同的形态和大小,比如说有的物体的bbox是长而窄的,有的是短而宽的。为了让模型能够学习到两种anchor,在训练阶段,只对结果中iou比较高的box做惩罚,iou低的那个不管,目的是使得模型天然地学会不同size/ratio的bbox。

yolov1的优点是简单快速,缺点是每个cell只能预测一个类别,如果出现重叠的话就无法识别。并且对小物体检测效果一般。

yolov2

yolov2又称yolo9000,因为它在做目标检测的同时完成了对9000个类别的分类。yolov2相对v1做出了比较多的改动。

  1. 使用darknet作为backbone。
  2. 添加BN层。
  3. 使用k-means聚类的方法来获得anchor,v2中使用的是5个anchor。
  4. 使用受限的位置坐标预测。预测相对于anchor的offset而不是直接预测坐标。
  5. fine-grained features。使用passthrough将高低层特征融合。

它的缺点是target生成的部分和v1一致,所以即使有5个anchor,每个cell也只有一个物体。

对于一个大小为416x416的输入图像,最终得到的输出大小为$13\times13\times k\times(4+1+classes)$。在这里k=5,classes=20。

yolov3

yolov3使用Darknet53作为backbone,不使用池化层,而是使用卷积层来进行下采样。

在yolov3中,开始使用上采样处理多个feature map,也就是FPN的结构。因此它会在多个feature map上进行预测。对于一个大小为416x416的输入,v3会在13x13,26x26,52x52的输出上进行预测。

三个输出层,每一层都有3个anchor,一共是9个anchor。每一个层的输出大小为$W\times H\times3\times(4+1+80)$。3是anchor的数量,4是xywh,1是confidence,80是coco数据集分类的类别。

在正负样本匹配上,yolov3采用的方法是:anchor和目标框左上角对齐后计算iou,和目标框重合度最大的anchor为正样本。【为了扩充正样本数量,实际操作中可以将大于某个阈值的全设为正样本】。

yolov4

在网络结构上,yolov4采用的是CSPDarknet53作为backbone,也就是在darknet中引入了CSP结构。在backbone中使用的激活函数都是Mish,在后续的结构中使用的则是leakyReLU。

此外,yolov4使用了path aggregation network,在完成自上向下的信息传递后,再自底向上,将浅层信息再次带回给深层。

yolov4还采用了一些别的优化策略。

  1. eliminate grid sensitivity。对于形如$bx = \sigma(tx)+cx$的中心点预测,$\sigma(tx)$很难处理落在边界上的情况,因此加入一个缩放因子来解决这个问题$bx = 2\times\sigma(tx)-0.5 +cx$。
  2. mosaic data augmentation
  3. 正负样本匹配。因为修改了中心点预测的边界,所以正样本的范围更广了。变成了三倍。
  4. 针对512x512的输入优化了anchor。
  5. 注意力机制。使用了spatial attention module。

yolov5

作为一个一直在维护更新的框架,v5的模型结构也发生过一些变化:

  • Backbone: Focus + CSP + SPP -> 6x6conv + CSP + SPPF
  • Neck: FPN+PAN -> FPN+CSP-PAN
  • Head: 3个检测头
    同时它也在v4的基础上进一步消除grid敏感度。在v4中使用了缩放因子优化中心点预测的结果,在v5中则是加强了对w和h的限制。
    $$ bw = pw\times e^{tw} ->bw = pw\times(2\times\sigma(tw))^2 $$
    在之前的版本中$e^{tw}$是不受限的,很容易出现梯度爆炸,修改后预测结果的范围被限制在了[0,4]之间。这也带来了正负样本匹配机制上的变化,v4增加了距离上的可选择性,v5增加了anchor大小上的可选择性,只要大小比在[0.25,4]之间,都会认为是正样本。

yoloX

yolox的主要变化在于,使用了解耦的检测头。作者认为reg和cls关注点不同,放在一起会限制表达。

并且yoloX进行的是anchor free的预测。没有anchor的大小来参考了,而是直接预测目标的相对位置。对于一个大小为640x640的输入,会得到在三个featuremap上的结果$W\times H\times(5+80)$。三个feature map的大小分别是20x20,40x40,80x80,也就是说一共会得到8500个框。

同时采用了名为SImOTA的正负样本匹配策略。在GT附近挑选候选框作为备选,并根据cost和iou进行筛选。

yolov7

yolov7在模型中引入了re-parameter的方法,并且使用了更多的跳层结构:ELAN,ELAN-W,MP Conv,SPPCSPC等。

在正负样本匹配上,它将v5的匹配方法和x的进行结合。使用v5方法中选出的正样本框作为备选,然后根据cost和iou进行筛选。

yolov7在某些实现中还是用了辅助预测头。辅助头的正负样本匹配的备选范围更广。

YOLOv8

首先来看一下yolov8中都做了哪些改进。

  1. backbone: 基于yolov7中ELAN的设计,将yolov5中的C3模块用C2F取代。
  2. head: 使用解耦的检测头,并且从anchor-based转为了anchor-free。
  3. loss: 使用了名为TaskAlignedAssigner的正负样本匹配策略。并且使用了Distribution Focal Loss。
  4. data augmentation: 在最后10个training epoch里会不使用mosaic。

Backbone

image.png

首先来看一下backbone整体上的差别。

yolov5的stem layer使用的是一共kernel_size=6的卷积,在早期版本中使用的其实是Focus结构,后发现直接使用size=6的卷积能达到同样的效果,所以才直接使用卷积。

在yolov8中使用的是kernel_size=2的卷积,这其实是和v7中一致的。

在剩下的stage中,args都没有什么变化,比较明显的区别是v5中的C3在v8中换成了C2f。并且,the block number has been changed from 3-6-9-3 to 3-6-6-3.
image.png

来看一下C3和C2f的区别。

yolov5中的C3就是第一个backbone图中的CSPLayer。C3也就是CSP Bottleneck with 3 convolutions,与之相似的还有C1和C2。
image.png

下面源码中的self.cv2代表的就是上图左侧的分支。self.cv1代表的是右侧分支第一个ConvModule。而后经过n个bottleneck后,和左侧分支concat在一起,再经过self.cv3,也就是最下面的这个ConvModule。

这里的bottleneck只要最终的输出结果,用nn.Sequential()进行组合,在forward中会顺序执行多个module。

class C3(nn.Module):
    """CSP Bottleneck with 3 convolutions."""

    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)))

    def forward(self, x):
        """Forward pass through the CSP bottleneck with 2 convolutions."""
        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
        # 这一部分代码展开来写的话就是
        # x1 = self.cv1(x)
        # x2 = self.cv2(x)
        # out = torch.cat((self.m(x1),x2),1)
        # return self.cv3(out)

在C2f中增加了更多的跳层连接,看起来也更复杂。
image.png

虽然看起来很复杂,但是看代码实现还是比较简单的,和C3的主要区别是,C2f中每个bottlenect的输出都被拿出来,用于最后的concat了。在源码中提供了两个版本的forward,一个是带split的,一个是不带的。

下面源码中的self.cv1就是上图的第一个ConvModule。self.cv1的输出通道是2*self.c,也是为了split的时候可以分成两个大小为self.c的部分。理论上来说这个下面代码中的split和chunk的结果应该是一样的。

self.cv2的输入维度是(2+n)*self.c,因为它的输入concat了split的2个结果和n个bottleneck的结果。

这里的n个bottleneck是用nn.ModuleList()组合在一起的,nn.ModuleList()本身没有forward()的实现,在下面的代码中是按index的顺序来调用bottleneck的。

class C2f(nn.Module):
    """CSP Bottleneck with 2 convolutions."""

    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        """Forward pass through C2f layer."""
        y = list(self.cv1(x).chunk(2, 1))
        y.extend(m(y[-1]) for m in self.m)
        # 这行代码展开来写的话就是
        # for m in self.m:
        #    cur = y[-1]
        #    out = m(cur)
        #    y.append(out)
        return self.cv2(torch.cat(y, 1))

    def forward_split(self, x):
        """Forward pass using split() instead of chunk()."""
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

Neck

下面两个图,左图为yolov8的neck部分的结构,右图为yolov5的neck部分的结构,首先一个比较明显的区别是,yolov5中neck部分的C3结构在yolov8中也被替换成了C2f。




此外,另一个比较明显的区别是,yolov5中上采样阶段使用的ConvModule在v8中被去掉了。

上采样部分对应的config如下图。我们可以看到v8中去掉了v5config中第31行和第36行的两个1x1卷积,并且把C3的结构改成了C2f。
image.png

而再往后的下采样+融合部分,除了block的改变外,没有其它改动。

要注意的是,在yolov5 anchor-based版本中,最后一层detect的输入参数还包括了anchor,下面的图中没有包括是因为图例是从yolov8的repo中截取的。
image.png

Head

head部分的改动同样是很大的。

  1. 使用了解耦的检测头。将分类和回归分开。
  2. anchor-based预测改为了anchor-free的预测。
  3. 移除了objectness(也就是confidence)的分支。
    image.png

yolov5 detect

首先来看一下yolov5部分detect的源码。

先看一下构造函数init()的部分。它的输入参数中,nc表示分类的类别,如果使用的是coco数据集那么nc默认是80,anchors是预设的anchor的大小。ch是用于预测的三个feature map的通道数。

 def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

这个构造函数中,和我们的检测头相关的部分就是

self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)

ch中有多少个feature map,就需要接多少个检测头,检测头就是这个ModuleList里的

nn.Conv2d(x, self.no * self.na, 1)

self.na是anchor的数量,在实际使用中是3,self.no = self.nc(分类数)+ 5,在实际使用中是85。

这个分类头就是用1x1的卷积实现feature map通道数的改变,通道这一维度就是我们的预测结果。

在forward部分,输入x是三个featuremap,对每个featuremap用我们的ModueList中的卷积进行处理就好。

    def forward(self, x):
        z = []  # inference output
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

yolov8 detect

同样先来看一下init()的部分,因为在yolov8中是一个anchor-free的预测,所以输入参数中不再有anchor了。

yolov8中使用了DFL(Distribution Focal Loss)。在解耦检测头的设计上也有一点小细节。

  def __init__(self, nc=80, ch=()):  # detection layer
      super().__init__()
      self.nc = nc  # number of classes
      self.nl = len(ch)  # number of detection layers
      self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
      self.no = nc + self.reg_max * 4  # number of outputs per anchor
      self.stride = torch.zeros(self.nl)  # strides computed during build
      c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))  # channels
      self.cv2 = nn.ModuleList(
          nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
      self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
      self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()

先来详细看一下代码部分。同时回顾一下结构图。
image.png

self.cv2和self.cv3都是用ModuleList组合起来的和featuremap个数相同的block。

和代码中相对应的,self.cv2就是结构图的上面这条分支,用于预测bbox;self.cv3就是下面这条分支,用于预测cls。从代码中我们可以看到,self.cv2中的hidden layer的通道数是c2,self.cv3中hidden layer的通道是c3,两者并不是相等的。因为yolov8认为两个检测头需要有两种不同的表征,所以hidden layer也应该是不一样的。

c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))
self.cv2 = nn.ModuleList(
          nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)

v8中的预测头已经不包括对objectness的预测,更符合one-stage的概念。

在forward中,对输入的每一个featuremap,进行两个检测头的预测,并把结果concat在一起。

 def forward(self, x):
        """Concatenates and returns predicted bounding boxes and class probabilities."""
        shape = x[0].shape  # BCHW
        for i in range(self.nl):
            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
        if self.training:
            return x

Loss

在前面已经提到,yolov8的检测头只包括了box的检测头和cls的检测头,不再有objectness的检测头,那么它的损失函数也就不需要再包括confidence损失,而只分为了类别损失位置损失

源码链接:https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/utils/loss.py

正负样本匹配

首先来看一下yolov8中采用的正负样本匹配方法。
源码连接:
https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/utils/tal.py#L57
yolov8中采用的Task-Aligned Assigner是一种在训练过程中动态地调整正负样本分配比例的方法。

正样本是按照分类和回归的加权分数来进行选择的。
$$ t = s^\alpha + u^\beta $$

  1. 对于每一个ground truth,assigner都会计算它对于每一个anchor的alignment metric。
  2. 对于每一个ground truth,基于alignment metric的结果,最大的k个样本被选为正样本。

这部分可以稍微看一下源码【不一定能看懂】。

流程解析

为什么叫流程解析不叫源码解析,因为不想花时间去看复杂的源码。

def forward(self, pd_scores, pd_bboxes, anc_points, gt_labels, gt_bboxes, mask_gt):

输入的几个参数分别是:pd_scores(预测分数),pd_bboxes(预测框),anc_points(anchor),gt_labels(真实类别),gt_bboxes(真实框)。

在yolov8的损失计算中,就会使用assigner的forward()完成正负样本的匹配。

 _, target_bboxes, target_scores, fg_mask, _ = self.assigner(
            pred_scores.detach().sigmoid(), (pred_bboxes.detach() * stride_tensor).type(gt_bboxes.dtype),
            anchor_points * stride_tensor, gt_labels, gt_bboxes, mask_gt)

loss的计算也是基于这个assigner返回的target_boxes和target_scores。

看一下在forward()中都做了哪些计算。

get_pos_mask

筛选正样本。整个过程分为三部分:

  1. 初步筛选,筛出candidates。
  2. 计算candidates的score。
  3. 从candidates中选出最终的目标。

首先是筛选出落在gt范围内的candidates。
得到gt的左上角和右下角的值,让anchor_point减去lt,让rb减去anchor_point,如果结果都是正数,说明anchor_point在lt-rb的范围内,也就是落在gt的内部。

def select_candidates_in_gts(xy_centers, gt_bboxes, eps=1e-9):
    n_anchors = xy_centers.shape[0]
    bs, n_boxes, _ = gt_bboxes.shape
    lt, rb = gt_bboxes.view(-1, 1, 4).chunk(2, 2)  # left-top, right-bottom
    bbox_deltas = torch.cat((xy_centers[None] - lt, rb - xy_centers[None]), dim=2).view(bs, n_boxes, n_anchors, -1)
    # return (bbox_deltas.min(3)[0] > eps).to(gt_bboxes.dtype)
    return bbox_deltas.amin(3).gt_(eps)

得到了初筛的结果后,要在该结果的基础上进行精细化筛出,保留topk的正样本。

下一步就是计算alignment metric,计算的方式是分别计算出box和gt的iou和score,代入公式后得到最终的分数。

得到分数后,从中选出top K的备选。

select_highest_overlaps

同别的动态分配的策略一样,这样分配也可能出现一个anchor被分配给多个gt的情况。

首先判断是否有anchor被分配给了多个gt。采用的是用mask求和的方法,如果大于1,说明存在重复分配。
比较该anchor和多个gt的iou结果,保留iou最大的那个。

def select_highest_overlaps(mask_pos, overlaps, n_max_boxes):
    # (b, n_max_boxes, h*w) -> (b, h*w)
    fg_mask = mask_pos.sum(-2)
    if fg_mask.max() > 1:  # one anchor is assigned to multiple gt_bboxes
        mask_multi_gts = (fg_mask.unsqueeze(1) > 1).expand(-1, n_max_boxes, -1)  # (b, n_max_boxes, h*w)
        max_overlaps_idx = overlaps.argmax(1)  # (b, h*w)

        is_max_overlaps = torch.zeros(mask_pos.shape, dtype=mask_pos.dtype, device=mask_pos.device)
        is_max_overlaps.scatter_(1, max_overlaps_idx.unsqueeze(1), 1)

        mask_pos = torch.where(mask_multi_gts, is_max_overlaps, mask_pos).float()  # (b, n_max_boxes, h*w)
        fg_mask = mask_pos.sum(-2)
    # Find each grid serve which gt(index)
    target_gt_idx = mask_pos.argmax(-2)  # (b, h*w)
    return target_gt_idx, fg_mask, mask_pos

asiigned target

在前面的步骤中,我们已经获得了要用的target_gt_idx和fg_mask。

target_gt_idx: 代表与每个anchor最匹配的gtbox的索引。
fg_mask: 代表该anchor的正还是负。

类别损失

分类的类别不需要额外处理,直接进行计算就可以。从代码中找出来和类别损失相关的部分。通过sigmoid函数来计算每个类别的概率,然后再计算全局的类别损失。

首先是init()部分,类别损失使用的是BCEWithLogists。也就是sigmoid和BCELoss的结合。

self.bce = nn.BCEWithLogitsLoss(reduction='none')

然后来看一下forward()部分。这一部分没有对输出的cls和gt进行特别的处理,就是中规中矩的计算过程。

loss[1] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum

位置损失

yolov8中的bbox的损失分为了两部分,第一部分是iou损失,第二部分是dfl损失。

iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, CIoU=True)
        loss_iou = ((1.0 - iou) * weight).sum() / target_scores_sum
target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
            loss_dfl = self._df_loss(pred_dist[fg_mask].view(-1, self.reg_max + 1), target_ltrb[fg_mask]) * weight
            loss_dfl = loss_dfl.sum() / target_scores_sum
相关文章
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络的核心原理
本文将深入浅出地介绍深度学习的基本概念,包括神经网络的结构、工作原理以及训练过程。我们将从最初的感知机模型出发,逐步深入到现代复杂的深度网络架构,并探讨如何通过反向传播算法优化网络权重。文章旨在为初学者提供一个清晰的深度学习入门指南,同时为有经验的研究者回顾和巩固基础知识。
62 11
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的自适应神经网络:原理与应用
【8月更文挑战第14天】在深度学习领域,自适应神经网络作为一种新兴技术,正逐渐改变我们处理数据和解决问题的方式。这种网络通过动态调整其结构和参数来适应输入数据的分布和特征,从而在无需人工干预的情况下实现最优性能。本文将深入探讨自适应神经网络的工作原理、关键技术及其在多个领域的实际应用,旨在为读者提供一个全面的视角,理解这一技术如何推动深度学习向更高效、更智能的方向发展。
|
12天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
42 1
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的奥秘:探索神经网络背后的原理与实践
【9月更文挑战第29天】本文将带你深入理解深度学习的核心概念,从基础理论到实际应用,逐步揭示其神秘面纱。我们将探讨神经网络的工作原理,并通过实际代码示例,展示如何构建和训练一个简单的深度学习模型。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技能。
38 2
|
2月前
|
机器学习/深度学习 人工智能 监控
深度学习中的图像识别:原理与实践
【9月更文挑战第21天】本文将深入浅出地探讨深度学习在图像识别领域的应用。我们将从基础的神经网络概念出发,逐步深入到卷积神经网络(CNN)的工作机制,最后通过一个实际的代码示例来展示如何利用深度学习进行图像识别。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能。
60 1
|
2月前
|
机器学习/深度学习 自然语言处理 自动驾驶
深度学习的奥秘:从基本原理到实际应用
在这篇文章中,我们将探索深度学习的神秘世界。首先,我们将介绍深度学习的基本概念和原理,然后深入探讨其在不同领域的应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和思考方式。让我们一起揭开深度学习的面纱,探索其无限可能!
|
3月前
|
机器学习/深度学习 人工智能 TensorFlow
利用深度学习进行图像识别的基本原理与实践
【8月更文挑战第27天】在这篇文章中,我们将探索图像识别技术的核心原理,并借助深度学习框架实现一个基本的图像识别模型。通过简洁的代码示例和直观的解释,我们旨在向读者展示如何从零开始构建自己的图像识别系统,以及这一过程中可能遇到的挑战和解决方案。无论你是AI领域的初学者还是有一定基础的开发者,这篇文章都将为你提供有价值的见解和指导。
|
3月前
|
机器学习/深度学习 算法 PyTorch
【深度学习】深度学习基本概念、工作原理及实际应用案例
深度学习是一种机器学习方法,它试图模拟人脑中的神经网络结构,以解决复杂的问题。深度学习的核心在于构建多层非线性处理单元(即神经元)的网络结构,这些网络可以从原始数据中自动提取特征并进行学习。
378 1
|
3月前
|
机器学习/深度学习 人工智能 算法
深度学习的奥秘:探索神经网络的核心原理
深度学习,一个听起来既神秘又充满魔力的词汇,它如同一扇通往未知世界的大门,背后隐藏着无尽的智慧与可能。本文将以一种通俗易懂的方式,带领读者走进深度学习的世界,探索那些构成神经网络核心的基本原理。我们将从最初的感知机模型出发,逐步深入到复杂的多层网络结构,揭示数据如何在这些网络中流动、变化,最终实现智能决策的过程。通过这篇文章,你将了解到深度学习不仅仅是技术的堆砌,更是对自然界智慧的一种模仿与致敬。
56 1
|
3月前
|
机器学习/深度学习 人工智能 TensorFlow
深度学习中的卷积神经网络(CNN)原理与实践
【8月更文挑战第31天】在人工智能的浪潮中,深度学习技术以其强大的数据处理能力脱颖而出。本文将深入浅出地探讨卷积神经网络(CNN)这一核心组件,解析其在图像识别等领域的应用原理,并通过Python代码示例带领读者步入实践。我们将从CNN的基本概念出发,逐步深入到架构设计,最后通过一个简易项目展示如何将理论应用于实际问题解决。无论你是深度学习的初学者还是希望深化理解的实践者,这篇文章都将为你提供有价值的洞见和指导。

热门文章

最新文章