Ladies and gentlemen,欢迎来到机器学习烹饪秀。在今天的节目中,我们将微调一个小型视觉语言模型(VLM)或多模态大语言模型(MLLM)。我们将本地处理多种模式,包括视觉和语言,具体是在 Nvidia DGX Spark 上进行。虽然从你的角度看不到,但我这里确实有一个漂亮的小金盒子。我将在这一台机器上完成所有的训练工作。

在机器学习烹饪秀中,我们将一步步进行演示。如果你想直接跳到代码部分,请跳转到时间戳位置;但这将是一个简要概述,使用 Keynote 进行讲解,然后我们将逐步 walkthrough(演示)代码。顺便说一下,这是一个快速版本,所以我会相对快速地过一遍所有代码。如果你想看完整的演示 walkthrough,我本周全程直播了编码过程。

我会把相关链接以及你所需的一切内容链接放在描述中。但让我们先讨论一下我们要做什么。首先,我们将探讨什么是 VLM(视觉语言模型)。接着,我们将从 HuggingFace 下载一个模型,具体是 Small VLM2 500M(5亿参数)。现在我将小型视觉语言模型定义为参数量在10亿以下的模型。

这类模型是专门设计用于尝试运行在边缘设备上的。是的,你可以在边缘设备上运行更大的模型,例如拥有50亿参数的 GEMMA 2B。然而,我将把小型分类留给那些参数量在10亿以下的模型,尽管随着硬件越来越好,这一定义未来可能会改变。

我们还将下载一个数据集,因为如果没有特定的数据集,我们就无法微调模型,对吧?这个数据集将是 Food Extract 1K Vision(食物提取1千视觉)。如果你想要一步步了解我是如何制作这个数据集的,我也有一个直播录像。稍后我们也会看到相关内容。

接下来,我们将使用 TRL(Transformer Reinforcement Learning)和 Transformers 库,以监督微调的方式对模型进行微调。我们将上传我们的微调模型到 Hugging Face,以便我们可以分享它并让其他人使用它。

我们将创建一个演示供人们交互体验,因为作为程序员,我们喜欢写代码并与模型通过代码进行互动。但很多人更喜欢直接将图片拖入我们的演示中然后尝试使用。我最喜欢的工作流之一就是数据、模型、演示(Data-Model-Demo)。

然后我们将讨论使模型变得更好的扩展方法。那么,什么是 VLM?为了不深入太多细节,我在这里留下两篇博客文章链接,其中还有一些论文你可以阅读以了解更多。但这是 VLM 的结构,也是我们要处理的今天这个 VLM 的结构:你获取一些图像和一些文本。

然后有一个投影层(projector),它将图像和文本输入结合起来,接着是一个文本解码器。解码器负责逐个生成那些 token(词元)。这也是许多现代 LLM 的发展方向,它们拥有视觉组件、语言组件,甚至音频组件,现在有些还在加入视频组件。

因此,你只是将所有数据放入一个大型模型中。但很多时候会有一个特定的图像编码器。在我们的案例中,我们将使用 SIGLIP 2。至少 Small VLM 2 使用的是它作为读取图像的图像编码器。然后文本被转换为文本嵌入(embeddings)。

接着我们有一个投影层,将这两者合并到相同的嵌入空间中,然后进入文本解码器。在我们的案例中,这是一个 LLM(大型语言模型),即 Small LM2,然后我们得到答案。但这我们将在稍后的实践中看到。那么,为什么要微调你自己的模型呢?

第一点当然是所有权。拥有该模型。它可以在你自己的硬件上运行。数据保持本地化和私密性。再次强调,我在视频中运行的所有内容都将在这个房间里进行。它运行在我桌子上的 Nvidia DGX Spark 上,很简单。

如果你的任务足够简单,有一个重复性的任务你想一遍又一遍地做,你可以直接使用小型语言模型。你不需要通过 API 使用最大的模型。当然可以通过 API 入门,但如果你发现自己在做重复性任务,你可能会想训练自己的模型。

或者如果你是企业并且需要保持数据私密,那么你肯定应该考虑为重复性用例训练自己的模型。无限推理(Infinite Inference)。因为不需要任何 API 调用,这个模型可以离线运行。我们可以断开互联网连接并在手机上运行它,例如在野外环境中使用。

我最近有一个项目,客户需要训练一个必须运行计算机视觉的模型。我使用了目标检测模型(object detection model),而不是 VLM,但它需要在没有互联网连接的环境中运行。这就是无限推理发挥作用的地方:你不需要 API 调用。

批量模式用于扩展(Batch Mode for Scale)。因此,你可以以批量模式运行此模型以获得比单次 API 调用更快的推理速度。是的,你可以通过 API 进行批量处理。然而,这当然仍然需要 API 使用,而这通常会花费美元费用。

你会用批量模式做什么呢?假设我们有一个特定的 VLM,旨在提取结构化数据,这正是我们今天关注的重点。我们可以针对大型图像数据集运行它,以提取我们寻找的目标图像。因此,过滤大型数据集是此功能的最佳用例之一。

第五点:使基础模型变得更好。那么,为什么要微调你自己的模型呢?通常当发布一个基础模型时——我使用“基础模型”或“预训练模型”,这两个术语可以互换使用。当发布一个模型时,它通常在不同的基准测试中表现相当不错。

我想向你介绍微调的强大力量:通常如果模型在基准测试中表现得还可以,这是一个好迹象,表明如果你喂给它你自己的定制数据,它肯定会在你的定制数据上得到改进。所以这是需要注意的一点。

这里有一个笔记:我希望你建立一种针对 LLM 和 VLM 的思维模式。它们是 token(词元),其中 token 是数据的数值表示。无论数据是文本、图像、视频还是音频,你都能想到它都会被表示为 token。如何对它们进行分词(tokenize)当然取决于你使用的模型。

幸运的是,在 Hugging Face 中,所有的分词器都会加载进来,但你只需要思考:数据变成 token。Token 和数字。计算机喜欢理解数字并识别数字中的模式,然后输出 tokens(词元)。所以我们有输入 token。

在我们的案例中,将是食物和非食物的图像。我们稍后会看到这一点。然后是输出 token,在我们的案例中将是……

需要结构化数据。因此,如果你理解大型语言模型(LLM)和视觉语言模型(VLM)中“输入令牌、输出令牌”的概念,你可以通过将数据集构思为“输入令牌、输出令牌”的格式来创建自己的专业模型。你目前有哪些令牌?你想要输出什么令牌呢?以 Neutrify 为例,这是一个具体的用例。这是我哥哥和我开发的一款应用,已上架 App Store,并非虚构产品,而是正在生产环境中运行的实际产品。网址是 neutrify.app。

我拍了一张前几天午餐的照片,Neutrify 旨在识别完整食物。我们不使用条形码,只处理图像。因此照片被发送到 Gemini。正如你所见,加载花了几秒钟时间,因为这是从图像到作为 API 的 Gemini,然后再处理照片的过程。它生成了 JSON 数据,我们得到了食物标题:“沙丁鱼配山核桃、枣和腌黄瓜”,这完全正确。确实是沙丁鱼、山核桃、腌黄瓜和枣子。

我想展示的是这是一个视觉语言模型,对吧?在 Neutrify 的后端,我们有图像到 Gemini 和文本到 Gemini 的处理流程,然后由 Gemini 输出结果。我今年的目标之一是用端侧模型复制这一工作流。因此,我们实际上不需要 API 调用,这可以在设备上本地运行。在这个小教程中,我们将训练的模型就是这一目标的最初雏形版本。

如果你想通过 Neutrify 追踪完整食物的摄入量,请访问 neutrify.app。这是另一个用例示例:我们拍摄配料盒或包装的照片。这里使用的是 Gemma 3N,这是一个拥有 50 亿参数的模型。它使用 Google AI Edge Gallery,直接在设备上运行。我忘记关闭 Wi-Fi 了,但这确实是一个在设备上运行的 VLM 示例。

但正如你所见,输出结果并不是非常完美。当然,我们可以通过提示词优化来提升效果,但我们也可以进行微调。我会把 Gemma 3N 留到未来的视频中讲解,因为就硬件需求而言,它是一个相当大的 VLM。我们可能会在专注于 LoRA/QLoRA 的视频中讨论它,那是另一个话题了。如果你想看相关内容,请在下方留言评论。

我们可以对其进行微调,专门提取我们想要的配料信息,而不是所有额外信息(如地址)。这其中的美妙之处在于速度相当快,而且我们能实时看到过程。对用户来说,这是一种极佳的体验。正如我之前所说,这是在设备上运行的。因此,我可以关闭 Wi-Fi,或者在信号不佳的超市中也能正常工作。

这是另一个具体的用例:如果我经营一家名为“机器学习与肌肉”(Machine Learning and Muscle)的企业,对吧?我们在肌肉和模型方面都取得了巨大进步。嗯,这有点尴尬,但无论如何,这是我们要处理的项目详情:包括我的咨询费率总额。如果我们是企业,每天收到上千张此类发票或邮件呢?我们可以将其提取为 JSON 格式。

这就是我所指的结构化数据。这是一张图片,这是我们要获取的结构。那么这会是什么样呢?当然,它会填写这些信息。然后我们可以将其放入易于访问的数据库中,例如 Excel 电子表格或 pandas DataFrame 等。事实上,让我们试一试:嗨,如果我们进入 Gemini,上传我们的发票测试图片……

我只想展示一下,如果我们要提取这张发票的详细信息到 JSON 中。我现在不使用快速模型 Gemini Flash,也不给它提供特定结构,只是为了求快。好了,看吧!那基本上正是我们想要的结果。目前这使用的是 Gemini,但如果涉及机密信息不能发送到 Gemini 模型中,这就是我们可以创建自定义 VLM 以从图像中提取结构化数据的地方。

这里有图片,也有结构化数据。让我们回到 Keynote 继续讲解。以下是我们要使用的“食材”:基础模型是 Small VLM2,拥有 5 亿参数(Video Instruct)。这是你在 Hugging Face 上能找到的最小的 VLM 之一。还有一个更小的版本,事实上你可以尝试看看这个工作流是否适用于那个模型。

我们将参考一篇论文,现代机器学习和 AI 的美妙之处在于最新成果通常会先发布到研究文献中。你看这个,它发表于近一年前的三月左右?但后来才逐渐流入编码库等工具中。关于小型微调(Small Dockling)的许多创新,我们将在接下来的代码实践中 firsthand 地观察和练习。

这包括以你希望的格式构建数据集,这也是大部分工作所在之处。说到数据集,我们有一个从头创建的数据集:Food Extract 1K Vision,稍后我们会查看它。既然聊到数据集,让我们现在就看看:这是我们的数据集。我们只有 1500 个样本,相比之下非常少。

这里提到了小型微调论文中微调的拥有 2.56 亿参数的模型,Small VLM 256M。他们的数据集包含数百万个样本。如果我们往下看……他们使用了多少样本呢?我粗略估算大约有 2000 万个左右。我们可以将这个数据作为多模态模型传递给 Gemini,让它统计样本总数。

他们使用的数量远超我们,但我们将从小处着手,仅使用 1500 个样本。更具体地说,其中包含来自 Food-101 的 1000 张食物图片,以及 500 张非食物图片。我们希望训练模型不仅能检测食物及其类型,还要具备识别能力:如果有人上传电脑照片,我们不想让它输出关于食物的信息。这些被称为“困难负样本”。

标签是我合成的:抱歉,我是通过提示 Qwen 3 VL 8B Instruct 生成的。我们本质上是将一个较大的模型(拥有 80 亿参数的 8B 模型)蒸馏到一个拥有 5 亿参数的模型中。这大约减少了 16 倍,是一个相当大的缩减幅度。这里的目标是输出结构化数据。

就像这样。如果你还记得我们之前展示的 Neutrify 视频,这就是我们能做到的效果:图像标题、食物项目,然后我们将这些映射到数据库中,或者直接在大型图像数据库上大规模运行此模型以提取食物图片。让我们看一个单独的样本。如果我们查看这里,我们会得到“美味”,那是冷冻酸奶吗?所以它是冰淇淋和马卡龙。这是我们要让模型预测的 JSON 输出。

鉴于这张图片,我们希望微调我们的模型以进行此预测。还记得我之前说过吗?大型语言模型(LLM)和视觉语言模型(VLM)都是“令牌输入,令牌输出”。因此,我们的图像将被标记化,转换为令牌格式。而抱歉,模型将预测此输出标签 JSON 作为令牌。理想情况下,它将保持这种确切格式,以便我们可以轻松将其加载到某种数据库中。

这里有一些关于此的元数据。我让你去探索这个数据集,它在 Hugging Face 数据集上可用。我还有一段直播视频展示了我是如何创建它的。但如果你希望未来看到更多关于数据集创建的內容,请告诉我。所以我们有一个基础模型、一篇我们将遵循其指令的论文以及一个数据集工具。我们使用 TRL 库,这当然是由 Hugging Face Transformers 支持的。

我将全部在 Nvidia DGX Spark 上实时运行这些内容,但你也可以在 Google Colab 上运行,因为我们使用的是非常小的模型。事实上,我们要使用的笔记本链接将在下方提供,那里有一个大按钮,如果你想打开它并在 Colab 中运行,你可以这样做。但正如我所说,我将使用 Nvidia DGX Spark。这是我的设置:这就是我正在编码的地方,所有计算都将在这里运行;而这是我在 Mac Mini 上编写代码(抱歉),然后通过 SSH 在 DGX Spark 上运行它,因为我们可以获得 CUDA 生态系统,而且这真是一台美丽的小机器。

让我们讨论一下数据集格式。在处理任何模型时,拥有数据集是非常重要的事情。但对于视觉语言模型(VLM)而言,如果不深入探讨太多细节的话,你需要有视觉组件和文本语言组件。本质上,我们的数据集将只是图像和文本,也就是说,我们会给它一个提示说“从这张图片中提取 XYZ”。就是这样:图像和文本对。简单明了,对吧?

如果你想了解更多关于那方面的内容或如何创建自己的数据集,我在笔记本中有一个指向 TRL 视觉数据集格式的链接。但这基本上就是你将会看到的内容:如果你想知道我是如何创建这个数据集的,我使用了 Hugging Face 的数据集图像数据格式。然后我们有一个直播视频,我在其中逐步构建了从头开始构建这个数据集的过程。你可以在这里看到它,我只是高亮了一个我们将数据上传到 Hugging Face Hub 的小点。这正是我们之前看到的内容。

你可以在哪里获取模型?你可以从我基本上是我的主页的 huggingface.co/models 上获取。如果我们去首页,然后点击模型,另一种说法 VLM 是“图像转文本”。所以这些都是最近的。你会注意到这里的数字是数十亿参数数量,或者有时会是百万。再次强调,所有这些都非常小(除了这一个)。它大约是一十亿参数。我对“小型”的定义是低于一十亿。例如,Google 刚刚发布了 MedGe Gemma 1.54B。它的大小是多少?大约接近让我们说 9 GB,对吧?要运行它。你可以通过量化使其更小,但这是旨在从医学图像中提取医学信息的模型。我们要更小的,但这只是你找到模型的地方。

然后这是我们的任务:从自然图像中提取结构化数据。这被称为 Food Extract Vision(食物提取视觉)。我们有 Food-101 图像集。理想的输出将是 JSON。我们希望模型精确地输出这个内容。但我想让你在心里思考一下,这里的输入就像我们的 LLM 视频一样可以是任何图像,而这里输出也可以是任何结构。你可能不想(对于小型模型来说可能效果不好)输出一段比这长 20 倍的内容,因为那样可能不会工作得那么好。但小型 Dockling(可能是指某种模型或技术)实际上运行得很好,它可以输出长达三页的文本。所以嘿,试一试吧,为什么不呢?但这里的原理是我们只是拥有图像和文本对。

你可能会想,Daniel,你能不能训练一个分类器来分类食物和非食物图像?你会完全正确。我们实际上在 Neutrify 中已经这样做了。那就是实时通过摄像头运行的内容,它以大约每秒 300 帧的速度运行。它是一个快速的模型。但 VLMs 或一般 LLMs 的美妙之处在于它们具有无界输出,因为它们是解码器输出,只要训练模型以输出那种令牌格式,你就可以输出任何你想要的令牌。所以我们不仅可以获得分类,还可以一次性获得标题、食物项目和饮料项目。

感谢 Tron Lee 在 Unsplash 上提供的这张美丽的瑞士照片。我们可以在哪里使用这个模型?我们可以用这个模型来过滤包含食物和饮料项目的大型图像数据集,例如 Data Comp 1B(Hugging Face 上的十亿张图像)。或者作为设备端的食物饮料提取模型。所以请记住 Neutrify 应用程序的目标是用设备端模型替换 Gemini 依赖关系。因此,本教程的最终输出是:如果你完成端到端的流程,你将拥有一个微调好的 Food Extract Vision Small VLM2 模型,并且你还将拥有一个可公开分享的演示版本。

如果你愿意,你现在就可以访问它。所以废话不多说,让我给你展示一下这个演示(或者说是微调好的模型)。看这里,这在我的 Hugging Face 个人资料上。这里是演示。不剧透(抱歉),但我会展示它是如何工作的。如果一切按计划进行,原始模型现在需要几秒钟才能加载,因为我们在免费的 GPU 上运行。实际上我可能需要刷新一下页面。抱歉,我想我是重建了它。好了,看这里。我们尝试演示。好的,所以原始模型未经微调。我们有这个输入提示来告诉它将给定图像分类为食物或非食物。

我们给它这个输入提示,模型就输出这个内容。但我们的微调模型知道它不是食物,所以给它一个零分(表示否定)。然后我们有薯条。好的。

这是我前几天晚上用一些牛脂做的晚餐。很漂亮。所以原始模型的输出就是这样,一个空列表,这不是我们想要的。然后我们有微调后的模型,它的食物图片标题是“炸薯条”。所以这是一个非常简单但非常有用的用例,特别适用于我的任务,即从食物图像中提取结构化信息。正如我之前所说,如果你想要笔记本……或者我还没提到这部分内容,你可以访问 learnhuggingface.com,那里将提供所有代码和文章。

它以书籍形式呈现。你可以浏览它、滚动查看,轻松阅读我们提供的所有信息。我还链接了许多其他资源以及一些鸣谢名单。所以这里有一些很棒的资源。我们在哪里找到它们呢?哦,是的。Hugging Face 网站上有一些很好的文档。此外,还有关于为 Gemma 3n 微调视觉模型的 Google Docs 示例。还有一篇关于 LoRA 的博客文章,但这将是未来视频的内容。如果你想看那个……请告诉我。现在,让我们开始吧。

如果你想在 GitHub 上运行代码,那里有链接指向所有直播流。但现在我将跳转到本地 VS Code。这只是一个笔记本。我要重启它。我刚刚在我的硬件上运行了这些代码。让我给你展示一下 Nvidia SMI(系统管理界面)。这是 Nvidia GB10 Grace Blackwell 10,也就是 DGX Spark 正在运行的硬件。所以让我们开始吧。我们将运行一些代码。我们有一些导入依赖项。

实际上,对于这个任务我们不需要 bitsandbytes 库,因为我们没有使用 LoRA(低秩适应)。但在未来训练更大的 VLM 或 LLM 时,我们肯定会使用 LoRA。所以让我们运行这个代码。我们有 CUDA。我们要加载我们的数据集。这里遵循的座右铭是“数据、模型、演示”。所以如果我们查看数据集中的一些示例样本,我们会看到一些字段。我要放大一点,这样你就可以看清发生了什么。

这里有图像 ID 等所有元数据。我们主要关心的是图像和输出标签 JSON。我们要给模型这张图片,我们的基础模型,并微调它以产生这个输出。而这个输出是由 Qwen 3 VL8B 作为合成数据标注器生成的。我之前在 RTX 4090 上为另一个项目训练(或者说生成)了这些标签,现在我将它们重新用于这个 VLM 微调任务。所以我们得到了一些图像和文本对。让我们看一个例子,稍微缩小一点视图。

所以这是我们的示例,期望输出是“食物1”,即0或1表示是否为食物。图像标题是一个简单的食品图片标题。然后我们有食品项目,没有可见的饮料项目。这些 again 都是由 Qwen 3 VL8B 生成的。如果我们想将其转化为生产系统,我们肯定会创建一个包含约一千张图像的手动标注数据集,然后可以在这些手动标注的图像上进行测试。所以我们有吐司、蘸酱、香草装饰和奶酪角。

现在为与 VLM 配合使用格式化数据。正如我之前所说,有一些文档。让我们看看 TRL 视觉数据集格式。这就是我们需要的格式。我们需要将其转换为类似这样的内容:包含消息的内容。他们在这里给出了一个示例数据集,但我只展示我们数据的样子。我们将给它一个系统消息提示词:“你是一个专业的食品和饮料图像提取专家。”在未来视频中,我想在不使用这两个提示词的情况下训练它。

所以我们直接从图像到输出。但目前我们也在使用一些输入提示词。“你是一个专业的食品和饮料图像提取专家。”你将结构化数据提供给视觉输入,将其分类为可食用食物或饮料或非食品类,同时用简单的食品饮料相关标题给图像命名。最后,提取任何可见的食品或饮料项目列出。“然后我们有用户提示词。”系统提示词基本上是让模型做好准备。

用户提示词是我们会在 Gemini 或 ChatGPT 界面中输入的内容。“将给定的输入图像分类为食物或非食物。”如果存在可食用食品或饮料项目,将其提取到列表中。如果没有可见的食品或饮料项目,返回空列表。“所以如果那里没有食物……”我只想要空列表。只以下列格式返回有效的 JSON,然后我们有我们的 JSON 结构。

然后我们给它一些关于我们想要什么的信息。这只是代码版本的说明。这里我们要格式化数据。这将接收来自数据集的一个样本。如果我们向上看,这些行中的一行……因为这包含了我们需要的所有信息,它只是接收其中一个样本并将其转换为带有“messages”键的字典。然后在消息内部,我们将有第一条消息,即系统消息角色,将是上面的那个提示词。

然后我们会有用户角色,这将是我们这里的图像和文本对。我们有图像,然后是文本。所以这是我们的文本,当然还有图像……如果我们查看示例样本的话是在“image”字段中。所以如果我们在那里查看,现在它是 PIL 格式(Python Image Library)。然后如果我们向下滚动,最后一条消息是模型。所以这是我们理想的模型输出。

注意它是助手角色。我们有用户和系统角色。我们要训练我们的模型来预测这里的输出,即样本输出标签 JSON。“所以给定这个输入……”我们希望我们的模型预测这个输出。这在机器学习术语中意味着什么?如果你不知道,没关系。这是一个监督微调问题(Supervised Fine-Tuning, SFT)。我的意思是说这是理想的输出,而这里是输入。

在监督微调中,我们给模型提供输入的示例和输出的示例,然后它找出将它们连接起来的模式。所以这是我们的理想输出示例,这里是我们的理想输入示例。模型将找出将这些连接起来的模式。我们不告诉它如何做的方法。我们只是说:“嘿,这是我的输入,这是我理想的输出。”你预测这两者之间的内容是什么。

所以如果我们将样本格式化为消息格式,我们会得到稍微复杂一点的结构。但好消息是 Hugging Face Transformers 和 TRL 在后台很好地处理了这一点。对于不同的模型可能会有稍微不同的格式,但对于使用 Transformers 的 VLMs 来说,“图像和文本”这一部分是非常通用的。

pairs(配对)。现在有一点需要注意,我尝试过使用 map 方法。通常你可以调用 dataset.mmap(内存映射)以及我们上面提到的这个 helper function(辅助函数),但不知为何,这样做会卡住。如果有人知道原因或有解决办法,请告诉我,我很乐意听听你们的建议。接下来我们要创建训练集和验证集的划分。这遵循的是 80/20 法则,即我们使用 80% 的数据进行训练。

这样模型就可以利用这些数据来学习,而剩下的 20% 则用于验证或测试。在训练过程中,模型不会接触到这些数据。因此,当我们在验证集上评估模型时,可以很好地了解它在未见数据上的表现,因为这才是我们理想的应用场景。好了,看这里……哦,抱歉,我刚才跳到哪里去了?啊,数据集已预处理。我忘了运行这一步了,对吧?有点太激动了。好的,训练集和验证集的划分完成,漂亮!

这里展示了来自每个集合的示例。这些格式看起来有点复杂,因为对我来说个人阅读起来稍微有些困难。如果我逐步拆解的话我也能看懂,而且我知道数据集的结构。但如果你想仔细阅读这些内容,课程的书本版本中有格式更好的版本可供参考。如果我们加载数据集并查看输出,就可以开始看到其输出格式了。现在让我们回到代码中。

接下来我要做的第一件事是将一个样本输入到基础模型中进行测试,以便尽快获得结果并尝试你的基础模型。在我们的案例中,我们的基础模型是 Small VLM2 500M(5亿参数)。那么我们要传递给模型的应该是什么?我们只想传递图像和文本的配对,而不希望它看到输出结果。

也就是这里的 assistant(助手)角色部分在哪里?助手的内容我们不想让它看到,只想传递图像和文本给它,因为在实际生产环境中它只会接收到这些。这就是我们理想的输入情况:只有图像和文本。这是我们要隐藏给模型的内容,除非是在训练阶段。所以我这里提取了前两条消息:system(系统)和 user(用户)。注意,assistant 的消息现在被移除了。

接下来我们要加载基础模型。如果你看过所有的直播流,其实不需要特意去记,我现在就告诉你:我尝试过使用 Gemma 3N。这是一个对于 50亿参数来说相当不错的模型,甚至可能对我们的特定任务来说“太好”了。我们进行微调的原因正是希望将一个原本表现平平的小模型通过微调变得出色。

当然,Gemma 3N 肯定也能通过少量微调得到提升,但我打算留到未来的视频中再讲,届时我们会使用一种在更大模型上效果更好叫做 LoRA 的技术。所以现在我们用 pipeline(管道)来加载它。transformers 库中的 Pipeline 是一个非常棒的方法,可以让我们立即开始推理(inference)。

但正如我们稍后所见,当我们想要进行训练时,我们需要使用 AutoModel 类或其子类来加载模型。因此 Pipeline 非常适合推理,但对于训练和微调,你应该使用 AutoModel 类。这里我正在加载这个 pipeline。由于我已经下载了该模型,它已经缓存在本地了。如果你没有下载过,它会开始下载这个模型,具体取决于你的网络连接速度可能需要一两分钟。

我们可以直接将基础模型的示例传入 pipeline 中。让我们回顾一下这个输入长什么样,我们直接将其传递给模型并告诉它生成最多 256个 token(新令牌)。然后我们就能看到输出结果。Pipeline 的美妙之处在于它在幕后处理了所有的格式化和预处理工作,所以让我们试试这个基础模型。这是输入部分,然后是输出结果。

效果并不好,对吧?我们期望的输出是“食物图片标题:芝士拼盘”,之前我们已经见过这个标签了。所以我们可以肯定地说,我们的基础模型表现并不出色。我们能否通过微调来改善它呢?我们要拭目以待了。检查一下图片,是的,这张图与当前的描述不匹配;而另一张图则能对应上。这就是我们的任务目标。

现在我们要不使用 pipeline 来加载模型了。我之前展示 Pipeline 是因为它是开始进行推理的简便方法,也是探索不同模型的快速途径之一,无论是在 DGX Spark 上还是在 Google Colab 中。现在让我们使用 AutoProcessor(自动处理器)和 AutoModel for ImageTextToText(图像文本到文本模型)来加载它。

请记住,在 Hugging Face 平台上,VLM(视觉语言模型)被称为 ImageTextToText。也就是将图像和文本转换为纯文本输出。所以让我们加载它,这里我们得到了模型和处理程序(processor)。处理器负责将我们的图像和文本转换为目标模型所需的格式。

由于我已经下载过这个模型,它只是开始将其加载到内存中。让我展示一下文件和版本信息,这就是它会下载的内容:model.safetensors 文件有2GB大小;还有 pre-processor config(处理器配置)等。里面包含的各种不同内容基本上都是模型的设置参数。如果你想了解更多关于这些的信息,我鼓励你点击进去查看。

所有这些都是公开可用的信息。虽然会有大量的信息过载感,但随着实践的增加,你会开始理解这些微小组件各自的作用。正如我之前所说,这是一个快速版本,所以我们不会讨论每一个细微之处。但如果我们检查模型的架构,你会注意到几个主要部分:首先是整体模型结构,然后是视觉模型(vision model),接着是连接器(connector)。

之后是文本模型(text model)和语言模型头(LM head),在这些主要部分之间还有较小的层级。如果你听说过神经网络,它是由多层组成的;而这里则是由多个模型组成,而这些模型内部又包含多层结构。让我们回到我们的 Keynote 演示文稿中,让我简要概述一下我们目前的进度。

如果我们记得一开始的那个图表——它来自其中一篇博客文章,我们会看到:图像编码器在我们的案例中就是视觉模型;多模态投影器(multimodal projector)就是我们的连接器。还记得我们需要连接文本和图像的数值表示吗?然后我们有文本解码器,也就是 LM 头。

让我们跳回代码中看看:视觉模型、连接器、文本模型、LM 头,非常完美。如果你想阅读更多关于这些的内容,你可以去查阅相关资料。在我们的案例中,视觉模型使用的是 SigLIP,这是一个很棒的视觉编码器,但它同时也经过语言训练。在文本模型方面,我们使用的是 Small LM2 360M Instruct(指令微调版)。

连接器,通常是一个多层感知机(MLP),用于合并视觉和文本模型。接着是语言模型的头部,它负责输出我们想要的令牌(tokens)。现在,如果我们想仅使用模型和处理程序进行推理,步骤会比通过管道运行多一些。首先最重要的是应用聊天模板(chat template),这会添加一些特殊令牌。如果你想看它是什么样子的,可以查看信息中的聊天模板。我在熟悉不同事物时喜欢尽可能多地打印出来,然后换行或向下滚动。

接着我们会使用 torch.no_grad 传递数据,这意味着我们不希望使用梯度。然后调用 model.generate,并将示例聊天模板中的所有键值对传入其中。最大新令牌数设为 256,然后过滤生成输出,只保留模型生成的部分。我们需要索引从输入之后的一切内容。例如,如果我们的输入是 400个令牌,我们希望看到之后的所有内容,因为模型会接收输入并生成新令牌,这才是我们想要的输出。

由于计算机和模型喜欢数值表示,“令牌”实际上是将文本转换为一串数字,比如 125, 1, 6, 30,000 等,稍后会有示例。然后我们要将其解码回文本,这就是 decode 的作用。让我们看看实际结果:这是完整的聊天模板,包括像素值。对于视觉模型来说,这些像素值用于编码图像,已被转换为张量形式的数值表示。继续往下看还有很多内容,接着是注意力掩码(attention mask),全为 1,因为我们要关注这些像素值;如果是 0 则表示屏蔽。

然后是输入 ID(input IDs),这是文本被转换为令牌的地方,正如我之前所说,这是一长串数字。这里有很多数字,我就不一一读出了,你可以自行检查。同样,它们也有全为 1 的注意力掩码,因为我们要关注所有这些令牌,这就是注意力机制;如果是 0 则表示忽略。这是输入到模型的原始数值令牌输入,这里我只是过滤了输入 ID,展示了一串令牌。省略号表示后面还有很多内容。

这是我们的像素值,由视觉模型编码或创建。继续往下看,这就是进入模型的内容:人类可读的格式化版本。注意这里有 IM 开始(instant message start),虽然我不确定 IM 具体代表什么,但这标志着开始。我们有图像令牌。在视觉语言模型(VLM)中需要注意的一点是,这些只是占位符。它们告诉模型去关注像素值而不是图像占位符令牌,但这涉及较深的原理,只需将其视为占位符即可。

模型会查看像素值而不是这些图像令牌。接着是我们的文本,这也是人类可读的,不是模型直接看到的;模型看到的是大量数字。这是基础模型的输出结果,没有反应是因为它尚未针对我们的需求进行微调。这里有关于图像特殊令牌的小注记:“准备模型用于训练”。在不深入论文细节的情况下,我想展示这个技巧的来源。

我们常说欢迎来到机器学习烹饪秀,我认为机器学习在很大程度上就像做饭:你有食材、方法,然后得到输出。在这种情况下,他们的数据集是食材,“Small Dockling”是一个从文本中提取结构化数据的模型。他们使用了与我们相同的架构,即从文档中提取结构化数据。Small Dockling 的输出就是从 OCR(光学字符识别)中生成的结构化数据,它根据输入预测输出。这就是它的食谱,而这里的“食材”就是 Small Dockling 的原料。

而在我们的案例中,食材是食物图像和文本输出。Gemini 统计总共有 2700万个样本,无论对错,这大概是个合理的数字,但比我们使用的多得多。我们只使用了 1500个样本。重要的是往下看:他们使用了 64块 Nvidia A180 GB GPU,进行了四个 epoch(轮次),耗时约 38小时。4乘以38大约是160,准确说是152,使用的是 AdamW 优化器。这些都是类似食谱中的小细节和设置,正式名称叫超参数(hyperparameters)。

作为厨师,我们会读到这里心想:“这有点酷,可以试试。”机器学习很大程度上就是找到与你相似的食谱,然后根据自己的喜好进行调整,我们正是对食物图像做了这样的处理。他们使用了 2 * 10^-4的学习率,稍后会看到这一点。对于视觉编码器,一旦解冻……这可能是最关键的一点,隐藏在四个字里:视觉编码器是冻结的。我们还采用了梯度裁剪至 1.0,稍后会在设置中看到这一点,以及一个预热比例(warm-up ratio)为 0.03。

如果你想阅读这篇论文,我强烈推荐它,因为我们的许多实践都基于此,而且 Small Dockling 模型效果很好。根据 Small Dockling 论文的第 5.1节,我们将冻结视觉模型。冻结意味着在训练期间不更新它,即固定其梯度。当我们的模型看到示例(输入和输出)时,有一个名为 AdamW 的优化器会指出:“你从该输入产生的输出不够好”,然后它会调整模型参数。

5亿参数可供我们使用,视觉模型将被冻结,因为这是Small Dockling团队发现的有效配方。事实上,我曾尝试在不冻结视觉模型的情况下训练该模型,结果并不理想。剧透一下:我遵循了论文的建议——冻结视觉模型,这样效果就很好了。因此,笔记本中并未展示我在幕后进行的所有实验:运行一、二、三(另一个模型)、五、六等等。我无法想象像这样的研究论文背后进行了多少轮实验。因此,请记住机器学习和AI具有极强的实验性:对于每一个成功的结果,背后可能都有成百上千次未发表的失败尝试。请保持实验精神,不断尝试新事物。我们将冻结视觉模型,即其权重和模式在训练期间不会更新,仅更新模型的其余部分(语言部分)。

如果我们有更大的数据集——目前我们的训练样本仅有1000个,规模很小——我们本可以执行两阶段训练,正如论文中所做的那样。第一阶段:使用冻结的视觉编码器进行训练,以对齐LLM部分与输出(此处原文有误,应为“对齐”)。这是我们的期望格式。我们希望将LLM与独特的期望格式对齐,这正是微调的原因。第二阶段:训练整个模型(包括视觉模型),将所有特征与目标数据集对齐。目前,我们将保持简单,专注于第一阶段;如需扩展,可进入第二阶段。笔记本中留有相关代码供你尝试:如何冻结模型的一部分?首先计算视觉编码器冻结前后的总参数和可训练参数。

我们的模型结构如下:若要访问视觉编码器,由于这是一个Python对象,我们可以通过model.vision_model获取。这是一个小型VLM视觉Transformer。我们需要统计总参数、可训练参数和冻结参数。在冻结之前,我们先进行计数。接着通过遍历model.vision_model.parameters()并将param.requires_grad设为False来冻结视觉编码器,这意味着训练期间不更新其梯度。运行后查看:总参数为5亿(这在以前是巨型模型,但现在被视为小型;三年后可能更小),可训练参数为4.202亿,冻结参数为8650万。至此视觉编码器已冻结,进入下一步:创建数据整理器(Data Collator)。

机器学习模型或NVIDIA GPU在批量处理样本时效率最高。例如,DGX Spark拥有128GB内存(确实如此),我们希望尽可能充分利用它,因为GPU在显存满载时性能最佳。但这并非越满越好:就像吃饭一样,适量感到满足,过量则会导致不适甚至呕吐。数据整理器(Collation Function)的作用正是将样本堆叠成批次。之前的推理是逐个进行的,但训练通常以批量方式进行。我们不会设置过大的批大小;过去视觉模型的批大小可达128,具体取决于硬件。虽然有人调侃“朋友不会让朋友的训练批大小超过32”,但这已是旧闻,现在可以使用更大的批大小。

然而由于我们的模型非常大,我们实际上不会这样做。数据整理器将张量编译为批次。这里借用了Gemma文档中的一个辅助函数,非常感谢Gemma团队:将消息转换为图像列表。该函数遍历我们之前创建的格式中的消息列表,并将图像堆叠成列表。接着是整理函数:创建文本和图像的空白列表,通过处理器将其放入批次格式中,然后返回批处理输入。此外,我们还进行了一些掩码操作:我们要屏蔽图像标记ID(即之前的占位符)。我们不希望模型识别该图像标记ID,而应关注像素值(即之前看到的张量)。

那个带有括号的图像标记只是一个占位符,意在告诉模型“此处将有图像输入”,但不希望其关注该占位符本身。我们希望模型基于像素值和标记输入,输出我们提供的令牌。因此我们要运行该代码:Small VLM2使用的图像令牌ID为...,我们对此进行掩码处理,因为它只是序列中的占位符。接下来有两个部分:SFT配置和SFT训练器。正如我之前所说,我们进行的是监督微调(Supervised Fine-Tuning),即提供输入示例和输出示例,让模型填补差距。SFT配置包含所有设置(超参数)。大多数超参数来自Hugging Face文档的默认值,这些源自TRL(Transformer Reinforcement Learning)。我已在此处留下链接供你阅读文档。

虽然无法完全理解所有内容,但阅读它们是非常有帮助的练习。对于基于 Transformers Trainer 构建且属于 TRL(Transformer Reinforcement Learning)库的训练器也是如此。如果我们查看 Small Dockling 论文,这就是我之前提到的关于学习率的内容。让我直接看这里:他们从 2 * 10^-4 的学习率开始,使用 AdamW 优化器。我们可以记住三件事:梯度裁剪设为 1.0,预热比例为 0.03。我们可以记住四件事:预热比例 0.03,梯度范数裁剪为 1.0,学习率为 2^-4(即 10^-4),以及优化器为 AdamW torch fused。

我将输出这些设置到此处。我在这里附加了视频,但在生产版本或我要链接的演示版本中,我没有包含那个视频标签。我只是把它放在这里,以便你知道这个训练是在本视频中进行的。关于训练轮数(epochs),我只设置了一轮,你可以设置为四轮。我发现对于特定的数据集,四轮效果较好,但为了加快演示速度,我选择了一轮。关于每设备批次大小(per device batch size),我的 GPU 显存较大,如果你遇到错误,可能需要将其降低到二甚至一。如果你的 GPU 是 16GB,可能只能容纳一个批次,但请尝试两个;如果四个不行就试两个;如果两个还不行再试一个。我将继续使用四,并让你自行阅读其余设置。

哦不,我们可能遇到了一些断连问题。最近我确实经历了不少连接中断的情况。“5 4 3 2 1. Elephant elephant unp...”好的,我们不得不重置 Spark。我不确定是否是连接问题,今天这种情况确实发生了很多次。我也不确定是不是我的网络原因,因为我们是通过 SSH 连接的,也就是通过 Wi-Fi 操作。未来我可能需要研究直接连接的方式,但我们已经完成了 SFT(监督微调)配置文件的创建。太棒了!现在我们可以使用 FSF 来训练模型了。“SFT Trainer”这个词组说起来有点拗口。

这些是关于我们如何训练模型的设置,也就是模型应该基于什么进行训练。你会注意到这里列出了训练数据集。我们可以使用所有样本进行训练,如果这样做,我们也可以设置一千个样本作为限制。让我们回顾一下训练数据集的长度:我们有多少个样本?以及验证数据集呢?看这里,我们有 1200 个训练样本和 300 个评估样本。为了加快速度,我将这些数量分别最小化为 50 和 10。正如我之前所说,我也将轮数减少了。我在 Hugging Face 上的那个模型是在所有数据上训练了四轮,耗时大约一小时或多一点。

而我们在这里只使用 50 个样本进行训练和 10 个样本进行验证。我们将传入我们的收集函数(collection function),请记住它只是遍历训练数据集,将所有内容转换为批次。这里还有我们的预处理器类(pre-processor class),也就是 processor。它告诉模型如何处理图像等内容,并包含所有标记或特殊令牌的大列表,但我们不会逐一讲解。如果你想查看,请随意浏览。现在让我们共同训练或微调我们自己的 VLM(视觉语言模型)。

这可能需要一两分钟才能开始,然后训练过程大约需要一到两分钟。请记住,这是在大幅减少的数据集上进行的,样本量比实际少约 20 倍。是的,在完整的训练运行中我做了四轮,耗时大约一个半小时左右。看这里,我们已经完成了三分之一。应该很快。作为机器学习工程师,你需要弄清楚的一件事就是在训练期间该做什么。我个人喜欢做开合跳(star jumps)。啊,做了大约 20 个后我们回来了。太棒了!

这里有一些输出结果:训练损失(Training loss)理想情况下应该下降,损失值代表模型的错误程度。验证损失(Validation loss)理想情况下也应降至零,这意味着模型完美地预测了输入对应的输出。不过,如果损失降到完全为零,我通常会持怀疑态度,因为可能存在数据泄露的情况。“标记数量”表示模型已经看到了多少样本。请记住,我们的预处理器会将所有输入转换为令牌(tokens),而模型喜欢处理的是令牌而不是原始的图像和文本。

“平均标记准确率”(Mean token accuracy)是原始准确度值,100 意味着模型在 100% 的时间内都预测对了输出令牌。因此,我们可以获得相当高的平均标记准确率,但这些只是数字。理想情况下损失应该下降;如果确实下降了,那就是好事。我们可以看到在更长的训练运行中它实际上确实下降了。但是最好的评估发生在哪里?它们发生在查看实际样本时,我们稍后会这样做。我们可以将模型保存到文件。

然后如果我们回到这里,我们应该能找到它:checkpoint(检查点),这是训练期间保存的检查点。这些文件都是我们通过 trainer.save 保存到文件的。如果我们查看原始模型的文件和版本,即 small VLM video instruct,我们这里有什么?有 models.safetensors 文件以及一堆 JSON 配置文件。这正是我们要得到的结果,当然我们的 model.safetensors 现在已上传或更新到数据中。如果我们查看损失曲线,我们会看到在训练期间损失确实下降了,这正是我们想要的:损失随时间下降是完美的趋势。

现在我们可以将微调后的模型上传到 Hugging Face Hub。如果你是在本地运行,你需要通过 hf login 登录;我在下方留下了一些相关链接供你参考。如果你是在 Google Colab 上运行,你需要设置 HF token(Hugging Face令牌)。让我在这里展示一下:small VLM2 playground 是我测试 small VLM2 的地方。你需要在那里设置你的 HF token。我会展示一下,在 learnhf.com 上有设置的指南。你可以为 Google Colab 设置令牌。

我们往下找,我将删除其中的检查点文件。你不必非要这样做,这是可选的。但要小心运行 rmrf 命令(递归强制删除)。刷新后,现在我们要清理模型文件了。我将把这个整个目录上传到 Hugging Face。所以如果我们运行这段代码,这是来自 huggingface_hub 库的:我们创建一个仓库(repo),仓库 ID 将是这个。

注意,我在视频标签那里做了标记,以便大家知道这个模型是基于视频创建的。接下来我们要进入 API 上传文件夹,它会把这个文件夹上传到这里。当然,这取决于你的网速。不过我们可以看到,这是我之前在一个类似“所有优秀的烹饪节目”的例子中训练的那个模型,只不过那个我们训练的轮数更多。大家还记得吗?看看这是不是视频版本?是的,是视频。好了,它刚刚上传完毕,大约两小时前的事。目前还在进行中,所以我们稍等几秒让上传完成,然后我会展示新版本,看看它在 Hugging Face 上是否刷新。5、4、3、2、1,完美!我们得到了一个提交 URL。现在让我们回到这里看看是否已更新?好了,不到一分钟前更新的。

上传到 Hugging Face 的美妙之处在于有一个非常棒的数据层,叫做 Zet Z(Zett)。它只会更新那些发生变化的内容。因此,如果本地文件没有变化——比如所有这些模板文件在本地都没有改变——那么唯一变化的文件是 model.safetensors,因为我们训练了它。这就是使用 Hugging Face Hub 的美妙之处:有一个令人难以置信的数据层。所以,如果我们将来更新了数据集,比如向 food-vision-extract-3k 或这个特定模型中添加了一千个更多的样本,它只会更改那些实际发生变化的样本。或者如果我们进去编辑某一行说“哦,这其实可以更好一点”,我们可能会更新这些内容,那么在提交时它只会更改那一行。这是一个非常酷的功能。

现在模型已上传,让我们删除模型和 Trainer 对象,清空我们的缓存(empty our cache)。我们要将微调后的模型与基础模型进行测试对比。请记住评估指标,始终在自定义数据上测试你的数据或模型。虽然这些指标看起来不错,但我们的模型的实际输出效果如何呢?如果我们想使用训练检查点中的视频模型(记得那个只训练了大约一分钟),我们可以直接使用 training_args.output_dir。我会展示一下那是什么样的,太棒了!但是像所有优秀的烹饪节目一样,我提前准备了一个。所以我打算使用我个人资料页面上的那个模型。

当然,基础模型将是 Hugging Face 的小型 VLM(Small VLM)。我们将比较基础版本和微调后的版本。因此,我们要创建两个管道:PT 管道代表预训练管道(Pre-trained Pipeline),也就是我们的基础模型,我们在那里获得了模型 ID。FT 管道将是微调管道(Fine-tune Pipeline)。我们将加载 checkpoint_dir,也就是我 Hugging Face 上的那个模型。让我们加载它,它将加载两个管道,意味着有两个模型将被放入内存中。接下来,我们要从验证集中选择一个随机样本。这是一些来自验证集的随机选择代码(抱歉说是随机代码)。然后我们将对两个模型进行推理,即分别用预训练模型和微调后的模型进行推理并比较输出。

女士们先生们,看这里!我们已经成功微调了一个 VLM(视觉语言模型)。这是预期的示例模型输入。我们有图像,这是理想输出:蛋花汤、食品项目、高汤、鸡肉切碎、蘑菇、蛋白丝、辣椒碎。这是一个相当不错的标签,对吧?这是来自 Qwen2-VL-7B 模型生成的,它的参数量是我们的模型的 16 倍。这是预训练模型的结果:基础模型返回了一个空列表,正如我们之前所见的那样。但现在看看由我们的微调生成的输出文本:“食物一张图片标题汤食品项目红辣椒软壳蟹虾萝卜芦笋”。完美吗?不。但它是否完全符合我们想要的格式呢?是的,它相当接近我们要的效果了。

请记住,这仅基于一千个样本(一千个训练样本)。实际的生产模型通常像我们之前看到的小狗分类器那样,是在 2700 万个样本上训练的。因此,我们的扩展方向之一可能是升级数据集。这是我前几天花几个小时制作的一个数据集。现在我知道它有效了,我下一步就是真正扩大规模。我只使用了一千张左右的食物图片和 500 张非食物图片。所以我开始思考如何将这变成一万张食物图片和两千张(甚至更多)非食物图片。这就是规模化的法则。好消息是我们的模型有效,我们的微调 VLM 以及可选的第二阶段训练也起作用了。

你可以解冻视觉编码器(vision encoder)。你需要创建一个新的 config/trainer,因为第二阶段有一些新的设置参数,但我将其作为扩展留给大家。然后你可以使用第二阶段管道进行推理。由于我们的数据集非常小,我尝试了一下,改进并不显著。如果我们有更大的数据集,我认为第二阶段管道可能会带来稍多的提升。接下来,当然我们可以通过代码检查模型的性能。但如果我们想以易于交互的方式与他人分享我们的模型呢?这时我们可以使用 Gradio 创建一个演示。我们需要三个文件:app.py、requirements.txt 和 readme.md。

我们将为演示创建一个目录,比如在这里创建 demos/food-extract-vision-1。然后我们可以编写一个文件 app.py。Gradio 的核心理念是我们在这里定义模型,这些就是我们之前做的原始管道:基础模型管道和微调后模型管道。我们有一个辅助函数来创建输入格式,因为 Gradio 需要接收图像,我们需要将其格式化消息。然后我们可以像以前一样将该内容传递给我们的模型。我们将为我们的模型分配一个 GPU 来运行,这是在 Hugging Face Spaces 中可以做的操作。让我去 Hugging Face Spaces 看看,这些都是演示应用,但你也可以创建自己的应用。你可以通过 Web 界面完成此操作,但因为我们是程序员,我们将使用代码来实现。你也可以不使用 GPU(Zero GPU),我是 Hugging Face 的付费订阅用户,所以我能获得更多 Zero GPU 的使用权限,但我相信这也是免费的(如果我错了请纠正我)。我们可以通过上传代码来实现这一点。

输出结果。因此,它将输入图像作为输入,获取原始管道的输出,然后获取微调后管道的输出并返回这两者。这是用 Markdown 编写的标题和演示描述,接着我们创建了 Gradio 界面。这里是我们的函数 extract foods from image inputs title description outputs(从图像输入中提取食物、标题、描述和输出),同时我们也准备了一些示例。我会将这些写入文件,它们将放入其中。嗯,所以我们有演示文稿。哦,抱歉。Demos food vision extract app.py(演示食品视觉提取应用)。好的,很漂亮。然后我们有一个 readme 文件,就放在那里。我说要把这个放这里。“注意:此 README 是在为 YouTube 录制的现场教程中编写的。链接即将推出。”好的,我们将其保存到这里。这将保存到这里的 readme.md 文件上。好了,很漂亮。

接着是我们的 requirements(依赖要求)。其中一个你可能不太熟悉的库是 numba words(注:此处原文似为误听,通常指特定依赖或拼写错误,但根据指令需忠实翻译)。所有的 transformers、torch、accelerate、gradio 和 torch vision 都是必需的。numba words 是我发现 Small VLM2 所需的依赖项。嗯,抱歉。如果你在使用 Google Colab,可能需要进行安装,但我相信你现在应该已经发现了这一点。最后,我们可以将演示上传到 Hugging Face Hub 上。我们可以创建一些关于参数的详细信息。我们还可以在这里创建一个 Space 仓库。因此,Hugging Face 仓库类型将是 space(空间)。我们将使用 Gradio SDK。然后获取完整的仓库名称,接着上传我们整个演示文件夹。

正在上传 food extract vision demo app.py(食品提取视觉演示应用),来自 YouTube 教程视频。所以,我们会运行它,正如我所说的,它将打包这个文件夹,如果一切顺利,我们应该能够访问 Hugging Face 上的 food extract(食品提取)页面。好的,因为它刚刚上传并进行了更改,所以正在重新构建。但我们的文件都在这里。就在不到一分钟前。你知道这是一个真实的直播烹饪节目吗?然后我们查看 readme 文件,它有一个小注释:“注意:此 README 是在为 YouTube 录制的现场教程中编写的。”当然,我还没有链接,但一旦视频发布或当你观看此视频时,我就会提供。所以我们要等待应用程序重新构建。但是,朋友们,这正式标志着微调视觉语言模型(VLM)的完成。我们在大约一小时内全部在本地完成了这项工作。

当然,准备数据集花了一些时间。如果你想知道那里发生了什么,我有一些直播流可供观看。所以是 food extract vision(食品提取视觉)。这是我们要喂给模型的提示词。“这可用吗?”让我们试试。好了,这个呢?这将是我们未来模型需要修复的问题之一。如果你想看到这一点,请告诉我。我们的微调模型倾向于输出重复的内容。(是的)这是我们要扩展的一个方面:我们如何修复这个问题?然后如果我们看这里,漂亮的法式炸薯条。所以请用你自己的图片试试。链接将在描述中提供,如果你想尝试的话。你可能也有自己的版本。但如果我们进入主题演讲部分,我有一些扩展/课外内容供那些想要继续深入的人参考。

实际上,这些是我个人会尝试的一些事情。首先是规范化评估(Formalize evaluations)。我们从基础模型开始,在自定义数据集上进行微调,开发出了一个人们可以使用的演示应用。我们注意到我们的微调 VLM 表现尚可,比基础模型好得多,但还没有达到我们希望的水平。所以如果我们想规范化这个过程并使其走向生产环境,我们绝对需要规范一些评估工作。我们可以编写一些评估代码,将微调后的小型 VLM 模型的输出直接与 Qwen-8B(注:原文为 Quenval,推测指代某大型模型如Qwen或类似基准)进行比较。目前我们只是凭感觉(vibe check)评估了我们的模型。

第二,我们可以改进数据采样,无论是数量还是多样性方面。如果我们发现模型会出错——是的,它已经在过度生成内容了;对于某些图像没有提取出正确的食物等等——我们可以改进输入数据。大多数情况下是更多的样本和更多样化的样本。例如,我们可以在 Food-101 数据集之上引入一千多张真实的食品照片。Food-101 是一个公开的数据集,但图像质量并不是很高。而如果我们要优化模型以从餐盘中提取食物——就像我们在视频开头的 Neutrifi 演示中看到的那样——我们绝对需要将这类样本纳入其中。这是什么术语?我想是“现场测试”(Genesi Gumbatu,丰田的做法),即真实的测试、真实的位置。如果你想让模型改进,数据越真实越好,效果可能就会越好。

第三,移除输入提示词(Remove the input prompt)。目前,我们的模型是通过文本和图像对进行微调的。未来,我们可以直接移除该输入提示词,直接从图像到文本。这将为我们节省输入标记(tokens)。我们不需要输入那个提示词。让我在这里展示一下我的意思,就是这个提示词。我们必须向模型输入这段文本,因为这就是它被微调的基础。现在,如果我们想节省一些输入标记,为什么要这样做呢?让我们看看这个令牌计数器(token counter)。因为我们需要更少的令牌。这是使用 GPT-5 分词器计算的,但对于小型 VLM 来说也差不多。这意味着我们不需要对这些 160 个令牌进行推理,这可能会改善延迟。

最后,如果我们移除输入提示词,我们将直接从图像生成结构化输出。(第四点)我们已经讨论过这一点,但修复重复生成的问题。我认为我们必须要么在监督微调中训练更多数据,要么引入强化学习来避免重复生成。为此我会采用一种称为 RLVR(基于验证奖励的强化学习)的方法。我们会为模型设置一个奖励函数,以生成正确数量的 JSON 数据,即不重复内容。这就是奖励函数的作用:如果它生成了非重复的输出,得一分;如果生成了重复输出,得零分。这就是强化学习(RL)或一种 RL 技术。当然这背后还有很多内容,如果我们愿意可以深入探讨。

最后,我们将在另一个数据集上尝试微调过程。目前我们完成了食品提取(food extract),但我要挑战你的是:练习对另一个结构化数据任务微调小型模型,例如从发票中提取详细信息——就像我们在 Gemini 中看到的那样。所以拿一张发票,提取诸如日期、金额等细节信息。

如标题所示,包括日期、金额、描述等。或者你可以复刻类似苹果视觉智能的功能,从海报中提取事件信息。这样你就能获取日期、活动标题等信息,并将其格式化为 JSON 数据。随后你可以直接将其导入日历应用。这将是练习微调视觉语言模型(VLM)的一个绝佳方式。

因此,我相信该模型很可能已经见过许多类似的样本。所以从图像中提取文本时,它在这项任务上的表现可能会更好。以上就是我微调 VLM 的过程。如果你有任何问题,请在下方留言。此外还有另一段关于微调大型语言模型(LLM)的视频。如果你希望仅针对文本进行 LLM 的微调,不妨去看看那一期内容。但如果在未来你想看到其他主题的内容,请留下评论。