所有文章
瑞芯微RK3588 NPU开发环境搭建笔记
本文以RK3588+Debian Linux系统为基础,搭建瑞芯微平台的NPU开发环境与板端推理环境,并以Model Zoo中的Yolov5模型为例进行该模式上板运行流程的总结。 瑞芯微NPU应用开发框架 瑞芯微的NPU应用开发的整体开发流程上,基本上与君正AI应用开发的后处理流程(Post Processing)大致一致,首先是在PC上基于Pytorch或者tensorflow等进行模型的开发和训练,完成后把这个模型(如onnx格式或者pt格式)使用一个 RKNN-Toolkit2 的工具转换为RKNN格式,这个RKNN格式才是能够在瑞芯微芯片上跑起来的端侧模型格式,后续在生产环境中使用C/C++语言调用这个模型进行前向推理。 RKNN的软件框架如下图所示: !image.png RKNN-Toolkit2、RKNN-Toolkit-Lite2、RKNPU2 C++ API之间的关系: - RKNN-Toolkit2用于把PC或者服务器上训练好的模型转换为可以在瑞芯微NPU上运行的RKNN格式,运行在PC端。 - RKNN-Toolkit-Lite2是一个可以在瑞芯微NPU上基于RKNN格式的模型进行前向推理的工具,运行在瑞芯微处理器上。 - RKNN-Toolkit-Lite2本质上是一个在瑞芯微处理器上运行的Python包,所以其执行的前向推理是在瑞芯微硬件上跑的python环境中运行。如果对性能有更高要求,应该使用RKNPU2的C++ API来运行AI而不是RKNN-Toolkit-Lite2。 - 所以:RKNN-Toolkit2用于进行模型转换;RKNN-Toolkit-Lite2用于对转换后的模型在板端进行快速验证和测试;RKNPU2 C++ API才用于最终的项目实际落地。 > 我自己的看法,在板端的推理上,C/C++代码的效率要远远高于Python。所以其实没有太大必要在板端用RKNN-Toolkit-Lite2的python脚本去做推理验证,直接用RKNN-Toolkit2进行模型转换,然后在板端用C/C++进行推理才是一步到位的最成熟的做法。当然,使用RKNN-Toolkit-Lite2的python脚本在板端去做推理验证,优势则在于有大量Python包的支持,而且与PC端环境一致,写推理代码更容易。 RKNN-Toolkit2 能够支持的芯片平台列表: - RK3588 Series - RK3576 Series - RK3566/RK3568 Series - RK3562 Series - RV1103/RV1106 - RV1103B/RV1106B - RV1126B - RK2118 如上所述,瑞芯微NPU应用开发主要依赖于RKNN生态。在瑞芯微芯片的NPU上进行应用开发主要依赖于以下两个在Github上维护的代码仓库: - RKNN-Toolkit2: airockchip/rknn-toolkit2 ,模型转换工具。 - RK Model Zoo: airockchip/rknn_model_zoo ,模型上板运行的支持示例。 RKNN-Toolkit2开发环境的搭建 截至2026年初,最新的RKNN-Toolkit2的版本是2.3.2。 进行瑞芯微RKNN NPU生态的应用开发与测试,首先要进行RKNN-Toolkit2开发环境的搭建。从Github上下载RKNN-Toolkit2和RKNN Model Zoo仓库: 模型转换的Python环境 新建一个专门的rknn环境(Python版本指定为3.12)并安装RKNN-Toolkit2及其所有的依赖包(rknn-toolkit2-master/rknn-toolkit2/packages/x86_64): > 注意,按照以上流程安装的Python3.12环境,在后续在Model Zoo中把Yolov5的onnx文件转换为rkkn时会报错:AttributeError: module 'onnx' has no attribute 'mapping',这是因为Python 环境中安装的 onnx 库版本过高,与当前的 rknn-toolkit2 (v2.3.2) 产生了不兼容。应该在requirements_cp312-2.3.2.txt修改onnx依赖版本为:onnx==1.16.1, 而不是安装onnx的最新版本。 交叉编译环境 板端C/C++代码的交叉编译工具(rk3588为aarch64架构,所以安装64位系统的编译工具):https://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/aarch64-linux-gnu/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu.tar.xz ,后续在编译模型上板推理的C/C++ demo时要用。 板端NPU驱动环境 此外,如以上RKNN的软件框架图所示,在RK3588板端上所执行的AI推理,同时还需要依赖于板端NPU的相关支持环境,包括板端kernel的NPU驱动、瑞芯微RKNN网络的运行时库以及其他的支持库。 首先一点,需要在板端的Kernel中启动NPU驱动支持,并且确保kernel的NPU驱动版本与RKNN-Toolkit2及其运行时库的版本相匹配。可以在Kernel的启动信息或者通过dmesg得到NPU驱动的版本信息: !image.png 除了以上NPU驱动支持以外,板端应用程序在NPU上执行推理还需要运行时库librknnrt.so的支持。librknnrt.so在rknn-toolkit2-master/rknpu2/runtime/Linux/librknn_api/aarch64位置,在进行板端推理的C/C++ demo编译时应该链接到这个动态链接库,并且把这个库提前放到板端系统的LD_LIBRARTY_PATH路径中。 此外,对于模型上板推理的C/C++ demo运行,除了以上的NPU驱动以及NPU运行时支持动态链接库librknnrt.ko,可能还需要其他的多媒体与硬件加速库: - RGA (Rockchip Graphics Architecture):用于硬件加速的图像缩放、裁剪、格式转换(如从摄像头拿到的 YUV 转为 NPU 需要的 RGB)。需要 librga.so 库文件及其头文件。 - MPP (Media Process Platform):用于进行硬件编解码(H.264/H.265),需要 librockchip_mpp.so。 至此,在瑞芯微平台上进行NPU开发的Python、C/C++交叉编译环境以及板端运行环境就已经准备好了。接下来通过Model Zoo中的Yolov5 demo对环境和NPU开发的流程进行总结。 基于Model Zoo中的YOLOv5进行上板测试 在前面已经下载的model zoo的仓库rknn_model_zoo-main的examples中,包含有多个已经在瑞芯微平台上适配好的模型案例,经过简单修改编译后即可上板运行测试。整个流程可以分为两个步骤:模型转换和板端C/C++ demo编译。 模型转换 在examples/yolov5/model目录下,执行download_model.sh,这个脚本的功能就是从指定的网络地址上下载一个onnx的yolov5s模型,所以这一步如果自己有已经训练好的yolov5s模型,完全可以使用自己训练的模型来替代。 接下来在examples/yolov5/python目录下,执行convert.sh脚本来进行模型转换(onnx->rknn)的动作。convert.sh脚本中指定了数据集描述文件路径DATASET_PATH和模型转换输出文件路径DEFAULT_RKNN_PATH,有必要的话可以对这两个值进行修改。然后执行python convert.py ../model/yolov5s_relu.onnx rk3588进行模型转换,转换后的模型会放到DEFAULT_RKNN_PATH所指定的路径中(以rknn为后缀)。 至此上板运行的模型文件就转换好了。 C/C++推理demo 在rknn_model_zoo-main目录下有两个build脚本文件:build-linux.sh和build-android.sh,分别用于针对板端的Linux和Android环境编译C/C++推理demo。此处以RK3588的Debian Linux环境为例,所以首先修改build-linux.sh,在最前面增加交叉编译工具链的路径: !image.png 然后调用build-linux.sh脚本进行yolov5板端推理demo程序的编译:./build-linux.sh -t rk3588 -a aarch64 -d yolov5 -b Release。build-linux.sh实际上会跳转到examples/yolov5/cpp/目录下,找到其中的CMakeLists.txt,调用cmake来执行完整的编译动作。 build-linux.sh的参数说明如下: build-linux.sh执行完成后会在rknn_model_zoo-main目录下生成一个install目录,其中包含了demo在板端运行所需要的所有文件:demo可执行程序,运行时依赖库librknnrt.so,板端rknn模型文件,测试图片文件。所以只需要把这个install目录打包后上传到板端执行其中的demo程序即可:
一文彻底搞懂Transformer模型的Decoder结构与计算流程
本文详细总结了Transformer模型架构中Decoder部分的总体网络结构,以及数据在其中的运算和流转流程。 之前在一文彻底搞懂Transformer模型的Encoder结构与计算流程一文中,详细整理了Transformer模型的Encoder架构,本文继续学习和解析Transformer的Decoder的结构以及完整的计算流程。因为Decoder与Encoder的模型结构与计算流程非常类似,所以最好参考一文彻底搞懂Transformer模型的Encoder结构与计算流程以及如何理解Transformer架构中的多头注意力机制?把Transformer的自注意力概率与计算流程先搞清楚。 下图是一个完整的Transformer Encoder-Decoder的网络架构: - 左侧为连续N个Encoder Block组成的Transformer Encoder,用于实现把输入的自然语言文本转换为向量表示的语义信息。 - 右侧为连续N个Decoder Block组成的Transformer Decoder,用于基于Encoder输出的语义信息的向量表示来实现自然语言功能的输出,如机器翻译,文本续写等。 !image.png Decoder的输入和输出 Transformer的输入和输出结构如下图所示。 !image.png Decoder的输入包含两部分数据: - 下方输入(Outputs Shifted Right):用于执行掩码自注意力 (Masked Self-Attention)计算。其内容是模型已生成的 Token 序列,从而让模型知道“我已经说了什么”。 - 中间输入(来自Encoder提取的语义向量):用于执行交叉注意力 (Cross Attention)计算。其输入内容是基于 Encoder 的语义向量计算得到的 K 和 V 矩阵,让模型知道“源句子的意思是什么”。其计算逻辑是 Decoder 会用自己的 Q(我想找哪个语义)去跟 Encoder 的 K, V 进行匹配。 Decoder的输出为上图最上方的Output Probilities。输出内容的维度等于模型输出词表大小(Vocabulary Size)的向量。通过最后的 Linear 层和 Softmax 层处理之后,这个向量代表了模型输出词表中每一个单词作为“下一个输出单词”出现的概率分布。通常选取概率最大的那个索引(Index),然后去字典里查出对应的单词作为下一个输出单词。 Outputs shifted right输入与自回归预测 以上提到Decoder下方输入Outputs Shifted Right的内容是模型已生成的 Token 序列,这个在推理流程中要如何理解? 在初始状态下,模型是没有输出的,此时 Outputs Shifted Right 部分的输入内容就是一个起始信号 (Start of Sentence)。此时模型会根据 Encoder 的语义和起始信号,在Decoder端执行一次前向推理,预测生成第一个词(例如 Hello)。接下来,新生成的这个词 hello 就会被接入到decoder的输入端,此时decoder的输入序列就变成:[, Hello],模型继续基于这个新的输入序列执行前向推理,预测出第二个词 world,第二个词同样被接入到输入端,这样输入端的输入序列就变成 [, Hello, World],依此类推,直到最终模型预测输出的结果为 (End of Sentence,结束符),此时推理停止。 以上就是Decoder执行前向推理预测生成一个个单词的完整流程,因为每生成一个单词,这个单词都会被重新拼接到输入预测序列的最后,用于推理新的单词,这个过程也就是所谓的自回归预测过程。 Decoder Block的结构 Transformer 中的Decoder部分由多个相同的Decoder Block组成,其主要功能是根据Encoder所提供的语义表示(如上图中Encoder部分的输出)和先前已生成的输出标记(如上图中的Outputs Shifted Right部分)生成最终的输出序列。 !image.png 每个Decoder Block由三个主要子层组成: - Masked Multi-Head Attention:与编码器的自注意力机制类似,但其主要目的是防止关注未来的 token,以保持自回归属性(确保生成过程中不会作弊)。 - Multi-Head Attention:该子层允许解码器关注编码器输出表示的相关部分。这使得解码器能够专注于输入的相关部分,这对于翻译等任务至关重要。 - Feed Forward:前馈神经网络,该子层处理屏蔽自注意力和编码器-解码器注意力机制的组合输出。 从网络结构和推理的计算流程上,Multi-Head Attention多头注意力模块与Feed Forward前馈模块与Encoder的相应模块没有差异,主要的区别在于Masked Multi-Head Attention模块以及Encoder输出作为Decoder输入的交叉注意力机制上。 Masked Multi-Head Attention 从工程实现的角度看,Masked Self-Attention(掩码自注意力)和 普通的 Self-Attention 在代码结构以及计算流程上 90% 是相同的。唯一的差异只是两个注意力的视野范围不同: - 普通的 Self-Attention (例如Encoder 使用自注意力模型):该模块对一段文本进行语义提取的时侯,可以同时看到从开头到结尾的所有词,因此可以根据全句的语境来理解每一个词,并提取其语义,因此普通的Self-Attention模块的视野范围是全局的。 - Masked Self-Attention (Decoder 使用):在使用掩膜自注意力模块的时候,当前词的输出只能参考之前已经输出的词,不能参考尚未在当前词位置后边的词。也就是说,掩膜自注意力模块的视野氛围是历史视角,即只能看到之前的词。 Masked Self-Attention在计算中的历史视角这一点,对于训练过程非常关键。 因为在训练阶段,训练样本中包含有针对某个问题的完整的正确答案序列。为了提高效率,训练过程的执行,不是像推理解码那样一个词一个词地跑 N 遍模型,而是把整个序列一次性输入,然后执行一次矩阵运算,通过一次推理得到答案序列。这样的话,如果采用普通的自注意力模块就会存在一个问题,在解码器生成文本的过程中,位置在前面的词会看到后面的词,这样的话,后面位置的词就会对前面位置的词产生影响,相当于模型在训练的过程中直接把答案看到了,这当然是模型训练不希望看到的情况。 此时,就可以在执行注意力的矩阵运算的过程中,如下图所示,通过一个三角掩码矩阵,在数学上强制让第 t 个词只能看到第 1 到第 t 个词,无法看到后面的词。这样就既实现了并行化训练,同时又完美模拟了无法看到未来的因果律。 !image.png 相应的,Masked Self-Attention模块的计算公式就变成了: 其中,M 是一个下三角矩阵。其逻辑如下: - 对于所有 j > i 的位置(即当前位置之后的词),将 设为 。 - 对于所有 j ≤ i 的位置(即当前和之前的词),将 设为 0。 > 注意:在正常的前向推理过程中,模型实际上是执行一次前向推理生成一个token,在这种情况下,每次执行前向推理时,通过outputs shift right传递给decoder模块的文本序列自然就不存在未来的token(因为这个时候未来的token还没有生成),这样的话,这个掩码机制其实根本就没必要了。只不过因为训练的过程中,Transformer中的参数都是在有掩码的情况下生成的,所以如果在前向推理的过程中,把这个掩膜自注意力模块去掉,可能会导致参数数值分布的细微偏移,从而影响预测的准确性。所以,即使前向推理推理过程实际上已经不再需要掩膜机制,仍然需要保留这个掩膜自注意力模块机制,确保模型在推理阶段的输出与训练阶段输出的准确性基本一致。 Encoder的输出与交叉注意力 在Encoder的自注意力(Self-Attention)机制的实现中,Q、K、V 全部来自同一个输入。而对于Decoder的多头注意力模块而言,其输入来源于两个不同的来源,这就是所谓的交叉注意力机制: - Query (Q)来自于 Decoder 上一层的输出,实际上也就是掩膜注意力模块的输出。其语义内涵代表“我已经生成了这些词,我现在需要寻找什么样的上下文信息来翻译下一个词?” - Key (K) 和 Value (V)则来自于 Encoder 的最终输出。其语义内涵表示“原文(源语言)中每个词的特征和含义是什么?” 因此,对于交叉注意力机制,可以把它想象成 Decoder 持有问题(Q),去 Encoder 的知识库(K, V)中寻找答案。 如下图所示,基于Decoder前一模块的输出,与 Wq 矩阵运算得到 Queries;基于Encoder 的输出分别于 Wk 和Wv 矩阵运算得到Keys和Values,然后基于Queries、Keys和Values调用相同的注意力机制的计算流程(可参考如何理解Transformer架构中的多头注意力机制?),得到交叉注意力模块的计算结果。 !image.png Decoder部分的工作流程总结 最后再来总结一下整个Decoder部分的工作流程如下。 !image.png 首先是从outputs shifted right端输入模型推理输出的Token序列,这个token序列会使用与Encoder相同的预处理方式,对token进行向量化(Embedding)并增加位置编码信息。 第二步是掩膜自注意力模块的处理流程,其输入是叠加了位置编码信息的Token向量序列,这部分计算流程在前文的Masked Multi-Head Attention 已做了非常详细的解释。掩膜自注意力模块的输出通过标准的Add & Norm进行残差连接和层归一化。 第三步是交叉注意力模块的处理流程,这部分计算流程在前文的 Encoder的输出与交叉注意力 已做了详细的解释。同样,其输出通过标准的Add & Norm进行残差连接和层归一化。 第四步是由两层全连接神经网络所组成的前馈网络Feed Forward。其作用是在经过了两次注意力机制(掩膜自注意力和交叉注意力)后,对提取到的复杂特征进行非线性变换和进一步的维度提取。最后仍然通过 Add & Norm 进行残差连接和层归一化。 以上的第二步到第四步的处理流程构成了一个Decoder Block,每个Decoder Block的输入数据和输出数据的维度相同。整个Decoder 部分包含有N个Decoder Block,上一步Decoder Block的输出作为下一个Decoder Block的输出,直到N个Decoder Block处理完毕。 最后是输出结构的转换层,包含两个部分:线性层和Linear和Softmax层。 - Linear (线性层):全连接映射层,用于将 Decoder 提取出来的抽象向量(例如 512维,768维等)投射到一个巨大的空间,即模块输出所能支持的完整词表的空间。如果输出的字典里有 30000 个单词,那么这一层的输出维度就是 30000。 - Softmax (归一化层):通过Softmax 将线性层输出的原始数值(Logits)转换为概率分布,最终输出一个预测概率向量,每个位置的值代表词典中对应单词成为下一个词的概率。接下来系统就根据预测概率向量,选取概率最高的单词作为结果输出,并反馈回底部的输入端,开始下一个词的预测。 参考资料 - Architecture and Working of Transformers in Deep Learning - GeeksforGeeks - How Transformers Work: A Detailed Exploration of Transformer Architecture | DataCamp - Cross-Attention Mechanism in Transformers - GeeksforGeeks
详解QWen3-VL模型的测试以及vLLM生产环境部署流程
本文对阿里于2025年9月份发布的Qwen3-VL视觉语言模型进行了简单介绍,并对其预训练模型进行了本地推理测试以及在生产环境中使用vLLM进行部署的流程进行了总结。 QWen3-VL模型简介 阿里在 2025 年 9 月推出了Qwen3-VL 系列视觉语言模型。相较于其前代产品(如 Qwen2-VL),Qwen3-VL 的发布标志着多模态大模型从简单的视觉感知正式跨越到了深层的逻辑推理与自主 Agent 交互的新阶段。Qwen3-VL 延续了阿里的开源精神,推出了涵盖从端侧到云端的多种规模模型。 !image.png 在模型的总体设计架构上,Qwen3-VL 延续了阿里一贯的Dense(稠密)+ MoE(混合专家)双线并行的策略,参数量从2B到235B不等。 - Dense:该模型中所有的参数在处理每一个 Token 时都会被全部激活并参与计算。 - 其代表版本是Qwen3-VL-2B / 8B / 32B。 - 优点是在同等总参数量下,知识密度最高,部署相对简单。但缺点则是当参数量变大时,计算的开销线性增长,推理会变得非常缓慢。 - 所以适用于参数量不是很大的应用场景。 - MoE:该模型结构在包含很多专家(Experts)子模型,在处理特定输入时,会由门控网络(Gating)动态挑选其中的一小部分参与计算,大部分参数是不需要激活的。 - 其代表版本是:Qwen3-VL-30B-A3B (表示总参数30B,激活参数3B);Qwen3-VL-235B-A22B (表示总参数235B,激活参数22B)。 - 优点是拥有超大规模模型的知识库,但推理速度和计算开销却像小模型一样快。缺点则是对显存的要求仍然很高,虽然计算时只激活一小部分,但推理时必须将全部参数加载进显存。 此外,Qwen3-VL首次引入了 Thinking 版本:当面对复杂的 STEM、数学题或深层因果分析,模型不再是快思考直接给出直觉答案,而是通过强化学习(RL)生成的推理链,逐步拆解图像细节。因此 Qwen3-VL模型支持两种交互范式:Instruct和Thinking。 - Instruct (指令微调版):快思考,接收指令后直接生成并给出最终结果。 - 其特点是低延迟、生成速度快、输出简洁。 - 适用场景为日常对话、简单的图片描述、基础 OCR、实时 UI 操控等对实时性要求高的任务。 - Thinking (推理增强版):慢思考,在给出最终答案前,模型会先生成一段不可见的(或可展开的)思维链 (Chain-of-Thought, CoT)。 - 其特点是准确率更高(尤其在 STEM、数学、逻辑推理的复杂任务中),但生成时间长,会消耗更多 Token。 - 适用场景是复杂的数学几何题、逻辑推理、长视频的因果分析、需要严谨证明的场景。 QWen3-VL的推理测试 以下基于HuggingFace的Transformers下载 Qwen3-VL-4B-Instruct-FP8 这个版本的预训练模型文件,并在本机上做一些简单的推理测试。以下给出一个对手写文字图片进行OCR识别的推理测试。 首先import必要的包并使用Transformer从Huggingface上下载预训练模型文件列表到本地,并加载进来: 以上代码会自动下载预训练模型文件到本地 HF-HOME 环境变量所指向的位置,模型权重文件比较大,下载需要比较长的时间。下载后的模型文件如下所示: !image.png Qwen3VLForConditionalGeneration和AutoProcessor在每次启动加载模型文件的时候,都会对比本地下载的模型文件与HuggingFace Hub仓库中文件的文件的md5是否匹配,这个过程需要的时间会比较长,所以第一次下载模型文件完成后,可以通过以下代码设置HF Offline模式,避免每次加载的校验过程: 写一个接口函数用于对传入的图片进行OCR识别: 向上面的接口函数传入一张图片并打印出来OCR识别的结果: 这里传递了一张手写文字的图片: !image.png 最终OCR识别的结果如下:可以看到,在图片中字迹很潦草的情况下,模型的识别正确率仍然非常高。 QWen3-VL的vLLM生产环境部署 vLLM 是目前支持 Qwen 系列最好的引擎。它可以把 Transformers 模型包装成一个高性能的 OpenAI 兼容接口,后续直接使用HTTP协议来对模型进行访问。 首先安装vllm和qwen_vl_utils支持包。因为vllm的运行对于pytorch以及cuda的版本有要求,所以最好在一个独立的环境中安装和运行vllm: > 注意,vllm在windows环境下的安装总是会报配置文件的错误,但是在Ubuntu下面没有这个问题,一般生产环境部署用的都是Linux,所以我也就没有追究这个问题了。 以上vllm包安装完成后,使用以下命令就可以一键启动QWen3-VL模型在vLLM的部署和运行了: 其中: - -model参数用于指定模型文件所在的目录。 - -served-model-name:指定后续通过HTTP API访问模型的模型名称 - -trust-remote-code:必须加上,因为 Qwen3-VL 有自定义的视觉处理逻辑。 - -gpu-memory-utilization 0.75:告诉 vLLM 占用 75% 的显存,vLLM 会预先占满显存来管理PagedAttention。如果显卡显存少的话需要调低这个值。 - -max-model-len: 设置最大上下文长度。如果显存小,可以调小这个值。 - -limit-mm-per-prompt: 使用json格式限制一次对话最多传几张图。 如果有以下打印信息,就表示vLLM环境中部署的Qwen3-vl模型已经运行起来了: 使用下面的代码测试本机所部署的模型: !image.png 针对上面url参数所指定的图片,从模型得到的返回信息如下,模型部署测试验证OK: 参考资料 - Introduction to Qwen3-VL
开源软件项目学习中源码管理的最佳实践
在基于Github上的开源软件(像 Ultralytics 这样活跃且更新频繁的仓库)进行二次开发或预研学习的过程中,最头疼的莫过于:项目源仓库中的上游代码始终处于频繁更新的状态,而我自己在学习和预研的过程中所做的修改到处乱飞,最后连我自己都忘了改过哪里,一单合并代码就出现大量冲突,解决起来非常低效。 本文基于对网络上查询到的资料进行学习,找到了解决以上问题的一个合理的技术路径。而这一套工作流程也是很多团队做开源项目预研过程中的标准做法。简单总结起来就是:_在Git 版本控制层面和 软件项目代码架构层面双管齐下,使用Fork + 分支隔离 + 模块化侵入的方式保证开源软件项目学习和研究的高效率_。 本文以Github上的qiuqiangkong/audioset_tagging_cnn这个项目详细总结整个流程的具体实践方式。 1.Git 仓库与分支架构的隔离 首先,不要直接克隆Github上的原始仓库,然后把自己的修改简单的推送到 main 分支。而应该利用 Git 的 Fork 和 Upstream 机制,从原始仓库进行 fork 操作的同时,保持上下游仓库之间的关系。 点击 GitHub 上 audioset_tagging_cnn 项目的 Fork 按钮,将其派生到你自己的 GitHub 账号下。然后基于其主干分支 master 创建一个独立的 research 分支: !366088a7-9efe-4461-80ff-c4f630499595.png 后续在学习和预研的过程中,要确立严格的分支使用规范: - master 分支: 绝对不要在这个分支上修改代码!这个分支仅用于同步官方仓库的最新代码。 - research 分支: 该分支是专门的预研和学习的工作区。所有的代码实验、修改都在这个分支上进行管理和维护。 然后把自己 Github 账号下的 audioset_tagging_cnn 项目 clone到本地: 此时,还有关键的一步:需要与原始的官方仓库绑定,使其作为 master 分支的上游(Upstream),后续 master 分支就是要从这个 upstream 来同步原始仓库的最新代码: 2.同步上游仓库代码的更新流程 通过以上流程已经把原始仓库 qiuqiangkong/audioset_tagging_cnn 设置为上游仓库路径,那么后续在原始仓库中的代码有更新的情况下,就可以使用以下流程把更新的代码同步到本地的 master 和 research 分支: 以后在上游的原始仓库有更新的情况下,每隔一段时间执行一次上面的流程,就可以把上游的更新合并到自己仓库的 master 和 research 分支上。 3.目录结构的隔离 即使是在 research 分支中,也应该避免把自己写的测试脚本等随便丢在根目录或官方的源码包里。 更合理的做法是:在项目的根目录下建立一个带有特殊前缀的文件夹(例如 _my_research/,下划线能让它在目录树中置顶,方便查找),把自己的测试脚本、推理代码、自定义配置文件统一放在这个目录下。 如下图所示,在预研和开发过程中自己所产生的所有文件应该统一放到这个特殊的文件夹中,避免影响到原始仓库中的代码和文件。 !0591d6e3-cf31-4c02-8cb0-3f5d27365a92.png 4.使用 Editable Mode (开发模式) 安装(针对Python项目) 部分Python开源项目中,在调试和开发中需要安装并执行软件包到当前的python环境中(例如ultralytics项目中要在命令中中执行yolo命令,就需要安装ultralytics包),这类项目的根目录下一般包含有setup.py或者pyproject.toml文件。 为了能够在 _my_research/ 里写的代码直接引用官方源代码中实现的模块,同时你在官方源码里所做的修改能立刻生效,应在虚拟环境中使用可编辑模式安装: 开发者模式安装(pip install -e .)和普通安装之间的区别(pip install .)在于: - 执行普通安装时,pip 会把当前源码目录里的所有 Python 文件复制一份,放入系统(或虚拟环境)的 site-packages 路径中。在这种情况下。如果在原来的 ultralytics/engine/trainer.py 里加了一行代码 print("Hello CV"),再次运行代码时,这行打印信息无法打印出来。因为系统执行的是之前复制的那份老代码。所以在调试的过程中每次改完代码,要使修改的内容生效的话,都必须重新执行一遍安装命令,非常麻烦。 - 而如果在安装这个软件包时使用了 -e 参数,pip就不会直接复制源码文件。相反,它会在系统的 site-packages 目录下建立一个快捷方式(通常是一个 .egg-link 的文件),这个快捷方式指向当前源码目录。在这种情况,你在本地源码里做的任何修改,只要一保存,下次运行代码时会立刻生效,不需要重复安装,这样对调试过程更友好。
从头实现一个Vision Transformer(ViT)模型
从头实现一个Vision Transformer(ViT)模型 本文针对简单的MNIST手写数字数据集识别的需求,完成了一个最简单的Vision Transformer模型的实现、训练和验证测试,建立对Vision Transformer模型实现流程的完整理解。 MNIST手写数字数据集是最简单的机器视觉数据集,基于MNIST实现一个Vision Transformer模型来实现手写数字字符的识别,难度不会太大,对于模型训练所需要的数据以及算力资源要求也不高,因此通过训练一个MNIST数据集的ViT识别模型,是一个绝佳的入门Vision Transformer模型的实验。 !image.png 关于Vision Transformer模型的整体架构,可以参加另外一篇笔记[[一文入门Vision in Transformer(ViT)模型的架构]]。 !image.png > _注:参考资料1中提供了一个完整的、对MNIST手写字符集进行识别的Vision Transformer模型,本文实际上是对以上资料的学习记录,并增加自己对于实现过程的理解。_ 如[[一文入门Vision in Transformer(ViT)模型的架构]]这篇笔记所总结的,ViT模型的架构和工作流程大致可以分为以下几个阶段:图像切片、添加Cls Token、位置编码、基于多头自注意力机制的Transformer Encoder、最终的训练头。因此,本文仍然按照以上结构来实现一个完整的Vision Transformer结构,并使用MNIST数据集对其进行训练以及最后的测试验证。 1. 导入必要的包 - 导入torchvision.transforms用于实现对MNIST字符集图像分辨率的resize,确保模型输入分辨率是patch分辨率的整数倍;以及把MNIST字符集的图像数据转换为Tensor格式。 - 训练过程所使用的优化器使用torch.optim中所包含的Adam。 - 利用torchvision.datasets.mnist从Pytorch官网自动下载MNIST数据集。 2. Patch Embeddings 这一步实现把输入图像切片为固定大小的patch,并且序列化。如[[一文入门Vision in Transformer(ViT)模型的架构]]所提到的,对于这部分功能,一般是通过一个步长(Stride)等于卷积核大小(kernel_size)的 2D 卷积(Conv2d)来实现的。 以上的 Conv2d 操作实际上是一个卷积投影操作: - kernel_size=patch_size :是用 patch_size×patch_size 的卷积核提取每个 patch - stride=patch_size :步长等于 patch 尺寸,从而实现非重叠的切分动作 - 输出通道数=d_model :每个 patch 被映射为 d_model 维的向量,实现每个patch的向量化 patch embedding的完整代码如下: 在以上的forward操作中,self.linear_project(x)这一步把一张输入图像的维度从(C,H,W)转换为(d_model,P_col,P_row); !image.png x.flatten(2)这一步把所有的patch展平,得到P个序列化Patch,每个patch的维度长度都是d_model,这实际上就是patch embedding操作的输出结果; !image.png 最后的x = x.transpose操作就只是交换输出数据的维度,以适配 Transformer 架构的标准输入格式。 !image.png 3. Cls Token与位置编码 为Batch中的每张图片的Patch列表的开头,增加一个Cls Token的逻辑相对比较简单,就是新建一个长度与Patch向量相同的cls token,然后把这个token放到patch向量的开头作为其0号token即可。 除了Cls Token以外,还需要为Patch向量中增加其位置编码。每个位置编码对于它所代表的位置来说都是唯一的,这使得模型可以识别每个Patch的位置。为了将位置编码添加到Patch向量中,它们必须具有相同的维度 d_model。位置向量的构建公式如下: !image.png 因此总的来说,这一步包含两个步骤:为Batch中每张图片的Patch向量列表中的初始位置增加一个Cls token;为所有的向量增加其位置编码。完整代码如下: 4. 单头及多头注意力模块的实现 有关多头注意力模块的理论以及计算流程等方面的内容,在[[如何理解Transformer架构中的多头注意力机制?]]一文中已有非常详细的总结和解释,在此不再赘述。 单头注意力机制的计算公式如下: 可结合上面的公式以及代码中的注释来理解单头注意力的计算逻辑和流程,基本上就是以上公式的一步步计算而已: 基于以上的单头注意力模块,实现多头注意力模块就比较简单了,就是对多个注意力模块独立计算,把计算结果合并起来,最后再通过一个线性层把多个单头输出的特征进一步融合并输出: 以上单头和多头注意力模块的流程和结构如下图所示: !image.png 5. Transformer Encoder 接下来是完整的Transformer Encoder的实现。Transformer Encoder的结构如下所示: !image.png 其对应的代码如下: 6.整个Vision Transformer模型 最终实现的整个Vision Transformer的模型架构如下: !image.png - 输入图像首先在PatchEmbedding模块中进行切分和向量化、序列化的操作。 - 序列化的Patch向量再在PositionalEncoding模块中增加额外的Cls Token,以及位置向量。 - 以上数据再经过连续N个TransformerEncoder模块的处理,反复通过多头注意力模块提取和融合图像中的特征。 - 最后在一个分类头处理后通过Softmax输出分类识别的概率。 完整Vision Transformer模型的实现如下: 7. 训练和测试 最后对以上构建出来的模型进行训练,以及对训练出来的模型使用MNIST的测试数据集进行验证。 超参数设置 对模型进行训练的超参数设置如下: 训练以及测试数据集的准备 训练和测试使用从Pytorch官网下载的MNIST数据集,使用以下代码下载数据集并准备dataloader: 模型训练 接下来就是对已经构建出来的模型参数进行训练,整个训练的执行流程实际上与[[基于Pytorch实现手写数字识别的卷积神经网络]]一文所描述的基于卷积神经网络的手写数字识别的训练流程大同小异: 训练了15个epoch,从打印的损失值可以看到其损失在稳定向下收敛过程中: 模型测试 最后就是使用以上训练好的模型,对MNIST的测试数据集进行测试: 经过上面的15轮测试,模型对测试集数据的测试准确度就达到了93%。 参考资料 - Building a Vision Transformer Model From Scratch | by Matt Nguyen | Toward Humanoids | Medium --- 尝试从底层原理的角度去理解和解释技术问题:音视频/摄像头/智能家居/蓝牙/WiFi/无线通信/AI。 敬请关注微信公众号:Pavel Han。
一文入门Vision in Transformer(ViT)模型的架构
本文详细总结了Vision in Transformer模型的设计架构,以及图像其该模型架构中的完整处理流程,帮助建立对ViT模型的基础认识。 Transformer架构在自然语言处理领域(Natural Language Processing)中的各种大语言模型中的到了非常广泛的应用,而2020年Google的一篇论文《An Image is Worth 16×16 Words》,把Transformer架构引入了计算机视觉领域,利用其Patch Embedding和自注意力机制对计算机视觉领域中传统的卷积神经王CNN的统治地位造成了极大的冲击,从而在人工智能的各个领域中已经显示出一统江湖的态势。 要理解ViT模型的架构及其图像数据在其中的处理流程,首先要对Transformer架构尤其是其中的Encoder部分的架构有较为清楚的理解和认识,可参考另外一篇笔记[[一文彻底搞懂Transformer模型的Encoder结构与计算流程]]。 ViT架构简介 Transformer 架构最初是为序列化数据(一维)设计的,而图像是高维的(宽 高 通道)。如果把图像数据送入到Transformer架构中进行处理,首先要解决的问题就是把图像的高维数据转换为Transformer可接受的一维序列化数据。针对这个问题,最直观的做法就是直接把图像中的每个像素都当作一个 Token 喂给模型,但这样存在的问题就是计算量会呈指数级爆炸。因此ViT 的解决方案是:对图像进行切片操作(Patch),把切片后的结果序列化输入Transformer模型。 因此。ViT 模型就是将输入图像分割为一系列固定大小的块(patch),并把这些块序列化以后应用Transformer模型的自注意力机制提取图像特征,这使得模型能够获取图像不同部分之间的长距离依赖关系,而无需依赖CNN的卷积运算。 下图是一个基于ViT架构的图像分类模型。可以看到,输入图像被分割为多个固定大小的块,这些块被序列化并且向量化以后,送入Transformer Encoder中提取特征,最终通过一个MLP Head输出检测结果。所以从模型的结构上讲,与自然语言语义提取的Transformer Encoder架构相比,两者最大的区别就只是ViT架构对图像所做的切片分块处理而已。 !image.png 以上这个基于ViT架构的图像分类模型的处理,可以分为多个步骤,以下注意解析和描述。 1. 图像切片Image Patching 这个步骤有点类似于Transformer在自然语言处理流程中的对语句段落分词(Tokenization)和嵌入(Embedding)的操作。 首先是对图像进行的切片操作(Patching)。例如将一张 224 x 224 的图像切成固定大小 16 x 16的方块(每个方块都是一个Patch),那么整幅图就会被切成 14 x 14 = 196 个块。 !image.png 因为一般来讲,每张输入图像都包含有RGB三个通道,所以经过切分以后的每个切片patch的维度就是 16 x 16 x 3 。这个切片仍然是一个三维的向量,而Transformer的自注意力机制运算需要在固定长度的一维向量上展开。所以下一步仍然需要把这个 16 x 16 x 3 的三维向量展平(Flatten)为一维向量。 在具体的工程实践中,对输入图像的切片以及对各个切片Patch展平的操作通常是通过一个步长(Stride)等于卷积核大小(kernel_size)的 2D 卷积来实现的。例如切片大小为 16 x 16 ,每个patch展平操作输出的一维向量长度是768,就可以使用以下2D卷积操作来执行切片和展平操作: 因为卷积核的小大刚好是一个 Patch 的大小,那么卷积一次就提取了一个 Patch 的特征,这在 GPU 上的计算效率更高。 经过以上操作以后,一张224x224分辨率的输入图像,就被切分为196个patch,并且每个patch都被展平并映射为一个长度为768的一维向量。 2. 添加Cls Token 在对以上得到的196个切片向量进行进一步的处理之前,还需要为其额外增加一个相同长度(768)的分类向量,即Cls Token。这个分类向量不代表任何图片块,它的唯一任务是跟其他 196 个块建立联系,最后要从它的输出中读出整张图的分类结果。 具体的操作是:手动创建一个形状为 (1, 768) 的可学习向量,放在前面得到的 196 个patch向量的最前面。经过这一步处理以后,输入序列就变成了 196 + 1 = 197 个向量,每个向量的长度都是768。 3. 位置编码Positional Encoding 接下来的处理与自然语言文本序列的处理逻辑相同,为197个向量增加位置编码信息,以保留每个patch在原始输入图像中的位置空间感。与自然语言序列中各个字词的位置对于其语义的理解的重要性相同,每个patch位于图像的什么位置,对于整个图像的特征提取同样非常关键。所以在把各个patch的向量序列送入Transformer处理之前,需要为这些向量增加明确的位置信息。 位置向量的维度与输入矩阵( 如197 x 768)的形状完全一致,这一步操作就是直接将这个位置向量矩阵加到(Addition)之前输入的 Patch 向量矩阵上。 !image.png 至此,输入图像数据经过前述的一系列处理以后,就已经做好了进入Transformer Encoder中进行注意力机制运算的准备了。一张 224 x 224 分辨率的图像变成了一个增加了位置编码的 (197,768)序列。 4. Transformer Encoder 在Transformer Encoder中的处理逻辑与流程与自然语言文本的处理没有没有任何区别。整个Transformer Encoder部分包含有连续L个相同的Encoder Block: !image.png 每个Transformer Block中则包含有多头注意力模块、前馈网络模块、残差与归一化层等结构。整个Transformer Encoder的工作逻辑与计算流程可以参考[[一文彻底搞懂Transformer模型的Encoder结构与计算流程]],而其中的多头注意力模块的计算流程可以参考[[如何理解Transformer架构中的多头注意力机制?]]。 每个Encoder的输入和输出数据维度保持不变,因此经过全部 Encoder 层的处理后,其输出依然是一个 (197, 768) 的矩阵。 5. 分类头MLP Head 如上所述,Transformer Encoder模块的输出为一个(197,768)的矩阵,其中包含了输入图像的完整特征提取信息。而在分类任务中,实际上只需要取出第一个位置CLS Token 向量(即我们在第二步所额外增加的那个向量)。 对于图像分类任务而言,分类头的作用本质上就是信息压缩与映射:将高度抽象的 768 维特征向量,转化为对具体类别的概率预测。因此通常情况下,ViT 模型的分类头一般由一个简单的多层感知机(MLP)组成。例如一个标准的分类头网络的结构就是两个线性层中间加一个激活函数以提升非线性映射能力,最后再通过Softmax概率化: - 从Transformer Encoder输出的(197, 768)矩阵中,切片取出索引为 0 的向量。 - Linear (768 → 768) - tanh 或 GELU 激活函数 - Linear (768 → K),K 是分类数量 - Softmax 测试预训练的ViT图像分类模型 以下代码通过使用Huggingface的Transformers库下载一个基于ViT架构的图像分类预训练模型,并基于这个预训练模型执行前向推理,得到最终的推理结果: 其中: - ViTImageProcessor用于对输入图像进行预处理,这个预处理包括resize、归一化等,最终把原始图像转换为ViT模型可以接受的格式。这个预处理过程有些类似于自然语言文本在进行Transdformer处理之前的分词流程。 - ViTForImageClassification是真正的ViT分类模型,其接收经过ViTImageProcessor预处理的图像Tensor输出,输出分类结果的logits数据,因此最终需要自行通过softmax得到分类识别的概率。 参考资料 - Vision Transformers (ViT) Tutorial: Architecture and Code Examples | DataCamp - Building a Vision Transformer Model From Scratch | by Matt Nguyen | Toward Humanoids | Medium - Vision Transformer (ViT) Architecture - GeeksforGeeks
如何理解Transformer架构中的多头注意力机制?
自注意力机制简介 Transformer模型的自注意力机制,在Google AI研究员Vaswani 2017年的论文《Attention is All You Need》中首次被提出,该机制随之成为transformer模型的核心思想,而transformer模型又成为各种大语言模型LLM和机器视觉模型VLM的中心所在。自注意力机制的伟大之处在于,它赋予了模型全局视野:让图像中的每一个像素(或特征点)都能直接与其他所有像素发生联系(ViT),让序列化的自然语言段落中的每一个字词(Token)直接与整个段落中的所有其他字词产生联系(LLM)。 以下面这个例子来进行说明: !image.png 对于上面的这个简单的句子的理解来讲,要理解这个句子,当我们看到sat这个单词的时候,注意力会自动放在与之密切相关的cat和mat这两个单词,而其他单词对于sat的理解意义不大,基本可以忽略。因此,所谓的注意力就是:基于语义或语法关系,帮助模型决定整个文本中其他单词与其正在处理的单词之间的相关性和密切程度。当两个单词之间的关系越密切,其权重就越高,否则其权重就越低。 预处理 当然,在进行自注意力运算之前,首先要把序列化的文本数据进行预处理。对于文本数据而言,数据的预处理环节主要包含三个部分: - 分词(Tokenization)。把整个段落和句子从语义上切分为一个个独立的单词(token)。 - 向量化(Embedding)。把以上切分出来的token向量化,大致的方法就是类似于查字典,基于token找到与其对应的固定长度的向量。 - 增加位置编码(Positional Embedding)。 此外还要给每个token的向量加上与其长度相同的位置编码,位置编码反映了这个token在整个段落和句子中的位置。 !image.png 至此,整个段落的序列化文本就被转换为一个个独立的token,并且每个token都对应于一段固定长度(如512,768等)、增加了编码信息的向量。而这些向量就是下一步进行自注意力运算的输入数据。 Q,K,V矩阵 自注意力机制最核心的设计在于将输入向量变换为三种不同的角色:Query(查询)、Key(键)和Value(值)。 我们可以使用搜索引擎来对这三种不同的角色进行类比: - Query (): 你在搜索框输入的“关键词”(我想找什么?)。 - Key (): 数据库里视频的“标题/标签”(我有什么特征可以被匹配?)。 - Value (): 搜索结果对应的“视频内容”(如果匹配上了,我能提供什么信息?)。 从数学运算流程的角度去看,自注意力运算的第一步,就是把前述的序列化数据所转换的向量作为输入,分别乘以三个权重矩阵 ,得到 Q, K, V 向量。 !image.png 假设输入的段落文本被切分为64个token,每个token的向量长度为512,那么输入数据的维度就是(64,512),三个权重矩阵 的维度均为(512,512),因此最终得到的 Q, K, V 这三个向量的维度与输入数据的维度相同,仍然是(64,512)。 !image.png 对每个Token的输入向量,分别执行以上的QSK公式的计算,得到三个与之对应的Q、S、K向量。然后在对每个token进行处理的时候,可以通过自己的 Q 向量和其他token的 K 向量之间的点积来计算其相似度,以此来衡量两个元素之间的相关性,即 。 例如,针对上面的句子,使用“sat”的查询向量 Q 与语句中其他的每个单词(包括其sat本身)的键向量 K 进行以上计算,其结果是一组相似性分数(这些是标量),这些分数告诉我们每个单词与“sat”之间的相关程度,而这些分数就是所谓的注意力分数。 为了防止以上计算出来的注意力分数梯度消失或爆炸,还要除以一个缩放因子 (其中 是特征维度,如512),然后再通过 Softmax 转化为概率分布(权重之和为 1),最终用这些权重去乘以 V,就得到最终的输出。 以上复杂的计算过程,最终得到了一个在当前的上下文中表示“sat”这个单词的新的向量表达形式,这个新的向量信息中综合考虑了整个句子中所有单词的语义,但主要来自于 cat、mat 等与sat相关的token。 这个计算过程对于句子中的每个token都会进行计算。每个token的语义提取都会同时关注其他所有的token,甚至关注其自身,这就是为什么我们称之为自注意力机制。 !image.png 注意力计算公式如何体现 Q, K, V 矩阵的关系? 以上在解释 Q, K, V 向量时提到: - Q 是Query,代表当前token要从整个段落包含的所有token中要查询的语义是什么。 - K 是Key,代表了整个段落中所有token所具有的特征列表。 - V 是Value,代表了 Q和K 匹配的情况下,其他token能够给当前token所提供的辅助语义是什么。 而QKV矩阵的计算公式则是: 那么以上的这两者之间究竟是如何建立关系呢? 在以上公式中,首先是计算 。对 Q 向量(我想找什么)与 K 向量(我有什么特征)做点积运算时,本质上是在计算:“当前的 Query 描述的需求,与这个 Key 提供的属性有多契合?” 从矩阵计算的角度上讲, 是整个文本段落中token的总数量, 是每个token的向量长度(如512,768等),那么整个段落所有token对应的 Q 是 的矩阵, 是 的矩阵,而所有token的 相乘后得到的 矩阵(这个结果实际上就是所有token之间的注意力分数矩阵),每一行就代表了某个 Token 对整个段落所有token在段落中精确语义解析的匹配分数表。 以上经过点积算出来的分数可能是任何实数,没法直接用来合并信息。此时 Softmax 登场了:Softmax 会放大分数高的,抑制分数低的,然后再归一化,把所有的分数转化成 0 到 1 之间的概率,且总和为 1。 最后一步就是把经过以上处理的注意力分数矩阵,与所有Token的 V 向量相乘。从数学计算的本质上,这是一个加权求和的过程。其输出是经过权重(权重就是注意力分数矩阵)过滤后的、包含了整个段落上下文信息的新特征向量。 > 总的来说,Q, K, V 向量及其注意力计算公式的本质,就是要对当前Token在该段落中所代表的精确语义来进行提取。具体的计算过程,则是首先计算当前token的 Q 向量与段落中所有Token(包括当前token)的 K 向量之间的匹配程度,其计算结果就是在解析当前token精确语义的过程中,段落中所有token的相对重要程度(即注意力分数),然后基于其重要程度提取出所有token的 V 向量,其加权和就是当前token在当前这个段落中的精确语义。 至此,自注意力机制的逻辑概念,以及完整的计算过程就解释完毕了。 多头Multi Head? 接下来思考一个问题:如果只用一组 Q, K, V(单头),当前 token 每次只能从一个维度去提取语义。 但在复杂的文本语义处理中,一个token元素可能在不同的维度上具有多种关系。 例子: “小明在图书馆借了一本厚厚的书。” 那么在提取“书”这个词的精确语义时就包含有多个不同的维度: - 语法方面:关注到“借”,识别出“书”是动词的宾语。 - 属性方面:关注到“厚厚的”,提取出“书”的物理特征。 - 位置方面:关注到“图书馆”,提取出动作发生的场景。 _因此,如果只有一个头,模型必须在同一组 Q, K, V 权重中同时编码语法、属性和位置场景信息。这会导致注意力被稀释,模型很难同时精准地捕捉这些不同维度的特征。而多头机制的提出,就允许模型在不同的子空间去独立学习这些不同维度的关系。_ 多头注意力在具体的操作和计算流程上其实是比较简单的,计算流程上与以上描述的单头注意力机制完全相同,只不过把输入的特征向量切分为多个相同长度的子向量而已: - 拆分 (Split):将输入的高维特征向量(例如 512 维)切分成多个低维的子向量(比如 8 个头,这样的话每个头长度就是 64 维)。 - 并行计算 (Parallelism):以上切分出来的 8 个头独立、并行地进行前述的 Q, K, V 计算。 - 拼接 (Concat):把 8 个头各自的计算输出(每个都是 64 维)重新再拼成一个 512 维的长向量。 - 投影 (Linear):最后乘以一个权重矩阵 ,让这些来自不同维度的计算结果进行最终的融合处理。 !image.png 参考资料 - Understanding Multi-Head Attention in Transformers | DataCamp - What is Attention and Why Do LLMs and Transformers Need It? | DataCamp
一文彻底搞懂Transformer模型的Encoder结构与计算流程
下图是一个标准的Encoder Only类型的Transformer架构的示意图: !image.png 总的来说,Encoder Only类型的Transformer架构的执行流程依次为: - 数据预处理环节,包括输入数据的向量化(Embedding),增加位置编码等。 - 连续N个Encoder Block。 - 数据的输出环节。 对于Encoder Only Transformer架构而言,数据的输出环节很简单,例如上图所示的Linear+Softmax输出,所以本文的重点是前两个部分的计算流程。 1. 数据预处理 对于自然语言处理类型的Transformer架构来说,数据在进入Transformer架构的Encoder和Decoder中处理前,至少要完成以下工作: - 分词(Tokenization),把一个完整的句子或者完整的语句段落,分为一个个有独立语义的字或单词(也就是所谓的token),这一步可以使用目前已经非常输出的分词工具来完成。 - 基于以上分词的结果,把一个个独立的字或者单词转换为相同长度(如512,768等)的向量,这一步操作就是所谓的嵌入(Embedding)。经过这一步向量化后,每个token都对应于一组相同长度的向量。 - 以向量长度768为例,如果要进行处理的文本包含有100个token,经过这一步的向量化后,这段文本就会被转换为(100,768)的向量。 - 在之前的句子中给向量化后的token增加位置编码(Positional Encoding),位置编码的长度与Token的向量长度相同,所以每个token的向量直接与其位置编码的向量直接相加即可,相加以后的数据维度不变。 - 仍然以前面的(100,768)维度的向量为例,增加了位置编码后,其维度仍然是(100,768)。 下图是一个对于文本"How are You"进行数据预处理的完整流程: !image.png 经过以上的数据预处理环节以后,一段文本(分割为N个token,每个token的向量长度为dimension)就会把转换为(N,dimension)维度的向量,做好了进行Encoder处理环节的准备了。 2.1 Multi-Head Attention中的Q、K、V矩阵 众所周知,Transformer 的核心是自注意力机制(Self-Attention),它的工作逻辑是计算输入序列中每一个元素与其他所有元素之间的相关性。这个自注意力机制的关键组件是:Q、K、V,可以把自注意力想象成一个“信息检索”过程: - Query (Q):我想找什么?(当前像素/块的特征) - Key (K):我有什么?(其他所有像素/块的特征标签) - Value (V):我能提供什么?(实际的内容信息) 只是看以上的定义的话,感觉还不是很直观,究竟这个所谓的Q、K、V在数据处理的流程中是如何发挥作用呢?直接说结论:如果每个token对应的向量的长度是dimension的话,Q、K、V就是三组独立的维度为(dimension,dimension)的权重参数矩阵,这三组权重矩阵就是要在训练过程中要训练的参数。 也就是假设一个 Token 的向量 的维度是 ,那么 就是三组维度为 的矩阵,那么针对这个 Token 向量 x 进行Q、K、V的的计算过程就是直接把Token向量与三个参数矩阵各自相乘,这样就可以将输入的 Token 向量映射到新的空间: 如下图所示:“Data visualization empowers users to”这个文本被分为6个词(Token),每个词对应的向量长度是768。在这种情况下,每个token的维度就是(1,768),而 这三个权重矩阵的维度分别是(768,768),三组矩阵合在一起的维度就是(768,2304)。 !image.png 总结起来,在这一步,每个token对应的(1,dimension)维度的向量,经过以上计算以后,就会变成三组独立的(1,dimension),分别对应于这个Token的Q、K、V向量,这三组向量合并起来的维度就是(1,3 x dimension)。 2.2 Multi-Head Attention中的Attention计算流程 经过上一步的处理以后,就有了一段文本中所有单词的Q,K,V向量。下一步要进行的计算就是:基于Q向量和K向量,提取出来对应的V向量。 首先对一段文本中所有Token之间的Q向量和K向量进行点积计算,通过这种方式计算出来第 个 Token 对第 j 个 Token 的关注程度: 如果 和 的向量方向越接近,其计算得到的分值就越高。经过以上计算以后(假设这段文本的token数量为N),得到的分数矩阵的维度是(N,N)。 而为了防止以上的计算结果梯度爆炸,并且需要把以上的计算结果转化成概率,还需要对以上计算结构额外执行两步计算: - 缩放:对以上每个除以 。其中的 是一个缩放因子,它的存在是为了让模型在深层堆叠时依然能够稳定训练。对于一个设计好的Transformer模型而言, 是一个常数,一般是数据维度处于多头数量。 - Softmax:将以上的分数矩阵变成 之间的权重矩阵 。 以上两步的计算流程如下图中的红框所示:以下经过softmax处理后的矩阵 的维度仍然是(N,N),对于下图而言,这段文本只有6个token,所以这个矩阵的维度是(6,6)。 !image.png 有了以上的权重矩阵 ,就可以用这个权重矩阵去融合所有的 Value (即V矩阵): 如下图的红框所示:假设这段文本中的token数量为N,每个token的向量长度为dimension,这部分计算就是一个(N x N)维度的矩阵(以上通过Q和K计算出来的权重矩阵)与一个(N x dimesion)维度的矩阵的乘积,最终的结构仍然是(N x Dimension)维度。 !image.png 至此,Encoder模块中的自注意力Q、K、V矩阵的计算流程就算是结束了,整个Encoder的计算以(N,dimension)维度的数据作为输入,计算完成以后仍然是一个(N,dimension)维度的输出数据,可见数据维度并没有发生变化。 等等!还有个问题,前面对单个Token进行的Embedding嵌入转换的计算过程中,明明提到每个token的向量长度是768,但是上面在进行最后一步V矩阵运算的时候明显可以看到V矩阵的维度是(6,64)而不是(6,768),最终的输出也是(6,64),而不是(6,768)?这就是牵出多头(Multi Head)的概念了。 2.3 Multi-Head Attention中的Multi-Head 下面看这个完整的Multi-Head Self Attention Block的框图:左侧是所有输入token的Q、K、V矩阵,数据维度为(N,3 x dimension);右侧为这个多头注意力模块的处理结果,维度为(N,dimension)。 !image.png 下方用红框的标记,是指整个注意力模块的运算被分为12个独立、并行的计算模块,同时进行计算,最后再把计算结果合并在一起,这就是所谓的多头。 举例而言,上图中包含有6个token,每个token的数据维度为768,那么对于整个自注意力模块而言,其输入为三个独立的(6,768)维度矩阵,即6个token各自的Q、K、V向量。那么在以上的多头处理上,计算流程会分为12个头,把输入数据也同样分为独立的12组,对于每个Token的Q、K、V向量而言,每个长度768的向量都被分为12段,每段的长度就是768/12=64(这就是上面V向量计算中所出现的64)。这样的话,对于每个head的计算而言,其输入数据的维度就变成了三个(Q,K、V向量)独立的(6,64)维度矩阵,单个head的计算输出数据也是(6,64)维度,所有head计算完成后再合并在一起成为(6,768)维度。 > 这就是所谓的多头:就是把输入的每个token的Q、K、V向量平均分成N份,这N份数据同时进行计算,计算完成后再合并在一起。最终输入数据和输出数据的维度保持一致。 可结合下面的这段代码更深入的理解整个多头自注意力机制的工作流程: 3.0 Feed Forward Encoder模块中的前馈网络相对就比较简单了,实际上就是两个线性层中间加一个非线性激活函数来实现输出数据的非线性变换而已,其输入输出数据的维度仍然保持为(N,dimension): 4.0 完整的Encoder Block 一个完整的Encoder Block的框图如下图所示: !image.png 可以看到,一个独立的encoder block,实际上就是一个多头注意力模块及其残差块,加上一个前馈网络模块及其残差块。基于以上对多头注意力模块和前馈网络的模块,理解下面的encoder block的执行流程也就比较容易了: 至此,单个Encoder Block的完整计算流程就算是比较完整了。注意,对于单个encoder block而言,其输入数据和输出数据的维度都是相同的,即都是(N,dimension),这样也就很容易实现把多个encoder block按顺序叠加在一起形成完整的transformer架构。 5.0 完整的Transformer Encoder架构 再回过头来看完整的transform encoder的架构,理解起来就比较简单了: - 首先是数据预处理部分。输入的文本等其他序列数据,经过分词和向量化的处理后,把每个token转换为固定长度的向量,然后再叠加位置编码信息得到相同维度的带位置编码的向量,送给Encoder进行处理。数据维度为(N,dimension)。 - 接下来中Transformer Encoder架构的核心所在,即连续多个Encoder Block的叠加,每个encoder block的输入输出数据维度均为(N,dimension)。 - 最后是输出层,这部分就是根据应用需求来进行针对性的设计了。 !image.png 参考资料 - Transformer Explainer: LLM Transformer Model Visually Explained - How Transformers Work: A Detailed Exploration of Transformer Architecture | DataCamp - Transformer Architecture : Part 1- Encoder | by Abhishek Jain | Medium - Working of Encoders in Transformers - GeeksforGeeks
YOLOv5模型的损失函数计算总结
本文以YOLOv5模型的v6.0/v6.1版本为基础,详细总结和解释了损失函数各个部分的计算流程。 YOLOv5模型的损失函数由三个部分组成: - 目标框定位损失,Box Loss - 目标置信度损失,Obj Loss - 分类损失,Cls Loss 其中,边界框回归损失采用了CIoU(Complete IoU)损失,而目标置信度和分类损失均使用二元交叉熵(BCE)损失。 目标置信度损失Obj Loss YOLOv5的目标置信度损失Obj Loss是唯一一个正样本和负样本(背景)都要参与计算的损失项。它的核心任务是教模型回答一个问题:“这个网格里到底有没有物体?如果有,这个预测框的可信度有多高?” 此外,在进行损失计算的过程中,build_targets所返回的正样本列表中,所有正样本的置信度标签值并非固定是1,而是作为正样本的这个预测框与真值框的IOU。也就是说,正样本与真值框之间的重合度越高,这个正样本的置信度标签值也就越高。这就是YOLOv5损失计算所采用的软标签(Soft Label)策略: - 所有的负样本 Target () = 0 - 所有的正样本 Target () = CIoU Score 整个目标置信度损失 L_{obj} 的计算可以分为四个步骤。 1.预测值 (p_{obj})的预处理 YOLOv5模型的前向推理流程在三个特征层(P3, P4, P5)输出预测张量。对于置信度通道,其推理输出的是未经激活的数值。因为计算置信度损失值使用二元交叉熵BCE算法,因此要首先对模型输出的 Logits 值进行 Sigmoid 激活,将数值映射到 (0, 1) 区间。 2.构建目标值 (t_{obj}) 如YOLOv5模型损失函数的背景知识总结一文所总结的,YOLOv5模型损失函数计算类ComputeLoss中的build_targes负责基于训练样本的真值框列表以及各个检测层的Anchor列表构建并返回一个正样本列表。 此处计算目标置信度损失需要构建目标值 : - 首先初始化一个与预测特征图形状完全一样的全零张量。 - 然后遍历以上build_targets返回的所有正样本: - 计算当前作为正样本的预测框(Pred Box)与真值框(GT Box)的CIoU值。 - 然后将该 CIoU 值填入全零张量中对应的坐标位置 (b, a, gj, gi)。 - 最终得到的 的结果就是一个大部分位置是 0(背景),少数位置是 浮点数(前景 IoU)的 Target 张量。 3.计算二值交叉熵 (BCE Loss) 上面有了预测值 和 目标值 ,就可以计算其二元交叉熵损失了。YOLOv5 使用 PyTorch 的BCEWithLogitsLoss。对于针对单个预测框而言,其计算公式是: - 对于负样本 ():前半部分消失,只优化后半部分 ,迫使 趋近于 0。 - 对于正样本 ():前后两部分都存在,迫使 趋近于 CIoU 值。 此外,如YOLOv5模型损失函数的背景知识总结所描述的,如果通过超参数开启了Focal Loss功能,还需要对其进行Focal Loss处理。目的是压制负样本背景框的损失,提升正样本的损失权重。 以上公式计算的是一个预测框的置信度损失值,而一个检测层的置信度损失,则是该层输出的所有预测框计算置信度损失的总和: 其中 B 是 Batch Size,G 是网格尺寸,A=3 是每个网格设置的 Anchor 数量,默认为3。 4.层级加权平衡 (Layer Balancing) 因为小目标的特征信息少,背景干扰多,回归难度大,模型需要更强的梯度信号来学习如何检测它们。所以在对三个检测层的置信度损失求和得到总的置信度损失时,还需要对各个检测层的损失值加权求和:对输出小目标特征信息的 P3 层乘以更高的权重,对输出大目标特征信息的 P5 层乘以较小的权重,以强迫模型更加重视高分辨率特征图(P3)上的预测结果,从而提升小目标的召回率和准确性。 再次强调一下,只有置信度损失的计算流程中,会被负样本考虑在内,以下的类别损失以及定位框损失均不考虑负样本。 类别损失Cls Loss 类别损失的目标非常清晰:对于被确定为正样本的预测框,它必须正确识别出该物体属于哪个类别。 类别损失 的计算流程具有以下两个关键特征: - 只计算正样本: 和定位框损失 一样,只有那些被分配了真实框的正样本(也就是build_targets所返回的正样本列表)才参与类别损失的计算。模型推理输出的两万多个负样本检测框(即背景框)的类别损失为零,不参与梯度回传。 - 多类别支持: 其目标是从所有可能的 C 个类别中,能够正确识别出目标的具体类别。 在YOLOv5中,类别损失同样采用标准的二值交叉熵损失BCE,但在工程实现上有一些巧妙的处理,使其能应用于多类别分类。其详细计算流程包含以下四个步骤。 预测值 ()的预处理 与置信度损失的计算流程相同,首先是要定位正样本。基于模型前向推理的输出张量以及模型在各个预测层所预设的Anchor框列表,调用build_targets函数提取出所有正样本的列表。后续类别损失值的计算只在这些正样本上进行计算。 因为要进行BCE计算,从build_targets返回的正样本列表中得到其中所包含的类别预测 Logits,然后对这些 Logits 进行 Sigmoid 激活。模型针对每个预测框,都会输出一个 C 维度的向量,其中 C 是总类别数(例如 COCO 数据集 C=80)。对每个类别输出的预测值都通过 Sigmoid 激活,将每个类别的分数独立地映射到 [0, 1] 区间,从而得到每个类别的预测概率_ 。 构建目标值 () 构造一个与正样本 维度相同的目标张量 。其维度为。对其中的标签赋值: - 对于其中的每个正样本,找到它对应真值框所标注的真实类别 k,在 向量的第 k 个位置上赋值 1,其余 C-1 个位置全部赋值0。 最终的结果就是,我们得到了一组稀疏的 one-hot 编码来作为下一步进行BCE计算的目标值。 在YOLOv5的损失计算流程中,为了防止模型对标签过拟合,其在训练中还会使用一种标签平滑(Label Smoothing)的技术。 大致就是将真实类别所设置的 t=1 的目标值稍微降低,将 t=0 的目标值稍微提高。 - 例如,对于某个正样本的类别标签设置而言,不是设置 ,而是 。 其目的是鼓励模型预测的概率不要过于绝对(如 1.0),从而提升模型的泛化能力和鲁棒性。 计算二值交叉熵 (BCE Loss) 的计算依赖于 BCE,但BCE的计算是针对每个类别独立计算的,然后对所有类别的损失求和。 假设 i 是正样本的索引,k 是类别的索引, 是模型预测的第 i 个样本属于类别 k 的概率, 是真实标签(0或1)。那么针对单个样本的单个类别 k 的损失公式就是: 由于 t_{i,k} 只有在真实类别 处为 1,在其他 C-1 个类别处为 0,这个计算可简化为: - 对于真实类别 :损失为 ,迫使模型提高对正确类别的预测概率。 - 对于错误类别 :损失为 ,迫使模型降低对所有错误类别的预测概率。 类别总损失的聚合 最后,将所有正样本在所有类别上的损失进行聚合(将每个正样本在 C 个类别上的损失加起来),就得到最终的 。 目标框定位损失Box Loss 在YOLOv5中,定位损失采用的是 CIoU Loss(Complete IoU Loss,完整交并比损失)。它的核心目标是:对于被确定为正样本的预测框,准确地调整其位置、大小和长宽比,使其尽可能地与真实框(GT)重合。 相比于普通的IOU指标,CIoU Loss 不仅仅关注预测框与真值框的重叠面积,还引入了中心点距离和长宽比一致性这两个惩罚项,从而提供了更全面、更稳定的梯度信号,极大地提升了模型的定位精度。CIoU Loss 的完整定义是: 其中, 是 CIoU 的惩罚项,它由中心点距离项和长宽比一致性项构成。 的计算流程如下所述。 筛选正样本并解码 这一点与以上计算类别损失相同。首先是定位正样本,即仅从 build_targets 函数生成的正样本列表中,取出对应的预测框(Logits),然后将模型输出的偏移量解码成真实的归一化坐标 (x, y, w, h)。 与类别损失相同,定位损失的计算同样只考虑build_targets所返回的正样本列表,不考虑其他的背景负样本。 计算 IoU 和 CIoU 基于每个正样本预测框,计算这个预测框与真值框之间的 IoU ,以及针对CIoU计算流程的距离和长宽比惩罚项。最终将所有项代入 CIoU Loss 的计算公式,计算每个正样本的 CIoU 损失 。 目标定位框的损失值聚合 对所有正样本的 值进行求和(通常会进行平均)得到总的定位框损失 。 总损失 经过以上三个部分的独立计算,就可以得到一个批次(batch size)的目标定位框总损失 ,目标置信度总损失 以及分类总损失 。整体的损失则是这三个损失的加权和: 其中的 , 和 分别对应于三个损失的加权权重,在超参数文件中进行设置,分别对应于超参数文件中的box,obj和cls设置项: 至此,针对训练过程中一个批次输出图片推理输出的完整损失值就计算出来了。
YOLOv5模型损失函数的背景知识总结
本文以YOLOv5模型的v6.0/v6.1版本为基础,详细总结和解释损失函数的计算流程及代码实现中所设计到的各项背景知识。 如下图所示的YOLOv5的检测头部分的结构,YOLOv5模型每个网格输出的检测框数据与其前几代类似,每个检测框包含的信息(5+NC)就是三个部分:检测边界框的坐标位置,检测的置信度,类别判断概率。 !image.png 相对应的,其损失函数也就由三个部分组成: - 边界框回归损失,Box Loss - 目标置信度损失,Obj Loss - 分类损失,Cls Loss 其中,边界框回归损失采用了CIoU(Complete IoU)损失,而目标置信度和分类损失均使用二元交叉熵(BCE)损失。 二元交叉熵(BCE) 首先以最简单的二分类问题解释二元交叉熵BCE的计算流程。假设训练数据的真实标签为 (为0或1),模型预测为 (为0到1之间的概率值),那么BCE损失函数的计算公式为: _因为对0到1之间的小数进行log计算,其结果永远小于0,所以以上的BCE公式的计算结果总是正数。对于log(x)的计算而言,当x=1时,log(x)=0;当x在0到1之间时,log(x)小于0且随着x变小,log(x)越来越小;当x接近0时,log(x)趋向于负无穷大。_ 因此: - 当标签 时,以上损失函数的计算可以简化为 。此时预测概率 越高,损失值越小越接近0。 - 而当 时,其损失可以简化为 。此时预测概率 越低,损失值越小越接近0。 如上所示,进行交叉熵计算的预测值 应该在0-1之间,所以对于YOLOv5模型三个检测头所输出的logits数据而言,在执行BCE损失计算之前,需要先对logits值进行sigmoid计算,把sigmoid的计算结果再进行BCE计算,因此实际上YOLOv5源代码中计算目标置信度损失和分类损失均使用了BCEWithLogitsLoss函数: BCEWithLogitsLoss函数的实现实际上对应于以下公式: 其中, 是模型输出的logit原始值, 是sigmoid激活函数, 是目标值。 正样本损失计算的权重控制 需要注意的是,以上的BCEWithLogitsLoss函数定义通过超参数的形式传递了两个pos_weight参数:cls_pw和obj_pw,默认情况下,这两个参数在超参数文件中的值均为1.0。此时,以上BCE计算公式中,正样本(t>0)的损失会被乘以 γ,负样本(t=0)的损失保持不变,实际上的等效公式变为: 因此,如果在模型训练的过程中,需要对类别损失和置信度损失计算过程中的正样本损失值的比重进行调节,就可以适当调节cls_pw和obj_pw这两个参数。设置这两个参数为大于1.0的值,将会强化训练正样本的损失值;否则将会弱化训练正样本的损失值。 - 例如,如果训练中观察到高漏检率或正样本预测置信度偏低的情况,可以尝试设置 obj_pw > 1.0(如 1.5~5.0),以加强正样本惩罚。而反之,如果出现过多较多的假阳性即把环境误判为正样本的情况,可以略微降低obj_pw( 总的思路是,如果模型对这个样本很有把握(置信度比较高),在进行损失计算时就把这个样本的Loss权重降到接近0;如果对这个样本没有把握(置信度比较低),损失计算时就保留这个样本的Loss。 具体的计算上,Focal Loss 在标准的 BCE 结果上乘了一个系数 : 这里 (gamma,也就是超参数fl_gamma的设置值) 是一个超参数,通常取值为 2。 - 对简单样本来说,模型的预测输出非常准,比如 = 0.99。在这种情况下按照以上算式计算出来的Focal Loss系数为: 。其结果就是原本的 Loss 被缩小了 10000倍,对梯度几乎没有贡献了。 - 而对于困难样本而言,模型的预测很差,例如 。其Focal Loss的系数为: 。结果就是Loss 只是稍微变小了一点,但依然保持了较大的数值,保留了对模型的指导意义。 当然,在 YOLOv5 中,Focal Loss 只应用于 Objectness Loss ()和 Classification Loss ()的计算,不用于 Box Regression Loss。
SAM3模型环境搭建以及图片+视频分割测试
本文详细记录了Meta在2025年11月份开源的图像分割模型SAM3的环境搭建,以及基于其中的Sample进行图片和视频分割的流程。 SAM3(Segment Anything Model 3)是Meta发布的最新版本图像分割模型。它延续了前一代SAM模型的设计理念,进一步提升了在图像分割任务中的性能和适应性。 !image.png 源代码及其权重文件下载 首先是从sam3的Github仓库中下载源代码:facebookresearch/sam3: The repository provides code for running inference and finetuning with the Meta Segment Anything Model 3 (SAM 3), links for downloading the trained model checkpoints, and example notebooks that show how to use the model.。 除了在Github上下载源代码以外,还需要提前下载好模型的权重文件sam3.pt(3.2GB)。按照Github仓库Readme的介绍,正规的流程应该是在Huggingface上提出申请(facebook/sam3 · Hugging Face),申请通过后就可以从Huggingface上进行下载,但是这个申请经常会莫名其妙的reject掉申请,而且不给出任何原因,不清楚是怎么回事: !image.png 所以可以通过其他办法提前把这个权重文件下载到。知乎一位UP主提供了权重文件的下载路径:互链高科,下载速度非常快。因此可以从以上站点先下载权重文件,放到sam3工程的根目录下。 环境搭建 开发环境的需求如下: - Python 3.12版本及其以上 - Pytorch 2.7版本及其以上 - Nvidia显卡,CUDA12.6版本及其以上 按照项目的readme文件创建sam3的conda环境并且激活该环境: 然后安装pytorch,以及进入sam3的源代码安装sam3 package:(我的显卡是5070Ti,Cuda版本是12.9,所以直接按照pytorch官方的命令进行以下安装即可) 需要注意:在sam3环境中要运行以下的静态图片以及视频测试的例程时,会提示No module named 'triton'错误,此时还需要额外装一个triton-windows的包: 图片/视频分割测试 静态图片测试 以下代码对一张静态图片中所包含的熊目标进行检测,并画出结果。 > 以上代码需要在官方源代码plot_results代码的最后增加一句plt.show()才能把图像真正显示出来。 以上代码执行的图像分割效果如下所示: !image.png 以上代码中,processor执行检测和分割操作返回三个变量masks、boxes和scores: - masks:与原图分辨率相同的单通道mask图像,在这个mask图像中以不同的色彩标记各个检测目标的像素位置,有几个分割目标就有几张独立的mask图像。 - boxes:对指定目标类型的检测框,在图像中检测到几个目标,就有几个box,每个box中都包含有检测到目标的检测框的左上角和右下角的坐标。 - scores:检测到的各个目标的置信度。 下图是以上熊图像中使用bear来进行检测的输出信息,可结合以下信息理解三个输出变量的含义: 视频测试 基于对Bilibili Up主Coding茶水间的教学视频(参考资料3)继续测试SAM3的视频分割功能。这个测试需要比较高的显卡配置,我的笔记本32GB内存,RTX5070Ti显卡,跑一段22s的视频足足花了6分40秒,所以做这个测试还是尽量选一些比较短的视频来进行,否则每次调试等待的实际太磨人了。 以下是使用SAM3对视频文件中的每一帧图像基于提示词的提示进行指定目标的检测和分割,整体流程沿用了Coding茶水间的代码逻辑,但是再完成视频文件中每一帧图像分割以后,对分割得到的mask图像帧与原始图像帧之间进行叠加生成分割后保存的图像流程做了一些简化,使用render_masklet_frame来进行mask与原始图像帧的合并操作,代码理解上更简单一些。 本质上,以下代码经过propogate_video_frames处理以后,就得到了目标分割图像mask的列表sam3_output_frames,这个结构中包含有所有检测到指定目标的图像帧列表,video_file_frames结构中保存了从视频文件中所读取到的所有图像帧列表,然后在循环中把分割图像帧mask与原始图像帧进行合并操作,就得到了两帧合并的图像。而没有检测到目标的图像,依然使用原始图像帧,最终把所有的合并图像帧保存在一个mp4文件中。 关键代码如下所示: 参考资料 - facebookresearch/sam3: The repository provides code for running inference and finetuning with the Meta Segment Anything Model 3 (SAM 3), links for downloading the trained model checkpoints, and example notebooks that show how to use the model. - SAM3模型来了,手把手带你运行SAM3模型代码,SAM3模型初探!_哔哩哔哩_bilibili - SAM3源码实战之二,手把手带你执行视频分割处理。_哔哩哔哩_bilibili - 连载1:Meta SAM3开发实战——最简图像分割 - 知乎
机器学习领域的Python Web UI框架Gradio
Gradio是目前机器学习和数据科学领域最流行、最易上手的 Python Web UI 框架之一。简单来说,它的核心使命是:让你用极短的 Python 代码(通常只需几行),瞬间为你的机器学习模型构建一个可视化的演示界面。它特别适合用于快速原型设计、向团队/客户展示模型效果,或者在 Hugging Face Spaces 上托管 Demo。 Gradio的官方网站是:Gradio !image.png Gradio的重点特性 - 纯 Python 体验 (No HTML/CSS/JS): 不需要任何前端知识。所有的组件(输入框、按钮、滑块、图像上传等)都可以通过 Python 类调用。 - 极速构建: 对于标准的“输入 -> 模型 -> 输出”流程,通常 3-5 行代码即可完成,非常简单,容易上手。 - 丰富的组件库: 针对机器学习场景内置了大量专用组件,如 Image(图像)、Audio(音频)、Video(视频)、Dataframe(表格)、Code(代码)等。 - 一键分享 (Shareable Links): 运行 .launch(share=True) 后,Gradio 会自动生成一个公开的临时 URL(有效期 72 小时),你可以直接发给客户或同事,让他们在自己的浏览器里测试你的模型(虽然模型实际运行在你的电脑上)。_这个功能对于做一些demo的临时演示而言确实太方便了,不再需要浪费时间去搭建基于云服务的演示环境了。_ - 生态整合: 它由 Hugging Face 收购并维护,因此与 Hugging Face 的生态系统(Spaces, Transformers)结合得非常紧密。 > 以上的这个一键分享的功能对于模型demo的演示和试用实在是太方便了,只需要启动web应用的时候,传递一个share=true的参数,外网就可以直接通过系统生成的一个public URL访问,不再需要浪费时间去搭建基于云服务的演示环境了。* 开发环境搭建与Hello World gradio的开发环境安装非常简单,直接使用pip安装即可,如果需要在Jupyter notebooks中使用的话,还需要安装ipykernel: 总体上就,Gradio构建UI界面主要有两种构建方式: - gr.Interface:提供最高级的抽象,使用起来最简单,比较适合单功能。 - gr.Blocks:更加灵活,如果需要更复杂的布局(例如多列排版、多个标签页、控制组件间的数据流向),可以使用 Blocks 像搭积木一样控制页面的每一个显示细节。 以下代码以两种方式分别演示基本的Hello World功能: 以上代码执行后,就可以在本地浏览器http://127.0.0.1:7860看到Web UI界面。此外,在launch的share选项设置为True的情况下,Gradio还会自动产生一个外网可以访问的公开URL,这样就可以把这个URL分享给其他的同事或者客户,请他们体验当前模型的功能状况,而模型及其实际的运算仍然是在本地计算机上(_实际上Gradio只是在公共URL服务和本地计算机之间创建了一个tunnel,所有访问这个公共URL的流量都会被转发到本地计算机上_)。这个公开的URL可在1周内使用,对于演示和体验性的测试而言足够了。 以下是上面的Blocks模式的UI界面: !image.png Gradio的主要组件 Gradio框架的使用主要建立在以下四个核心概念上: - Blocks/Interface: 负责整个Web UI架构的布局管理。 - Component:超过30个内建的UI组件,用于方便地提供对文本、音频、图像等的输入和输出管理。 - Functions:标准的Python功能函数,从输入组件中得到数据,在functions中进行各种处理,最终通过输出组件输出和呈现出来。 - Launch:启动整个Gradio Web App的运行。 Gradio内置了 30 多种封装好的组件,这些组件通常既可以作为输入,也可以作为输出。具体取决于是将其放在inputs参数中还是outputs参数中。以下按照功能类型进行整理: 文本与对话类 gr.Textbox:最常用,主要用于显示文本框。 - 在输入模式中使用时,用于提供用户输入文本的界面,可支持设置 lines (行数)、placeholder (占位符)、type="password" (密码掩码)。 - 在输出模式中使用时,用于显示处理后的文本结果。 gr.Chatbot:大语言模型必备,专门用于显示对话历史。 - 通常作为输出组件,接收一个包含 (用户消息, 机器回复)元组的列表。 gr.Markdown:可支持 Markdown 语法的文本内容展示。常用于页面顶部的说明、公式渲染、或者富文本输出。 gr.Code:专门用于展示代码片段,支持语法高亮和一键复制按钮。 多媒体类 (Video和Audio) gr.Image:上传和显示图片。 - 在输入模式中使用时,可支持上传静态图片、使用 Webcam 拍照或者提供画板 (canvas) 让用户手绘图像。且可以自动将图片转为numpy数组或PIL对象。 - 在输出模式中使用时,可用于展示处理后的图片。 gr.Audio:上传和播放音频文件。 - 在输入模式中使用时,可支持上传音频文件或者使用麦克风录音。 - 在输出模式中使用时,可用于播放Functions功能所生成的语音(TTS)或音乐,并提供波形图显示和下载按钮。 gr.Video:与Image组件类似,可支持上传或录制视频,以及播放生成的视频流。 gr.Gallery:用于以网格的形式同时展示多张图片,非常适合图像生成模型(一次生成 4 张图)或图像检索任务。 数值与控制类 (参数控制) 这部分组件通常作为输入,用于调节模型的参数或运行选项。 gr.Slider:滑动条组件,适合用于选择一定范围内的数值(如 0 到 1 之间的置信度阈值)。 gr.Dropdown:下拉菜单组件,适合从预定义的列表中选择一个值。 gr.Radio:单选按钮组。功能同 Dropdown,但所有选项直接展开显示。 gr.Checkbox/gr.CheckboxGroup:复选框,用于作为布尔值开关(True/False)或多选标签。 gr.Number:用于作为精确的数字输入框。 数据与文件类 (分析与通用) gr.File:通用文件上传/下载。如果处理的是 PDF、Zip 包或特定格式的文件,可以用这个组件上传到程序中。可支持多文件上传。 gr.Dataframe: - 在输入模式中使用时,类似 Excel 的表格编辑器,用户可以手动输入数据或粘贴 CSV。 - 在输出模式中使用时,可用于展示 Pandas DataFrame 数据,可支持排序和筛选。 gr.Plot:专门用于展示图表,可完美支持 matplotlib、plotly、bokeh等绘图库生成的对象。 gr.JSON:用于以树状结构漂亮地展示 JSON 数据或 Python 字典,可方便查看复杂的 API 返回结果。 基于Gradio演示OpenCV图像模糊化效果 下面使用Gradio的多个不同组件,演示一个使用Opencv实现对输入图像进行模糊化处理并显示出来的Web UI效果。代码如下所示: 最终的UI显示效果如下: !image.png 参考资料 - Gradio - Building User Interfaces For AI Applications with Gradio in Python | DataCamp
开源数据集管理工具FiftyOne
本文对Voxel51所开发的开源数据集可视化和管理引擎Fiftyone的工作特性、环境搭建以及常用的应用场景进行总结。 FiftyOne是由 Voxel51 开发的一个开源数据集可视化和管理引擎,其官网地址为:Maximize your Computer Vision Performance | Try Voxel51。 !image.png 对于数据集的管理而言,Fiftyone的核心功能包括: - 数据集的可视化(The App): Fiftyone提供了一个网页界面,可以让你像刷朋友圈一样查看图片及其叠加的标注框(Bounding Boxes)信息,检查和审核标注数据的质量。 - 高效过滤(Querying): 允许你用 Python 语句快速筛选数据。例如使用简单的代码快速找出数据集中所有包含指定目标类型且图片分辨率大于指定分辨率的数据。 - 模型分析:训练完自己的模型(例如YOLO)后,可以把预测结果倒回 FiftyOne,直接在其中对比标准答案和模型预测,一眼看出模型在什么情况下会出现漏判或者误判,从而更有针对性的改善数据质量。 环境的准备 首先,需要安装 FiftyOne 及其依赖。建议在一个独立的虚拟环境中进行。 浏览数据集 使用 FiftyOne 浏览本地及其在线数据集非常直观。通过使用Fiftyone,不仅能通过其Web页面看到数据集中所包含的图片及其标注信息,还能通过强大的查询语句像操作数据库一样过滤和操作这些视觉数据。 以下代码分别演示了对Fiftyone维护的在线数据集以及本地的YOLOv5格式的数据集进行访问的代码流程。 执行以上代码后,直接在本地通过浏览器访问 http://localhost:5151/ 即可对对应的数据集机器中的标注信息进行浏览和管理。 !7815904f-7a16-4925-8e6a-46ec239303b7.jpg 其实fiftyone来访问数据集的代码比较适合在Jupiter Notebook中运行。如上图所示,通过 launch_app 启动fiftyone的运行以后,在其Web UI界面中可以浏览数据集,此外,launch_app返回一个session变量,那么在Jupiter Notebook中运行以上代码时,就可以在代码中通过这个session访问打开的数据集,对其进行一些在线的处理和操作。 例如,我们可以在web UI界面中选择质量不好的图片,然后在notebook中对这些选中的图片执行删除操作,并导出到一个新的数据集中: !image.png session支持的选项很多,通过以上方式,就可以实现与已打开的数据集进行动态的交互。 从公开数据集中下载并清洗数据 FiftyOne中内置了一个叫做 Dataset Zoo 的模块。该模块预先写好了对接 COCO、Open Images、VOC 等几十个主流公开数据集的下载和解析脚本。在没有 FiftyOne的情况下,对于这些公开数据集的访问和使用,往往需要你去官网注册、下载几十 GB 的压缩包、研究它们复杂的 JSON 或 CSV 格式并写脚本解析和转换不同格式的坐标和标注信息。在FiftyOne的帮助下,只需要一行命令 foz.load_zoo_dataset("coco-2017", ...),它就会自动完成筛选、下载、解压、格式对齐等工作。 通过使用以下代码,就可以实现从fiftyone官网上自动下载COCO-2017数据集中前1000张人形的标注照片,并且把标注信息格式自动修改为YOLOv5格式,保存在当前脚本目录的my_yolo_dataset/coco_subset/train/子目录下。 > 注意,按照以下代码的流程,从指定数据集(coco-2017)中下载指定数量(1000张)的指标类别(human)的图片集,FiftyOne 在下载时只会流式下载用户指定的这个图片子集(先下载到C:\Users\用户\fiftyone,然后整理到当前脚本所在目录下),不需要把完整的几百个GB的数据集全部下载下来,这样的按需下载方式可以极大地节省本地硬盘空间和下载所需要的时间。 此外,从在线数据集中下载数据的过程中,为了提升数据集的质量,还可以在导出之前,对下载的数据集进行一定程度的清洗,例如以下代码,可以实现从下载的数据集中自从删除掉无标注框的样本,以及删除掉那些过小的标注框: 对数据经过以上清洗操作以后,再通过export命令把这些清洗后的数据导出来,可以有效的提升训练数据集的质量。 辅助YOLOv8模型微调 不只是可以简化训练数据集的下载、数据清洗和浏览数据集的过程,Fiftyone还可以与模型的推理过程相结合,以可视化的形式直观的显示出模型在推理数据上的表现以及分析其失效模式。例如,通过配合使用Fiftyone和YOLO模型,可以在YOLO模型训练完成后,可视化和评估该模型在验证数据集上的预测结果,从而更好地了解模型的预测能力在哪里失效,并针对性的改善模型的训练数据和指标。 以下代码演示了把YOLO模型的验证集数据导入FiftyOne,在FiftyOne中同时显示标注框和预测框的流程。这样训练人员就可以非常直观的从FiftyOne的Web UI界面上看到验证集上的标注框和训练模型的推理框,直观的了解到当前模型会在哪些数据上失效: 以下为在FiftyOne中显示YOLO模型验证集数据、标签以及推理结果的效果。 !a95a1162-1e30-459e-8466-b3e44ed840f6.jpg 参考资料 - Maximize your Computer Vision Performance | Try Voxel51 - Fine-tune YOLOv8 models for custom use cases with the help of FiftyOne — FiftyOne 1.11.0 documentation
实例讲解OpenAI的轻量级多模态模型CLIP
CLIP(Contrastive Language-Image Pre-Training) 模型OpenAI在2021年初发布的用于匹配图像和文本的预训练神经网络模型,是近年来多模态研究领域的经典之作。 CLIP解决的问题是什么? 作为计算机视觉(CV)领域最具里程碑意义的模型之一,OpenAI 在2021年发布的 CLIP 彻底打破了传统视觉模型依赖固定类别标签和类别的局限。因此,它不仅是一个模型,更是一种将视觉与语言深度对齐的范式革命。 在CLIP之前,计算机领域的图像识别、目标检测等领域的主流模型,如Resnet、VGGnet、SSD、YOLO等,通常是在 ImageNet或者COCO 这种人工提前标注好的数据集上进行训练的。如果这个模型训练了一个 1000类别的分类器,那么它就只能识别这 1000个类别。此时如果要增加一个新的类别,就必须重新收集数据并重新训练。 而CLIP是直接利用互联网上现成的海量图片+描述性文本,训练模型对于图像/文字的理解,并建立两者之间的语义关联,即:图片的视觉特征与其对应的文字描述在语义上的匹配性。这意味着,一旦CLIP模型训练完成,你不需要为新任务训练任何参数,只要能用语言描述出来,CLIP 就能识别出来。 !image.png 下面以一个实际案例的方式,解析基于CLIP模型以及Meta的Faiss向量数据库,来实现一个对本地数据集使用自然语言命令进行搜索的实际案例,通过这个案例来强化对CLIP模型概念和工作流程的理解。 以上案例主要包含三部分内容: - 基于CLIP实现图片文件到Embeddings向量的编码 - 保存图片向量数据到本地Faiss向量数据库中 - 基于自然语言的文本命令从向量数据库中查询图片并显示 Image Embedding 对于图片文件所执行的编码,要使用CLIP模型的preprocess和encoder,所以首先要import CLIP模型: CLIP模型支持不同的架构变体,其主要区别在于视觉编码器的架构和规模大小。 > CLIP模型的不同架构变体:'RN50', 'RN101', 'RN50x4', 'RN50x16', 'RN50x64', 'ViT-B/32', 'ViT-B/16', 'ViT-L/14', 'ViT-L/14@336px' 其中ResNet系列为RN前缀,Vision Transformer系列为ViT前缀,一般来说,ViT架构普遍优于同规模的ResNet架构,而更大的模型通常能提供更好的zero-shot分类性能,但也会消耗更多计算资源。这里使用ViT系列中的最小规模模型ViT-B/32,使用32x32的patch。 接下来就是调用CLIP模型的model和preprocess分别对图片和文本进行向量化操作: 可以看到,CLIP对于图片和文本信息进行的encode操作大同小异,都是先进行encode,然后再执行归一化,区别只在于: - 对图片进行的encode操作调用encode_image接口,对于文本则是调用encode_text接口。 - 文本信息要先使用CLIP的tokenize分词操作后再进行encode转换为向量。 使用Faiss保存Image向量 以上通过调用get_image_embedding接口所得到的图片向量,需要保存在一个向量数据库中,这样下次程序启动的时候,就可以直接从向量数据库中加载并进行检索,而不需要从头再生成图片的向量了。 这里使用由 Meta 开发的高性能向量搜索库Faiss(Facebook AI Similarity Search)。大致的思路和流程是:使用 faiss.write_index 将图片文件的向量索引保存为 一个 .index 文件,同时使用 pickle 或 json 保存所有的图片路径列表 metadata。后续在进行检索时,使用get_text_embedding 生成检索文本的向量,并利用 Faiss 的内积搜索(Inner Product)来实现图像和文本向量之间的余弦相似度匹配,通过检索得到的图片文件 id 在图片路径列表 metadata 中就可以找到图像的路径了。 每次程序启动时从硬盘上尝试加载现有的向量库索引文件以及图片路径文件。第一次运行时这些文件还不存在,此时需要创建一个 IndexFlatIP 格式的向量数据库文件。Faiss 能够支持多种向量索引模式,IndexFlatIP 是其中最基础且精度最高的模式。这种模式不压缩向量,也不建立复杂的树状或图状结构,而是原封不动地存储所有的原始向量数据(通常是 float32)。在查询时,它会将查询向量(即查询文本转换的向量)与数据库中的每一个向量进行逐一计算,这种方式虽然速度最慢,但召回率(Recall)是 100%,即结果绝对精确。对于50000张图片以下的图片库基本上足够用了。如果图片库的数量超过了 10 万张,且查询速度明显变慢,可以使用 IndexIVFFlat 模式,它能将查询速度提速 10-100 倍。 以下代码从本地硬盘的图片库中分批次逐一对图片文件进行向量化(调用get_image_embedding),最后把图片向量列表和图片文件的路径名称列表分别保存在index_path指定的向量数据库文件中和image_paths指定的图片路径列表文件中。 Image Search 前面的步骤实现了本地图片库的向量转换并保存在向量数据库中,接下来就可以利用文本检索命令从这个图片向量数据库中检索指定的图片了。 这部分比较简单,主要可以分为三个步骤: - 对文本内容进行分词、向量化以及归一化操作,这一点与前面的图像向量化的操作流程基本一致。 - 以检索文本的向量为参数调用 Faiss 向量数据库的search接口查询图片,返回值为查询结果的相似度和在图片库中的索引。 - 把查询到的图片列表显示出来。 最终在我的数据集中查询”a boy who is playing football“得到的接入如下: !image.png 参考资料 - 讓AI幫你穿搭!用CLIP實作一個時尚穿搭資料庫 - Building Image search with OpenAI Clip | by Antti Havanko | Medium
开源VLM模型MoonDream
本文详细介绍了业界非常流行的开源轻量级视觉语言模型MoonDream,总结了该模型的核心功能列表、基本功能测试流程以及相关的license等情况。 Moondream 是由开发者 Vikhyat Korrapati 发起并维护的一个开源、轻量级的视觉语言模型(Vision-Language Models, VLM)。该模型的定位是专为端侧设备(Edge Devices)和资源受限环境而设计的高性能 VLM,其核心优势是能以极低的能耗和内存占用(不到 4GB VRAM 甚至在 CPU 上流畅运行),实现与大模型(如 LLaVA 甚至早期 GPT-4V)相媲美甚至在特定任务上超越的视觉理解能力。 MoonDream的官网地址:Moondream !image.png 截至2025年底,MoonDream已经演进迭代到第三代,其前两代模型Moondream 1/2的参数规模大约为 1.6B - 2B,甚至还针对资源更紧张的应用提供了 0.5B参数量的版本。而最新的Moondream 3 模型采用了 MoE(混合专家)架构,总参数量达到了 9B,但单次推理的激活参数只有2B。 什么是VLM? 首先要更深入的理解所谓的视觉语言模型VLM究竟是什么?简单来说,VLM(Vision Language Model) 是一类能够同时理解图像和文字并实现跨模态交互的 AI 模型。 更通俗地讲,所谓的VLM模型,从其整体结构和工作的流程上可以如此理解: - 在输入端,可以认为其输入包含两个部分,一张图片,针对这张图片的一段自然语言的命令。 - 在输出端,VLM的输出是一段文本信息(例如以json格式封装的文本内容),即使是要输出位置坐标等数字内容,其输出的也是一段包含数字字符串的文本信息。 - 模型的工作流程:模型会按照自然语言的命令对输入的图像进行描述解释、目标搜索和定位等,最终输出描述这个图像内容和其他检索性信息的文字内容。 - 最典型的应用就是对图片内容进行描述、从图像中检测出特定物体所在的位置坐标、针对图像生成其OCR检测结果等。 其他的VLM模型包括:阿里的Qwen2-VL,开源的LLaVA 系列,微软的Phi-3.5/Phi-4 Vision以及Florence-2,谷歌的PaliGemma,国产AI Lab的InternVL 2。 MoonDream的环境搭建与核心功能演示 以下以应用上最为成熟、License最为宽松的MoonDream2描述环境搭建以及进行功能演示。 首先是从huggingface上下载Moondream2的模型仓库和权重文件,其模型仓库地址为:vikhyatk/moondream2 at main。 但是Moondream2的权重文件接近4GB,从HuggingFace上直接下载耗时很长,国内网络的情况下可以从Huggingface的镜像网站hf-mirror.com上进行下载。 以下是模型及其权重文件的下载代码,模型相关文件会被下载到D:\models\moondream2下: Moondream 的核心功能矩阵包括四种模式:caption, query, detect, point。这四种模式分别对应于不同的图像理解的颗粒度(从整体到局部)和输出形式(从非结构化文字到结构化坐标)。以下代码演示了以上几种模式在本地下载的MoonDream2模型文件上的推理流程。 首先安装演示代码的依赖包: 推理代码如下: 代码的执行结果如下图所示: !image.png MoonDream的License Moondream 的不同版本采用了完全不同的授权策略。 !image.png 可以看到。Moondream的版本1和版本2均采用了最宽松的Apache 2.0 license,可以自由的在商业领域中使用。而最新的MoonDream 3在商业领域中的使用则存在一定的附加条件: - 可自由使用的情况:在服务器上部署后仅供公司内部使用,做为公司产品的功能嵌入使用,其他的非盈利和研究使用。 - 禁止使用的情况(此时需要另外获得商业授权):搭建服务器后通过API方式以HTTP或者SDK等方式对外提供收费服务,与官方的付费服务高度重合的应用。 参考资料 - Overview | Moondream Docs