女士们先生们,欢迎来到机器学习烹饪秀。我是主持人丹尼尔·伯克(Daniel Burke),在本集中,我们将本地微调一个小语言模型或称 SLM。那么,什么是小语言模型呢?这取决于个人的定义。对我来说,参数量在十亿以下的都算。具体来说,我们将执行以下步骤:从 Hugging Face 下载模型 Gemma-3-270M;从 Hugging Face 下载数据集;然后使用自定义特定任务的数据集对 Gemma-3-270M 进行微调,这将作为我们的基础模型;接着将我们微调后的模型上传到 Hugging Face,以便与他人分享或自己复用;最后创建一个演示供人们交互。所有需要的链接都在下方。
简单说明一下,这是一个快速版本。我不会逐步讲解所有代码细节,我已经在频道的直播中做过详细演示了,稍后也会把那些链接放在下面。因此,我们将快速过一遍流程,但所有需要的资源都在下方供你按自己的节奏逐步操作。那么,为什么要微调你自己的模型呢?第一点:我们希望拥有该模型。我们可以用自己的硬件运行它,数据保持本地化。我们不需要将自定义数据发送到任何 API 或其他公司。因此,如果你是大型企业,你可能希望训练一个定制模型在自己的硬件上运行以执行非常特定的任务。
第二点:我们的任务足够简单,只需使用小语言模型即可。很多时候我们不需要大型语言模型(LLM),也不需要那些在整本互联网上训练过的庞大模型。我们只需要它执行特定任务,例如从文本中提取结构化数据。稍后我们将看到具体效果。第三点:无需 API 调用,因此如果需要的话我们可以离线运行。我认为这是模型乃至机器人领域的一个巨大未来趋势,甚至包括将来在手机上运行的模型,因为并非总是有网络连接。因此,能够在本地设备上运行这些功能会很酷。
第四点:我们可以以批处理模式运行它,从而获得比单次逐个进行 API 调用快得多的推理速度。稍后我们将在视频中看到,如果需要批量推理,我们每天可以处理多达约一百万个样本。第五点:默认情况下,当我们下载 Gemma-3 270M 时,它在我们的任务上表现并不好;但由于在自定义数据集上进行微调后,它表现得非常好。当然还可以改进,但这只是一个小型数据集。
我想让你记住的一点是:大型语言模型本质上是“Token 输入,Token 输出”。这里的 Token 指的是数据的数值表示形式。数据可以是几乎任何东西,比如图像、视频、文本(你所能想到的任何内容)、DNA 序列等。因此,如果你拥有正确的 Token 集、自己的数据集以及理想的输出结果,你就可以为特定任务定制专属的专家型 LLM。这就是我们即将要做的事情。
现在我们有了“食材”。当然,我前面提到过基础模型:Gemma-3 270M。还有一个数据集叫作“Food Extract 1K”。让我展示一下,就像所有好的烹饪节目一样,这是我提前准备好的。Food Extract 1K 的目标是:给定一段示例文本序列(可以是任何东西,比如乱码、图像描述等),从中提取出食物和饮料。为此,我准备了一系列图像描述的序列,其中有些是真实的,有些是合成的,还有一些只是乱码字符串。
然后我使用 GPT-OSS-120B 对这些数据进行了标注。为什么要这么做呢?因为 GPT-OSS-120B 是一个强大的开源模型,但它相当庞大,拥有 1200 亿个参数。具体来说,它比我们即将训练的 Gemma-3 270M 大了约 444 倍。因此,我们本质上是在将大型强大开源模型的知识蒸馏到一个非常小的模型中,以应对特定的任务。
现在让我们回到工具层面。我们将主要在软件方面使用 Transformers 库,当然还有 TRL(Transformers Reinforcement Learning)库。虽然名为强化学习库,但它也可用于监督微调,这正是我们将要使用的方法。简而言之,监督微调包含输入和输出。在我们的案例中,输入是原始字符串(可以是任何类型的文本),而输出则是围绕食物和饮料的结构化数据。我们之所以选择监督微调,是因为它是最直接的方法之一:你有示例输入和对应的示例输出,然后训练模型去复现这些结果。
我将使用 Nvidia DGX Spark 运行所有这些代码,因为 Nvidia 寄给了我一台,它非常酷,我想展示如何使用它。我个人也非常想要一台拥有这种硬件的设备。如果你没有 Nvidia DGX Spark,也没关系。我已经验证过所有代码都可以在 Google Colab 的 L4 GPU(免费实例之一)上运行。你甚至可以使用 T4 GPU,只要显存约为 16GB,就能顺利运行。不用担心,你可能只需要将批次大小(batch size)从默认的 16 稍微调低一点,比如改为 4。
让我继续吧,我说过这是快速版本。这是我当前的设置:我正在运行的代码全部在 Nvidia DGX Spark 上执行,这是一件很棒的小型硬件设备。我在 Mac Mini 上进行编码,但通过 SSH 连接到这台漂亮的金色机器上(如下方所示)。不过正如我之前所说,我已经验证过所有代码在 Google Colab 中都能正常工作。因此,无论你是使用专用的 AI 硬件还是在谷歌提供的免费在线软件上运行,在现代人工智能世界中,你都可以随心所欲。
让我们看看我们的任务。如前所述,项目是“Food Extract”(食物提取)。我们需要一些输入文本,可以是任何东西。它可能是食物的描述,也可能是展厅里卡车的图片说明等。无论是什么,我们都要输出一些结构化数据。为什么要这样做呢?因为我们可以随后使用这个模型来过滤大型图像描述数据集,例如 Data Comp 1B。让我们来看看那个数据集。既然在这里,我想让一切尽可能实用化。
这就是 Data Comp 1B:这里有十亿个图像 URL 和对应的描述,对吧?如果我们是一个食品相关企业……
像Neutrify.app这样的初创公司,我和我弟弟实际上就是做这个的,这就是我们的应用。你现在就可以去苹果App Store下载并试用一下。我们可能需要大量大量的食物图片,但我们并没有这些资料。因此,我们可以像这样使用一个开源数据集,筛选出所有关于食物的文字描述作为食物图片的标题,然后我们就可以针对我们的用例使用这些专门的食物图片。所以如果
我们回到这里,我想强调一下——剧透预警——这就是代码部分,这非常广泛。我所说的结构化数据可以指任何结构。这里只是输入标记和输出标记。这就是这一切的关键要点:大语言模型就是“输入标记,输出标记”。大语言模型的优美之处在于它们是解码器架构。我的意思是说,你的输入和
输出几乎可以是任何东西。所以如果你想改变这种结构,没关系,尽管去做吧。如果你想要修改这段输入文本,也许你是一家销售保险的企业,每天收到10,00封客户邮件,并且希望在大语言模型上运行本地硬件,因为这些都是私密数据,对吧?而且你希望它提取诸如客户问题、客户位置等
信息。你可以将这些放入Google表格或数据框中,并非常轻松地对其进行排序处理。所以在我们的案例中,我们有食物或饮料以及一个字段零或一,这表示这是关于食物的吗?然后我们还有标签。嗯,我会展示一下它来自一个标签字典。在这种情况下是食物项、饮料项,呃,因为这样我们也可以很容易地对其进行筛选。我们还有菜单标签、食谱标签等
各种各样的内容。我们还有一些食物项,这些只是这里所含食物的名词部分。比如我前几天午餐吃的是什么:牛肉、薄荷叶、白米饭、辣酱,还有一份腌菜和胡椒以及一杯水。所以正如你所料,我们有牛肉、薄荷叶、白米饭、辣酱、腌菜、胡椒和水。现在看这里的输入文本,我们有一张照片显示一辆汽车在山丘上行驶
背景阳光明媚,几只鸟儿在天空中翱翔。如果我们只想要食物图片的话,我们并不关心那张图像。所以这将得到一个零和空的标签、食物及饮料项列表。正如我所说的,你可以根据需要将其更改为任何形式。我只是把它构建成了食物提取器,因为嘿,我们都喜欢食物,大家都吃食物。你甚至可以尝试一下。实际上,我有点
操之过急了。让我展示一下我们要完成的内容是什么。我们再次拥有代码,所有链接将在下方提供。我们有创建可重用的微调Gemma 3 270M模型的代码,这是目前公开可用的。我们将有一个可分享的演示版本供你自己尝试。所以让我们现在就试一试吧。我们会回到Chrome浏览器中,这就是我刚才提到的那个模型
它在我的HuggingFace个人资料页面上,用户名是Mr. Durk。如果你跟随本教程到最后,你的用户名很可能也会在那里出现。然后我们有了模型和演示功能。所以让我们试试这个这里有一些输入文本,记得我说过这可以是任何东西吗?我们的模型输出会完美无缺吗?不会。但机器学习的优美之处在于呃,错误实际上
对你非常有帮助你可以利用这些来在未来改进你的模型。那么为什么我们不这样做呢?今天午餐我吃的是沙丁鱼、腌菜和胡椒以及辣酱。这是真实的故事我最近一直在大量食用沙丁鱼、腌菜、胡椒和辣酱的组合。所以你看,美丽的食物或饮料一食物项是的我们得到了一个标签沙丁鱼、腌菜、胡椒和辣
酱嗯,午饭后我喝了一杯咖啡。实际上我没做那件事我今天早上确实喝了一杯咖啡,但这没关系对吧?我们只是在测试我们的LLM是否有效。这是经过微调的看这里咖啡是食物吗?嗯,这可能有待商榷当然现在我们有饮料在这里也有食物在这里所以这只是一个非常基础的例子
请记住,这仅基于一千个样本我有1K的数据。事实上我们其实喜欢模型出错的情况因为我们可以说好吧,我将来需要更多这方面的数据样本如果你希望看到关于如何创建自定义数据集的视频请在此下方留言我很乐意制作它。现在我正在DGX Spark上运行,这是一台
非常漂亮的小型硬件设备看啊这么小。它的优美之处在于你可以获得新的软件模型优化来加速Nvidia DGX Spark的性能我个人还没有尝试运行GPT-O 12B,但据说现在速度提高了30%左右所以也许再次那将是未来的视频或直播我们在其中创建一些合成数据以潜在地改进我们的模型。你看这就是
AI在2026年及以后的优美之处你可以创建高质量的合成数据来为特定工作用例引导数据集。正如我所说的所有代码都可在下方找到这就是我们要逐步讲解的内容我会留下链接在下面如果你希望获得分步指导请参阅我频道上的直播我们前几天从头开始编写了这段代码
此外Zero to Mastery平台上也有一门视频课程所以如果你访问这个链接我会逐步讲解请继续关注LLM部分尚未上线但我们有两个项目文本分类和目标检测我逐行详细讲解了每一步代码。正如我所说的这个是快速版本所以如果你想要分步指导那在Zero to Mastery上是
可用的。那么让我们到这里来Hugging Face LLM微调教程嗯我将在VS Code中运行它正如你所见那里有可用的Spark如果你想看到这个我会展示给你看这是我们正在运行的GPU如果你想要运行这个你可以也在Google Collab上运行它。我希望我的Esc键能正常工作那该
多好不是吗?Nvidia SMI也许我会重启这个经典的演示环节我去展示演示时它却不工作了。无论如何这里有一些信息正如我所说的所有你需要的链接都在下方Nvidia这是……抱歉呃由于某种原因我必须重启整个笔记本。但让我们开始吧Nvidia SMI这是我的GPU我们在那里运行着它是Nvidia GB10这意味着
Grace Blackwell 10这就是DGX Spark内部的东西。但让我们开始吧首先最重要的是我们将导入我们的依赖项如果你在Google Collab上你可能需要安装这些然后我们可以检查我们有多少可用内存DGX Spark的美丽之处在于它拥有128 GB的内存所以我们有很多空间可以发挥在这里首先
我们将设置基础模型,即 Gemma 3270M。我使用的是指令微调版本。实际上我没有尝试过另一个基础版本,也许那个更好?这或许是一个你可以尝试并告诉我是否效果更好的实验。我将使用指令微调版本,因为它经过调整以遵循指令。然后我们向下滚动到这里获取分词器(tokenizer)。什么是分词器?在机器学习和人工智能中,计算机不喜欢文本、图像、视频或音频等数据,它们喜欢数字。因此,分词器只是一种将原始数据转换为数字的方法。每个模型通常都有自己特定的分词器。Hugging Face 的美妙之处在于,它会自动从我们的模型名称 ID 中加载正确的分词器。
如果我们运行模型,就能直接执行,但在这里我们得不到好的输出结果,得到的是 logits(对数概率),那是另一个故事的话题。我们希望这些内容对人类可读。因此,我们的下一步是获取数据集。这是食品提取数据集(food extract dataset)。我们可以使用 Hugging Face datasets 库中的 load_dataset 方法下载它。也许我稍微放大一点以便看清,正如我所说,这是一个相对较小的数据集,包含 1400 个示例。有些是真实的,有些是合成的,但它们都由合成模型标注过。我个人没有对这些数据进行任何标注。
未来如果我们希望使我们的模型更加稳健,我们肯定会着手创建一个评估数据集,可能至少包含 100 个由我们自己亲自审核的样本,以确保它们是正确的且符合我们的需求。因此,当我们训练模型并在我们人工制作的测试集上评估它时,如果它在这些样本上表现良好,我们就可以知道它的性能很好。每当我下载新数据集时,我都喜欢查看至少 100 个随机样本。让我们来看看这个数据:我们拥有输入的营养信息标签,例如“五籽面包片”,以及关于营养信息的许多内容。
这是一个示例结构化输出,也就是我们希望模型预测的内容:是食物还是饮料为真(true),然后是一些标签。注意这是 JSON 格式,但我将其精简为了更像 YAML 的格式。我为什么要这样做呢?让我先取这个片段,我认为这对于从事小型大语言模型(LLM)工作的人来说是一个重要的提示。让我们进入一个令牌计数器分词器,这是 OpenAI 的分词器。虽然不完全符合我们的需求,但足够接近了。这里有 71 个令牌(token)。
令牌是数据单位,对吧?而我们的模型是一个解码器。因此,它必须逐个输出这些令牌。那么,那里的限制因素是什么?如果我们必须逐个输出一组这样的令牌,那么我们输出的每一个多余令牌都会导致更长的生成时间。所以,如果我们能获取相同的信息但进行精简呢?你看这是 JSON 格式,但其中很多部分是冗余的,比如括号。每次我们必须生成这里的一点信息时,都在消耗计算资源。
让我们看看这里的精简输出有多少令牌?我们有 71……等等,让我把它们并排比较一下会很好。哦,抱歉,我们可以直接输入文本进行比较:50 个令牌。你可能会想,“丹尼尔,21 个令牌没什么不同啊,只少了 21 个。”是的,但乘以一百万呢?这正是你使用这些模型进行推理的方式:在大型数据集上进行推断。如果你进行一百万次推理,那就是节省了 2100 万个令牌。我们实际上可以进一步精简这个格式。
这又是另一个项目,也许我们可以将其缩写为 FD 然后 TFD,这样甚至可以进一步精简。我还没有深入研究过这一点,我对这个特定示例节省 20 个令牌感到满意。让我们回到正题。我们已经讨论了输入是什么,这些输入和输出来自哪里?抱歉,我有点跑题了。这是来自我们的数据集。记得我们有序列(sequence)、图像 URL 以及那一堆列数据。我刚刚上传了这个文件,所以这是来自 GPT-OSS-12B 的原始 JSON 格式标签。
这是一个精简后的格式。因此,我们将根据这里的输入字符串(序列)来训练模型,让它创建这种精简版本。然后我们可以写一个解析器,我可以把它交给 Claude 之类的工具并说:“嘿,把这个转换成这种格式”,很简单。这里的想法是生成更少的令牌,这样在进行推理时速度会更快。因为我们希望使用模型来潜在地过滤大量语料库,并让它为文本分配各种标签。
这里是我之前提到的那些标签:NP 代表营养面板(nutrition panel),还有成分表、菜单、食谱、食品项目。我想知道你稍后是否可以在演示中尝试生成其中一些令牌,但现在我们先留在这里继续。那么,我们做了什么?加载了一个模型,加载了一个数据集。接下来我们需要做的是将数据集格式化为大语言模型风格的输入和输出。现在让我们看看其中一个随机样本,这只是我们在 Google Chrome 中看到的数据集内容,但现在是以 JSON 格式呈现的机器可读数据。
序列将是模型的输入,这是文本。它可以是任何字符串,对吧?不一定非要是关于食物的,可以是任何字符串,这就是大语言模型的美妙之处。现在我们有一些 UUID 和一些信息在这里,你可以自己阅读那些内容,它们相当不言自明,但这是我们目前关心的东西:GPT-OSS-12B 标签和精简版。因此,我们希望根据这个序列训练模型生成这个精简后的标签。
然而,如果我们将直接文本放入我们的模型中,它可能会产生非理想的输出。原因是因为每个大语言模型或 Hugging Face 上的每个模型通常都是按照特定风格训练的。在我们的案例中,Gemma 3 是对话风格的。这意味着我们需要使用一个模板(RoPE/Chat Template),采用这种小格式:只需将你的样本序列放入带有“user”角色的内容字典中。我是用户,我输入这些信息;然后是系统角色,模型或系统输出这些内容。
这就是我们的输入和理想输出。我们希望训练模型针对任何此类输入产生理想的输出。现在如果我们进行样本到对话的处理,就会得到这里的消息列表(messages)。如果我们要在整个数据集上运行这个辅助函数,我们可以这样做……
映射函数。这基本上是说,嘿,取数据集中的所有样本,将“sample to conversation”应用到这些样本上,然后我们就得到了精彩的结果。现在如果我们注意到上面的序列,我们没有消息字段(messages field),但现在有了。好的,这只是样本零。我们可以去我最喜欢的数字42。所以,另一个可选的问答发生在风落岛(Windfall Island)的夜晚戏剧中。好的,这跟食物无关。我们不想那样做。那么,我们要得到了什么?食物还是饮料?零。没有标签,没有食物,也没有饮料。系统轮盘转动。太棒了。这正是我们想要的。
现在,机器学习的第零条规则是创建训练集和测试集的分割(split)。当你看到这些大型语言模型所做的所有基准测试时,如果你发现某项表现过于出色,一定要保持警惕。因为如果一个大语言模型在互联网上进行了训练,并且在某些基准测试中表现得非常好,那么其中一些基准测试可能已经泄露到了训练数据中。这就好比你在大学里复习期末考试,然后当你参加期末考试时,你已经知道了所有内容。因此,结果并不能真正反映你的知识水平,你只是在复述你基本上作弊的内容,对吧?
所以,我们要创建训练集和测试集的分割。我们将进行80%的训练数据和20%的测试数据划分。这意味着我们大约有1000个样本用于训练,300个样本用于测试。因此,在训练过程中,我们的模型将看到这些样本,并在这些(测试集)上进行评估。所以,当我们稍后评估我们的模型时,它从未见过这些样本。这是机器学习的第零条规则(注:原文为number one rule,但在语境中通常指代基础原则或零号法则,此处忠实翻译为第一条规则)。所以当你训练自己的模型时,总是留一些样本在旁边,以便你可以评估它并了解其后续表现。
现在让我们尝试使用管道(pipeline)来运行模型。进行任何类型的Hugging Face模型推理的最简单方法之一就是使用管道(pipeline)。我们可以输入文本生成。我们拥有的目标模型记得是什么吗?那是Gemma 3。这是我们的架构。它使用的是注意力机制(attention architecture)。这里有一个解码器(Decoder)。解码器只是意味着它可以输出几乎任何东西。我让你以后更深入地探索这一点。现在,我们只专注于实际训练它。我们要有一个分词器(tokenizer)。我们有一些输入文本。现在,我们将使用这个小助手函数将其转换,正如我所说,我们需要它以对话模式呈现。
所以,我们是在训练它吗?不,抱歉,我们只是将其格式化为用户的角色,其中用户的内容是输入。嗯,“嗨,我叫Daniel。”请回复我一首关于机器学习的诗(palms)。这就是我们的输入,对吧?对。所以,如果我们将这个交给Gemini这样的东西,让我们去Gemini。“请回复我一首关于机器学习的诗。”完全正确。很好。好的,我不需要大语言模型给我写诗(palms),但我只是向你展示输入和输出是什么样子的。我们有一个输入提示词(input prompt)。
问题是,我们的这里输入文本在进入模型之前需要一点格式化。这是所有机器学习面临的最大障碍之一:你的数据必须以与训练该模型相同的方式进行格式化。所以我们要在这里做的是,我们将对输入提示词进行分词(tokenize)。我们将应用聊天模板(chat template),这本质上是应用一些特殊标记。嗯,然后我们要进行这个操作:这是输入提示词,然后我们将创建一些输出。这将是我们第一次使用Gemma 3进行推理。
对吗?我们实际上在上面做过,但我们没有看到它输出了什么,因为它输出了一些logits(对数几率),我读不懂这些logits。也许你可以读懂。我喜欢阅读文本。所以,让我们开始吧。管道输入提示词(pipe input prompt),最大新标记数512。如果我们想缩短输出,我们可以设置为256,但我将让模型尽可能多地写出诗(palm)。所以,如果我们在这之前运行这个代码,这是输入提示词。我们有这些特殊标记。我不会逐一解释所有特殊标记。你基本上需要知道的是,如果模型是用特殊标记训练的(Gemma 3就是这样),你一定要将这些应用到你的输入中。否则,你的输出可能不会很好。
对吗?所以这里的特殊标记有BOS(beginning of sentence,句子开始),然后是用户的对话开始标记。然后这是我的提示词:“嗨,我叫Daniel。”对吗?所以在幕后,当你在Gemini或Chatbot中输入内容时,它可能正在做类似的事情。我不知道它们具体使用什么格式,但它肯定在幕后做着类似的操作。嗯,然后你只看到可爱的文本输出,不是吗?所以,“请回复我一首关于机器学习的诗”。这是我的输入:“我叫Daniel。请回复我一首关于机器学习的诗。”
现在,让我们看看Gemma 3(2.7亿参数的小语言模型)能生成什么。哦,这实际上还不错。就像只是看着它而不阅读内容时,你会觉得:“好的,几年前这在模型中是不可能的。”但现在看起来有点像一首诗。我们甚至不打算读它。我们就只是看着它。但为了好玩,让我们读一下。你可以随时跳过30秒。这可能会很糟糕。好的,“Daniel,这是一首关于数据力量的机器学习诗。”
“无声的嗡嗡声,学习的溪流。低语着数据的生动梦想。从明亮的像素到深层的模式,数据守护的秘密……”(此处省略部分诗歌内容以符合段落长度要求,实际翻译包含全诗)“……让数据指引你的道路,穿越逻辑的日子和白天。强大的工具,引导的手,去建设一个更好的土地。”看,这并不糟糕,但也不是很棒。嘿,这是一个2.7亿参数的模型。那是个小语言模型、小型语言模型。现在,你可能会得到与我不同的结果。嗯,但这是另一个关于为什么大语言模型的输出是不可复现的故事(non-reproducible outputs)。Thinking Machines有一篇很棒的博客文章讨论了这一点。我们有点跑题了。这是我前几天得到的一个例子。不是完全相同的东西,但现在让我们尝试基础模型(base model)。
记住,我们对Gemma 3还没有做任何操作。我们只是从互联网上下载了它。让我们在我们的随机样本之一上进行尝试。所以正如你可以看到的,在我运行这个代码单元之前(因为这将获取一个随机样本),我特意在这里放入了乱码文本,对吧?所以像“JPEG”这样的序列。那我为什么要这样做呢?因为我想……
对大规模图像描述数据集进行推理,其中可能包含各种内容。因此,我想教会我的模型:如果它看到这类文本,就输出“非食物”;但如果它看到与特定食物相关的文本,我要确保将其识别为食物。让我们在一个随机样本上测试一下,看看模型的输出结果如何。这是基础的 Gemma 3 模型。
标签显示为“饮料的营养成分表”。你可以自行阅读,篇幅较长,但基本上只是转录了营养面板。这是模型输出的结果:“该饮料的营养信息”。它基本上是复述了我们输入的内容,但这属于通用输出。你可能会觉得这很糟糕,但实际上,模型能这样输出是非常好的表现。
这意味着它具备对语言、模式以及我们提供的标记的基础理解能力。我们可以利用这些标记,将其塑造成我们想要的样子。我们再试一次。这些输出通常都比较通用。“放入这张 JPEG 图片。”“我无法查看或处理图像。”这是事实。
我们的模型实际上知道它能处理什么,它只能处理文本。它是真的知道这一点,还是仅仅返回了相关的标记?这属于哲学辩论的范畴了。“一位年轻女性与一群人用红酒碰杯。”“一位拿着红酒的年轻女性坐在桌边,周围有一群人。”这些回应确实很通用。
好消息是它们并非完全不可用。过去你可能会尝试通过提示词来引导模型,那么为什么不在默认输入中加入一些指令呢?通常我们看到,如果给模型几个示例,它的表现会有所提升。以下是我们的提示指令:“给定以下来自图像描述的输入文本……”
这主要也是我们要处理的目标数据。“请提取食物和饮料项目到一个列表中。如果没有,则返回空列表。”我们希望得到这种格式,虽然不是完全精确的格式,但也足够接近了。接着我们给出一个示例:“你好,我叫丹尼尔。”输出应为空,因为这句话里没有食物或饮料。
接下来是:“一盘米糕、三文鱼、农家干酪和小樱桃番茄,配一杯茶。这是我妻子前几天给我做的早餐,非常美味。谢谢美丽的妻子。”食物项目:米糕、三文鱼、农家干酪、樱桃番茄。明白了吗?我们给它一些示例。“仅返回格式化后的输出,不要其他内容。”
目标输入文本将通过一个简单的替换函数与数据集中的随机示例进行格式化。让我们看看效果如何。“这是输入的文本,对吧?”它可能会是一串乱码。如果我们再试一次,也许不会得到乱码。“McCormack 食品澳大利亚辣椒酱。”很好。
“一只手拿着一瓶 McCormack 食品源(应为辣椒酱)。标签醒目地显示了配料:番茄泥、蒸馏醋。”很好,这里大约有十个食物项目。这正是我们希望模型提取的内容。没有饮料项。“让我们看看它怎么做。”输出显示,它直接返回了我们给出的那个示例。
为什么会这样?我们确实指示过这样做。问题是我们的模型只有 2.7 亿参数。你可以将参数视为模型的某种学习能力。通常,参数量越大,它记住数据模式的能力就越强。虽然 2.7 亿听起来很多,但与拥有万亿参数的 Gemini 等模型相比……
我猜它学习的能力要少得多。那么,如果把这段完全相同的文本输入到 Gemini 中会怎样?我想展示一下区别。“现在我们的模型没有达到预期效果。”“哦,在这里。”我要复制这段提示词。这是我们喂给 Gemma 3(2.7亿参数)的内容,如果传给 Gemini 呢?
这是一个庞大的生产系统,我们希望它能正确执行。“复制粘贴进去。”完美。它完全按照我们的要求做了,以我们想要的精确格式列出了食物项目。“那我们能本地复现这个结果吗?”“这是我们的样本辣椒酱。”甚至我们的人工标注(Ground Truth)也不是很完美。
如果把这个输入到我们的演示中,它会给出什么结果?正如我所说的,机器学习中的错误是可以接受的。如果它没有做对也没关系,我们可以在后续的示例中改进。“这是我们的微调版本。”“看看它与 Gemini 的对比。天哪,看这里:辣椒酱、番茄泥、醋、糖、糖蜜、麦芽糊精、盐、香料、黑胡椒、玉米淀粉、红糖、增稠剂……”
洋葱、大蒜、烟熏风味。“Gemini 做得差不多吗?”“差不多。虽然不是完全完美,但我对微调模型的输出很满意。”再次剧透一下,这正是我们要努力的方向。“现在看起来我们的 LLM 不愿意按我们想要的方式工作。”这在许多你遇到的大语言模型中都是常态,尤其是小模型更是如此。
如果你有特定任务需要微调,可以通过提示并使用拥有更大容量、可能更聪明的大模型的 API 来实现。或者如果你的任务足够简单,你可以直接说:“我有一些数据,我要微调一个小模型。”那么让我们开始吧。我们可以通过设置 SFT 配置来微调模型。“我想在 Google Chrome 中打开,但它会在 Safari 中打开。”
没关系。SFT 配置指的是 TRL(Transformer Reinforcement Learning,变压器强化学习)。TRL 内置了强化学习算法,但我们仅使用监督微调(SFT),即输入、输出、输入、输出的模式。强化学习是未来视频的话题,如果你感兴趣请在下方留言。“这里有很多设置,我不会一一讲解。”
这是你的作业:请阅读这些内容。我放入了一些默认设置。“我们需要一个学习率。”这意味着给定输入时,模型将调整多少。我们的模型将尝试产生……
默认输出。请记住,当模型看到这些数据时,它们都已经处于数值空间中了。学习率将引导我们的模型朝着一个方向调整,试图使模型的输出更接近真实值(Ground Truth)。所以,正如我刚才所说,我不会逐步演示所有这些细节。你可能想要更改的一个参数是每台设备的批次大小(per device batch size)。如果你使用的是 Google Colab,也许可以尝试将批次大小设置为 4。因为我是在 DGX Spark 上运行,所以我将使用批次大小为 16。我有 128 GB 的显存可供使用,并且我将充分利用所有这些资源。
我们在这一行设置了优化器。我尝试过使用 AdamW,你可能见过 Adam 优化器,那是我的最爱之一,但不知为何它没有起作用。我使用了 AdamWrus(可能是口误或特定变体),它是有效的。现在,这里有一些信息。配置本质上是指我们希望训练设置做什么。另一个主要部分是我们要在哪里保存我们的模型?所以我们有了检查点(checkpoint)模型。实际上,我要删除这个设置,因为我希望这是一个完全实时的运行过程。好了,这里没有检查点模型了。所以让我们设置配置并运行它。
请记住关于配置的要点,你只需要记住监督微调(Supervised Fine-Tuning)配置。这些是训练我们模型的设置。我们将把这个传递给 SFT Trainer(监督微调器)。现在,Trainer 来自 Transformers 库。它是你传入模型、配置、数据集和预处理器的东西,它会在后台处理所有训练工作。那么废话不多说,让我们在我们的自定义数据集上微调我们的第一个小语言模型(Small Language Model)。
如果我们还记得的话,这就是我们的数据集。我们在上面完成了格式化步骤的数据集。如果我们要查看一下训练集有 1,000 个样本,测试集也有 1,000 个样本,并且在最后我们有我们的消息(messages)。很好。好吧,这将成为我们模型的输入。所以让我们开始训练吧。当然,这将发生在本地。我能听到我的 Nvidia DGX Spark 的风扇加速旋转的声音。现在,作为一名机器学习工程师并训练自己的模型,其中一个问题是你必须等待这个过程完成。
5 分钟的时间相当不错吧?在本地硬件上微调模型只需 5 分钟。在 Google Colab 上的时间可能也差不多。这其中的美妙之处在于,因为我们使用的是小语言模型并且只有大约一千个样本。所以我打算去喝杯水,让它运行起来。我会加速视频播放,这样你就不必坐在这里盯着进度条向上走了。虽然看着它进展确实非常令人满足。
哦,太棒了。我回来了。看等待模型训练完成的一个美妙之处在于,如果你想的话甚至可以打个盹。我在梦中看到了损失曲线下降的样子。看起来我们的平均标记准确率(mean token accuracy)进展良好。那么,这意味着什么呢?所以这个数字是令牌的数量,也就是我们的模型已经看到的令牌数量。熵(Entropy),实际上我不确定我们希望它达到什么状态,但它应该下降。损失肯定也应该下降。训练确实在降低,这很好。
那么最后一个指标是什么呢?好吧,准确率在 57% 左右徘徊。这意味着什么呢?鉴于我们的令牌集合——这又有点偏离主题了,但令牌可能取决于模型的词汇表大小有大约 150,000 种不同的令牌——这意味着数量很多,对吧?所以在输出可以是任何东西的标记空间中达到 57% 的准确率是非常棒的。而且由于训练损失还可以,所以我们的模型可能过拟合了(overfitting)。我们怎么知道这一点呢?让我们往下看。它现在已经查看了超过 500,000 个令牌,也就是少量的数据样本通过我们的模型。
但这太酷了,5 分钟就训练完成了。我们在 DGX Spark 上在 5 分钟内微调了一个模型,这太不可思议了。好了,我们的训练损失呈下降趋势。验证损失表明我们的模型可能过拟合了。我们怎么知道这一点?因为训练损失低于验证损失。但在这种情况下,过拟合可能并不是坏事。为什么呢?好吧,基于我的直觉(我还没有证实这一点,也许某处有相关的论文),我们需要非常特定格式的数据供我们的 LLM 使用。我们希望它每次输出相同的内容。
所以,实际上模型是否过拟合并不太重要,因为对我来说,这意味着我们的模型正在坚持我们特定的数据格式。它会吗?我不知道,我们需要测试一下。所以在这里我们应该有一些检查点模型,我们刚刚把它保存在这里了。所以我们得到了这些文件。我们可以使用这些检查点,但我们的 Trainer 最终会默认加载最佳模型。所以就是这里的这些文件:model.safetensors。
现在,如果我们去我们 wonderful 的 Google Gemma 3 270M(这里可能口误,应为 GEMMA),找到了。如果我们去看 Google 的那个 wonderful 模型,我们这里有一些文件。现在你会注意到这些文件实际上与我们在这里保存的文件是一样的。所以美妙之处在于,如果我们想删除检查点,我们可以这样做。只是使用 rm -rf 时要小心。我们已经把它清除了。现在我们在文件结构上与 Google Gemma 3 270M 相同。你能听出我在过去几天里说了太多 Google 和 Gemma 了吗?
所以,我们有了检查点模型。让我们首先加载我们的训练好的模型并看看它的表现如何。所以你看,我们是从检查点加载的。现在我们有了加载好的模型。接下来我们要创建我们的加载模型的 pipeline(流水线)。之前这个 pipeline 使用的是默认的 GEMMA 3270M,现在它将使用我们加载的模型。现在我们有了测试数据集(test set),我们将在测试集上执行检查。总是在测试集上进行验证,因为我们的模型从未见过这些样本。
好了,它在训练数据上已经训练完毕了。我们将在测试集上进行评估,看看效果如何?这里显示了一包有用的收获干燥蔬菜。配料包括各种蔬菜,如西葫芦。所以它应该能提取出这些信息:胡萝卜、蘑菇、洋葱、甜椒(capsic)、芹菜等。那么我们得到了什么?输出是食物或饮料吗?标签为:NP 表示营养面板,IIL 表示成分列表,FI 表示食品项,FP 表示食品包装。是的,是食物:西葫芦、胡萝卜、蘑菇、洋葱、甜椒、芹菜、形状奇怪的以及过度丰富的农产品。牛奶、鸡蛋、大豆、芝麻籽、榛子、开心果、腰果、杏仁。太棒了,这正是我们想要的输出类型。
它是 100% 正确的吗?我们需要进一步评估这一点。但为了做一个直觉检查(gut check),这也是我首先喜欢做的事情,就是随机查看 100 个样本。锦鲤(Koiish/Koi fish)手持特写镜头,正在从木棍上吃东西。天哪,锦鲤在日本是可以食用的吗?我不完全确定,但至少它输出了这个信息。警察在背景中站岗。这是我们的模型输出。注意它是如何遵守我们结构的。
这是因为我们在结构化数据上对其进行了微调。背景中有警察站岗,前景中是有着粉色头发、手持彩虹旗的女性。这是一段乱码文本,对吧?这是我们的模型输出,也就是微调后模型的输出。正是我们想要的格式,对吧?我们现在不需要再提示它了。只需输入文本,就能得到试图理清头绪的结果:绵羊、马里诺、羔羊的羔羊农场生活澳大利亚哺乳动物。好吧,我们大概可以从中提取出“羔羊”,但它可能学到了这实际上不一定与食物有关。它只是一张农场的图片。
现在,我们来看这个:零食包装。然后得到日期、花生。好吧,所以这里有一个潜在改进模型的好方法。我们会记录下这些模型没有输出正确饮料类型的样本,然后在数据集中加入一些训练示例,要么包含更多饮料,要么不包含任何饮料。我们要确保模型明白,它可能需要看到更多的样本,其中既有饮料又有食物输出出来(如果这说得通的话)。这就是机器学习的实验循环。我们已经训练好了模型,耗时5分钟。
如果我们要让它变得更好,我们可能会查看100个样本并写下10个常见的错误点。例如,它将某些食物分类为饮料。然后我们可能会创建另外500个样本来解决这些问题,或者给模型提供示例。接着重新运行训练过程并循环重复此操作。所以如果我们看这里,这是GPT OSS 12B的输出。这是真实标签(Ground Truth)。也就是我们理想中希望模型预测的内容:日期、花生、可可、花生酱、海盐、椰子、坚果。我们的模型接近了这个结果,虽然不是完美,但很接近了。
好吧,我们正逐步取得进展,对吧?对于一个比GPT OSS 12B小44倍的模型来说,表现已经不错了。我们的模型有2.7亿个参数,而我们从中获取标签的GPT OSS 120B有1200亿个参数。所以我们的模型小44倍,且表现相当(performing on par)。没有显著变差,只是稍弱一些,但我们可以通过更多数据来改进这一点。接下来,我们要将微调后的模型上传到Hugging Face Hub。为什么要这样做呢?你可以将其设为公开,我打算这么做;或者可以设为私有。然后你可以在组织内分享它。
如果将其公开,你可以与任何人共享。所以我现在上传的这个模型,你将能够在你的电脑上下载并本地运行,亲自尝试一下。所以,从澳大利亚布里斯班一路训练到这里。无论你在世界何处,你都可以运行它。好吧,我们只是确保它能正常工作。好了。“你好,我叫Daniel。”那么这是从哪里来的呢?模型路径。我们自己加载的。抱歉,“你好,我叫Daniel”。它会输出什么吗?你知道为什么它没有正确输出这个吗?那是因为我们没有用所需的提示符格式正确地格式化它。
所以我们只是做了一个默认管道(pipeline)。这就是我之前提到的意思。我们没有得到我们的默认输出或微调后的输出,因为我们没有用正确的聊天模板格式化这个测试输入消息。所以这是这里的“内容”,应用聊天格式后,我们得到:“你好,我叫Daniel”。同样的文本,但带有聊天模板。现在我们运行微调模型,“砰”,女士们先生们,好了!正是我们寻找的结果。记住另一件事:LLM是“输入什么Token就输出什么Token”。训练集和测试集,并且始终将模型训练时使用的相同模板应用于任何推理代码。否则你会得到与预期不符的输出。
我的学校有一个素食课程,但有点拥挤,出席率也不如应有的高。看,我不知道这个是从哪里来的。它是在互联网文本上训练的。谁知道它是从哪里冒出来的?食物和饮料。现在,这是前几天直播时有人给出的一个样本:英式早餐配焗豆、煎蛋、黑布丁、香肠、培根、蘑菇、一杯茶和烤番茄。所以我们得到食物或饮料:一、食物,焗豆、煎蛋、黑布丁。你懂的套路了。我们的模型运行得相当不错。我们不再尝试它,而是将其上传到Hugging Face。
我们正在实时运行这个(好吧,不是实时的,你是在未来观看这段视频)。我正在实时运行它。好了。我们的模型已上传到Hugging Face。这相当快,每秒179兆字节。这要归功于Hugging Face的Z层(Zero Layer)。ZE Z,抱歉。好吧。所以如果我刷新这个“food_extract_gemma_3270m”。好了,不到一分钟前和大约1小时前。为什么我没有上传上一个?我们有检查点模型(checkpoint models)。也许它没有改变。我还在想为什么这么快。无论如何,你可以想象我刚训练的那个模型已经上传了。让我暂停一下看看我能做什么。
好了。我做的就是手动删除了这个并重新上传它。所以我们确切地知道这就是我们在这里训练的模型。好了,不到一分钟前。所以我们的模型现在在Hugging Face Hub上。如果我们转到这个URL并想使用它,你可以复制那个链接。然后我们回到代码中,说你可以在这里下载它。太棒了。所以我们可以使用我们的模型了。我们微调了它,并确认它能很好地工作。也许我们要把它变成一个演示(demo),因为现在我们必须写一些代码,而并不是每个人都是程序员,对吧?你不能做。我们怎么把它弄进去呢?不是每个人都能做到。抱歉。我不想说“你做不到”,因为如果你在看这个,你可能能做到。那么我们如何把它变成这种格式呢?这基本上就是Hugging Face工作流的精髓:数据、模型、演示。
数据、模型、演示。跟我一起说:数据、模型、演示。这实际上是“学习Hugging Face”课程的座右铭:数据、模型、演示。所以让我们把它变成一个演示。为此,我们可以使用Gradio,这是一种通过Python文件完成此操作的好方法。你会得到一个很好的输入函数输出界面。在我们的案例中,输入将是文本。函数将是我们的模型。输出也将是文本。所以首先,我想向你展示……我只是要删除这个(我之前已经做过一次了),因为毕竟我们是一个烹饪节目。当然,我提前准备了东西。所以我想删除demos文件夹。好吧。这里有点危险操作(Acting dangerously here)。我们要在这里创建另一个文件夹:demos。
食物提取演示需要三个主要部分:app.py 文件、requirements.txt 文件和 README。虽然实际上并不强制要求有 README,但我还是会上传一个作为示例。如果你访问 Hugging Face Spaces(我们将在此托管演示),可以手动创建一个新的 Space,例如命名为 "food-extract-v1"。许可证选择 Apache 2.0 开源协议,计算资源可以选择零 GPU(Zero GPU)。这可能是一项高级功能或免费特性,我不确定具体细节,但我会使用零 GPU 并将 Space 设为公开。你也可以选择私密模式;在我的客户合作经验中,如果需要微调模型,可以聘请我创建私有 Space 供他们试用。不过这次我将使用零 GPU 并通过编程方式自动完成,而不是手动操作。
我们需要创建 app.py 文件,在其中构建整个应用程序结构:导入依赖项、定义函数并添加 spaces.gpu 装饰器以处理输入文本推理。默认使用我们刚刚上传的模型,即 Mr. Durk(或你的用户名)下的 "food-extract-gemma-3-270m-fine-tune"。这确认了正在运行我们的模型路径,我们创建了一个管道(pipeline),并添加了一些描述文本。让我们看看实际效果:虽然标签页很多,但我们成功构建了“使用微调 Gemma 3 270M 的结构化食物提取”界面。刷新后确认运行正常,这解释了我们在做什么:即将上传演示并添加示例输入。
我们可以添加一些示例数据,例如:“不是食物或饮料”、“今晚晚餐:我想吃牛排、土豆和蒜泥蛋黄酱,在柔术训练后”。如果视频能顺利结束,我下午就能去练柔术了。结果显示提取出了“牛排、土豆、蒜泥蛋黄酱”,听起来很美味。回到代码,我们获取演示界面入口点 Gradio(通常使用 gr.Interface),传入推理函数、输入输出定义。机器学习本质上就是输入和输出的映射,还包括标题、描述和示例(以列表形式呈现)。这些示例来自之前的直播互动。
我们将演示代码写入 app.py,并处理 README 文件。注意 Hugging Face Spaces 有一些前置元数据(front matter),用于配置 Space 的名称、表情符号、颜色、SDK(如 Python/Gradio)等。我们将名称设为 "food-extract-fine-tune-llm",描述为“从文本到结构化数据”,颜色设为绿色至蓝色,SDK 选 Gradio,应用文件为 app.py,不固定版本(pinned: false),许可证 Apache 2.0。在 README 中,我们放入标签字典以解释模型输出含义;同时创建 requirements.txt 文件。
现在进入神奇的部分:回顾之前手动创建的界面,我们现在可以全部通过编程方式完成。如果你想训练模型、更新它、重新训练并同步演示,你可以编写脚本自动化这一过程。为了保持简单,我们使用一个笔记本执行所有操作。运行代码后,它将导入 Hugging Face Hub 库,定义上传参数(如要上传的文件夹路径 "demos"),指定 Space 名称、仓库类型(Space 而非模型或数据集)、SDK 为 Gradio。
我们可以选择性地在此处输入 HF Token,但我已将令牌隐藏在后台以避免公开暴露。如果你想知道如何在本地计算机或 Google Colab 上设置,可以访问 learnhuggingface.com/setup。也可以在 Hugging Face 设置的 Tokens 页面查看本地配置方法,这并不困难;如果遇到难题,LLM 也能提供帮助。接着调用 create_repo 方法创建 Space 仓库,传入所有必要信息,获取完整的仓库 URL(格式为用户名/空间名称)。
最后上传演示文件夹。此时去 Hugging Face 查看,Space 应该已经存在了。由于我们重新上传了文件,系统可能会重建它。查看“Files”标签页显示更新时间为一小时前。但这次上传没有触发更新,是因为我没有修改之前已提交的任何文件内容。系统提示“自上次提交以来没有文件被修改,跳过以防止空提交”。这是因为我在录制前已准备好所有素材且未做更改。
为了触发更新,我修改了 README 中的标签映射说明,并添加了指向笔记本的详细步骤链接。再次运行上传脚本后,系统检测到 README 已更新,开始构建 Space(显示“Less than a minute ago”)。我确认演示已成功上线。整个过程就是这些:添加逐步指南链接,确保所有功能正常运行。
DGX Spark Nutri,我们不需要所有这些。Gemini,谢谢你的帮助。现在让我们回去看看。嗯,这就是我们已经完成的工作。我们……这视频有多长?在我这边大概一个小时。我会剪辑一下,去掉所有的训练过程。我想让这视频更紧凑一些,但请告诉我节奏如何?嗯,我会做更多的直播。
一旦我们的演示上传完毕,我们现在就可以直接在笔记本中运行它了。这有多酷?好吧,现在可能因为正在重建而无法工作,但如果我们输入“你好,我叫Daniel。”对吧?这正是在Hugging Face硬件上远程运行的笔记本中。太棒了!所以如果我们愿意,也可以分享这个应用。我是怎么做到的?让我去App页面……
在这里嵌入空间(Space)。对,我们可以把代码复制到别处。你可以发送链接给别人分享你的应用,或者直接使用这个URL试试。如果你试了,请在评论区告诉我。嗯,我很想看看效果。或者如果你训练了自己的模型,也请链接在评论中或发邮件给我。太酷了!
这里有两个补充内容:通过批量推理加速模型。嗯,我不运行这段代码,只是剧透一下。目前我们的模型是一个样本进、一个样本出,耗时大约一秒或半秒(取决于硬件)。但如果我们同时运行多个样本——这就是批量推理——我们可以获得显著的速度提升。
看这个图表,所有测试样本(共284个)逐一推理的默认总时间是144秒。但如果我们将批量大小设为16,速度提升了约5倍。这有多酷?顺便说一下,在DGX Spark上测试数据集的平均速度从每秒2个样本提升到了10个样本(不同硬件可能有所不同)。
如果你想看更多相关内容,比如量化等技术细节,可以在未来的视频中看到。我们可以尝试这些方法。嗯,我们也许能获得更高的每秒样本吞吐量,因为一旦模型训练完成就可以反复使用,随时进行推理。
那么每秒10个样本,一天能处理多少?大约82万个样本/天。这就是为什么我一开始说要节省Token:如果进行这82万次推理,每次平均输出约20个Token……我们每天能节省生成1600万个Token。这太惊人了!
我们还可以进一步压缩输出。如果你想扩展功能,可以计算模型输出的精确率、召回率和F1分数。你可能需要一些代码来将所有测试数据推理出来,收集输出并与原始GPT-oss标签进行比较。不过你可能需要先将它们转换为JSON格式。这是一个很好的课后练习!
最后是一些后续步骤来结束本期内容:让我们回到主题演讲,接下来做什么?评估。这就是我们刚才说的——我们的模型与GPT-oss 12b等模型相比如何?嗯,我很快会在演示笔记本上做一些评估。敬请期待!这将在learnhuggingface.com发布。
改进数据采样:如果模型出错,我们能否通过优化输入数据来更好地训练它?量化。目前我们的模型有2.7亿参数,大小约500MB。嗯,能否借助INT8量化让它更小?这将是一个很好的扩展方向!
输出压缩:我们的输出被压缩成了YAML格式。记得我们输出的不是JSON,而是纯文本行吗?有没有更好的方法来压缩并生成更少的Token?这样可以加快推理速度。
最后,我们可以在一个大型未见样本集上测试模型。例如在大量图像描述语料库中练习使用模型,我可能会选择至少1000到1万个随机样本……然后检查它们以发现失败案例,未来我们可以将这些错误反馈回来改进数据采样。
如果你有任何问题请在下方留言评论,我会回复你。如果你想让我帮你训练或微调自定义模型……我可以接私活!我的邮箱会在网站上公布,否则我们下期视频见。如果你有想法也请留言评论链接或文字!
嗯,下周我想做视觉语言模型(VLMs)的内容。没错就是Vision Language Models!但不剧透,我们下期见!