最新文章
-
pip install
是什么 pip install 是python包管理器,用于python软件包的下载、安装、卸载等功能。 怎么用 在线安装 pip install 软件包名 pip install 软件包名==版本号 例如pip install requests,或pip install requests==1.1。 也可以从文件列表中获取安装 pip install -r requirements.txt 从requirements.txt文件安装依赖,通常用于项目的依赖管理。 pip的软件包一般有两种格式: whl (Wheel) 格式: 文件是一种预编译的Python包格式,类似于Windows的.exe安装文件,但专门用于Python。 tar.gz:包含了Python包的源代码,需要先解压,然后pip会根据其中的setup.py文件进行编译和安装。 whl文件是pip推荐的安装包格式,因为它更快,而.tar.gz文件则用于源代码分发和离线安装。 torchvision-0.17.1-cp311-cp311-macosx_10_13_x86_64.whl 这个命名规则是什么?第一个cp311是编译是python版本为3.11,第二个cp311表示ABI(应用二进制接口)兼容 Python 3.1,确保与 Python 3.11 环境完全适;操作系统架构为macos 10.13以上,x86_64 intel/AMD 64位。 离线升级 pip install --no-index --find-links=./offline_packages -r requirements.txt no-index:表示不从网上获取安装。 find-links:选择本地包的路径 r:下载所有依赖,可省略。 获取软件包可以通过u盘或者下载的方式,看看怎么下载。 pip download -d ./offline_packages -r requirements.txt 升级 pip install --upgrade 软件包名 或简写方式: pip install -U 软件包 用于升级软件包名称。包括升级pip。 软件包源 查看源 pip config list 安装软件时不指定源就会默认从当前的源获取,对应的配置文件路径:~/.config/pip/pip.conf 设置源 pip config set global.index-url <源地址> 示例:pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 永久设置源,将会写到配置文件。 pip config unset global.index-url 删除全局配置的源。 指定源 pip install xxx -i https:xxx 或者pip install xxx --index-url https:xxx 也可以从github中获取 pip install git+<仓库地址> 常用源 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:https://mirrors.aliyun.com/pypi/simple/ 中国科学技术大学:https://pypi.mirrors.ustc.edu.cn/simple/ 卸载 pip uninstall 软件包名 卸载对应的软件包。 查看 pip --version 查看pip的版本。 pip list 列出安装了那些包 -
ONNX Runtime Python端侧模型部署YOLOv5
ONNX Runtime介绍 ONNX Runtime不依赖于Pytorch、tensorflow等机器学习训练模型框架。他提供了一种简单的方法,可以在CPU、GPU、NPU上运行模型。通常ONNX Runtime用于端侧设备模型的运行推理。要使用ONNX Runtime运行模型,一般的步骤如下: 用你最喜欢的框架(如pytorch、tensorflow、paddle等)训练一个模型。 将模型转换或导出为ONNX格式。 在端侧使用ONNX Runtime加载并运行模型。 模型的训练和导出为ONNX格式这里就不再阐述了。下面基于python在端侧运行模型的示例: import numpy # 导入numpy模块 import onnxruntime as rt # 导入onnxruntime模块 sess = rt.InferenceSession( "logreg_iris.onnx", providers=rt.get_available_providers()) # 加载模型logreg_iris.onnx input_name = sess.get_inputs()[0].name # 获取模型的输入名称,对应的是使用https://netron.app/中intput name。 pred_onx = sess.run(None, {input_name: X_test.astype(numpy.float32)})[0] # 运行模型推理,返回结果到pred_onx中 print(pred_onx) 上面给出的python示例中,端侧运行模型可以总结为2个步骤。加载模型,模型推理。 加载模型 class onnxruntime.InferenceSession( path_or_bytes: str | bytes | os.PathLike, sess_options: onnxruntime.SessionOptions | None = None, providers: Sequence[str | tuple[str, dict[Any, Any]]] | None = None, provider_options: Sequence[dict[Any, Any]] | None = None, **kwargs) path_or_bytes: 模型文件名或者ONNX、ORT格式二进制。 sess_options: 会话选项,比如配置线程数、优先级。 providers: 指定执行提供者优先级(['CUDAExecutionProvider','CPUExecutionProvider']) provider_options: 字典序列,为每个提供者配置专属参数(如CUDA设备ID) options = onnxruntime.SessionOptions() options.SetIntraOpNumThreads(4) # 多设备优先级配置 session = InferenceSession( "model.onnx", sess_options=options, providers=[ ('CUDAExecutionProvider', {'device_id': 0}), 'CPUExecutionProvider' ] ) 模型推理 outputs = senssion.run(output_names, input_feed, run_options=None) output_names:输出节点名称,字符串列表,指定需要获取的输出节点名称,若为None则返回所有输出 input_feed:输入数据,字典类型,结构为{"输入节点名": numpy数组/ORTValue},建议使用ORTValue封装输入数据以减少CPU-GPU拷贝开销。 run_options:运行参数,如日志级别。 import numpy as np import onnxruntime as ort # 创建示例数据 cpu_data = np.random.rand(1, 3, 224, 224).astype(np.float32) # 转换为GPU上的ORTValue gpu_ort_value = ort.OrtValue.ortvalue_from_numpy( cpu_data, device_type='cuda', # 关键参数:指定GPU设备 device_id=0 # GPU设备ID(多卡时指定) ) print(gpu_ort_value.device_name()) # 输出: 'Cuda' results = session.run( ["output_name"], {"input_name": gpu_ort_value} # 避免CPU->GPU拷贝 ) 在运行模型是,需要获取模型的输入和输出名称,可以通过调用对应的函数session.get_inputs(),session.get_outputs()来获取。inputs和outputs函数返回的是onnxruntime.NodeArg类,该类是ONNX Runtime中表示计算图节点输入/输出参数的核心类,该类有3个成员变量,如下: property name: 参数唯一标识符,对应计算图中的节点名称。 property shape: 张量形状。 property type:数据类型(如tensor(float32)/tensor(int64)) 以下是获取输入名称和输出名称的示例。 input_name = session.get_inputs()[0].name output_names = [output.name for output in session.get_outputs()] 详细请参考:https://onnxruntime.ai/docs/api/python/api_summary.html YOLOv5运行示例 加载模型 session_options = ort.SessionOptions() session_options.intra_op_num_threads = 1 # 加载 ONNX 模型 session = ort.InferenceSession( "yolov5_n.q.onnx", sess_options=session_options, providers=["XXXExecutionProvider"]) 创建SessionOptions对象用于定制化会话行为,限制算子内部并行线程数为1,加载名为yolov5_n.q.onnx的量化版YOLOv5模型,指定自定义执行提供者XXXExecutionProvider。 图像预处理 image = cv2.imread(args.image) #image shape (375, 500, 3) image_shape = image.shape[:2] #image_shape的值(375, 500),取前面2个值为图像的宽高 # 获取图像的尺寸大小高和宽。 input_tensor = preprocess(image) # 图像预处理函数 def preprocess(image, input_size=(640, 640)): # 调整图像大小为640*640, image = cv2.resize(image, input_size) # 转换颜色空间RGB image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 归一化处理 #astype(np.float32)将图像像素值从整数类型(如uint8)转换为32位浮点数, #避免后续除法运算的精度损失,/ 255.0将像素值从[0,255]的原始范围线性映射到[0,1]区间, #符合神经网络输入的典型数值范围要求 image = image.astype(np.float32) / 255.0 # 转置模型为CHW格式,原输入是HWC格式。 # 输入的数据是(640,640,3),需要调整为NCHW模型格式 [batch, channel, height, width] # 使用np.transpose进行转置,变换成(3,640,640) image = np.transpose(image, (2, 0, 1)) image = np.expand_dims(image, axis=0) # 接着再加上一个轴变化成(1,3,640,640)tensor。 return image 如何理解深度学习中的轴了? 在深度学习中,轴可以理解为维度。如上图是一个NCHW排布的格式,把N当成第一个维度即位轴0,C第二维度即为轴1,H第三维度即为轴2,W为第四维度即为轴3。np.expand_dims(image, axis=0)即拓展了轴0,原来只有3个维度现在变成4个维度了,N为1。还可以按照指定的轴进行求和,即做压缩。执行np.sum(data, axis=0)时,也就是沿着N的维度就行压缩求和,就变成如上图。由原来的(N,C,W,H)变成了(C',W',H'),即N个CWH中的各自相加。如果是np.sum(data,axis=1),那就是按照C维度方向进行相加,结果就是(N,W,H),即如RGB格式就是每个图像RGB 3通道的像素相加,如下图所示。 模型推理 模型推理前,需要获取计算图输入和输入的名称 input_name = session.get_inputs()[0].name output_names = [output.name for output in session.get_outputs()] print('input name', input_name) print('output name', output_names) 输出结果与下图对应。 input name input output name ['dets', 'labels'] 获取到intput_name和ouput_names后,即可调用运行推理。 outputs = session.run(output_names, {input_name: input_tensor}) 模型后处理 # 把batch这个维度去掉 dets = outputs[0].squeeze() labels_pred = outputs[1].squeeze() #将坐标进行缩放以适应实际图片的大小。 input_size = (640, 640) scale_x = image_shape[1] / input_size[0] scale_y = image_shape[0] / input_size[1] dets[:, 0] *= scale_x dets[:, 1] *= scale_y dets[:, 2] *= scale_x dets[:, 3] *= scale_y 模型outputs有两个输出,一个是dets,这是一个二位数组dets[n][5],其中det[5]包含了坐标x1, y1, x2, y2,score,前面4个预选框的坐标,后面一个为预选框的分数。 def visualize_results(image, dets, labels_pred, labels, conf_threshold): for i in range(len(dets)): det = dets[i] score = det[4] #每个框的分数 if score > conf_threshold: #小于分数的剔除 class_id = int(labels_pred[i]) x1, y1, x2, y2 = map(int, det[:4]) label = labels[class_id] cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(image, f'{label}: {score:.2f}', (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) return image 根据阈值分数进行画框,最终完成结果的后处理,注意上面并没有进行极大值抑制。 -
密码保护:端侧vscode AI开发环境搭建
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
llama.cpp部署大模型
安装llama.cpp 从GitHub上下载官方的源码。 git clone https://github.com/ggml-org/llama.cpp.git cd llama.cpp 使用camke进行编译,先创建build环境 cmake -B build 发现有报错curl没有安装。 -- The C compiler identification is GNU 11.3.0 -- The CXX compiler identification is GNU 11.3.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Found Git: /usr/bin/git (found version "2.34.1") -- Looking for pthread.h -- Looking for pthread.h - found -- Performing Test CMAKE_HAVE_LIBC_PTHREAD -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success -- Found Threads: TRUE -- Warning: ccache not found - consider installing it for faster compilation or disable this warning with GGML_CCACHE=OFF -- CMAKE_SYSTEM_PROCESSOR: x86_64 -- GGML_SYSTEM_ARCH: x86 -- Including CPU backend -- Found OpenMP_C: -fopenmp (found version "4.5") -- Found OpenMP_CXX: -fopenmp (found version "4.5") -- Found OpenMP: TRUE (found version "4.5") -- x86 detected -- Adding CPU backend variant ggml-cpu: -march=native -- Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR) CMake Error at common/CMakeLists.txt:85 (message): Could NOT find CURL. Hint: to disable this feature, set -DLLAMA_CURL=OFF 使用apt-get安装libcur14,如下。 sudo apt-get update sudo apt-get install libcurl4-openssl-dev 安装curl成功后,解决了,继续执行cmake -B build,会生成build目录。 cmake -B build -- Warning: ccache not found - consider installing it for faster compilation or disable this warning with GGML_CCACHE=OFF -- CMAKE_SYSTEM_PROCESSOR: x86_64 -- GGML_SYSTEM_ARCH: x86 -- Including CPU backend -- x86 detected -- Adding CPU backend variant ggml-cpu: -march=native -- Found CURL: /usr/lib/x86_64-linux-gnu/libcurl.so (found version "7.81.0") -- Configuring done -- Generating done -- Build files have been written to: /root/autodl-tmp/llama.cpp/build 接着llama.cpp的源码。 cmake --build build --config Release 编译完成之后,生成的二进制都在llama.cpp/build/bin目录下。 模型下载 使用wget下载模型。 wget https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q8_0.gguf llamap.cpp只能使用GGUF格式的大模型,使用的模型可以在Hugging Face获取https://huggingface.co/。也可以在modelscope上获取https://modelscope.cn/models。 这里有个技巧,可能仓库里面有很多量化参数的模型,如果使用git全部clone下来会比较久,这里可以只下载指定的GGUF模型,点击要使用的模型,如下: 然后,获取到下面的下载链接。 如果是modelsscope,找到下载,然后鼠标长按左键不松手拖到上面的输入网址框获取到下载链接。 这样就可以使用wget进行下载了。 wget https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q8_0.gguf wget https://modelscope.cn/models/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/master/qwen2.5-3b-instruct-q8_0.gguf 模型测试 运行大模型 ./llama.cpp/build/bin/llama-cli -m model/Llama-3.2-3B-Instruct-Q8_0.gguf 运行日志如下,可以看到使用的是CPU,没有使用GPU,因为前面编译的时候没有使能CUDA。 llama_perf_sampler_print: sampling time = 8.06 ms / 80 runs ( 0.10 ms per token, 9920.63 tokens per second) llama_perf_context_print: load time = 1070.39 ms llama_perf_context_print: prompt eval time = 859.42 ms / 15 tokens ( 57.29 ms per token, 17.45 tokens per second) llama_perf_context_print: eval time = 20880.31 ms / 65 runs ( 321.24 ms per token, 3.11 tokens per second) llama_perf_context_print: total time = 37979.41 ms / 80 tokens load time: 模型加载时间,耗时1070.39ms,属于一次性开销,与模型大小和硬件I/O性能相关。 prompt eaval time: 有些也称为prefill(TPS),表示提示词处理时间,处理15个输入Token耗时859.42ms,平均57.29ms/Token,速度17.45 Token/s。 eval time:有些也称为decode (TPS), 表示生成推理时间,生成65个Token耗时20880.31ms,平均321.24ms/Token,速度仅3.11 Token/s,显著低于采样阶段的9920.63 Token/s,说明生成阶段存在计算瓶颈。 sampling time: 采样80次仅8.06ms,速度高达9920.63 Token/s,表明采样算法本身效率极高,非性能瓶颈。 total time: 输入到输出的总耗时,包括模型加载时间、提示词处理时间、生成推理时间,其他时间(可能含内存交换或调度延迟) 可以使用vscode的打开多个终端,一个执行大模型交互,一个使用htop看看CPU和内存使用情况。 从上面看输入是17.45 token/s,输出是3.11 token/s,速度还是比较慢。 没有使用GPU,都是用cpu在推理。那么怎么使能使用gpu了?使用下面的方式,构建编译的时候打开CUDA,然后重新编译试一下。要用多线程编译,否则编译贼慢。 cd llama.cpp cmake -B build -DGGML_CUDA=ON cmake --build build --config Release -j16 重新运行模型后,看到硬件信息用了GPU了。 llama_perf_sampler_print: sampling time = 10.88 ms / 105 runs ( 0.10 ms per token, 9649.85 tokens per second) llama_perf_context_print: load time = 959.88 ms llama_perf_context_print: prompt eval time = 573.18 ms / 14 tokens ( 40.94 ms per token, 24.43 tokens per second) llama_perf_context_print: eval time = 17212.83 ms / 91 runs ( 189.15 ms per token, 5.29 tokens per second) llama_perf_context_print: total time = 34584.56 ms / 105 tokens 输出token有提升,但是看起来不明显,为啥了? -
transformer
模型结构 transform使用了自注意力机制,由编码器和解码器组成。 编码器 transformer的编码器输入一排向量,输出另外一排同样长度的向量。transformer的编码中加入了残差连接和层归一化,其中N X表示重复N此。首先在输入的地方需要加上位置编码,经过自注意力处理后,再嘉盛残差连接和层归一化。接下来经过全连接的前馈神经网络,再做一次残差连接和层归一化,这就是一个完整的块输出,而这个块重复N此。 上图中的块就是前面说的多头注意力+残差连接和层归一化+全连接前馈神经网络等组成。编码器可以理解为就是对输入进行编码处理。 解码器 解码器分为自回归解码和非自回归解码。 自回归解码(Autoregressive,AT) 以语言识别为例,输入一段声音,输出一串文字。 首先,将一段"机器学习"的音频输入给编码器,编码器会输出一排向量。然后将这一排向量送入到编码器中。 其次,解码器输入一个代表开始的特殊符号BOS(Begin of Sequence),这是一个特殊的词元(token),代表开始。编码器读入BOS后,就会输出一个向量。这个向量代表了词表中每个词的概率,跟分类一样,经过了softmax操作,总和为1。向量的长度和词表一样大,每个中文一对应一个分值。 接着,在向量中挑选分数最高的作为解码器的第一个输出。这里应该就是"机"。 最后,把编码器的输出"机"当成解码器新的输入,输入为特殊符号"BOS"和"机",解码器同样输出一个向量,这个向量里面给出了每一个中文字的分数,这里应该是"器"分数最高,这个过程反复持续下去。 上面的运作过程中,解码器把上次的输出当做输入反复下去,那么如何让解码器停止了?要让解码器停止,也需要准备一个特别的结束符号"EOS",当产生完"习"之后,再把"习"当做编码器输入以后,解码器要能够输出"EOS",这个EOS的概率必须最大,输出了EOS,整个解码产生的序列就结束了。 总结一下,自回归模型就是,解码器先读入编码的输入,然后输入BOS,输出W1,再把W1当做输入,再输出W2,直到输出EOS为止。 非回归解码(NAT) 自回归编码是根据上次解码器的输入一个字一个字的输出,假设要输入长度一百个字的句子,就需要做一百次的解码。那能不能一次性全部输出了?这就是非自回归解码器,假设产生的是中文的句子,非自回归不是一次产生一个字,而是一次把整个具体都产生出来。那要怎么做了,有两个做法。 方法1:用分类器来解决,将编码器的的输出结果先给分类器,分类器得到一个数字,这个数字达标的是解码器要输出的长度,比如输入是5,非自回归的解码器就是吃5个BOS,这样就产生了5个中文的子。 方法2:给编码器一堆的BOS词元,因为输出的句子有上限,假设不超过300个字,那就输入300个BOS,解码器就输出300个字,输出句子中EOS右边的输出就裁掉。 简单来说,非自回归解码是一次性输出句子,与自回归解码不同的是,非字回归解码输入的全是BOS,而自回归解码输入的是上一轮的输出。 transform的训练 既然要训练,就要去衡量误差,这个误差怎么衡量了? 解码器的输出一个是概率分布,以输出的"机"为例,当输入"BOS"的时候,输出的答案应该要跟"机"这个向量越接近越好。 参考书籍:《深度学习详解》 -
自注意力机制
运作原理 自注意力机制要解决的是让机器根据输入序列能根据上下文来理解。举个例子,输入句子为"我有一个苹果手机",对于机器来说这里的"苹果"应该是指水果还是手机品牌了?所以要解决这个问题,就需要在上下文中去理解,那怎么在上下文中去理解了?那就是由句子中的其他词对于施加权重,让"苹果"更靠近"手机"。具体怎么做了?来看看下面的图。 上图中的a1~a4是输入的词,每个输入的词都需要跟句子中的其他词做运算得到一个输出b1~b4。如a1要得到b1,那么a1需要与a2、a3、a4输入的词进行相关运算得到b1,同理其他a2、a3、a4对应输出b2、b3、b4。注意这里a1到b1的输出并不是a1与其他a2~a4的简单相乘或相加,那具体是怎么个相关运算了? 计算向量关联程度的方法有点积和相加,目前比较常用的是点积。下面以点积来进行说明。在自注意力模型中,采样查询-键-值(Query-Key-Value)的模式。主要分为3个步骤,分别是计算QK内积、再计算V向量、最后加权得到b。 QK内积 q: q称为查询,就是使用搜索引擎查找相关文章的关键字。q的计算方式为输入乘上Wq矩阵得到,如把a1乘上Wq得到q1。 k: k称为键值,输入乘上Wk得到向量k。如a2,a3,a4乘以Wk得到k2,k3,k4。 qk:把q和k做点积就得到a12,a13,a14,即表征a1与a2,a3,a4之间的关联性了。 通常情况下,得到最终的qk内积结果(记为axx)会进行一次归一化处理得到a',可以使用softmax也可以使用别的激活函数,如下图所示。 最终处理的结果a'表示的是输入a1与其他a2~a4存在的关联性分数,也称为注意力分数,也可以说是一个权重值,上下文中其他的词对a1最终词的解释权重。 V向量 qk内积计算了注意力分数,那接下来需要根据注意力的分数提取出信息得到最终的b。那么要进行提取,那必然需要先获取到其他词的特征信息,怎么获取了,获取的方式非常简单,就是让各自输入乘以Wv矩阵得到一个向量V。比如a1乘以Wv得到V1,a2乘以Wv得到V2。 加权和b 得到了各自的注意力分数qk,也获取到了各自输入的特征信息,最后就可以计算最终的输出b了。公式为: $b^1 = \sum_{i} \alpha_{1,i}' v^i$ 。就是特征信息V和注意力分数进行相乘,然后把所有结果加起来。 如果a1和a2的关联性很强,那么a12'的值就大,跟V2相乘值对应也就大,这样b1的值就可能比较接近V2。所以谁的注意力分数越大,谁的V就会主导抽出的结果。 小结 上面通过以a1进行相关运算后输出b1过程,a2、a3、a4计算过程同理,同时输入的各自计算是并行的,不需要各自依赖,这也是与RNN的本质区别。同时计算过程中出现的Wq、Wk、Wv都是要学习的参数。而在实际过程中,并行运算都是通过矩阵的方式进行的,这里就不再过多阐述了。 多头注意力,所谓多头注意力,就是对应的qk有多个,也就是说W参数也有多个。 位置编码,在计算QKV的时候,引入位置编码,让输入的位置也占一定的权重。 参考书籍:《深度学习详解》 -
密码保护:YOLOv5端侧部署代码分析
此内容受密码保护。如需查阅,请在下列字段中输入您的密码。 密码: -
端侧部署YOLOv5模型
导出 ONNX模型 python export.py --weights runs/train/exp2/weights/ NPU不支持动态输入,使用onnxim工具进行转换为固定输入,先安装onnxsim工具。 pip install onnxsim -i https://pypi.doubanio.com/simple/ 接着进行转换 python -m onnxsim runs/train/exp2/weights/best.onnx yolov5s-sim.onnx --input-shape 1,3,640,640 模型裁剪 在实际端侧中,NPU端量化的后处理运算不适合使用uint8量化,一般使用float的混合量化,但这样相对麻烦,本文示例将后处理放在CPU测进行,所以我们需要把下图中的后处理部分裁剪掉。 import onnx onnx.utils.extract_model('./yolov5s-mask.onnx', './yolov5s-mask-rt.onnx', ['images'], ['/model.24/Reshape_output_0','/model.24/Reshape_8_output_0','/model.24/Reshape_16_output_0']) 使用上面的python可以进行裁剪,输入为[images],输出为3个节点,下面是最后一个节点的示例截图。实际要根据模型文件进行调整。 python extrat-mask.py 接着使用上面的命令,就输出了裁剪后的模型yolov5s-mask-rt.onnx。 如果原生的模型没有将后处理裁剪,输入输出如下: 输出的tensor[1,25200,85],其中25200=3x(20x20+40x40+80x80),即3个特征图一共25200个先验框。 原生模型使用裁剪后使用netron.app打开得到输入输出如下: YOLOv5模型参数含义详细解释如下: 模型名称: 表示模型或图结构的名称,此处为从主图结构中提取的名称。 输入参数:float32[1,3,640,640],表示输入张量的数据类型和维度。1表示一次处理的样本,3为通道数,输入的高宽均为640,此前YOLOv3是416。 输出参数:有3个输出张量。 -- /model.24/Reshape_output_0:表示8倍降采样率,输出为float32[1,3,85,80,80],网格划分为80x80,每个网格有3给先验框,每个先验框预测包含85个元素(坐标4、置信度1、类别80)。 -- /model.24/Reshape_8_output_0:float32[1,3,85,40,40],网格尺寸为40x40。 -- /model.24/Reshape_16_output_0:float32[1,3,85,20,20],网格尺寸为20x20。 模型参数数量:parameter参数为7225908,表示模型中参数的总数,这是模型复杂度和计算资源需求的重要指标。 再来看看此次针对口罩微调后裁剪后的模型输入输出,使用netron.app打开得到输入输出如下: 上图可以看到,网格划分、先验框数量是一样的,不一样的是预测的元素为8(坐标4,置信度1,类别3)。 创建端侧转换环境 sudo docker images sudo docker run --ipc=host -itd -v /home/xxx/ai/docker_data:/workspace --name laumy_npu_v1.8.x ubuntu-npu:v1.8.11 /bin/bash sudo docker ps -a sudo docker exec -it 55f9cd9eb15e /bin/bash 在进行端侧部署前,需要准备量化环境,这里直接使用的是docker环境。 模型转换 创建目录 |-- data | |-- maksssksksss0.png | |-- maksssksksss1.png | |-- maksssksksss10.png | |-- maksssksksss11.png | |-- maksssksksss12.png | |-- maksssksksss13.png | |-- maksssksksss14.png | |-- maksssksksss15.png | |-- maksssksksss16.png | |-- maksssksksss17.png | |-- maksssksksss18.png | |-- maksssksksss19.png | |-- maksssksksss2.png | |-- maksssksksss3.png | |-- maksssksksss4.png | |-- maksssksksss5.png | |-- maksssksksss6.png | |-- maksssksksss7.png | |-- maksssksksss8.png | `-- maksssksksss9.png |-- dataset.txt `-- yolov5s-mask-rt.onnx 准备数据量化的数据data、数据配置dataset.txt(内容如下)、裁剪的模型yolov5s-mask-rt.onnx。 ./data/maksssksksss0.png ./data/maksssksksss1.png ./data/maksssksksss2.png ./data/maksssksksss3.png ./data/maksssksksss4.png ./data/maksssksksss5.png ./data/maksssksksss6.png ./data/maksssksksss7.png ./data/maksssksksss8.png ./data/maksssksksss9.png ./data/maksssksksss10.png ./data/maksssksksss11.png ./data/maksssksksss12.png ./data/maksssksksss13.png ./data/maksssksksss14.png ./data/maksssksksss15.png ./data/maksssksksss16.png ./data/maksssksksss17.png ./data/maksssksksss18.png ./data/maksssksksss19.png 模型导入 pegasus import onnx --model yolov5s-mask-rt.onnx --output-data yolov5s-mask-rt.data --output-model yolov5s-mask-rt.json 模型导入将输出yolov5s-mask-rt.json和yolov5s-mask-rt.data文件。前者为后期量化需要的网络结构文件,后者为模型网络权重文件。 前后处理配置文件yml 生成前处理配置文件 pegasus generate inputmeta --model yolov5s-mask-rt.json --input-meta-output yolov5s-mask-rt_inputmeta.yml 生成后处理配置文件,因为后处理是在cpu上处理并且我们已经裁剪掉了onn模型的后处理,可以不生成。 pegasus generate postprocess-file --model yolov5s-mask-rt.json --postprocess-file-output yolov5s-mask-rt_postprocess_file.yml 上面根据模型网络结构文件yolov5s-mask-rt.json转化生成后续量化需要的前处理和后处理描述文件,格式为yml格式。 input_meta: databases: - path: dataset.txt #表示模型量化需要的数据描述文件 type: TEXT ports: - lid: images_205 #表示输入节点名称。 category: image dtype: float32 sparse: false tensor_name: layout: nchw #输入数据的排列格式,n表示batch,c表示channel,h表示高,w表示宽 shape: #模型输入的形状 - 1 #输入数据的batch,如果后面量化的batch参数不为1,需要改这里。 - 3 - 640 - 640 fitting: scale preprocess: reverse_channel: true mean: - 0 - 0 - 0 scale: #3通道的缩放值,yolov5s需要改为0.00392157 - 1.0 - 1.0 - 1.0 preproc_node_params: add_preproc_node: false #是否添加预处理节点,用于格式转化和裁剪,这里要改为true preproc_type: IMAGE_RGB #预处理输入的格式 preproc_image_size: - 640 - 640 preproc_crop: enable_preproc_crop: false crop_rect: - 0 - 0 - 640 - 640 preproc_perm: - 0 - 1 - 2 - 3 redirect_to_output: false 对于模型的输入yml,需要将scale修改为0.00392157,同时默认使用cpu预处理图像,所以add_preproc_node设置为true。 量化 pegasus quantize --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --device CPU --iterations=12 --with-input-meta yolov5s-mask-rt_inputmeta.yml --rebuild --model-quantize yolov5s-mask-rt.quantize --quantizer asymmetric_affine --qtype uint8 下面是量化模型的参数解析: model: 模型的网络结构文件 model-data:模型需要的权重文件 with-input-meta:模型需要前处理配置文件 model-quantize: 输出的模型文件 iterations: 模型量化使用的数据量,设置1(默认)使用dataset第一条数据。若设置20,会先遍历dataset.txt中的20行数据。 qtype:量化数据类型,有int8,uint8,int16等。 batch_size: 量化多少轮,如果不为1,需要改输入的yml文件shape,先用默认。 量化后,会生成量化文件yolov5s-mask-rt.quantize。 推理 pegasus inference --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --dtype quantized --model-quantize yolov5s-mask-rt.quantize --device CPU --with-input-meta yolov5s-mask-rt_inputmeta.yml --postprocess-file yolov5s-mask-rt_postprocess_file.yml 模型推理是为了验证量化后的模型效果,这步可省略。 导出模型 pegasus export ovxlib --model yolov5s-mask-rt.json --model-data yolov5s-mask-rt.data --dtype quantized --model-quantize yolov5s-mask-rt.quantize --save-fused-graph --target-ide-project 'linux64' --with-input-meta yolov5s-mask-rt_inputmeta.yml --output-path ovxilb/yolov5s-mask-rt/yolov5s-simprj --pack-nbg-unify --postprocess-file yolov5s-mask-rt_postprocess_file.yml --optimize "VIP9000PICO_PID0XEE" --viv-sdk ${VIV_SDK} 模型导出,最终会生成ovxilb/yolov5s-mask-rt_nbg_unify/network_binary.nb 端侧部署 修改端侧后处理的分类名和数量配置文件。 改完之后,执行./build_linux.sh -t \编译生成端侧的应用,将可执行应用推到设备端。 ./yolov5 -b network_binary.nb -i mask-test.jpeg model_file=network_binary.nb, input=mask-test.jpeg, loop_count=1, malloc_mbyte=10 input 0 dim 3 640 640 1, data_format=2, quant_format=0, name=input[0], none-quant output 0 dim 80 80 8 3, data_format=0, name=uid_20000_sub_uid_1_out_0, none-quant output 1 dim 40 40 8 3, data_format=0, name=uid_20001_sub_uid_1_out_0, none-quant output 2 dim 20 20 8 3, data_format=0, name=uid_20002_sub_uid_1_out_0, none-quant nbg name=network_binary.nb, size: 7196096. create network 0: 35626 us. prepare network: 38972 us. buffer ptr: 0xb6996000, buffer size: 1228800 feed input cost: 94526 us. network: 0, loop count: 1 run time for this network 0: 119081 us. detection num: 1 1: 94%, [ 316, 228, 541, 460], 1 draw objects time : 154 ms destory npu finished. ~NpuUint. 参考: https://v853.docs.aw-ol.com/en/npu/dev_npu/ https://blog.csdn.net/weixin_42904656/article/details/127768309 -
云服务器搭建YOLOv5训练环境
介绍 本文使用AutoDL云服务搭建YOLOv5的运行环境。 获取云服务器 在这个链接上https://www.autodl.com/home订阅服务,这里选择的是按量计费。 镜像选择基础镜像Mniconda最新ubuntu环境。 交钱订阅完成后就可以获取到登录的信息了。 这里使用的是ssh工具根据获取到的登录名和密码进行登录,需要注意的是端口可能不是默认的22,按照实际的端口进行。 配置conda环境 由于autoDL的服务器可能并不能访问外网,这里先将conda的源更换为清华的源。 conda config --remove-key channels conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ conda config --set show_channel_urls yes pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 接下来创建和激活虚拟环境 conda create -n yolov5 python==3.8.5 # 创建虚拟环境名称为yolov5, python版本为3.8.5 conda activate yolov5 # 激活yolov5环境 conda init # 如果提示没有初始化conda环境的话,执行conda init后退出控制台重新登录,再次激活。 conda env list # 通过上面的命令可以查看当前创建的conda虚拟环境 拉取YOLOv5代码环境 通过官网链接获取YOLOv5的代码。 cd autodl-tmp/ #这里先切换到audodl-tmp目录,这个空间比较大,读写也比较快。 git clone https://github.com/ultralytics/yolov5 # 在github上拉取代码 如果要在本地使用vscode查看代码的话,可以参考:https://www.autodl.com/docs/vscode/ 这里需要注意的是,audoDL可能没有访问GitHub,处理的办法就是参考这个方法在服务器上面做一个代理,https://github.com/VocabVictor/clash-for-AutoDL 或者https://gitee.com/laumy0929/clash-for-AutoDL 获取到CLASH_URL如下示例: 截屏2025-07-17 12.34.36 截屏2025-07-17 12.39.15 按照yolov5的代码环境需要的包 pip install -r requirements.txt 安装完成后,进行推理测试,看看环境是否安装正常,执行detect.py 指定模型文件和输入图片进行测试。 python detect.py --weights yolov5s.pt --source data/images/bus.jpg 执行上面脚本是,会自动拉取预训练的模型yolov5s.pt,最终将推理结果存储到runs/detect/exp2目录下。 训练自定义数据集 这里从GitHub上抓了一个口罩的书籍集,https://github.com/iAmEthanMai/mask-detection-dataset.git,使用git clone拉取到本地。 git clone https://github.com/iAmEthanMai/mask-detection-dataset.git 查看mask的数据集配置,有3个分类。这里使用yolov5s进行微调,复制一份yolov5的模型配置文件,并修改分类为3,需要根据实际的数据集存放的位置调整一下路径。 cp models/yolov5s.yaml models/mask_yolov5s.yaml 配置完成后就可以进行训练了。 python train.py --data mask-detection-dataset/data/data.yaml --cfg models/mask_yolov5s.yaml --weights pretrained/yolov5s.pt --epoch 100 --batch-size 4 模型文件将输出到:runs/train/exp5/weights/ python detect.py --weights runs/train/exp5/weights/best.pt --source mask-detection-dataset/data/images/maksssksksss1.png 上面执行命令使用训练的模型文件测试看看效果。 -
YOLOv2和YOLOv3
YOLOv2 回顾一下YOLOv1有哪些缺陷? 边界框训练时回归不稳定,导致定位误差大。 每个网格只能预测两个边界框且只能识别一类目标。 小目标检测效果差。 针对以上的问题,YOLOv2进行了改进,下面从检测机制优化、网络结构优化、训练策略优化3个维度进行。 检测机制优化 锚框(Anchor Box)机制 YOLOv1每个网格只会预测一个目标,因为每个网格预测的B个边界框的类别概率都是共享的,要是有多2个目标的中心都落在了一个网格中,那么有一个目标就没法预测了。怎么解决了? 让每个边界框都对应一个类别概率,这样就能做到每个网格可以预测多个目标了。 每个边界框训练是没有基准的,这样训练的时候就很不稳定。如果预先定义边界框,使训练的时候按照这些预定义的边界框作为基准进行训练调整。这里做个类比来理解,假设我们的目标坐标是(8,8),那么如果没有设置基准,从坐标(0,0)找到(8,8)就相对比较远,那假设我们从(6,6)这个基准开始找,那找到(8,8)的概率就大了。 上面预先定义的边界框就称为先验框(anchor Box),那么新的问题来了,这个anchor Box我们每个网格设置多少个?设置什么样的形状了?实际的数据集中Ground Truth(真实标签)边界框有些是长方形、有些是正方形。 YOLOv2使用了K均值聚类算法用于生成先验框(Anchor Boxes),其核心目标是从训练数据中自动学习边界框的尺寸和比例,替代人工预设的锚框,从而提升检测召回率与定位精度。YOLOv2通过聚类COCO数据集,得到5个先验框尺寸(如(0.25,0.33), (0.5,0.75)...),覆盖常见物体形状。 pw,ph是先验框anchor的宽高(根据K类均值聚类得来),tx,ty,tw,th模型预测的偏移量(训练得到)。所以通过pw,ph,tx,ty,tw,th就可以计算出实际要预测的框bx,by,bw,bh。模型最终预测出tx,ty,tw,th这四个值就可以计算出bx,by,bw,bh。 这里需要注意 YOLOv2中对tx,ty进行了sigmod归一化,防止训练初期,中心点数值极大训练不稳定。 YOLOv2预测边界框的宽和高初始值是基于先验框而来,是模型对每个锚框输出宽高缩放因子(t_w, t_h),通过聚类生成的先验框指数变换得到最终宽高。而YOLOv1是根据图像实际宽高缩放而来。 全卷积网络与先验框 YOLOv1最后阶段使用的是全连接层,使用全连接层不仅仅参数量大,同时会将先前的特征图包含的空间信息破坏,在YOLOv2中改成了全卷积结构。 可以看到输出也发生了变化YOLOv1的输出是7 x 7 x (1+4+1+4+20),而YOLOv2输出是13 x 13 x k x (1 + 4 + 20),这里的k是每个网格的先验框数量,一般为5。(1+4+20)分别是锚框的置信度、边界框坐标、类别概率,每个anchor先验框都对应一个(1+4+20),也就是说每个先验框可以检测一个目标,这样就解决了YOLOv1中每个网格只能检测一个目标的问题,YOLOv2中每个网格有5个先验框,就可以检测最多5个类别。 网络结构优化 加入批量归一化: YOLOv1中每层卷积都是由线性卷积+非线性激活函数组成,由于批量归一化得到越来越普遍的应用,并且效果较好,因此在YOLOv2每层卷积层都加入了批量归一化。所以卷积层就变成了线性卷积+归一化+非线性激活函数组成。 融合高分辨率特征图:YOLOv1输出的特征图是13 x 13 x 1024,分辨率越低丢失的特征就越多。为了解决这个问题,YOLOv2在第17层单独抽出一层26 x 26 x 512的特征图,然后通过特殊的降采样得到13 x 13 x 256的特征图,最后将这个13 x 13 x 256的特征图与前面13 x 13 x 1024的相加,这样达到提高特征信息保留。 多尺度训练 YOLOv2在训练上也做了进一步的优化,因为同一张图像,缩放到不同尺寸,不同尺寸包含的图像信息也不同。因此为了提高精度,引入了多尺度训练训练机制。 具体就是在训练网络时,对图像按照320、352、384、416、448、480、512、544、576、608等不同输入尺寸进行训练。 总结一下,YOLOv2针对YOLOv1的改进点有以下。 增加先验框机制:每个网格使用K类均值聚类预设K个先验框作为基准训练。每个先验框负责预测一个目标。 加入批量归一化:每个卷积层对训练数据做批量归一化处理。 高分辨率特征图:网格划分13 x 13,主干网络中抽离一路高分辨率特征图进行特殊处理然后再加回去。 对尺度训练:训练阶段使用不同尺度的图像数据进行训练。 YOLOv3 对应目标检测网络可以由主干网络、颈部网络、检测头。 主干网络:提取多尺度特征。通过卷积层、池化层等操作,将输入图像逐层抽象化,生成不同层级的特征图。有浅层特征和深层特征。浅层特征是保留细节(如边缘、纹理),适合小目标检测;深层特征是蕴含语义信息(如物体整体结构),适合大目标识别。 颈部网络:融合与优化特征。连接主干网络与检测头,整合不同层级的特征图,增强模型对不同尺度目标的感知能力。 检测头:执行具体检测任务。基于融合后的特征,输出目标的位置、类别及置信度。 YOLOv2对于小目标的检测还是不够精确,这一缺陷的主要原因是YOLOv2只使用了32倍的降采样率。 浅层卷积层:没有经过更多的卷积层处理,提出的语义信息较少,具有较浅的语义信息;但对应没有过多的降采样因此具备较多的位置信息。 深层卷积层:经过更多的卷积层处理,提取更多的语义信息;但是位置信息经过了太多的降采样,丢失了位置信息。 语义信息可以理解为是什么类别的物品,位置信息是这个物品在图中的什么坐标位置。浅层卷积层更适合检测小目标(语义信息不需要这么多),深层卷积层适合检测大目标(需要更多的语义信息)。根据这个认知,YOLOv3主干网络就使用了3个不同尺寸的特征图,分布对应的降采样是32、16、8倍。对于小尺度目标使用的是8倍将采样并在浅层网络进行先提出输出针对性处理。而大尺度目标使用32倍降采样在最深层的网络中进行输出。 输入是416 x 416的图像,输出的是3个特征图,分布是C1=B X 256 X 52 X 52;C2=B x 512 x 26 x 26;C3=B x 1024 x 13 x 13,这里的B是先验框,一般为3。针对输入图像做了52x52、26x26、13x13三种不同疏密度的网格。 3个特征图根据多级检测结构推理后,得到最终预测的3个结果。y1= B x (4 + 1 + Nc) x 13 x 13;y2= B x (4 + 1 + Nc) x26 x 26;y3= B x (4 + 1 + Nc) x 52 x 52; 其中B为先验框数量,一般是3。Nc是类别个数,根据实际数据集,YOLOv3使用的是COCO数据集,有80个类别。 参考: 书籍《YOLO目标检测》