跳到内容

结构化生成工作流程:生成合成电话号码

这是 使用 LLM 进行结构化生成的编程 的精简版。

对于本示例,我们将构建一个 LLM 程序来生成华盛顿州逼真的电话号码形式的合成数据。使用 LLM 完成此任务有点大材小用,因为我们可以轻松地使用像 Faker 这样的工具来实现,但这个示例仍然是演示如何使用结构化生成工作流程的有用方式。

非结构化方法

在深入探讨如何使用结构化生成完成此任务之前,让我们先从一个非结构化示例开始。我们首先加载模型

import outlines

model_name = 'microsoft/Phi-3-mini-4k-instruct'
model = outlines.models.transformers(model_name)

接下来,我们需要为此模型准备一个提示词。由于我们专注于结构化生成,我们不会进行任何形式的“提示词工程”,并且在本示例的其余部分将保留此提示词不变。

tokenizer = AutoTokenizer.from_pretrained(model_name)

messages_phone = [
            {"role": "user", "content": """
            Please generate a realistic phone number for Washington State in the following format

            (555) 555-5555

            """}
]

# This allows us to properly format our prompt for
# Phi-3 Mini's 'Instruct' interface.
prompt_phone = tokenizer.apply_chat_template(messages_phone, tokenize=False)

准备好提示词后,我们现在可以生成 10 个示例电话号码。

phone_generator_unstruct = outlines.generate.text(model)
for _ in range(10):
    print(phone_generator_unstruct(prompt_phone,max_tokens=12))

乐意为您生成逼真的电话号码\ 我无法生成真实的电话号码,因为我只是\ 我是一个 AI,没有能力\ 当然!这里是一个随机生成的电话号码,格式为\ 这是一个符合以下格式的电话号码\ 在华盛顿州,电话号码通常有三位数\ 以下是一些可被视为\ 乐意帮助生成逼真的电话号码\ 乐意帮助您生成随机电话号码\ 根据您提供的格式,一个逼真的电话号码\

正如我们所见,这些输出甚至都不是电话号码!

让我们看看是否可以使用结构化生成来改进这一点。

结构化生成工作流程

为了解决这个问题,我们将引入一个在此图中概述的结构化生成工作流程

"Visual of Structured Generation Workflow"

让我们逐步讲解

真实示例

我们从一个真实的示例电话号码开始,在本例中是西雅图公共图书馆的号码,我们可以用它来验证我们正在创建的结构。

phone_number = "(206) 386-4636"

对于像这样简单的例子,我们将只使用一个电话号码;对于更复杂的例子,有更多示例可能会有帮助。

起草结构

过程中的下一步是定义一个我们认为能够正确模拟真实数据的简单 regex。

phone_regex_1 = r'\([0-9]{3}\) [0-9]{3}-[0-9]{4}'

接下来,我们需要根据真实数据验证此 regex。

通过匹配示例进行验证

每当使用结构化生成编写非简单的代码时,至关重要的是首先根据您的真实数据示例进行代码验证。

我们将从一个简单的验证方法开始:只需检查我们的 regex 是否与数据匹配。

import re
re.match(phone_regex_1, phone_number)

# <re.Match object; span=(0, 14), match='(206) 386-4636'>

现在我们已经匹配成功,可以继续生成结构化输出了!

生成结构

我们准备看看结构化生成是否能改进我们最初的非结构化方法。

phone_generator_v1 = outlines.generate.regex(model, phone_regex_1)
for _ in range(10):
    print(phone_generator_v1(prompt_phone))

(206) 555-1234\ (206) 555-1234\ (206) 555-1234\ (206) 555-1234\ (206) 555-1234\ (206) 555-1234\ (206) 123-4567\ (206) 555-1234\ (206) 555-1234\ (206) 555-1234

至少我们有电话号码了!但我觉得我们可以做得更好!

检查输出

在这种情况下,模型确实创建了电话号码,令人印象深刻的是,它正确地猜中了区号。因此,使用结构化生成确实有所改进。然而,这些号码相当无聊。让我们改进那个结构吧!

迭代

我们已经完成了一轮循环,所以现在可以快速进行每次迭代。

我们从改进我们的结构开始

phone_regex_2 = r'\([0-9]{3}\) [2-46-9]{3}-[02-9]{4}'

在急于进行下一轮生成之前,让我们验证这个新的 regex。我们将比上次检查增加一点复杂性。

re.match(phone_regex_2, phone_number)[0] == phone_number
# True
现在我们已经验证过了,让我们用这个新的 regex 来生成吧!

phone_generator_v2 = outlines.generate.regex(model,
                                             phone_regex_2)
for _ in range(10):
    print(phone_generator_v2(prompt_phone))

(206) 867-5309\ (206) 666-7777\ (206) 444-3333\ (206) 444-3333\ (206) 943-2222\ (206) 323-6789\ (206) 444-3333\ (206) 867-5309\ (206) 466-2255\ (206) 222-3333

好多了,但我不喜欢那些重复的序列。像优秀的软件开发者一样,让我们再次迭代!

再迭代 - 带调试

这是一个更花哨的 regex,应该能给我们带来更有趣的结果。

phone_regex_3_error = r'\([0-9]{3}\) [2-4][7-9][4-6]-[3-6][2-8][1-4]'

这对我来说看起来不错,但有一个细微的错误,这就是为什么我们总是需要根据真实数据验证我们的结构。这次我们将让我们的验证器做更多的工作来验证匹配的字符串是否正确。

if not re.match(phone_regex_3_error, phone_number):
    print("Regex fails match")
else:
    matched_string = re.match(phone_regex_3_error, phone_number)[0]
    if matched_string == phone_number:
    print("Successful match")
    else:
    print(f"Error {matched_string} != {phone_number}")
这会打印出

Error (206) 386-463 != (206) 386-4636

啊!我们漏掉了最后一位数字,让我们修复并重新生成。

phone_regex_3_fixed = r'\([0-9]{3}\) [2-4][7-9][4-6]-[3-6][2-8][1-4][6-9]'
phone_generator_v3 = outlines.generate.regex(model,
                                             phone_regex_3_fixed)
for _ in range(10):
    print(phone_generator_v3(prompt_phone))

(206) 494-3216\ (206) 374-6218\ (206) 494-3337\ (206) 476-3216\ (206) 484-3548\ (206) 495-3218\ (206) 494-5517\ (206) 375-4636\ (206) 384-6216\ (206) 385-6218

好多了!

现在您已经看到了结构化生成工作流程的一个快速示例,它可以作为构建和迭代更大规模结构化生成任务的基础!