LLM 提示可以通过在模板句子中注入特定术语来增强。选择正确的术语对于获得高质量的回复至关重要。该笔记本通过使用强化学习和 VowpalWabbit 的术语注入引入了自动化提示工程。
强化学习链提供了一种自动确定注入的最佳项的方法,无需微调底层基础模型。
为了说明这一点,请考虑送餐服务的场景。我们使用 LangChain 询问像 Tom 这样的顾客的饮食偏好,并从我们丰富的菜单中推荐合适的餐点。 rl_chain 根据用户偏好选择餐食,将其注入到提示模板中,并将提示转发到 LLM。然后,LLM 的响应(这是个性化推荐)返回给用户。
下面列出的示例是一个演示该概念的适用性的玩具示例。最后提供了高级选项和解释。
# Install necessary packages
# ! pip install langchain langchain-experimental matplotlib vowpal_wabbit_next sentence-transformers pandas
# four meals defined, some vegetarian some not
meals = [
"Beef Enchiladas with Feta cheese. Mexican-Greek fusion",
"Chicken Flatbreads with red sauce. Italian-Mexican fusion",
"Veggie sweet potato quesadillas with vegan cheese",
"One-Pan Tortelonni bake with peppers and onions",
]
# pick and configure the LLM of your choice
from langchain_openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
使用提供的默认值初始化强化学习链
需要定义用于查询LLM的提示模板。它可以是任何东西,但这里正在使用 {meal} 并将被上面的餐食之一取代,强化学习链将尝试挑选并注入最好的餐食
from langchain.prompts import PromptTemplate
# 导入PromptTemplate类,用于生成模板化的文本
# 下面定义一个模板字符串,其中的变量 meal 将在链式运行时被替换为上述的某个餐点
# 另外还有一些变量 user、preference 和 text_to_personalize,这些变量将在链式运行时提供
PROMPT_TEMPLATE = """Here is the description of a meal: "{meal}".
Embed the meal into the given text: "{text_to_personalize}".
Prepend a personalized message including the user's name "{user}"
and their preference "{preference}".
Make it sound good.
"""
# 模板字符串,用于生成文本模板
PROMPT = PromptTemplate(
input_variables=["meal", "text_to_personalize", "user", "preference"],
template=PROMPT_TEMPLATE,
)
# 创建一个PromptTemplate实例,指定输入变量和模板字符串
接下来,强化学习链的 PickBest 链正在初始化。我们必须提供选择的 llm 和定义的提示。顾名思义,该连锁店的目标是根据某些标准挑选最好的餐食。
import langchain_experimental.rl_chain as rl_chain
chain = rl_chain.PickBest.from_llm(llm=llm, prompt=PROMPT)
设置好连锁店后,我将使用我想要从中选择的餐食以及连锁店选择餐食的一些上下文来调用它。
response = chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Tom"), # 设置用户的名字为 "Tom"
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
print(response["response"])
What is the chain doing
链条在中间做什么事情
以下是强化学习链操作的逐步细分:
- 接受膳食清单。
- 考虑用户及其饮食偏好。
- 根据这种情况,选择合适的膳食。
- 自动评估膳食选择的适当性。
- 将所选餐点插入提示中并将其提交到LLM。
- 将 LLM 的响应返回给用户。
从技术上讲,该链通过采用上下文强盗强化学习模型(特别是利用 VowpalWabbit ML 库)来实现这一目标。
最初,由于强化学习模型未经训练,它可能会选择不一定符合用户偏好的随机选择。然而,随着它更多地接触用户的选择和反馈,它应该开始做出更好的选择(或者快速学习一个好的选择并选择它!)。
for _ in range(5):
try:
response = chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Tom"), # 设置用户的名字为 "Tom"
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
except Exception as e:
print(e) # 如果生成过程中出现异常,打印异常信息
print(response["response"]) # 打印生成的响应文本
print() # 输出空行,用于分隔每次生成的响应文本
强化学习链是怎么样学习的
值得注意的是,虽然强化学习模型可以做出复杂的选择,但它本质上并不能识别“素食”等概念,也不能理解“牛肉玉米卷饼”不适合素食。相反,它利用 LLM 将其选择建立在常识之上。
连锁店了解汤姆更喜欢素食的方式是通过连锁店内置的自动选择评分器。记分器将再次调用 LLM 并要求它使用 ( BasedOn ) 中包含的信息来评估选择 ( ToSelectFrom )。
如果您想查看自动评分器的详细信息,可以设置 set_debug(True) ,但您也可以自己定义评分提示。
scoring_criteria_template = (
"Given {preference} rank how good or bad this selection is {meal}"
)
# 定义评分模板,用于生成评分标准的文本
chain = rl_chain.PickBest.from_llm(
llm=llm,
prompt=PROMPT,
selection_scorer=rl_chain.AutoSelectionScorer(
llm=llm, scoring_criteria_template_str=scoring_criteria_template
),
)
# 使用 rl_chain.PickBest.from_llm 方法创建一个链式操作,以选择最佳的选项
# 参数 llm 是语言模型,用于生成文本
# 参数 prompt 是之前定义的模板化文本结构 PROMPT
# selection_scorer 是一个自动评分器,使用 scoring_criteria_template 作为评分标准模板
如果您想检查分数和其他选择元数据,您可以通过检查链返回的元数据对象
response = chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Tom"), # 设置用户的名字为 "Tom"
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
print(response["response"]) # 打印生成的个性化响应文本
selection_metadata = response["selection_metadata"] # 获取响应中的选项元数据
print(
f"selected index: {selection_metadata.selected.index}, score: {selection_metadata.selected.score}"
)
# 打印选中选项的索引和评分信息
在更现实的场景中,您可能对所选内容有一个明确定义的评分函数。例如,您可能正在进行几次提示,并希望为自然语言到 SQL 翻译任务选择提示示例。在这种情况下,记分器可能是:生成的 sql 是否在 sql 引擎中运行?在这种情况下,您需要插入评分功能。在下面的示例中,我将仅检查所选择的餐食是否是素食。
class CustomSelectionScorer(rl_chain.SelectionScorer):
def score_response(
self, inputs, llm_response: str, event: rl_chain.PickBestEvent
) -> float:
# 打印事件中的基于和选择来源信息,以便调试
print(event.based_on)
print(event.to_select_from)
# 可以在这里构建复杂的评分函数
# 偏好在 0 到 1 之间的范围,但不强制执行
# 获取选中的餐点
selected_meal = event.to_select_from["meal"][event.selected.index]
print(f"selected meal: {selected_meal}")
# 根据特定条件进行评分
if "Tom" in event.based_on["user"]:
if "Vegetarian" in event.based_on["preference"]:
if "Chicken" in selected_meal or "Beef" in selected_meal:
return 0.0 # 如果选中的餐点包含鸡肉或牛肉,并且用户是素食主义者 Tom,则评分为 0.0
else:
return 1.0 # 否则评分为 1.0
else:
if "Chicken" in selected_meal or "Beef" in selected_meal:
return 1.0 # 如果选中的餐点包含鸡肉或牛肉,并且用户不是素食主义者 Tom,则评分为 1.0
else:
return 0.0 # 否则评分为 0.0
else:
raise NotImplementedError("I don't know how to score this user")
# 如果用户不是 Tom,则抛出未实现错误
# 请注意,这只是一个简单的示例评分函数。实际应用中,评分函数可能会更复杂,根据具体需求和条件编写不同的评分逻辑。
chain = rl_chain.PickBest.from_llm(
llm=llm,
prompt=PROMPT,
selection_scorer=CustomSelectionScorer(),
)
response = chain.run(
meal=rl_chain.ToSelectFrom(meals),
user=rl_chain.BasedOn("Tom"),
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]),
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!",
)
如何跟踪强化学习链的执行进度
您可以使用提供的指标机制来跟踪链的进度。我打算将用户扩展到Tom和Anna,并扩展评分功能。我将初始化两个链,一个具有默认学习策略,一个具有内置随机策略(即随机选择一顿饭),并绘制它们的评分进度。
class CustomSelectionScorer(rl_chain.SelectionScorer):
def score_preference(self, preference, selected_meal):
# 根据饮食偏好和选中的餐点评分
if "Vegetarian" in preference:
if "Chicken" in selected_meal or "Beef" in selected_meal:
return 0.0 # 如果选中的餐点包含鸡肉或牛肉,并且用户是素食主义者,则评分为 0.0
else:
return 1.0 # 否则评分为 1.0
else:
if "Chicken" in selected_meal or "Beef" in selected_meal:
return 1.0 # 如果选中的餐点包含鸡肉或牛肉,并且用户不是素食主义者,则评分为 1.0
else:
return 0.0 # 否则评分为 0.0
def score_response(
self, inputs, llm_response: str, event: rl_chain.PickBestEvent
) -> float:
selected_meal = event.to_select_from["meal"][event.selected.index] # 获取选中的餐点
# 根据事件中的用户信息选择评分方法
if "Tom" in event.based_on["user"]:
return self.score_preference(event.based_on["preference"], selected_meal) # 对 Tom 进行评分
elif "Anna" in event.based_on["user"]:
return self.score_preference(event.based_on["preference"], selected_meal) # 对 Anna 进行评分
else:
raise NotImplementedError("I don't know how to score this user") # 对其他用户抛出未实现错误
chain = rl_chain.PickBest.from_llm(
llm=llm, # 指定语言模型,用于生成文本
prompt=PROMPT, # 指定预定义的模板化文本结构
selection_scorer=CustomSelectionScorer(), # 指定自定义的选项评分器
metrics_step=5, # 指定度量步骤,每隔多少步进行度量
metrics_window_size=5, # 指定度量窗口大小,用于滚动窗口平均值
)
random_chain = rl_chain.PickBest.from_llm(
llm=llm, # 指定语言模型,用于生成文本
prompt=PROMPT, # 指定预定义的模板化文本结构
selection_scorer=CustomSelectionScorer(), # 指定自定义的选项评分器
metrics_step=5, # 指定度量步骤,每隔多少步进行度量
metrics_window_size=5, # 指定度量窗口大小,用于滚动窗口平均值
policy=rl_chain.PickBestRandomPolicy, # 指定随机策略而不是默认策略
)
for _ in range(20):
try:
# 对用户 "Tom" 运行 chain 和 random_chain
chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Tom"), # 设置用户的名字为 "Tom"
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
random_chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Tom"), # 设置用户的名字为 "Tom"
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
# 对用户 "Anna" 运行 chain 和 random_chain
chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Anna"), # 设置用户的名字为 "Anna"
preference=rl_chain.BasedOn(["Loves meat", "especially beef"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
random_chain.run(
meal=rl_chain.ToSelectFrom(meals), # 从 meals 列表中动态选择一个餐点
user=rl_chain.BasedOn("Anna"), # 设置用户的名字为 "Anna"
preference=rl_chain.BasedOn(["Loves meat", "especially beef"]), # 设置用户的饮食偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!" # 固定的个性化消息
)
except Exception as e:
print(e) # 捕获并打印异常信息
强化学习链集中于这样一个事实:安娜更喜欢牛肉,而汤姆是素食主义者。随机连锁店随机挑选,因此一半的时间会向素食者发送牛肉。
from matplotlib import pyplot as plt
# 绘制默认学习策略和随机选择策略的评分变化曲线
chain.metrics.to_pandas()["score"].plot(label="default learning policy")
random_chain.metrics.to_pandas()["score"].plot(label="random selection policy")
# 添加图例
plt.legend()
# 打印最终滚动窗口内的平均评分
print(
f"The final average score for the default policy, calculated over a rolling window, is: {chain.metrics.to_pandas()['score'].iloc[-1]}"
)
print(
f"The final average score for the random policy, calculated over a rolling window, is: {random_chain.metrics.to_pandas()['score'].iloc[-1]}"
)
# 显示图形
plt.show()
强化学习链的选择涉及一点随机性,因为链探索选择空间是为了尽可能地了解世界(请参阅此处使用的默认探索算法的详细信息),但总的来说,默认链策略应该做得更好比学习时随机的
高级选项
强化学习链具有高度可配置性,以便能够适应各种选择场景。如果您想了解有关为其提供支持的 ML 库的更多信息,请查看此处的教程
部分 | 描述 | 示例 / 用法 |
---|---|---|
更改链日志级别 | 更改RL链的日志级别。 | logger.setLevel(logging.INFO) |
特征化 | 调整输入以适应RL链。可以设置自动嵌入以获取更复杂的嵌入。 | chain = rl_chain.PickBest.from_llm(auto_embed=True, […]) |
异步学习策略以进行异步学习 | 如果需要用户输入来进行评分,则异步进行评分。 | chain.update_with_delayed_score(score=<得分>, chain_response=response) |
学习策略的进展存储 | 存储变量注入学习策略的进展选项。 | chain.save_progress() |
停止学习学习策略 | 切换RL链的学习策略更新开关。 | chain.deactivate_selection_scorer() |
设置不同策略 | 在不同策略之间选择:默认、随机或自定义。 | 在链创建时设置自定义策略。 |
默认学习策略的不同探索算法和选项 | 为VwPolicy设置不同的探索算法和超参数。 | vw_cmd = [“–cb_explore_adf”, “–quiet”, “–squarecb”, “–interactions=::”] |
学习策略的数据日志 | 存储和检查VwPolicy的数据日志。 | chain = rl_chain.PickBest.from_llm(vw_logs=<日志文件路径>, […]) |
其他高级特征化选项 | 为RL链指定高级特征化选项。 | age = rl_chain.BasedOn(“age:32”) |
关于自动或自定义SelectionScorer的更多信息 | 深入了解如何确定选择评分。 | selection_scorer=rl_chain.AutoSelectionScorer(llm=llm, scoring_criteria_template_str=scoring_criteria_template) |
更改链日志级别
import logging
logger = logging.getLogger("rl_chain")
logger.setLevel(logging.INFO)
特征化
自动嵌入
默认情况下,对RL链的输入(ToSelectFrom、BasedOn)不会被篡改。这可能不足以进行特征化,因此根据场景复杂性,您可以将自动嵌入设置为ON。
chain = rl_chain.PickBest.from_llm(auto_embed=True, [...])
这将产生更复杂的嵌入和特征化输入,可能加快RL链的学习速度,尽管运行时间会增加。
默认情况下,将使用sbert.net的sentence_transformers的all-mpnet-base-v2模型进行这些嵌入,但您可以像本示例中所示一样,通过初始化链来设置不同的嵌入模型。您还可以设置完全不同的嵌入编码对象,只要它具有返回编码列表的encode()函数。
from sentence_transformers import SentenceTransformer
chain = rl_chain.PickBest.from_llm(
[...]
feature_embedder=rl_chain.PickBestFeatureEmbedder(
auto_embed=True,
model=SentenceTransformer("all-mpnet-base-v2")
)
)
显式定义嵌入
另一种选择是手动定义您认为应该被嵌入的输入:
auto_embed = False
可以将单独的变量包装在rl_chain.Embed()或rl_chain.EmbedAndKeep()中,例如user = rl_chain.BasedOn(rl_chain.Embed("Tom"))
自定义特征化
最后一种选择是定义和设置一个自定义的特征化/嵌入类,该类返回学习策略的有效输入。
异步学习策略以进行异步学习
如果需要从用户处获取输入来评分结果(例如,我的应用向Tom展示了所选的餐点,而Tom点击了它,但Anna没有),那么可以异步执行评分。方法如下:
- 在链创建时设置selection_scorer=None,或调用chain.deactivate_selection_scorer()
- 为特定输入调用链
- 保留链的响应(response = chain.run([…]))
- 一旦确定响应/链选择的评分,则调用链:chain.update_with_delayed_score(score=<得分>, chain_response=response)
存储学习策略的进展
由于变量注入学习策略随时间推移而演变,因此可以选择存储其进展并继续学习。可通过调用以下方法实现:
chain.save_progress()
这将把RL链的学习策略存储在名为latest.vw的文件中。它还将在带有时间戳的文件中存储它。因此,如果save_progress()被多次调用,将创建多个检查点,但最新的检查点始终在latest.vw中。
默认情况下,RL链模型检查点将存储在当前目录中,但您可以在链创建时指定保存/加载位置:
chain = rl_chain.PickBest.from_llm(model_save_dir=<目录路径>, [...])
停止学习学习策略
如果希望RL链的学习策略停止更新,可以关闭/打开它:
chain.deactivate_selection_scorer() 和 chain.activate_selection_scorer()
设置不同策略
当前有两种策略可供选择:
- 默认策略:VwPolicy,学习Vowpal Wabbit上下文强化学习模型
- 随机策略:RandomPolicy,不学习任何内容,只是随机选择一个值。此策略可用于将其他策略与随机基线策略进行比较。
- 自定义策略:可以在链创建时创建并设置自定义策略。
默认学习策略的不同探索算法和选项
默认的VwPolicy使用一些默认参数进行初始化。默认的探索算法是SquareCB,但可以设置其他上下文强化学习探索算法,并调整其他超参数(有关可用选项,请参阅此处)。
vw_cmd = ["--cb_explore_adf", "--quiet", "--squarecb", "--interactions=::"]
chain = rl_chain.PickBest.from_llm(vw_cmd=vw_cmd, [...])
学习策略的数据日志
VwPolicy的数据文件可以存储、检查或用于超参数调整的离线策略评估。
方法是在链创建时设置日志文件路径到vw_logs:
chain = rl_chain.PickBest.from_llm(vw_logs=<日志文件路径>, [...])
其他高级特征化选项
可以使用冒号分隔显式数值特征提供:age = rl_chain.BasedOn(“age:32”)
如果情况要求,ToSelectFrom可以更复杂,而不仅仅是字符串列表:
- 字符串列表的列表:
meal = rl_chain.ToSelectFrom([
["meal 1 name", "meal 1 description"],
["meal 2 name", "meal 2 description"]
])
- 字典列表:
meal = rl_chain.ToSelectFrom([
{"name": "meal 1 name", "description": "meal 1 description"},
{"name": "meal 2 name", "description": "meal 2 description"}
])
- 包含列表的字典列表:
meal = rl_chain.ToSelectFrom([
{"name": ["meal 1", "complex name"], "description": "meal 1 description"},
{"name": ["meal 2", "complex name"], "description": "meal 2 description"}
])
BasedOn也可以接受字符串列表:
user = rl_chain.BasedOn(["Tom Joe", "age:32", "state of california"])
由于提供了多个变量,因此不需要提供字典包装在BasedOn中
将数据日志存储到文件中允许检查不同输入对数据格式的影响。
有关自动或自定义SelectionScorer的更多信息
选择正确的选择评分器非常重要,因为策略使用它来学习。它确定了在强化学习中称为奖励的内容,特别是在我们的上下文强化学习设置中。
一般建议保持评分在[0, 1]之间,0表示最差的选择,1表示在可选择的ToSelectFrom变量中的最佳选择,根据BasedOn变量进行调整如果需要。
在上面提供的示例中,AutoSelectionScorer主要用于帮助用户入门,但在实际场景中,它很可能不是一个足够好的评分函数。
该示例还提供了更改AutoSelectionScorer用于确定选择是否良好的部分评分提示模板的选项:
scoring_criteria_template = "Given {preference} rank how good or bad this selection is {meal}"
chain = rl_chain.PickBest.from_llm(
llm=llm,
prompt=PROMPT,
selection_scorer=rl_chain.AutoSelectionScorer(llm=llm, scoring_criteria_template_str=scoring_criteria_template),
)
在内部,AutoSelectionScorer调整了评分提示,以确保llm评分返回一个单一的浮点数。
但如果需要,也可以提供一个完整的评分提示:
from langchain.globals import set_debug # 导入设置调试模式的函数
from langchain.prompts.prompt import PromptTemplate # 导入提示模板类
set_debug(True) # 开启调试模式
REWARD_PROMPT_TEMPLATE = """
Given {preference} rank how good or bad this selection is {meal}
IMPORTANT: you MUST return a single number between -1 and 1, -1 being bad, 1 being good
"""
# 奖励评分提示模板,包含用户偏好和餐点选择
REWARD_PROMPT = PromptTemplate(
input_variables=["preference", "meal"], # 输入变量包括偏好和餐点
template=REWARD_PROMPT_TEMPLATE, # 使用上面定义的奖励评分提示模板
)
# 从LLM创建PickBest RL链
chain = rl_chain.PickBest.from_llm(
llm=llm, # 语言模型
prompt=PROMPT, # 提示
selection_scorer=rl_chain.AutoSelectionScorer(llm=llm, prompt=REWARD_PROMPT), # 自动选择评分器
)
# 运行RL链
chain.run(
meal=rl_chain.ToSelectFrom(meals), # 选择餐点
user=rl_chain.BasedOn("Tom"), # 基于Tom用户
preference=rl_chain.BasedOn(["Vegetarian", "regular dairy is ok"]), # 基于偏好
text_to_personalize="This is the weeks specialty dish, our master chefs believe you will love it!", # 个性化文本
)
扩展知识点
- 强化学习:一种让机器通过试错来学习特定任务的算法,强化学习在给定环境下通过奖励反馈来优化行为策略。
- Vowpal Wabbit:一个快速、可扩展的机器学习库,常用于处理分类、回归和排序问题。
- LangChain:一个用于构建和操作AI助手的Python库,支持多种语言模型和自动化任务。
- Sentence Transformers:一个用于句子嵌入的库,可以将句子转换成向量表示,常用于NLP任务中的相似性度量。
总结
本文展示了如何通过强化学习自动选择和注入提示变量,以增强LLM的响应质量。通过一个餐饮服务的示例,我们了解了如何使用LangChain和VowpalWabbit来构建一个自动化的提示选择系统。随着模型的学习和数据的积累,该系统能够更好地理解用户偏好并做出更加精准的个性化推荐。