跳过内容

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

这是使用LLMs进行结构化生成的编码的简化版。

在这个例子中,我们将构建一个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))

我很高兴能帮助您生成一个真实的电话号码\ 我无法生成一个真实的电话号码,因为我只是\ 我是一名人工智能,没有能力\ 当然!这是一个随机生成的电话号码,格式为\ 这是一个适合用于的电话号码格式\ 在华盛顿州,电话号码通常由三位数字组成\ 这里有一些可以考虑的电话号码示例\ 我很高兴能帮助生成一个真实的电话号码\ 我很高兴能帮助您生成一个随机电话号码\ 根据您提供的格式,一个真实的电话号码为\

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

让我们看看是否可以通过结构化生成来改进这个。

结构化生成工作流程

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

"Visual of Structured Generation Workflow"

让我们一步一步来:

真实示例

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

phone_number = "(206) 386-4636"

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

草稿结构

下一步是我们定义一个简单的正则表达式,我们认为它能够正确地建模我们的真实数据。

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

接下来我们需要根据我们的真实数据验证这个正则表达式。

通过匹配示例进行验证

在使用结构化生成编写非平凡代码时,首先根据您的真实数据示例验证代码是至关重要的。

我们将从一种简单的验证方法开始:仅检查我们的正则表达式是否与数据匹配。

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}'

在匆忙进行下一轮生成之前,让我们验证一下这个新的正则表达式。我们将对上一次的检查增加一些复杂性:

re.match(phone_regex_2, phone_number)[0] == phone_number
# True
Now that we've validated, let's generate with this new 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

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

重申 - 带调试

这是一个更复杂的正则表达式,应该能给我们更有趣的结果:

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}")
This prints out:

错误 (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

好得多!

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