博主又来水文章了,最近在学习 YOLOv7 QAT 量化相关的一个 repo,本来想和大家直接分享 QAT 量化的,但转念一想貌似还可以水一篇 PTQ 量化的文章,因此博主就准备在这篇文章中分享基于 YOLOv7 的 PTQ 量化部署的相关实现,具体实现在 tensorRT_Pro 这个 repo 中已经提供,博主只是简单过了一遍流程。
博主为初学者,欢迎交流讨论,若有问题欢迎各位看官批评指正!!!
在正式开始之前我们先来回顾下关于 PTQ 量化的一些知识,具体可参考:TensorRT量化第四课:PTQ与QAT
TensorRT 有两种量化模式,分别是隐式(implicitly)量化和显式(explicitly)量化。前者在 TRT7 版本之前用得比较多,而后者在 TRT8 版本后才完全支持,具体就是可以加载带有 QDQ 信息的模型然后生成对应量化版本的 engine。
这篇文章主要分享隐式量化即 PTQ 量化,关于显式量化即 QAT 量化我们将在下篇文章中分享。
PTQ(Post-Training Quantization)即训练后量化也叫隐式量化,tensorRT 的训练后量化算法第一次公布是在 2017 年,那年 NVIDIA 放出了使用交叉熵量化的一个 PPT,简单说明了其量化原理和流程,其思想集中在 tensorRT 内部可供用户去使用。对用户是闭源的,我们只能通过 tensorRT 提供的 API 去实现量化。
PTQ 量化不需要训练,只需要提供一些样本图片,然后在已经训练好的模型上进行校准,统计出来需要的每一层的 scale 就可以实现量化了,大概流程如下:
具体使用就是我们导出 ONNX 模型,转换为 engine 的过程中使用 tensorRT 提供的 Calibration 方法去校准,可以使用 tensorRT 官方提供的 trtexec 工具去实现,也可以使用它提供的 Python 或者 C++ 的 API 接口去实现。
在 tensorRT_Pro 中 INT8 模型的编译就是 PTQ 量化,因此我们只需要提供好 ONNX 模型和校准数据即可,其它不用我们关心。
tensorRT 还提供了多种校准算法,分别适用于不同的任务:
通过上述这些校准算法进行 PTQ 量化时,tensorRT 会在优化网络的时候尝试 INT8 精度,假设网络某一层在 INT8 精度下的速度优于默认精度(FP32/FP16),则优先使用 INT8。
值得注意的是,PTQ 量化中我们无法控制某一层的精度,因为 tensorRT 是以速度优化为优先的,很可能某一层你想让它跑 INT8 结果却是 FP16,当然 PTQ 优点是流程简单,速度快。
OK!关于 PTQ 量化我们就简单聊下,让我们开始具体的实现吧!!!
首先我们需要训练一个 YOLOv7 模型,当然拿官方的预训练权重也行,博主这边为了完整性还是整体走一遍流程,熟悉 YOLOv7 模型训练的看官可以跳过直接到量化部分。
yolov7 的代码是开源的可直接从 github 官网上下载,源码下载地址是 https://github.com/WongKinYiu/yolov7,由于 yolov7 目前就只固定 v0.1 一个版本,而 v0.1 版本并未提供训练的详细说明,故采用主分支进行模型的训练和部署工作。Linux下代码克隆指令如下
git clone https://github.com/WongKinYiu/yolov7.git
也可手动点击下载,点击右上角的绿色的 Code
按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here【pwd:yolo】下载博主准备好的代码(注意该代码下载于 2023/10/14 日,若有改动请参考最新)
将下载后的 yolov7 代码解压,其代码目录如下图所示:
现在来对代码的整体目录做一个介绍:
关于深度学习的环境安装可参考炮哥的利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装—免额外安装CUDA和cudnn(适合小白的保姆级教学),这里不再赘述。如果之前配置过 yolov5 的环境,yolov7 可直接使用。
这里训练采用的数据集是 PASCAL VOC 数据集,但博主并没有使用完整的 VOC 数据集,而是选用了部分数据,具体分布如下:
这里给出下载链接 Baidu Drive【pwd:yolo】下载解压后整个数据集文件夹内容如下所示:
其中 images 存放训练集和验证集的图片文件,labels 存放着对应的 YOLO 格式的 .txt 文件。
完整的 VOC 数据集的相关介绍和下载可参考:目标检测:PASCAL VOC 数据集简介
由于大家可能从其它地方拿到的是 XML 格式的标签文件,这里提供一个 XML2YOLO 转换的代码,如下所示:(from ChatGPT)
import os
import cv2
import xml.etree.ElementTree as ET
import shutil
from multiprocessing import Pool, cpu_count
from tqdm import tqdm
import numpy as np
from functools import partial
def process_xml(xml_filename, img_path, xml_path, img_save_path, label_save_path, class_dict, ratio):
# 解析 xml 文件
xml_file_path = os.path.join(xml_path, xml_filename)
tree = ET.parse(xml_file_path)
root = tree.getroot()
# 获取图像的宽度和高度
img_filename = os.path.splitext(xml_filename)[0] + ".jpg"
img = cv2.imread(os.path.join(img_path, img_filename))
height, width = img.shape[:2]
# 随机决定当前图像和标签是属于训练集还是验证集
subset = "train" if np.random.random() < ratio else "val"
# 打开对应的标签文件进行写入
label_file = os.path.join(label_save_path, subset, os.path.splitext(xml_filename)[0] + ".txt")
with open(label_file, "w") as file:
for obj in root.iter('object'):
# 获取类别名并转换为类别ID
class_name = obj.find('name').text
class_id = class_dict[class_name]
# 获取并处理边界框的坐标
xmlbox = obj.find('bndbox')
x1 = float(xmlbox.find('xmin').text)
y1 = float(xmlbox.find('ymin').text)
x2 = float(xmlbox.find('xmax').text)
y2 = float(xmlbox.find('ymax').text)
# 计算中心点坐标和宽高,并归一化
x_center = (x1 + x2) / 2 / width
y_center = (y1 + y2) / 2 / height
w = (x2 - x1) / width
h = (y2 - y1) / height
# 写入文件
file.write(f"{class_id} {x_center} {y_center} {w} {h}\n")
# 将图像文件复制到对应的训练集或验证集目录
shutil.copy(os.path.join(img_path, img_filename), os.path.join(img_save_path, subset, img_filename))
def check_and_create_dir(path):
# 检查并创建 train 和 val 目录
for subset in ['train', 'val']:
if not os.path.exists(os.path.join(path, subset)):
os.makedirs(os.path.join(path, subset))
if __name__ == "__main__":
# 1. 定义路径和类别字典,不要使用中文路径
img_path = "D:\\Data\\PASCAL_VOC\\VOCdevkit\\VOC2007\\JPEGImages"
xml_path = "D:\\Data\\PASCAL_VOC\\VOCdevkit\\VOC2007\\Annotations"
img_save_path = "D:\\Data\\PASCAL_VOC\\dataset\\images"
label_save_path = "D:\\Data\\PASCAL_VOC\\dataset\\labels"
class_dict = {
"aeroplane": 0,
"bicycle": 1,
"bird": 2,
"boat": 3,
"bottle": 4,
"bus": 5,
"car": 6,
"cat": 7,
"chair": 8,
"cow": 9,
"diningtable": 10,
"dog": 11,
"horse": 12,
"motorbike": 13,
"person": 14,
"pottedplant": 15,
"sheep": 16,
"sofa": 17,
"train": 18,
"tvmonitor": 19
}
train_val_ratio = 0.8 # 2. 定义训练集和验证集的比例
# 检查并创建必要的目录
check_and_create_dir(img_save_path)
check_and_create_dir(label_save_path)
# 获取 xml 文件列表
xml_filenames = os.listdir(xml_path)
# 创建进程池并执行
with Pool(cpu_count()) as p:
list(tqdm(p.imap(partial(process_xml, img_path=img_path, xml_path=xml_path, img_save_path=img_save_path, label_save_path=label_save_path,
class_dict=class_dict, ratio=train_val_ratio), xml_filenames), total=len(xml_filenames)))
上述代码的功能是将 PASCAL VOC 格式的数据集(包括 JPEG 图像和 XML 格式的标签文件)转换为 YOLO 需要的 .txt 标签格式,同时会将转换后的数据集按照比例随机划分为训练集和验证集。
你需要修改以下几项:
\\
或者 /
防止转义XML 标签文件中目标框保存的格式是 [xmin, ymin, xmax, ymax] 四个变量,分别代表着未经归一化的左上角和右下角坐标。
YOLO 标签中目标框保存的格式是每一行代表一个目标框信息,每一行共包含 [label_id, x_center, y_center, w, h] 五个变量,分别代表着标签 ID,经过归一化后的中心点坐标和目标框宽高。
关于代码的分析可以参考:tensorRT模型性能测试
至此,数据集的准备工作完毕。
yolov7 预训练权重可以通过 here【pwd:yolo】下载,注意这是 yolov7-v0.1 版本的预训练权重,若后续有版本更新,记得替换。本次训练 VOC 数据集使用的预训练权重为 yolov7-tiny.pt
将准备好的数据集文件夹即 VOC 复制到 yolov7 项目环境中,将准备好的预训练权重 yolov7-tiny.pt 复制到 yolov7 项目环境中,完整的项目结构如下图所示。训练目标检测模型主要修改 cfg 文件夹下的模型配置文件 yolov7-tiny.ymal 以及 data 文件夹下的数据配置文件 coco.yaml
由于该项目使用的是 yolov7-tiny.pt 这个预训练权重,所以需要使用 cfg/training 目录下的 yolov7-tiny.yaml 这个文件(由于不同的预训练权重对应不同的网络结构,所以用错预训练权重会报错)。主要修改 yolov7-tiny.yaml 文件的第二行,即需要识别的类别数,由于这里识别 VOC 的 20 个类别,故修改为 20 即可,如下所示
修改 data 目录下相应的 yaml 文件,找到目录下的 coco.yaml 文件,主要修改如下:
在终端执行如下指令即可开始训练,参考自 yolov7 的 README.md/Training
python train.py --workers 8 --device 0 --batch-size 32 --data data/coco.yaml --img 640 640 --cfg cfg/training/yolov7-tiny.yaml --weights 'yolov7-tiny.pt' --name yolov7 --hyp data/hyp.scratch.p5.yaml --epochs 100
博主训练的模型为 p5 models 且使用的是单个 GPU 进行训练,显卡为 RTX3060,操作系统为 Ubuntu20.04,pytorch 版本为 1.12.0,训练时长大概 1 小时左右。训练的参数的指定和 yolov5 差不多,简要解释如下:
还有其它参数博主并未设置,如 –-multi-scale 多尺度训练等。大家一定要根据自己的实际情况(如显卡算力)指定不同的参数,如果你之前训练过 yolov5,那我相信这对你来说应该是小 case
训练完成后的模型权重保存在 run/train/weights 文件夹下,和 yolov5 不同的是它保存了多个权重文件,使用 best.pt 进行后续模型部署量化即可,这里提供博主训练好的权重文件下载链接 Baidu Drive【pwd:yolo】
由于后续我们要对模型进行 PTQ 量化,需要一些指标来衡量模型的性能,mAP 是一个重要的衡量指标。我们需要对比量化前后模型的 mAP,首先来看量化前原始 pytorch 模型的 mAP,测试的数据集直接选用验证集的 998 张图片。
我们将置信度阈值设置为 0.001,NMS 阈值设置为 0.65,方便与后续 PTQ 量化模型对比。
mAP 测试的指令如下:
python test.py --data data/coco.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device 0 --weights best.pt --name yolov7_640_val
测试完成后的结果会保存在 runs/test/yolov7_640_val 文件夹下,这里总结下原始 pytorch 模型的性能
Model | Size | mAPval 0.5:0.95 |
mAPval 0.5 |
Params (M) |
FLOPs (G) |
---|---|---|---|---|---|
YOLOv7-tiny | 640 | 0.491 | 0.744 | 5.8 | 13.3 |
由于博主手头没有合适的 Jetson 嵌入式设备,因此打算使用自己的主机完成 YOLOv7-PTQ 量化及部署工作,量化部署使用的 repo 是 tensorRT_Pro。
接下来我们主要是针对 tensorRT_Pro 项目中的 YOLOv7 完成 PTQ 模型的量化和部署,体现在 tensorRT_Pro 中其实就是 YOLOv7 的 INT8 量化,本次量化的模型是 YOLOv7-tiny.pt,数据集为 VOC,类别数为 20。
tensorRT_Pro 的代码可以直接从 GitHub 官网上下载,源码下载地址是 https://github.com/shouxieai/tensorRT_Pro,Linux 下代码克隆指令如下:
$ git clone https://github.com/shouxieai/tensorRT_Pro
也可手动点击下载,点击右上角的 Code
按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 Baidu Drive【pwd:yolo】 下载博主准备好的源代码(注意代码下载于 2023/9/24 日,若有改动请参考最新)
需要使用的软件环境有 TensorRT、CUDA、cuDNN、OpenCV、Protobuf,所有软件环境的安装可以参考 Ubuntu20.04部署YOLOv5,这里不再赘述,需要各位看官自行配置好相关环境,外网访问较慢,这里提供下博主安装过程中的软件安装包下载链接 Baidu Drive【pwd:yolo】
tensorRT_Pro 提供 CMakeLists.txt 和 Makefile 两种方式编译,二者选一即可
主要修改六处
1. 修改第 10 行,选择不支持 python (也可选择支持)
set(HAS_PYTHON OFF)
2. 修改第 18 行,修改 OpenCV 路径
set(OpenCV_DIR "/usr/local/include/opencv4/")
3. 修改第 20 行,修改 CUDA 路径
set(CUDA_TOOLKIT_ROOT_DIR "/usr/local/cuda-11.6")
4. 修改第 21 行,修改 cuDNN 路径
set(CUDNN_DIR "/usr/local/cudnn8.4.0.27-cuda11.6")
5. 修改第 22 行,修改 tensorRT 路径
set(TENSORRT_DIR "/opt/TensorRT-8.4.1.5")
6. 修改第 33 行,修改 protobuf 路径
set(PROTOBUF_DIR "/home/jarvis/protobuf")
完整的 CMakeLists.txt 的内容如下:
cmake_minimum_required(VERSION 2.6)
project(pro)
option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)
# 如果要支持python则设置python路径
set(HAS_PYTHON OFF) # ===== 修改 1 =====
set(PythonRoot "/datav/software/anaconda3")
set(PythonName "python3.9")
# 如果你是不同显卡,请设置为显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
#set(CUDA_GEN_CODE "-gencode=arch=compute_75,code=sm_75")
# 如果你的opencv找不到,可以自己指定目录
set(OpenCV_DIR "/usr/local/include/opencv4/") # ===== 修改 2 =====
set(CUDA_TOOLKIT_ROOT_DIR "/usr/local/cuda-11.6") # ===== 修改 3 =====
set(CUDNN_DIR "/usr/local/cudnn8.4.0.27-cuda11.6") # ===== 修改 4 =====
set(TENSORRT_DIR "/opt/TensorRT-8.4.1.5") # ===== 修改 5 =====
# set(CUDA_TOOLKIT_ROOT_DIR "/data/sxai/lean/cuda-10.2")
# set(CUDNN_DIR "/data/sxai/lean/cudnn7.6.5.32-cuda10.2")
# set(TENSORRT_DIR "/data/sxai/lean/TensorRT-7.0.0.11")
# set(CUDA_TOOLKIT_ROOT_DIR "/data/sxai/lean/cuda-11.1")
# set(CUDNN_DIR "/data/sxai/lean/cudnn8.2.2.26")
# set(TENSORRT_DIR "/data/sxai/lean/TensorRT-7.2.1.6")
# 因为protobuf,需要用特定版本,所以这里指定路径
set(PROTOBUF_DIR "/home/jarvis/protobuf") # ===== 修改 6 ======
find_package(CUDA REQUIRED)
find_package(OpenCV)
include_directories(
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/application
${PROJECT_SOURCE_DIR}/src/tensorRT
${PROJECT_SOURCE_DIR}/src/tensorRT/common
${OpenCV_INCLUDE_DIRS}
${CUDA_TOOLKIT_ROOT_DIR}/include
${PROTOBUF_DIR}/include
${TENSORRT_DIR}/include
${CUDNN_DIR}/include
)
# 切记,protobuf的lib目录一定要比tensorRT目录前面,因为tensorRTlib下带有protobuf的so文件
# 这可能带来错误
link_directories(
${PROTOBUF_DIR}/lib
${TENSORRT_DIR}/lib
${CUDA_TOOLKIT_ROOT_DIR}/lib64
${CUDNN_DIR}/lib
)
if("${HAS_PYTHON}" STREQUAL "ON")
message("Usage Python ${PythonRoot}")
include_directories(${PythonRoot}/include/${PythonName})
link_directories(${PythonRoot}/lib)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_PYTHON")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -std=c++11 -O0 -Xcompiler -fPIC -g -w ${CUDA_GEN_CODE}")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)
file(GLOB_RECURSE cuda_srcs ${PROJECT_SOURCE_DIR}/src/*.cu)
cuda_add_library(plugin_list SHARED ${cuda_srcs})
target_link_libraries(plugin_list nvinfer nvinfer_plugin)
target_link_libraries(plugin_list cuda cublas cudart cudnn)
target_link_libraries(plugin_list protobuf pthread)
target_link_libraries(plugin_list ${OpenCV_LIBS})
add_executable(pro ${cpp_srcs})
# 如果提示插件找不到,请使用dlopen(xxx.so, NOW)的方式手动加载可以解决插件找不到问题
target_link_libraries(pro nvinfer nvinfer_plugin)
target_link_libraries(pro cuda cublas cudart cudnn)
target_link_libraries(pro protobuf pthread plugin_list)
target_link_libraries(pro ${OpenCV_LIBS})
if("${HAS_PYTHON}" STREQUAL "ON")
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/example-python/pytrt)
add_library(pytrtc SHARED ${cpp_srcs})
target_link_libraries(pytrtc nvinfer nvinfer_plugin)
target_link_libraries(pytrtc cuda cublas cudart cudnn)
target_link_libraries(pytrtc protobuf pthread plugin_list)
target_link_libraries(pytrtc ${OpenCV_LIBS})
target_link_libraries(pytrtc "${PythonName}")
target_link_libraries(pro "${PythonName}")
endif()
add_custom_target(
yolo
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro yolo
)
add_custom_target(
yolo_gpuptr
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro yolo_gpuptr
)
add_custom_target(
yolo_fast
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro yolo_fast
)
add_custom_target(
centernet
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro centernet
)
add_custom_target(
alphapose
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro alphapose
)
add_custom_target(
retinaface
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro retinaface
)
add_custom_target(
dbface
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro dbface
)
add_custom_target(
arcface
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro arcface
)
add_custom_target(
bert
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro bert
)
add_custom_target(
fall
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro fall_recognize
)
add_custom_target(
scrfd
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro scrfd
)
add_custom_target(
lesson
DEPENDS pro
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspace
COMMAND ./pro lesson
)
add_custom_target(
pyscrfd
DEPENDS pytrtc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-python
COMMAND python test_scrfd.py
)
add_custom_target(
pyinstall
DEPENDS pytrtc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-python
COMMAND python setup.py install
)
add_custom_target(
pytorch
DEPENDS pytrtc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-python
COMMAND python test_torch.py
)
add_custom_target(
pyyolov5
DEPENDS pytrtc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-python
COMMAND python test_yolov5.py
)
add_custom_target(
pycenternet
DEPENDS pytrtc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-python
COMMAND python test_centernet.py
)
主要修改六处
1. 修改第 4 行,修改 protobuf 路径
lean_protobuf := /home/jarvis/protobuf
2. 修改第 5 行,修改 tensorRT 路径
lean_tensor_rt := /opt/TensorRT-8.4.1.5
3. 修改第 6 行,修改 cuDNN 路径
lean_cudnn := /usr/local/cudnn8.4.0.27-cuda11.6
4. 修改第 7 行,修改 OpenCV 路径
lean_opencv := /usr/local
5. 修改第 8 行,修改 CUDA 路径
lean_cuda := /usr/local/cuda-11.6
6. 修改第 9 行,选择不支持 python (也可选择支持)
use_python := false
完整的 Makefile 的内容如下:
cc := g++
nvcc = ${lean_cuda}/bin/nvcc
lean_protobuf := /home/jarvis/protobuf # ===== 修改 1 =====
lean_tensor_rt := /opt/TensorRT-8.4.1.5 # ===== 修改 2 =====
lean_cudnn := /usr/local/cudnn8.4.0.27-cuda11.6 # ===== 修改 3 =====
lean_opencv := /usr/local # ===== 修改 4 =====
lean_cuda := /usr/local/cuda-11.6 # ===== 修改 5 =====
use_python := false # ===== 修改 6 =====
python_root := /datav/software/anaconda3
# python_root指向的lib目录下有个libpython3.9.so,因此这里写python3.9
# 对于有些版本,so名字是libpython3.7m.so,你需要填写python3.7m
# /datav/software/anaconda3/lib/libpython3.9.so
python_name := python3.9
# 如果是其他显卡,请修改-gencode=arch=compute_75,code=sm_75为对应显卡的能力
# 显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
cuda_arch := # -gencode=arch=compute_75,code=sm_75
cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(cpp_srcs:.cpp=.cpp.o)
cpp_objs := $(cpp_objs:src/%=objs/%)
cpp_mk := $(cpp_objs:.cpp.o=.cpp.mk)
cu_srcs := $(shell find src -name "*.cu")
cu_objs := $(cu_srcs:.cu=.cu.o)
cu_objs := $(cu_objs:src/%=objs/%)
cu_mk := $(cu_objs:.cu.o=.cu.mk)
include_paths := src \
src/application \
src/tensorRT \
src/tensorRT/common \
$(lean_protobuf)/include \
$(lean_opencv)/include/opencv4 \
$(lean_tensor_rt)/include \
$(lean_cuda)/include \
$(lean_cudnn)/include
library_paths := $(lean_protobuf)/lib \
$(lean_opencv)/lib \
$(lean_tensor_rt)/lib \
$(lean_cuda)/lib64 \
$(lean_cudnn)/lib
link_librarys := opencv_core opencv_imgproc opencv_videoio opencv_imgcodecs \
nvinfer nvinfer_plugin \
cuda cublas cudart cudnn \
stdc++ protobuf dl
# HAS_PYTHON表示是否编译python支持
support_define :=
ifeq ($(use_python), true)
include_paths += $(python_root)/include/$(python_name)
library_paths += $(python_root)/lib
link_librarys += $(python_name)
support_define += -DHAS_PYTHON
endif
empty :=
export_path := $(subst $(empty) $(empty),:,$(library_paths))
run_paths := $(foreach item,$(library_paths),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))
cpp_compile_flags := -std=c++11 -g -w -O0 -fPIC -pthread -fopenmp $(support_define)
cu_compile_flags := -std=c++11 -g -w -O0 -Xcompiler "$(cpp_compile_flags)" $(cuda_arch) $(support_define)
link_flags := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'
cpp_compile_flags += $(include_paths)
cu_compile_flags += $(include_paths)
link_flags += $(library_paths) $(link_librarys) $(run_paths)
ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endif
pro : workspace/pro
pytrtc : example-python/pytrt/libpytrtc.so
expath : library_path.txt
library_path.txt :
@echo LD_LIBRARY_PATH=$(export_path):"$$"LD_LIBRARY_PATH > $@
workspace/pro : $(cpp_objs) $(cu_objs)
@echo Link $@
@mkdir -p $(dir $@)
@$(cc) $^ -o $@ $(link_flags)
example-python/pytrt/libpytrtc.so : $(cpp_objs) $(cu_objs)
@echo Link $@
@mkdir -p $(dir $@)
@$(cc) -shared $^ -o $@ $(link_flags)
objs/%.cpp.o : src/%.cpp
@echo Compile CXX $<
@mkdir -p $(dir $@)
@$(cc) -c $< -o $@ $(cpp_compile_flags)
objs/%.cu.o : src/%.cu
@echo Compile CUDA $<
@mkdir -p $(dir $@)
@$(nvcc) -c $< -o $@ $(cu_compile_flags)
objs/%.cpp.mk : src/%.cpp
@echo Compile depends CXX $<
@mkdir -p $(dir $@)
@$(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)
objs/%.cu.mk : src/%.cu
@echo Compile depends CUDA $<
@mkdir -p $(dir $@)
@$(nvcc) -M $< -MF $@ -MT $(@:.cu.mk=.cu.o) $(cu_compile_flags)
yolo : workspace/pro
@cd workspace && ./pro yolo
yolo_gpuptr : workspace/pro
@cd workspace && ./pro yolo_gpuptr
dyolo : workspace/pro
@cd workspace && ./pro dyolo
dunet : workspace/pro
@cd workspace && ./pro dunet
dmae : workspace/pro
@cd workspace && ./pro dmae
dclassifier : workspace/pro
@cd workspace && ./pro dclassifier
yolo_fast : workspace/pro
@cd workspace && ./pro yolo_fast
bert : workspace/pro
@cd workspace && ./pro bert
alphapose : workspace/pro
@cd workspace && ./pro alphapose
fall : workspace/pro
@cd workspace && ./pro fall_recognize
retinaface : workspace/pro
@cd workspace && ./pro retinaface
arcface : workspace/pro
@cd workspace && ./pro arcface
test_warpaffine : workspace/pro
@cd workspace && ./pro test_warpaffine
test_yolo_map : workspace/pro
@cd workspace && ./pro test_yolo_map
arcface_video : workspace/pro
@cd workspace && ./pro arcface_video
arcface_tracker : workspace/pro
@cd workspace && ./pro arcface_tracker
test_all : workspace/pro
@cd workspace && ./pro test_all
scrfd : workspace/pro
@cd workspace && ./pro scrfd
centernet : workspace/pro
@cd workspace && ./pro centernet
dbface : workspace/pro
@cd workspace && ./pro dbface
high_perf : workspace/pro
@cd workspace && ./pro high_perf
lesson : workspace/pro
@cd workspace && ./pro lesson
plugin : workspace/pro
@cd workspace && ./pro plugin
pytorch : pytrtc
@cd example-python && python test_torch.py
pyscrfd : pytrtc
@cd example-python && python test_scrfd.py
pyretinaface : pytrtc
@cd example-python && python test_retinaface.py
pycenternet : pytrtc
@cd example-python && python test_centernet.py
pyyolov5 : pytrtc
@cd example-python && python test_yolov5.py
pyyolov7 : pytrtc
@cd example-python && python test_yolov7.py
pyyolox : pytrtc
@cd example-python && python test_yolox.py
pyarcface : pytrtc
@cd example-python && python test_arcface.py
pyinstall : pytrtc
@cd example-python && python setup.py install
clean :
@rm -rf objs workspace/pro example-python/pytrt/libpytrtc.so example-python/build example-python/dist example-python/pytrt.egg-info example-python/pytrt/__pycache__
@rm -rf workspace/single_inference
@rm -rf workspace/scrfd_result workspace/retinaface_result
@rm -rf workspace/YoloV5_result workspace/YoloX_result
@rm -rf workspace/face/library_draw workspace/face/result
@rm -rf build
@rm -rf example-python/pytrt/libplugin_list.so
@rm -rf library_path.txt
.PHONY : clean yolo alphapose fall debug
# 导出符号,使得运行时能够链接上
export LD_LIBRARY_PATH:=$(export_path):$(LD_LIBRARY_PATH)
关于静态 batch 和动态 batch 有以下几点说明,更多细节请查看 YoloV8的动态静态batch如何理解和使用
静态batch
动态batch
静态 batch 的导出不需要修改任何内容,直接将训练好的 VOC 权重 best.pt 放在 yolov7 主目录下,在终端执行如下指令:
cd yolov7
python export.py --grid --weights=best.pt
执行完成后会在当前目录生成导出的 best.onnx 模型,用于后续量化部署。
动态 batch 的导出也不需要修改任何文件的内容,我们这次利用 onnxsim 第三方库来简化我们的 onnx 模型,首先确保你当前的环境中安装了 onnxsim,否则执行如下指令进行安装:
pip install onnxsim -i https://pypi.tuna.tsinghua.edu.cn/simple
然后将训练好的 VOC 权重 best.pt 放在 yolov7 主目录下,在终端执行如下指令:
cd yolov7
python export.py --dynamic-batch --grid --weights=best.pt
执行完成后会在当前目录生成导出的 best.onnx 模型,用于后续量化部署。
在开始PTQ 量化之前我们需要准备两个东西:模型和校准图片
模型我们采用动态 batch 导出的 best.onnx 模型,将它放在 tensorRT_Pro/workspace 文件夹下
校准图片我们从训练集随机选取 1000 张图片进行校准,将它也放在 tensorRT_Pro/workspace 文件夹下
1000 张校准数据集随机选取的代码如下:
import os
import random
import shutil
def random_copy_images(source_folder, destination_folder, num_images=1000):
# 确保目标文件夹存在
if not os.path.exists(destination_folder):
os.makedirs(destination_folder)
# 获取源文件夹中的所有图片文件
image_files = [file for file in os.listdir(source_folder) if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
# 随机选择1000张图片
selected_images = random.sample(image_files, min(num_images, len(image_files)))
# 复制选中的图片到目标文件夹
for image_file in selected_images:
source_path = os.path.join(source_folder, image_file)
destination_path = os.path.join(destination_folder, image_file)
shutil.copy(source_path, destination_path)
source_folder = '/home/jarvis/Learn/Datasets/VOC_PTQ/images/train' # 带有图片的文件夹路径
destination_folder = 'calib_data' # 目标文件夹路径
num_images = 1000 # 需要随机获取的图片数量
random_copy_images(source_folder, destination_folder, num_images)
你需要修改以下几项:
将上述模型和校准图片准备好后还要修改下源码,yolo 模型的推理代码主要在 src/application/app_yolo.cpp 文件中,我们就只需要修改这一个文件中的内容即可,源码修改较简单主要有以下几点:
具体修改如下:
test(Yolo::Type::V7, TRT::Mode::INT8, "best") // 修改1 177行"yolov7"改成"best"
static const char *voclabels[] = {"aeroplane", "bicycle", "bird", "boat", "bottle",
"bus", "car", "cat", "chair", "cow",
"diningtable", "dog", "horse", "motorbike", "person",
"pottedplant", "sheep", "sofa", "train", "tvmonitor"}; // 修改2 25行新增代码,为自训练模型的类别名称
for(auto& obj : boxes){
...
auto name = voclabels[obj.class_label]; // 修改3 100行cocolabels修改为voclabels
...
}
TRT::compile(
mode, // FP32、FP16、INT8
test_batch_size, // max batch size
onnx_file, // source
model_file, // save to
{},
int8process,
"calib_data" // 修改4 149行 "inference" 修改为 "calib_data"
);
OK!源码修改好了,Makefile 编译文件也搞定了,可以编译运行了,直接在终端执行如下指令即可:
make yolo
图解如下所示:
编译运行后在 workspace 文件夹下会生成 INT8 的 engine 模型 best.INT8.trtmodel 用于模型推理,同时它还会生成 best_Yolov5_INT8_result 文件夹,该文件夹下保存了推理的图片
模型推理效果如下图所示:
我们再来测试下经过 PTQ 量化后模型的 mAP,tensorRT_Pro 中已经提供了对应 mAP 测试的代码,在 src/application/test_yolo_map.cpp 文件中,我们就只需要修改这一个文件中的内容即可,源码修改较简单主要有以下几点:
修改后完整的 test_yolo_map.cpp 如下所示:
#include
#include
#include
#include
#include "app_yolo/yolo.hpp"
#include
#include
using namespace std;
bool requires(const char* name);
struct BoxLabel{
int label;
float cx, cy, width, height;
float confidence;
};
struct ImageItem{
string image_file;
Yolo::BoxArray detections;
};
vector<ImageItem> scan_dataset(const string& images_root){
vector<ImageItem> output;
auto image_files = iLogger::find_files(images_root, "*.jpg");
for(int i = 0; i < image_files.size(); ++i){
auto& image_file = image_files[i];
if(!iLogger::exists(image_file)){
INFOW("Not found: %s", image_file.c_str());
continue;
}
ImageItem item;
item.image_file = image_file;
output.emplace_back(item);
}
return output;
}
static void inference(vector<ImageItem>& images, int deviceid, const string& engine_file, TRT::Mode mode, Yolo::Type type, const string& model_name){
auto engine = Yolo::create_infer(
engine_file, type, deviceid, 0.001f, 0.65f,
Yolo::NMSMethod::CPU, 10000
);
if(engine == nullptr){
INFOE("Engine is nullptr");
return;
}
int nimages = images.size();
vector<shared_future<Yolo::BoxArray>> image_results(nimages);
for(int i = 0; i < nimages; ++i){
if(i % 100 == 0){
INFO("Commit %d / %d", i+1, nimages);
}
image_results[i] = engine->commit(cv::imread(images[i].image_file));
}
for(int i = 0; i < nimages; ++i)
images[i].detections = image_results[i].get();
}
void detect_images(vector<ImageItem>& images, Yolo::Type type, TRT::Mode mode, const string& model){
int deviceid = 0;
auto mode_name = TRT::mode_string(mode);
TRT::set_device(deviceid);
auto int8process = [=](int current, int count, const vector<string>& files, shared_ptr<TRT::Tensor>& tensor){
INFO("Int8 %d / %d", current, count);
for(int i = 0; i < files.size(); ++i){
auto image = cv::imread(files[i]);
Yolo::image_to_tensor(image, tensor, type, i);
}
};
const char* name = model.c_str();
INFO("===================== test %s %s %s ==================================", Yolo::type_name(type), mode_name, name);
if(not requires(name))
return;
string onnx_file = iLogger::format("%s.onnx", name);
string model_file = iLogger::format("%s.%s.trtmodel", name, mode_name);
int test_batch_size = 16;
if(not iLogger::exists(model_file)){
TRT::compile(
mode, // FP32、FP16、INT8
test_batch_size, // max batch size
onnx_file, // source
model_file, // save to
{},
int8process,
"inference"
);
}
inference(images, deviceid, model_file, mode, type, name);
}
bool save_to_json(const vector<ImageItem>& images, const string& file){
Json::Value predictions(Json::arrayValue);
for(int i = 0; i < images.size(); ++i){
auto& image = images[i];
auto file_name = iLogger::file_name(image.image_file, false);
string image_id = file_name;
auto& boxes = image.detections;
for(auto& box : boxes){
Json::Value jitem;
jitem["image_id"] = image_id;
jitem["category_id"] = box.class_label;
jitem["score"] = box.confidence;
auto& bbox = jitem["bbox"];
bbox.append(box.left);
bbox.append(box.top);
bbox.append(box.right - box.left);
bbox.append(box.bottom - box.top);
predictions.append(jitem);
}
}
return iLogger::save_file(file, predictions.toStyledString());
}
int test_yolo_map(){
/*
结论:
1. YoloV5在tensorRT下和pytorch下,只要输入一样,输出的差距最大值是1e-3
2. YoloV5-6.0的mAP,官方代码跑下来是[email protected]:.95 = 0.367, [email protected] = 0.554,与官方声称的有差距
3. 这里的tensorRT版本测试的精度为:[email protected]:.95 = 0.357, [email protected] = 0.539,与pytorch结果有差距
4. cv2.imread与cv::imread,在操作jpeg图像时,在我这里测试读出的图像值不同,最大差距有19。而png图像不会有这个问题
若想完全一致,请用png图像
5. 预处理部分,若采用letterbox的方式做预处理,由于tensorRT这里是固定640x640大小,测试采用letterbox并把多余部分
设置为0. 其推理结果与pytorch相近,但是依旧有差别
6. 采用warpAffine和letterbox两种方式的预处理结果,在mAP上没有太大变化(小数点后三位差)
7. mAP差一个点的原因可能在固定分辨率这件事上,还有是pytorch实现的所有细节并非完全加入进来。这些细节可能有没有
找到的部分
*/
auto images = scan_dataset("/home/jarvis/Learn/Datasets/VOC_PTQ/images/val");
INFO("images.size = %d", images.size());
string model = "best";
detect_images(images, Yolo::Type::V7, TRT::Mode::INT8, model);
save_to_json(images, model + ".prediction.json");
return 0;
}
上述代码会将 INT8 模型在验证集中所有图像的检测结果存储到一个 JSON 文件中,每个检测到的物体都被序列化为 JSON 格式信息,包括图像 ID、类别 ID、置信度和边界框坐标。后续我们就可以拿着这个预测结果的 JSON 文件和我们真实标签的 JSON 文件通过 COCO Python API 去计算 mAP 指标。
有以下几点需要注意:
将源码修改好后,直接在终端执行如下指令即可:
make test_yolo_map
图解如下所示:
运行成功后在 workspace 文件夹下会生成 best.prediction.json 文件,该 JSON 文件中保存着 INT8 模型在验证集上的推理结果。
我们拿到了模型预测结果的 JSON 文件后,还需要拿到真实标签的 JSON 文件,但是现在我们只有验证集真实的 YOLO 标签文件,因此需要将 YOLO 标签转换为 JSON 文件,转换代码如下:(from chatGPT)
import os
import cv2
import json
import logging
import os.path as osp
from tqdm import tqdm
from functools import partial
from multiprocessing import Pool, cpu_count
def set_logging(name=None):
rank = int(os.getenv('RANK', -1))
logging.basicConfig(format="%(message)s", level=logging.INFO if (rank in (-1, 0)) else logging.WARNING)
return logging.getLogger(name)
LOGGER = set_logging(__name__)
def process_img(image_filename, data_path, label_path):
# Open the image file to get its size
image_path = os.path.join(data_path, image_filename)
img = cv2.imread(image_path)
height, width = img.shape[:2]
# Open the corresponding label file
label_file = os.path.join(label_path, os.path.splitext(image_filename)[0] + ".txt")
with open(label_file, "r") as file:
lines = file.readlines()
# Process the labels
labels = []
for line in lines:
category, x, y, w, h = map(float, line.strip().split())
labels.append((category, x, y, w, h))
return image_filename, {"shape": (height, width), "labels": labels}
def get_img_info(data_path, label_path):
LOGGER.info(f"Get img info")
image_filenames = os.listdir(data_path)
with Pool(cpu_count()) as p:
results = list(tqdm(p.imap(partial(process_img, data_path=data_path, label_path=label_path), image_filenames), total=len(image_filenames)))
img_info = {image_filename: info for image_filename, info in results}
return img_info
def generate_coco_format_labels(img_info, class_names, save_path):
# for evaluation with pycocotools
dataset = {"categories": [], "annotations": [], "images": []}
for i, class_name in enumerate(class_names):
dataset["categories"].append(
{"id": i, "name": class_name, "supercategory": ""}
)
ann_id = 0
LOGGER.info(f"Convert to COCO format")
for i, (img_path, info) in enumerate(tqdm(img_info.items())):
labels = info["labels"] if info["labels"] else []
img_id = osp.splitext(osp.basename(img_path))[0]
img_h, img_w = info["shape"]
dataset["images"].append(
{
"file_name": os.path.basename(img_path),
"id": img_id,
"width": img_w,
"height": img_h,
}
)
if labels:
for label in labels:
c, x, y, w, h = label[:5]
# convert x,y,w,h to x1,y1,x2,y2
x1 = (x - w / 2) * img_w
y1 = (y - h / 2) * img_h
x2 = (x + w / 2) * img_w
y2 = (y + h / 2) * img_h
# cls_id starts from 0
cls_id = int(c)
w = max(0, x2 - x1)
h = max(0, y2 - y1)
dataset["annotations"].append(
{
"area": h * w,
"bbox": [x1, y1, w, h],
"category_id": cls_id,
"id": ann_id,
"image_id": img_id,
"iscrowd": 0,
# mask
"segmentation": [],
}
)
ann_id += 1
with open(save_path, "w") as f:
json.dump(dataset, f)
LOGGER.info(
f"Convert to COCO format finished. Resutls saved in {save_path}"
)
if __name__ == "__main__":
# Define the paths
data_path = "/home/jarvis/Learn/Datasets/VOC_PTQ/images/val"
label_path = "/home/jarvis/Learn/Datasets/VOC_PTQ/labels/val"
class_names = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus",
"car", "cat", "chair", "cow", "diningtable", "dog", "horse",
"motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] # 类别名称请务必与 YOLO 格式的标签对应
save_path = "./val.json"
img_info = get_img_info(data_path, label_path)
generate_coco_format_labels(img_info, class_names, save_path)
上述代码的功能是将 YOLO 格式的数据集(包括图像文件和对应的 .txt 标签文件)转换成 COCO JSON 格式的标注。转换后的数据包括一个 JSON 标签文件,JSON 标签文件中包含了每个图像的所有物体的类别和边界框信息。
你需要修改以下几项:
\\
或者 /
防止转义YOLO 标签中目标框保存的格式是每一行代表一个目标框信息,每一行共包含 [label_id, x_center, y_center, w, h] 五个变量,分别代表着标签 ID,经过归一化后的中心点坐标和目标框宽高。
JSON 文件中目标框保存的格式是 [x,y,w,h] 四个变量,分别代表着经过归一化的左上角坐标和目标框宽高。
关于代码的分析可以参考:tensorRT模型性能测试
至此,两个 JSON 文件都准备好了,一个是模型推理的预测结果,一个是真实结果。拿到两个 JSON 文件后我们就可以进行 mAP 测试了,具体代码如下:
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
# Run COCO mAP evaluation
# Reference: https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
annotations_path = "val.json"
results_file = "best.prediction.json"
cocoGt = COCO(annotation_file=annotations_path)
cocoDt = cocoGt.loadRes(results_file)
imgIds = sorted(cocoGt.getImgIds())
cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
cocoEval.params.imgIds = imgIds
cocoEval.evaluate()
cocoEval.accumulate()
cocoEval.summarize()
你需要修改以下几项:
执行后测试结果如下图所示:
我们将它与原始 pytorch 的模型放在一起进行对比下:
Model | Size | mAPval 0.5:0.95 |
mAPval 0.5 |
Params (M) |
FLOPs (G) |
---|---|---|---|---|---|
YOLOv7-tiny | 640 | 0.491 | 0.744 | 5.8 | 13.3 |
YOLOv7-tiny-INT8 | 640 | 0.346 | 0.562 | - | - |
可以看到相比于原始 pytorch 模型,PTQ 量化后的模型 mAP 下降了近 18 个点
那博主之前有测试过 YOLOv5 的 PTQ 量化,其 mAP 也就下降了近 6 个点,YOLOv7 的 PTQ 量化模型精度损失未免太严重了呀,是什么原因导致的呢?
经博主测试发现是由于校准图片数量的原因,1000 张校准图片能将 YOLOv5 量化得很好,但这并不适用于 YOLOv7,YOLOv7 的校准图片选取 10 张的校准结果也比 1000 张要好,具体细节可以查看 4.1 小节
OK!至此 YOLOv7 模型的 PTQ 量化到这里结束了,各位看官可以在自己的数据集测试下 PTQ 量化后模型的性能。
那可能有不少看官好奇为什么校准图片选择 1000 张呢?是由什么来决定的呢?
这小节我们就来看看校准图片数量对 PTQ 量化模型的影响,博主测试了在不同校准图片下量化的 PTQ 模型在同一个验证集上的 mAP,分别在训练集随机挑选了 10、50、100、500、600、700、800、900、1000、4013 张图片,其中 4013 张图片是整个训练集的数量。
测试结果如下表所示:
Model | Calib Data | mAPval 0.5:0.95 |
mAPval 0.5 |
---|---|---|---|
YOLOv7-tiny-INT8 | 10 | 0.399 | 0.621 |
YOLOv7-tiny-INT8 | 50 | 0.464 | 0.699 |
YOLOv7-tiny-INT8 | 100 | 0.469 | 0.703 |
YOLOv7-tiny-INT8 | 500 | 0.471 | 0.703 |
YOLOv7-tiny-INT8 | 600 | 0.345 | 0.559 |
YOLOv7-tiny-INT8 | 700 | 0.471 | 0.703 |
YOLOv7-tiny-INT8 | 800 | 0.473 | 0.705 |
YOLOv7-tiny-INT8 | 900 | 0.347 | 0.562 |
YOLOv7-tiny-INT8 | 1000 | 0.346 | 0.562 |
YOLOv7-tiny-INT8 | 4013(all) | 0.344 | 0.557 |
可视化图如下所示:
从表中的数据我们可以分析得到下面的一些结论:
1. 校准数据量与模型性能的关系:校准数据的数量对模型 PTQ 量化后的性能有明显的影响。特别是当校准数据从 10 增加到 500 时,模型的 mAP 明显增加,说明在这个区间内,增加校准数据可以有效提高模型的性能。
2. 最佳校准数据量:在这个测试中,当使用 800 张校准图片时,模型达到了最高点的 mAP(分别为 0.473 和 0.705)。这意味着并不是校准数据越多越好,需要找到一个适当的平衡点。
3. 校准数据过多可能导致性能下降:当校准数据从 800 增加到 900、100 或 4013 时,模型的性能反而有所下降。这可能是因为过多的校准数据可能引入了噪声,使得量化的过程过于复杂,从而降低了模型的性能。
4. 整个训练集并非最佳选择:尽管使用整个训练集(4013 张图片)进行校准可能看起来是一个直观的选择,但在这个测试中,它并没有提供最佳的性能。这可能意味着在实际应用中,只需要选择一个子集进行校准即可,无需使用整个训练集。
5. 初步校准数据的不足:当仅使用 10 张校准图片时,模型的性能也较低。这说明在实际应用中,如果只有有限的校准数据,可能需要考虑采集更多的数据以提高量化后的模型性能。
综上所述,选择合适的校准数据量是 PTQ 量化的一个重要步骤。不同的模型和应用场景可能需要不同的校准数据量。因此,为了得到最佳的量化性能,可能需要进行多次实验来确定最佳的校准数据量。
博主一般推荐校准图片的数量在 500~1000 张即可,没必要太多,当然也不能太少。
PTQ 量化的模型性能到底怎么样呢?与其它精度的模型相比有哪些优势又有哪些劣势呢?
这个小节我们就来看看不同精度的模型的性能对比,主要从 mAP 和速度两个方面衡量。博主测试了在同一个验证集上原始 pytorch 模型,FP32 模型,FP16 模型,INT8 模型的性能。
原始 pytorch 模型和 INT8 模型性能我们之前已经了解过了,下面我们来看看 FP32 模型和 FP16 模型的性能。
FP32模型:
FP16模型:
INT8模型:
值得注意的是,关于速度的测试我们之前似乎并没有提到,它具体是如何测试的呢?
其实在 inference_and_performance 函数中就有关于速度相关的测试,主要说明如下:
测试结果如下表所示:
Model | Precision | mAPval 0.5:0.95 |
mAPval 0.5 |
Elapsed Time/ms | FPS |
---|---|---|---|---|---|
YOLOv7-tiny.pt | - | 0.491 | 0.744 | - | - |
YOLOv7-tiny-FP32 | FP32 | 0.488 | 0.724 | 2.82 | 355.15 |
YOLOv7-tiny-FP16 | FP16 | 0.489 | 0.725 | 1.26 | 792.23 |
YOLOv7-tiny-INT8 | INT8 | 0.473 | 0.705 | 0.94 | 1066.55 |
可视化图如下所示:
从表中的数据我们可以分析得到下面的一些结论:
1. 精度与模型性能的关系
2. 速度与模型性能的关系
3. 权衡速度与精度
综上所述,在实际应用中,需要根据具体的需求权衡速度和精度。例如,对于实时应用,可能会选择 FP16 或 INT8 以获得更高的速度,尽管可能牺牲一些精度。而对于需要高精度的应用,可能会选择 FP32。
博主对比了同一张图片在不同精度模型下的推理效果,如下所示,让大家有个更直观的感受。
最后我们当然是来对比下 YOLOv5-PTQ 量化后模型的性能和 YOLOv7-PTQ 量化后模型的性能哪个会更好,那其实两个模型训练用的数据集都是同一个啦,所以还是有可比性的
结果对比如下表所示:
Model | Precision | mAPval 0.5:0.95 |
mAPval 0.5 |
Elapsed Time/ms | FPS |
---|---|---|---|---|---|
YOLOv5s.pt | - | 0.471 | 0.711 | - | - |
YOLOv7-tiny.pt | - | 0.491 | 0.744 | - | - |
YOLOv5s-FP32 | FP32 | 0.447 | 0.684 | 3.15 | 317.79 |
YOLOv7-tiny-FP32 | FP32 | 0.488 | 0.724 | 2.82 | 355.15 |
YOLOv5s-FP16 | FP16 | 0.448 | 0.683 | 1.34 | 748.93 |
YOLOv7-tiny-FP16 | FP16 | 0.489 | 0.725 | 1.26 | 792.23 |
YOLOv5s-INT8 | INT8 | 0.409 | 0.657 | 0.99 | 1008.93 |
YOLOv7-tiny-INT8 | INT8 | 0.473 | 0.705 | 0.94 | 1066.55 |
可视化图如下:
从表中我们可以看到对博主当前的 VOC 数据集而言,YOLOv7-tiny 模型似乎碾压 YOLOv5s 模型呀,不论是 pytorch 模型的效果,还是 PTQ 量化后模型的推理速度和效果,YOLOv7-tiny 都比 YOLOv5s 要优秀不少
当然也不排除 YOLOv5s 训练过程中并没有完全收敛得到最佳的性能,因为博主只训练了 100 个 epoch,那具体的对比结果各位看官可以自行测试,博主这边只是简单分析下。
OK!YOLOv7-PTQ 量化的内容到这里就结束了,各位看官可以自行测试。
本篇博客介绍了关于 yolov7 的 PTQ 量化以及部署流程,博主在这里只做了最基础的演示,如果有更多的需求需要各位看官自己去挖掘啦。下篇文章我们将会分享关于 yolov7 的 QAT 量化以及部署流程,感谢各位看到最后,创作不易,读后有收获的看官帮忙点个⭐️
软件安装包下载链接【提取码:yolo】
源代码、权重、数据集下载链接【提取码:yolo】