本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:“DL_ComputerVision”是一个聚焦于深度学习技术在计算机视觉领域实践应用的项目合集,内容涵盖从基础理论到高级模型的完整流程。项目以Jupyter Notebook形式呈现,便于交互式学习与实验复现,涉及卷积神经网络(CNN)、目标检测(如YOLO、Faster R-CNN)、语义分割(如U-Net)等核心技术。通过经典数据集(如MNIST、CIFAR-10、ImageNet)进行数据预处理、模型构建、训练验证与性能评估,帮助学习者掌握图像分类、目标检测和语义分割等关键任务的实现方法。同时包含TensorBoard可视化、模型优化策略及端到端项目实战,全面提升深度学习在视觉任务中的工程能力。

深度学习与计算机视觉:从原理到实战的完整旅程

在自动驾驶汽车识别行人、医疗影像系统发现早期肿瘤、安防摄像头自动追踪异常行为的背后,是深度学习赋予机器“看见”世界的能力。这不再只是科幻小说中的桥段,而是每天都在发生的现实。

但你有没有想过——当一张图片输入模型时,它究竟是如何一步步从像素变成理解的?为什么CNN能成为视觉任务的基石?YOLO为何能在毫秒内框出几十个物体?U-Net又是怎样做到连细胞膜都精准分割的?

这些问题的答案,藏在一个又一个看似简单的卷积操作里,也藏在工程师们对训练流程的精雕细琢中。今天,我们就来一次彻底拆解,不靠PPT讲概念,直接用代码和直觉带你走完这条从理论到落地的完整路径。


还记得第一次运行 torchvision.models.resnet18(pretrained=True) 时那种震撼吗?几行代码加载的模型,竟能准确识别猫狗、飞机汽车。但这背后,并非魔法,而是一场关于 层次化特征提取 的精密设计。

传统方法依赖人工设计特征,比如SIFT描述子或HOG梯度直方图。它们确实有效,但在面对复杂场景变化时往往力不从心。更致命的是——这些特征不具备泛化能力。换一个光照条件,换个角度,整个系统就可能失效。

而深度学习的突破点在于: 让模型自己学会该看什么 。不是我们告诉它“边缘很重要”,而是通过大量数据,让它自己发现“哦,原来垂直线条经常出现在门框上”。

import torch
from torchvision import models, transforms

# 就这么简单?
model = models.resnet18(pretrained=True)
transform = transforms.Compose([transforms.ToTensor()])

是的,就这么简单。可别小瞧这三行代码,它代表了整整一代研究人员的努力成果。预训练权重意味着这个模型已经在ImageNet上见过上百万张图像,学到了通用的视觉表示。你可以把它想象成一个已经读过无数本书的老教授,现在只需要稍加引导,就能胜任新任务。

卷积层:不只是滑动窗口那么简单 🌀

让我们回到最原始的问题:图像本质上是一个三维张量(高×宽×通道),每个像素都是数值。那么,怎么从中提取出“有意义”的信息?

答案就是 局部感受野 + 权值共享 。这两个词听起来很学术,其实非常直观。

设想你正在观察一幅画。你的视线不会一次性扫过整幅画面,而是聚焦于某个小区域——比如一只眼睛、一段纹理。这就是“局部感受野”的生物学基础。在CNN中,我们用一个小的滤波器(如3×3)在图像上来回滑动,每次只处理一小块区域。

import torch
import torch.nn as nn

conv_layer = nn.Conv2d(in_channels=1, out_channels=4, kernel_size=3, stride=1, padding=1)
input_image = torch.randn(1, 1, 28, 28)
output_features = conv_layer(input_image)

print(f"输入形状: {input_image.shape}")   # [1, 1, 28, 28]
print(f"输出形状: {output_features.shape}") # [1, 4, 28, 28]

注意这里的参数设置:
- kernel_size=3 → 我们只关心每个3×3的小邻域
- padding=1 → 边缘补零,保持空间尺寸不变
- out_channels=4 → 使用4个不同的滤波器,捕捉4种模式(可能是水平/垂直边缘、角点等)

关键来了: 同一个滤波器在整个图像上重复使用 。这意味着无论这只猫出现在左上角还是右下角,只要它的耳朵长一样,就会被同一种方式检测出来。这种“平移等变性”正是CNN强大泛化能力的核心所在。

而且!由于权值共享,参数量大大减少。一个5×5的滤波器只有25个参数,不管图像有多大。相比之下,全连接网络会随着图像分辨率爆炸式增长,根本无法实用。

💡 工程提示 :实际项目中,建议始终使用 padding='same' 策略(即适当填充使输出尺寸等于输入)。这样可以避免因尺寸错位导致后续模块报错,尤其是在构建深层网络时。

下面是卷积操作的整体流程:

graph TD
    A[输入图像] --> B[添加Padding]
    B --> C[滑动卷积核]
    C --> D[逐元素相乘+求和]
    D --> E[生成一个输出像素]
    C --> F{是否到达边界?}
    F -- 否 --> C
    F -- 是 --> G[输出完整特征图]

清晰吧?每一步都非常机械化,但也正因为如此,GPU才能高效并行执行数以亿计的乘加运算。

参数 含义 典型取值 影响
kernel_size 滤波器大小 3, 5, 7 控制感受野范围
stride 步长 1, 2 决定输出分辨率
padding 填充像素数 0 (valid), 1 (same) 维持空间维度
dilation 空洞率 1 (普通), >1 (膨胀) 扩展感受野而不增加计算量

说到这里,你可能会问:“那ReLU呢?为什么非得要激活函数?”
好问题!

如果没有非线性激活,再多层卷积也只是线性变换的叠加,最终等价于单层线性模型。换句话说——白练了 😅

所以我们需要像ReLU这样的函数来打破线性:

$$
\text{ReLU}(x) = \max(0, x)
$$

它不仅计算快(比sigmoid快十倍以上),还能缓解梯度消失问题。毕竟导数要么是0,要么是1,不会出现指数级衰减。

不过ReLU也有“死亡神经元”问题:某些神经元一旦进入负区,就永远不会再被激活。为此,Leaky ReLU应运而生:

$$
\text{LeakyReLU}(x) =
\begin{cases}
x, & x \geq 0 \
\alpha x, & x < 0
\end{cases},\quad \alpha = 0.01
$$

给负值留一条活路,防止永久失活。

近年来,Swish和GELU也开始流行起来,特别是在Transformer架构中表现优异。它们的形式更复杂,但能提供更平滑的过渡和更强的表达能力。

激活函数 公式 特点 适用场景
ReLU $\max(0,x)$ 快速、常用 大多数CNN
LeakyReLU $\max(\alpha x, x)$ 防止死亡神经元 训练不稳定时
PReLU 可学习$\alpha$ 自适应调节 性能敏感任务
ELU $\alpha(e^x{-}1)$ for $x{<}0$ 平滑负区,归一化特性 深层ResNet
GELU $x\Phi(x)$ 近似ReLU+Dropout Vision Transformers

实践建议:先用ReLU起步,如果遇到收敛困难再尝试Swish/GELU。

下采样:既要看得远,也要站得稳 🏔️

连续卷积后,特征图的空间尺寸越来越大,计算成本飙升。更重要的是,过多细节会让模型对微小位移过于敏感。

怎么办?降维!

最常见的做法是池化(Pooling),尤其是最大池化(Max Pooling):

max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
pooled_output = max_pool(output_features)
print(pooled_output.shape)  # [1, 4, 14, 14]

相比原来的 [1,4,28,28] ,直接压缩一半。但它不仅仅是“缩小图像”那么简单。

其核心作用有三:
1. 降维减参 :显著降低后续层的计算负担;
2. 增强平移不变性 :只要目标还在池化窗口内,最大值总会被捕获;
3. 防止过拟合 :丢弃部分细节,迫使网络关注宏观结构。

虽然池化一度被视为标配,但现在越来越多的设计倾向于用 带步长的卷积 替代它。比如:

strided_conv = nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1)

既能下采样,又能学习参数,灵活性更高。YOLOv5就在主干网络中广泛使用这种结构。

下表对比常见池化方式:

类型 操作 优点 缺点
Max Pooling 取局部最大值 保留显著特征,抗噪强 丢失背景信息
Avg Pooling 取局部均值 平滑特征,适合全局统计 易模糊关键响应
Global Avg Pooling 对每个通道整体平均 直接输出向量,常用于分类头 不适用于密集预测

典型的CNN结构就是“卷积→激活→池化”循环往复:

graph LR
    Input[Input Image] --> Conv[Conv Layer + Bias]
    Conv --> Act[ReLU Activation]
    Act --> Pool[Max Pooling]
    Pool --> Next[Next Conv Block]

这套组合拳贯穿了LeNet、AlexNet、VGG等经典模型,至今仍是许多轻量级网络的基础范式。

YOLO:一次扫描,全图皆知 🔍

如果说CNN教会了机器“看”,那YOLO则教会了它“找”。

传统两阶段检测器如Faster R-CNN先生成候选框,再逐一分类。听起来合理,但效率极低。你想啊,一张图生成几千个候选框,挨个裁剪、缩放、送入网络……这延迟谁受得了?

YOLO说:别搞那么麻烦了,我一次前向传播搞定所有事!

它的核心思想超级简单粗暴:把图像分成S×S个网格,每个网格负责预测若干边界框及其类别概率。整个过程就像做填空题,而不是写作文。

graph TD
    A[输入图像] --> B{检测方式}
    B --> C[两阶段检测]
    B --> D[单阶段检测]
    C --> E[RPN生成候选框]
    E --> F[RoI Pooling]
    F --> G[分类与回归]
    D --> H[网格划分]
    H --> I[Bounding Box预测]
    I --> J[置信度与类别输出]
    G --> K[最终检测结果]
    J --> K

差距一目了然:两阶段多了额外模块,串行处理自然慢;YOLO则是端到端回归,速度碾压。

特性 两阶段检测器(如Faster R-CNN) 单阶段检测器(如YOLOv5)
检测流程 区域提议 + 分类精修 端到端回归预测
推理速度 较慢(约5-10 FPS) 快速(可达100+ FPS)
准确率 高(尤其小目标) 中高(大中目标优秀)
计算复杂度 低至中等
是否适合嵌入式部署 是 ✅

所以如果你要做无人机避障、实时视频分析这类对延迟敏感的应用,YOLO几乎是唯一选择。

Grid Cell与Bounding Box预测机制

具体来说,假设我们将图像划分为13×13的网格,若某物体中心落在第(i,j)个格子里,那就由这个格子来预测它。

每个格子预测B个bounding box,每个box包含五个元素:
- (x, y) :相对于该网格左上角的偏移量
- (w, h) :相对于原图的比例
- confidence :$\Pr(\text{object}) \times \text{IOU}_{\text{pred}}^{\text{truth}}$

最后再加上C个类别概率,总共输出 $ S \times S \times (B \cdot 5 + C) $ 的张量。

多个anchor boxes的设计也很巧妙。不同尺度的目标用不同大小的anchor去匹配,相当于为各种体型的物体定制专属探测器。

推理阶段还要进行 非极大值抑制 (NMS)去除重复框。以下代码展示了完整的后处理逻辑:

import torch
import torchvision.ops as ops

def postprocess(pred, conf_thresh=0.5, nms_thresh=0.45):
    output = []
    for batch in pred:
        scores = batch[:, 4] * batch[:, 5:].max(1).values  # confidence * max class prob
        keep = scores > conf_thresh
        if not keep.any():
            continue
        boxes = batch[keep][:, :4]
        boxes[:, 0] -= boxes[:, 2] / 2  # cx -> x1
        boxes[:, 1] -= boxes[:, 3] / 2  # cy -> y1
        boxes[:, 2] += boxes[:, 0]       # w   -> x2
        boxes[:, 3] += boxes[:, 1]       # h   -> y2
        scores = scores[keep]
        classes = batch[keep][:, 5:].argmax(1)
        keep_nms = ops.nms(boxes, scores, nms_thresh)
        detections = torch.cat([
            boxes[keep_nms].cpu(),
            scores[keep_nms].cpu().reshape(-1, 1),
            classes[keep_nms].cpu().float().reshape(-1, 1)
        ], dim=1)
        output.append(detections)
    return output

⚠️ 注意:YOLO输出的是(cx,cy,w,h),必须转换成(x1,y1,x2,y2)格式才能可视化和评估。

YOLOv5架构剖析:轻巧背后的智慧 🛠️

YOLOv5虽未发表论文,但凭借出色的工程实现迅速风靡工业界。它的成功秘诀是什么?

Focus结构:信息重排的艺术

YOLOv5早期版本引入了一个叫 Focus 的模块,用来替代传统下采样:

class Focus(nn.Module):
    def __init__(self, c1, c2, k=3, s=1, p=1, act=True):
        super().__init__()
        self.conv = nn.Conv2d(c1 * 4, c2, k, s, p, bias=False)
        self.act = nn.SiLU() if act else nn.Identity()

    def forward(self, x):
        x = torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2],
                      x[..., ::2, 1::2], x[..., 1::2, 1::2]], dim=1)
        return self.act(self.conv(x))

它做了件什么事?把原图每隔一个像素切开,拼成四份,然后沿通道堆叠。原本H×W×3变成了(H/2)×(W/2)×12!

看起来像是“无损压缩”——没有丢任何信息,只是重新排列了一下。接着用标准卷积降维即可。

虽然后续版本已弃用此结构(因为计算代价仍高),但它启发了后来许多轻量化设计思路。

YAML配置:灵活定义模型规模

YOLOv5最大的亮点之一是 完全模块化 。通过yaml文件就能定义整个网络结构:

nc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  - layer channel multiple

backbone:
  [[-1, 1, Focus, [64, 3]],
   [-1, 1, Conv, [128, 3, 2]],  # P1/2
   [-1, 3, BottleneckCSP, [128]],
   ...
  ]
  • depth_multiple 控制残差块重复次数
  • width_multiple 调整每层通道数

这就实现了n/s/m/l/x等多种规格,适配从树莓派到服务器的各种设备。

字段 含义 示例
-1 上一层索引 表示从前一层获取输入
1 模块重复次数 执行一次 Conv
Conv 标准卷积模块 包含 BN 和 SiLU
[64, 3] 参数列表 输出通道64,卷积核3

太聪明了!改个数字就能生成不同体量的模型,再也不用手动改代码。

环境搭建:三步起飞 🚀

部署其实很简单:

conda create -n yolov5 python=3.8
conda activate yolov5
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt

验证CUDA是否就绪:

import torch
print(torch.__version__)           
print(torch.cuda.is_available())   
print(torch.cuda.get_device_name(0)) 

输出类似 "GeForce RTX 3080" 就说明可以开启GPU加速啦~

flowchart LR
    A[克隆仓库] --> B[安装依赖]
    B --> C[检查PyTorch版本]
    C --> D{CUDA可用?}
    D -- 是 --> E[启用GPU训练]
    D -- 否 --> F[使用CPU训练(较慢)]
    E --> G[开始训练]
    F --> G

整个流程丝滑流畅,新手也能快速上手。

U-Net:医学图像界的“老中医” 🩺

如果说YOLO擅长“找东西”,那U-Net就是“抠细节”的高手。

它最初为生物医学图像分割设计,要求对每一个像素做出判断。挑战在于:既要理解全局语义,又要保留精细边界。

FCN开了个好头,但上采样后边缘总是糊成一片。U-Net给出了解决方案: 跳跃连接

graph LR
    subgraph Encoder [Contraction Path]
        E1((Conv→ReLU)) --> E2((Conv→ReLU)) --> P1((MaxPool))
        P1 --> E3 --> E4 --> P2
        P2 --> E5 --> E6 --> P3
    end

    Bottleneck((Bottleneck Block))

    subgraph Decoder [Expansion Path]
        U1((Upsample)) --> C1((Concat with E5)) --> D1((Conv→ReLU)) --> D2((Conv→ReLU))
        D2 --> U2 --> C2((Concat with E3)) --> D3 --> D4
        D4 --> U3 --> C3((Concat with E1)) --> D5 --> D6
    end

    E6 --> Bottleneck
    Bottleneck --> U1

    D6 --> Final((1x1 Conv → Classify))

每一层解码器都能拿到对应编码层的特征图,相当于一边重建空间结构,一边参考原始细节。这就像修复古画时不断对照高清底片,确保每一笔都不走样。

实现起来也不难:

class UNet(nn.Module):
    def __init__(self, in_channels=3, num_classes=1):
        super().__init__()
        def double_conv(in_c, out_c):
            return nn.Sequential(
                nn.Conv2d(in_c, out_c, 3, padding=1),
                nn.BatchNorm2d(out_c),
                nn.ReLU(inplace=True),
                nn.Conv2d(out_c, out_c, 3, padding=1),
                nn.BatchNorm2d(out_c),
                nn.ReLU(inplace=True)
            )

        self.enc1 = double_conv(in_channels, 64)
        self.enc2 = double_conv(64, 128)
        self.pool = nn.MaxPool2d(2)
        self.bottleneck = double_conv(512, 1024)
        self.upconv4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)
        self.dec4 = double_conv(1024, 512)  # 512 from up + 512 from skip
        ...
        self.final = nn.Conv2d(64, num_classes, kernel_size=1)

    def forward(self, x):
        e1 = self.enc1(x)
        e2 = self.enc2(self.pool(e1))
        b = self.bottleneck(self.pool(e4))
        d4 = self.upconv4(b)
        d4 = torch.cat([d4, e4], dim=1)  # 跳跃连接!
        ...
        return self.final(d1)

最关键的就是这一句: torch.cat([d4, e4], dim=1) 。通道拼接带来了丰富的上下文信息融合。

损失函数推荐用 BCEWithLogitsLoss ,数值稳定还不用手动加sigmoid。

完整训练流程:不只是fit那么简单 🔄

很多人以为训练就是 model.fit() ,但实际上,一个稳健的训练系统涉及太多细节。

数据集划分:别让你的测试集“泄密”

标准做法是按8:1:1划分训练/验证/测试集:

from sklearn.model_selection import train_test_split

X_train_val, X_test, y_train_val, y_test = train_test_split(
    X, y, test_size=0.1, random_state=42, stratify=y
)
X_train, X_val, y_train, y_val = train_test_split(
    X_train_val, y_train_val, test_size=0.111, random_state=42, stratify=y_train_val
)

记住: 测试集只能用一次 !任何基于测试集调参的行为都会造成信息泄露,让你误以为模型很强,实则早已过拟合。

小数据集可用交叉验证,提升数据利用率。

DataLoader:别让GPU闲着

train_loader = DataLoader(
    dataset=train_dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4,
    pin_memory=True,
    drop_last=True
)
  • num_workers=4 → 多进程读取,防止I/O瓶颈
  • pin_memory=True → 提前搬运到 pinned memory,加速主机→GPU传输
  • drop_last=True → 丢弃最后一个不满批次的数据,避免维度错误

可以通过benchmark测试吞吐量:

start_time = time.time()
for i, (inputs, targets) in enumerate(train_loader):
    if i == 100: break
print(f"Average loading time per batch: {(time.time()-start_time)/100:.4f}s")

理想状态是“GPU never waits”——计算单元始终满载。

Epoch vs Iteration:别混淆时间尺度

  • Epoch :遍历一次完整训练集
  • Iteration :一次前向+反向传播
  • Batch Size :每次处理多少样本

关系如下:

$$
\text{Iterations per Epoch} = \left\lceil \frac{\text{Training Set Size}}{\text{Batch Size}} \right\rceil
$$

例如10,000张图,batch=32 → 每个epoch约313次iteration。

graph LR
    A[Start of Training] --> B{Epoch 1}
    B --> C[Iteration 1: Batch 1]
    C --> D[Iteration 2: Batch 2]
    D --> E[...]
    E --> F[Iteration 313: Last Batch]
    F --> G{Epoch 2}
    G --> H[Iteration 314: Batch 1]
    H --> I[...]
    I --> J{Epoch N}
    J --> K[Training Complete]

验证通常每个epoch做一次,平衡性能与开销。

防过拟合三板斧 🔪

模型在训练集上越来越好,但在验证集上却越来越差?恭喜你,遇到了经典过拟合问题。

早停(Early Stopping):及时止损

class EarlyStopping:
    def __init__(self, patience=5, min_delta=1e-4):
        self.patience = patience
        self.min_delta = min_delta
        self.counter = 0
        self.best_loss = None
        self.early_stop = False

    def __call__(self, val_loss):
        if self.best_loss is None or val_loss < self.best_loss - self.min_delta:
            self.best_loss = val_loss
            self.counter = 0
        else:
            self.counter += 1
            if self.counter >= self.patience:
                self.early_stop = True

当验证损失连续几个epoch没改善,果断终止。省时又省钱。

学习率调度:从快跑到慢走

初期大胆探索,后期精细微调:

scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=2)

验证损失卡住就降学习率,帮助跳出局部最优。

正则化组合拳

层类型 推荐组合
卷积层 Conv → BN → ReLU → Dropout(p=0.1)
全连接层 Linear → ReLU → Dropout(p=0.5)
优化器 Adam + weight_decay=1e-4

BN放在ReLU之前,Dropout最后。顺序错了效果打折!

端到端项目实战:从需求到上线 🚄

最后,让我们串起整个AI项目生命周期。

技术选型:PyTorch还是TensorFlow?

特性 PyTorch TensorFlow
动态/静态图 动态图(调试友好) 静态图为主
社区活跃度 更高(研究首选) 略低但稳定
移动端支持 有限(依赖ONNX) 强(TFLite完善)

建议:研发用PyTorch,生产部署转ONNX/TensorRT。

数据工程:DVC拯救实验复现

dvc init
dvc remote add -d myremote s3://mybucket/dataset
dvc add data/
git add . && git commit -m "Add raw data via DVC"
dvc push

数据与代码分离存储,保证任何人拉库都能还原结果。

快速原型:迁移学习打底

model = models.resnet18(pretrained=True)
model.fc = nn.Linear(model.fc.in_features, num_classes)

冻结主干,只训练头部,低成本建立baseline。

超参搜索:Optuna帮你找最优

def objective(trial):
    lr = trial.suggest_float('lr', 1e-5, 1e-1, log=True)
    batch_size = trial.suggest_categorical('batch_size', [16, 32, 64])
    ...
study.optimize(objective, n_trials=50)

告别手动试错,自动化调参提升效率。

成果交付:ONNX + FastAPI

导出模型:

torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)

封装API:

@app.post("/predict/")
async def predict(file: UploadFile = File(...)):
    image = preprocess(Image.open(file.file))
    result = session.run(None, {'input': image})
    return {"class": int(np.argmax(result)), "confidence": float(np.max(result))}

启动服务:

uvicorn api:app --reload --host 0.0.0.0 --port 8000

客户端POST上传图片即可获得预测结果,真正实现“一键部署”。

flowchart TD
    A[项目需求] --> B{任务类型}
    B --> C[分类]
    B --> D[检测]
    B --> E[分割]
    F[数据采集] --> G[清洗与标注]
    G --> H[DVC版本控制]
    H --> I[PyTorch DataLoader]
    I --> J[模型原型]
    J --> K[超参数搜索]
    K --> L[模型集成]
    L --> M[TensorBoard监控]
    M --> N[ONNX导出]
    N --> O[FastAPI部署]
    O --> P[线上服务]

至此,你已经掌握了一名合格CV工程师所需的核心技能栈。无论是发论文、打比赛,还是做产品,这套方法论都能为你提供坚实支撑。

技术一直在变,但底层思维永恒。希望你能带着这份理解,在AI的世界里走得更远 🚀

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:“DL_ComputerVision”是一个聚焦于深度学习技术在计算机视觉领域实践应用的项目合集,内容涵盖从基础理论到高级模型的完整流程。项目以Jupyter Notebook形式呈现,便于交互式学习与实验复现,涉及卷积神经网络(CNN)、目标检测(如YOLO、Faster R-CNN)、语义分割(如U-Net)等核心技术。通过经典数据集(如MNIST、CIFAR-10、ImageNet)进行数据预处理、模型构建、训练验证与性能评估,帮助学习者掌握图像分类、目标检测和语义分割等关键任务的实现方法。同时包含TensorBoard可视化、模型优化策略及端到端项目实战,全面提升深度学习在视觉任务中的工程能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐