☰
Flask + YOLO:打造浏览器端目标检测标注与训练平台
2026/9/29 18:46:46 网站建设 项目流程

简介:AI模型训练平台是一套基于Python Flask的完整Web应用,专为YOLOv8/11目标检测模型的图片标注、数据集管理、训练与导出而设计,面向AI开发者和研究人员,提供一体化工作流。平台内置多模态大模型与PT模型双重自动标注能力,构建“小数据→简单模型→辅助标注→大数据→精确模型”的迭代闭环,持续降低标注成本并提高数据质量。资源共296个文件,压缩包104.55MB,含136个jpg图像样本、48个txt标注文件、18个Python源码、30个pyc编译文件、12个HTML页面、5个pt权重及2个onnx导出模型等;txt对应标注结果、py/pyc处理后端逻辑、html为前端界面、pt与onnx覆盖训练权重与部署格式,并附TensorBoard训练日志与缓存文件。目前已有109人学习下载,适合快速搭建目标检测训练平台或研究Flask+深度学习项目架构的开发者。

1. AI模型训练平台:目标检测项目里,Flask 怎么把标注和训练串起来

很多人真正上手目标检测时,卡住的不是模型原理,而是脏活:图片标注完要转格式,数据集划分不对训练直接报错,想调几个参数就得在命令行和文件管理器之间来回切。Boyan 做的这个 AI 模型训练平台,就是把这一串动作搬进浏览器:基于 Python Flask 起一个 Web 应用,在网页上画框标注图片、管理数据集、发起 YOLOv8 / YOLOv11 训练,最后把模型导出成可用格式。它对个人开发者和小团队尤其合适,不需要额外装客户端,局域网里开个服务,同事也能一起标数据。本文就从工程实现角度拆它:目录结构怎么设计、标注坐标如何转成 YOLO 格式、Flask 后端怎么调度训练、参数怎么设不翻车。

2. 把标注和数据集收拾干净:YOLOv8/11 训练前的目录规范与坐标转换

2.1 YOLO 数据集的目录结构:images 和 labels 一一对应是底线

YOLOv8 和 YOLOv11 用的都是同一套数据组织方式:一张图片对应一个同名的 txt 标注文件,txt 放在 labels 目录下,图片放在 images 目录下,划分成 train 和 val 两个子集。平台在数据集管理模块里做的最基础的一件事,就是强制校验这套结构,因为它决定了model.train(data=...)能不能跑起来。

我拆这个平台时先看的是它如何生成目录。常见做法是在指定数据集根目录下自动创建images/train、images/val、labels/train、labels/val四个文件夹,并把用户上传或标注的图片按比例分进去。目录结构如下:

datasets/ └── steel_defect/ ├── images/ │ ├── train/ │ │ ├── 00001.jpg │ │ └── 00002.jpg │ └── val/ │ └── 00003.jpg ├── labels/ │ ├── train/ │ │ ├── 00001.txt │ │ └── 00002.txt │ └── val/ │ └── 00003.txt └── data.yaml

注意图片主文件名必须和 txt 主文件名完全一致,包括前后不能有空格。平台里有个校验函数,会扫一遍images目录和labels目录,找出“有图无标”和“有标无图”的文件,并在上传新数据时自动排查。我自己在本地复现时,习惯先把最小数据集跑通——两张训练图一张验证图,确认平台能正确生成目录和 yaml,再灌全量数据,这样出问题容易定位。

2.2 画框坐标归一化:从像素坐标到 YOLO 的 cx, cy, w, h

在网页上标注时,前端拿到的是鼠标画出的像素框,比如左上角(x1, y1)和右下角(x2, y2)。但 YOLO 的 txt 格式不认绝对像素,它要的是归一化后的中心点坐标和宽高,公式是:

x_center = (x1 + x2) / 2 / img_width y_center = (y1 + y2) / 2 / img_height width = (x2 - x1) / img_width height = (y2 - y1) / img_height

平台后端用一段转换函数处理前端传来的坐标,我把它单独摘出来看:

def convert_bbox_to_yolo(x1, y1, x2, y2, img_w, img_h): dw = 1.0 / img_w dh = 1.0 / img_h cx = (x1 + x2) / 2.0 * dw cy = (y1 + y2) / 2.0 * dh w = (x2 - x1) * dw h = (y2 - y1) * dh return round(cx, 6), round(cy, 6), round(w, 6), round(h, 6)

这段代码逻辑不复杂,但有两个细节决定训练是否正常。第一,x1, y1必须是左上角、x2, y2必须是右下角,如果前端画框时允许反向拖动(从右下往左上拖),后端要先把端点排序,否则宽高会出现负值。第二,返回值保留 6 位小数,YOLO 加载标注时对超过 1 的坐标会做裁切甚至报错,四舍五入到 6 位足够精度,也不会触发边界问题。

平台在保存标注后还会顺手做一步检查:逐行读取生成的 txt,确认每个数值都在[0, 1]区间内、每行恰好 5 个数字(类别 id 加四个坐标)。这一步在训练前拦截掉脏数据,能省下大量排查时间。

2.3 data.yaml 自动生成:类别清单和路径别手写

YOLO 训练必须读一个 data.yaml,里面写清path、train、val、nc、names。手写时最容易错的是path写成绝对路径且含中文,换台机器就崩。平台的逻辑是让用户在界面上填数据集名称和类别列表,后端自动生成 yaml:

path: D:/ai_train_platform/datasets/steel_defect train: images/train val: images/val nc: 3 names: 0: scratch 1: indentation 2: rust

注意train和val写的是相对path的路径,而不是绝对路径。这也是 Ultralytics 官方推荐写法,平台在生成时直接拼接,避免了用户手工改错。nc的值必须和names的长度一致,平台在创建数据集时会让用户先定义类别列表,再允许标注,所以类别 ID 从 0 开始连续编号,不会出现跳号问题。

我自己遇到过一个反面案例:别人给的数据集里类别 ID 从 1 开始写,学号从 2 开始,ng 是空的,训练出来的模型类别映射全错。这个平台的做法更稳——标注界面下拉框里直接列出 yaml 中的类别,画框时选中的是 ID 而不是纯文本,这就杜绝了拼写和编号错位。

3. Flask 后端调度训练:路由设计、线程执行与日志回传

3.1 训练请求怎么从网页传到后端

Flask 在这个平台里扮演的是“中间调度人”:前端页面收集用户填的模型类型、训练轮数、批次大小等参数,打包成 JSON 发到后端接口,后端校验通过后启动训练任务。核心路由长这样:

from flask import Flask, request, jsonify import threading, subprocess, os app = Flask(__name__) @app.route("/api/train", methods=["POST"]) def start_train(): cfg = request.get_json() required_keys = ["model", "data", "epochs", "imgsz", "batch", "project"] for key in required_keys: if key not in cfg: return jsonify({"error": f"missing param: {key}"}), 400 thread = threading.Thread(target=run_train, args=(cfg,)) thread.daemon = True thread.start() return jsonify({"status": "started", "message": "训练已启动"})

这段代码有两个容易被新手忽略的点。一是所有参数都得做存在性校验,前端表单漏传一项,后端会在启动前就拒绝,而不是等训练跑一半才报错。二是训练必须放到线程里执行,不能在 Flask 请求处理函数里直接调用训练逻辑,否则浏览器会一直挂起等响应,接口看起来像假死。

前端传参时还有一个“Flask 如何绑定到网页元素”的对应关系:前端表单控件的name属性值就是 JSON 里的字段名,后端用request.get_json()拿到的字典 key 必须和它一致。我自己在调试时习惯在start_train入口处加一句日志打印cfg,确认收到的参数和界面上填的一致,再往下走。

3.2 训练进程的子进程化:不阻塞主服务的做法

平台真正执行训练的方式不是直接写model.train(),而是把yolo train命令扔进subprocess,这样 Flask 主进程不会被训练占用的显存和 CPU 拖垮。训练日志会重定向到指定文件,前端页面轮询这个文件来展示进度。后端执行部分摘出来看:

def run_train(cfg): cmd = [ "yolo", "train", f"model={cfg['model']}", f"data={cfg['data']}", f"epochs={cfg['epochs']}", f"imgsz={cfg['imgsz']}", f"batch={cfg['batch']}", f"project={cfg['project']}", "device=0" ] log_file = os.path.join(cfg["project"], "train.log") os.makedirs(cfg["project"], exist_ok=True) proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) with open(log_file, "w", encoding="utf-8") as f: for line in iter(proc.stdout.readline, ""): f.write(line) f.flush()

这段代码把stdout和stderr合并成同一个管道,按行写入日志文件,并即时flush,保证前端轮询时能看到最新进度。bufsize=1表示行缓冲,在这里尤其重要——不加这个参数时,子进程的输出会积在缓冲区里,日志文件迟迟不更新,前端进度条就跟卡住了一样。

平台在界面上提供的进度展示是一个折中方案:通过fetch或定时器每 2 秒读一次日志文件,把包含Epoch、GFLOPs等关键字的行解析出来渲染成进度条。这种方式比 WebSocket 简单,但有一个小坑:训练结束前日志文件可能被yolo命令写坏(比如磁盘写满一半),所以前端要做异常兜底——解析失败时显示“训练运行中”而不是报错。

3.3 模型产物与项目目录组织

训练完成后,YOLO 会在project参数指定的目录下生成runs/detect/trainN系列文件夹,里面包含weights/best.pt、last.pt、confusion_matrix.png、results.csv等文件。平台的模型管理模块会扫描这些目录,把best.pt对应的评估指标(mAP50、mAP50-95)读取出来展示在界面列表里。

这里有个组织细节值得注意:平台给每个训练任务分配独立的project子目录,子目录名带时间戳,避免覆盖历史训练记录。我一般建议保留至少最近 5 次训练产物,因为对照实验时经常要回溯到旧权重重测,如果每次训练都放在同一个runs/detect/train下,旧模型就被静默覆盖了,没有后悔药吃。

参数映射关系前端的字段名和后端的cmd参数一一对应,平台界面上能填的其实就那几个:模型权重文件(或预训练权重名)、数据集 yaml 路径、训练轮数和图片尺寸。后面我会单独讲这些参数怎么配才能保住显存和进度。

4. 训练参数怎么定:预训练权重、epochs、batch 和 imgsz 的取舍

4.1 预训练权重从 n 到 x:显存和精度的直白换算

平台提供预训练权重的下拉选择,常见的是yolov8n.pt、yolov8s.pt、yolov8m.pt、yolov8l.pt,以及yolov11n.pt等。它们的体积和推理速度差距很大,选型逻辑不能只看精度表格。

权重大小(约)显存占用(batch=16, imgsz=640)适用场景
yolov8n.pt6.2 MB约 6 GB快速验证、边缘设备、小数据集
yolov8s.pt21 MB约 8 GB通用目标检测,精度略高
yolov8m.pt49 MB约 12 GB精度优先,可接受训练时间长
yolov8l.pt83 MB约 16 GB大模型,服务器训练,追求 mAP

平台默认给yolov8n.pt,这是符合实际经验的:大多数自制数据集规模在几百到几千张,用n做基线跑通,再用s或m做对比,比一上来就上l高效得多。yolov8l.pt和更大的x权重更适合 V100、A100 这类大显存卡,如果只有一块 8 GB 显存的消费级卡,训练中途会因为CUDA out of memory翻车,换n才是正路。

注意预训练权重不是随便一个.pt文件都能直接用的。平台的做法是让用户指定权重路径,或者填yolov8n.pt让 ultralytics 自动下载。如果网络环境受限,手动下载预训练模型后放本地,此时要确认文件名和架构匹配——用yolov8s.pt的架构去加载yolov8l.pt的权重会报 shape mismatch。

4.2 epochs 和早停:不是越大越好,也不是越小越安全

训练轮数的选择,取决于数据集规模和数据复杂度。平台默认建议 50 epochs,但实际工程里这个值经常需要调整。我个人习惯按数据集量级来分:小于 500 张图的小数据集,100 到 150 epochs 才可能收敛,因为每个 epoch 看到的样本太少;几千张图的中等数据集,50 到 80 epochs 足够;加上早停机制(patience=20),就能在 mAP 不再提升时自动中断,避免无效训练和过拟合。训练轮数不是越大越好,关键是监控results.csv里val/box_loss是否还在下降。

平台训练界面上还有一个细节值得提:它会显示“预计训练时长”。这个数字不是拍脑门算的,而是后台读取results.csv里每个 epoch 的平均耗时外推出来的。碰到显存接近上限的情况,这个预计时间会明显比实际短,因为每轮之间还有模型评估和混淆矩阵生成的开销,不算在单 epoch 训练时间里,需要有个心理预期。

4.3 batch、imgsz 和显存的关系:怎么配不爆显存

batch和imgsz是显存占用的两个直接决定变量。batch是每次迭代喂给显卡的图片数,imgsz是输入图片被缩放到的像素尺寸。显存占用基本正比于batch × imgsz²,因为每张图的张量大小是3 × imgsz × imgsz,batch 翻倍则显存近似翻倍。

显存推荐配置(yolov8n)备注
6 GBbatch=8, imgsz=640入门级显卡,能跑但慢
8 GBbatch=16, imgsz=640多数场景最舒服
12 GBbatch=32, imgsz=640常见工作站配置
24 GBbatch=48, imgsz=640可以用更大的模型

平台里默认 batch=16、imgsz=640,这个组合在 8 GB 显存上跑yolov8n.pt是安全的。如果你用的是 6 GB 卡,可以不动 imgsz 只把 batch 降到 8,对精度影响最小,因为把输入尺寸缩小到 416 对小目标检测的伤害远大于降低 batch。还有一种常见的做法是开amp=True(混合精度训练),平台默认开启,它能把显存占用再压掉三到四成,代价是训练日志里无法打印浮点 loss 的完整精度。混合精度在绝大多数数据集上不会掉点,但如果你要复现文献里的精确数值结果,建议关掉 amp 重新跑一遍。

大分辨率输入(比如imgsz=1280)确实能提升小目标召回率,但显存占用会涨到 640 的四倍。平台在界面上对这个参数做了软限制:先检测显卡剩余显存,再计算batch × imgsz² × 2,如果超出可用容量就给警示信息,用户仍然可以强行提交,但会得到“大概率 OOM”的提示。这个设计很务实,没有把决策权收走,而是把风险摆到明面上。

4.4 训练完看什么:loss 曲线、混淆矩阵和 mAP 的关系

平台在训练结束后展示的可视化图表有results.png(loss 曲线和 mAP 曲线)、confusion_matrix.png和confusion_matrix_normalized.png。很多人只盯着一张 mAP50 数字就下结论,这是不够的。我一般会按顺序看三个东西:

第一,train/box_loss和val/box_loss曲线。如果训练到最后 val loss 还在明显下降,说明还没收敛,应该加 epochs 继续训练;如果 val loss 从某个点开始反弹,而 train loss 还在降,那就是过拟合,早停机制在此时就发挥作用了。第二,confusion_matrix_normalized.png里对角线数字是不是明显大于非对角线。混淆矩阵总和并不会严格等于 1——非对角格子的数值来自错分样本,平台展示的是绝对计数而非概率,所以看归一化版本才有对比意义。第三,val/recall这个曲线。目标检测召回率上不去,往往是数据里目标太小或标注框不全,这时候调参数没用,回去补标注才是正经事。

5. 避坑排查:我在这个平台上踩过的五个实际翻车点

5.1 数据集与标注目录的坑

现象:训练一启动就打印found 0 images in val set,或者WARNING: found x valid images but y labels。训练还能跑,但验证集相当于空的,最终 mAP 数字虚高或随机跳变。

原因:images/val目录里的图片存在,但对应的labels/val里没有同名 txt。常见来源是标注时只标了训练集忘记标验证集,或者复制图片时没把同名标注文件一起复制。

解决:写一个校验脚本,按文件名前缀对比 images 和 labels 两个目录,列出缺失列表。平台在后端做训练前校验时就拦截这个错误,但如果拿到的是外部数据集,建议自己跑一遍:

import os img_dir = "datasets/steel_defect/images/val" lab_dir = "datasets/steel_defect/labels/val" img_names = set(os.path.splitext(f)[0] for f in os.listdir(img_dir)) lab_names = set(os.path.splitext(f)[0] for f in os.listdir(lab_dir)) missing_label = img_names - lab_names missing_image = lab_names - img_names print("缺标注:", missing_label) print("缺图片:", missing_image)

这段代码用集合差集找出两边不匹配的文件名,一秒钟就能定位问题。从那以后我每次新建数据集都会先把 train 和 val 两边都跑一遍这个脚本再开始训练。

5.2 训练环境与 GPU 的坑

现象:训练日志里显示Using CPU,或者第一次迭代后直接报CUDA out of memory,但nvidia-smi看显存明明还剩不少。

原因:Using CPU说明 PyTorch 装成了 CPU 版本,pip install torch默认不装 CUDA 支持,需要装torch的 CUDA 轮子(常见做法是走pip install torch --index-url ...指定 CUDA 版本,或直接通过pip3 install torch torchvision配合本机 CUDA 版本匹配)。CUDA out of memory而显存还有剩余,通常是 batch 和 imgsz 乘积瞬间峰值顶爆了显存,或者是训练时被其他进程占了显存。

解决:先跑一段 PyTorch 自检代码确认 CUDA 可用:

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

这里有个值得注意的细节:torch.cuda.is_available()返回True不代表显存检查通过。你还需要看torch.cuda.mem_get_info()返回的剩余显存,而不是看nvidia-smi显示的“已用/总量”。“已用/总量”是当前快照,训练起来后显存会迅速爬升,训练前看起来剩 6 GB,一开训练峰值就吃满。平台热启动训练前先调一次mem_get_info(),把当前空闲显存打印到日志,如果小于某个阈值就直接拒绝启动,比跑崩之后再分析高效得多。

5.3 模型导出与推理的坑

现象:导出 ONNX 后用onnxruntime推理,结果和 PyTorch 直接推理不一致,检测框位置偏了或者输出 shape 不对。

原因:导出时没有固定输入尺寸,或者opset版本太低导致部分算子不兼容。model.export(format="onnx", imgsz=640)没写死imgsz时,模型会按照最后一次训练时的动态尺寸导出一个动态 shape,ONNX Runtime 在动态 shape 下某些节点优化会做奇怪的重排,导致输出张量形状不确定。另一种常见原因是训练时开了rect=True(矩形推理),导出模型对宽高做了 padding,正常推理时没有做同样 padding,坐标就整体偏移。

解决:导出的参数需要显式、固定。我在平台上跑通一条稳定的导出链路,代码是:

from ultralytics import YOLO model = YOLO("runs/detect/train5/weights/best.pt") model.export( format="onnx", imgsz=640, opset=12, simplify=True, dynamic=False )

simplify=True会调用 onnxsim 对计算图做常量折叠,这个选项能解决大部分算子兼容问题,但要求额外安装onnxsim包。dynamic=False将输入输出 shape 固定成(1, 3, 640, 640),后面用 ORT 推理时不会有多余的动态维度。导出后用 ORT 对同一张测试图跑一遍,对比输出框和model.predict()的结果,偏差在一个像素以内才能放心用。

5.4 训练中 BN 崩溃与 loss 为 NaN

现象:训练进行到某个 epoch 之后,box_loss突然变成nan,所有损失和精度指标全部变乱,恢复不了。这种损失值在日志中打印出来是nan,模型文件虽然还在生成,但已经废了。

原因:这是目标检测训练中典型的 BatchNorm 崩溃,常见诱因有三个——学习率设置过大、训练后期数据集中出现极端的数值波动、mosaic 增强在小数据集上导致 BN 统计量失调。yolo命令里默认的学习率对大多数数据集是安全的,但当你把batch调得特别小(比如 2),BN 的统计量就会在每一个 batch 间剧烈抖动,积累几十个 epoch 后数值溢出。

解决:最直接的兜底方案是降低学习率并把batch调到不小于 4:

yolo train model=yolov8n.pt data=data.yaml epochs=80 batch=8 imgsz=640 lr0=0.005

lr0=0.005是初始学习率,默认值是 0.01,减一半往往就能避免 BN 崩溃。如果你用的是平台界面,确认界面上有没有暴露学习率参数。没有的话,按平台的后端实现来看,它是拼接命令时带上默认lr0=0.01,此时要改只能后端改配置。我经历过一次这种翻车之后,凡是换新数据集,前 10 个 epoch 都会盯着results.csv手动看一遍有没有出现nan,确认稳定后再挂机全量训练。

5.5 路径存在中文导致的隐蔽失败

现象:平台在自己的电脑上跑得好好的,同团队同事 clone 到另外一台机器报FileNotFoundError或者NotADirectoryError,而且错误发生在训练到一半的时候,不是启动时就报。

原因:YOLO 在读取data.yaml里的path时,如果路径里有中文或空格,部分底层文件读取函数会出现编码兼容问题。平台默认在 Windows 上运行时尤其明显——中文用户名的C:\Users\张三\...路径在 Ultralytics 的部分版本里会拼接异常。

解决:最省心的办法是强制约束数据集根目录为纯英文、无空格的路径。平台在创建数据集接口里对路径做了规则校验:只能包含字母、数字、下划线和斜杠,遇到中文直接拒绝并提示用户换路径。另一个兜底手段是设置YOLO_CONFIG_DIR环境变量指向纯英文目录,避免它在用户目录里默认创建的配置文件夹触发同类问题:

set YOLO_CONFIG_DIR=D:/yolo_config

平台没有自动做这个,所以本地部署时最好手动配一下。我一般在安装文档里就会提醒这个环境变量,没有它,即使数据集路径没问题,训练过程里写缓存文件时也可能踩到同样的编码坑。

6. 模型导出与验证:从 .pt 到 ONNX 的完整链路和推理对比

6.1 固定 imgsz 导出 ONNX

训练收敛后的模型文件是best.pt,这是 PyTorch 原生权重,保存了完整网络结构和超参数。要部署到 CPU 推理环境或用 OpenVINO、TensorRT、ONNX Runtime 跑,第一步都是导出为中间表示(IR)。优先导 ONNX 是最通用的选择:它不像 TensorRT 绑定 NVIDIA 显卡,也不像 OpenVINO 绑定 Intel 平台,任何带 ONNX Runtime 的环境都能跑。

from ultralytics import YOLO best_model = YOLO("runs/detect/train5/weights/best.pt") best_model.export( format="onnx", imgsz=640, opset=12, simplify=True, dynamic=False, half=False )

这个调用里imgsz=640必须写明,不能省。省略时 ultralytics 会按模型训练时最后一次的imgsz导出,而你如果训练时用的是rect=True或自动批次,这个“最后一次”不一定是你想要的固定尺寸。opset=12是 ONNX Runtime 对算子支持的均衡点,太高或太低都会遇到个别算子不支持的边界情况。half=False这里先不启用 FP16,放在导出后另跑一遍验证,能更干净地区分问题来源。

6.2 用 ONNX Runtime 做推理对比

导出完成后,不要直接拿去部署,先跑一遍与 PyTorch 输出的对比验证。这一步虽然多花几分钟,但能发现绝大多数导出翻车问题。我习惯写一个很小的验证脚本,输入同一张测试图,把两组输出框打出来比较。

import cv2 import numpy as np import onnxruntime as ort from ultralytics import YOLO img = cv2.imread("test.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) input_tensor = cv2.resize(img_rgb, (640, 640)).astype(np.float32) / 255.0 input_tensor = np.transpose(input_tensor, (2, 0, 1))[None, ...] input_name = "images" session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) outputs = session.run(None, {input_name: input_tensor}) print("ONNX 输出 shape:", [o.shape for o in outputs]) pt_model = YOLO("best.pt") results = pt_model.predict("test.jpg", imgsz=640, device="cpu") print("PyTorch 输出框数:", len(results[0].boxes))

这里需要注意的是input_tensor的通道顺序:PyTorch 模型期望的是(batch, channel, height, width),而 OpenCV 读出来的是 HWC 且是 BGR。平台导出的 ONNX 模型输入名固定为images,如果你拿到的模型输入名不是这个,可以从session.get_inputs()[0].name动态读取。输出 shape 是(1, 6, 8400)或(1, 84, 8400)都正常——第一个维度是 batch,第二个是 4 个框坐标加类别数加置信度(具体取决于类别数),第三个是候选框总数(640×640 输入下是 8400)。

对比时主要看三点:检测框数量是否一致(允许少量差异,因为 NMS 在不同实现里有微小阈值差异)、同一目标的中心点坐标偏差是否小于 2 个像素、置信度数值是否在同一量级(比如 PyTorch 是 0.87,ONNX 是 0.86,这属于浮点差异,正常)。如果偏差过大,回头检查simplify和opset参数,别怀疑模型本身。

6.3 FP16 导出的提速前提

导出 ONNX 后,NVIDIA 显卡上还能再做一步半精度优化。half=True可以导出 FP16 权重,在 T4、V100 这类显存和 INT8 计算单元都更充裕的卡上,推理速度能提高 30% 以上。但是 FP16 导出对算子和输入前处理有要求:输入张量必须转成np.float16,且某些 CPU 上的 ONNX Runtime 不支持 FP16 算子,会导致推理直接报错。所以平台里没有默认开这个选项,而是提供开关,让用户在确认部署环境支持 FP16 后才启用。我的经验是:先在 CPU 上跑一遍 FP32 版本确认结果正确,再换 GPU 跑 FP16 版本对比速度,两个版本输出框偏差在一个像素以内就可以上线。

从那以后我每次拿到训练好的模型,都强制走一遍“导出 ONNX → ORT 推理对比 → 再谈部署”的流程。即使只改了数据集没有改模型结构,也不会跳过这一步,因为导出这个环节和训练是独立的,出问题不会在训练日志里报出来。这个习惯帮我挡掉了至少三次部署现场的诡异 bug——检测框偏半个身位、输出 shape 不符导致程序崩溃,全是导出阶段埋的雷。希望这篇拆解能帮你在自己的目标检测项目里少走这几段弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询