TowardsDataScience 2023 博客中文翻译(六十六)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

Chat GPT 能下国际象棋吗?

原文:towardsdatascience.com/can-chat-gpt-play-chess-4c44210d43e4

使用多策略 AI 和深度强化学习击败 GPT3.5 的国际象棋比赛

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Octavio Santiago

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 12 月 18 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片:使用 DALLE-3 生成的机器人下国际象棋

2021 年,我开发了一个人工智能模型,它使用多种策略和深度强化学习,旨在成为一个真实且专业的国际象棋玩家,我将该 AI 机器人与许多不同的机器人进行了对抗。你可以在下面的文章中获得更多关于这段旅程的细节:

## 使用决策制定深度强化学习破解国际象棋

使用机器学习创建国际象棋冠军

towardsdatascience.com

2023 年,我使用相同的人工智能来测试 ChatGPT 是否可以下国际象棋,以及它在真实比赛中的表现如何。

在开始之前,首先要问 ChatGPT 是否可以下国际象棋。ChatGPT 说它可以下棋,但不是一个完美的玩家(这是否是它输掉比赛的借口?)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答 — 作者提供的图片

好的,我的 AI 正在使用白方,让我们开始以经典的 e4 开局!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答 — 作者提供的图片

在开局的几步中,我们可以看到 ChatGPT 理解开局,并对每一步给出逻辑分析。目前比赛进展均衡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答和国际象棋棋步 — 作者提供的图片

在 8 步之后,我的 AI 模型在 castling 后犯了一个错误,它将兵移动到 h3 后,选择了将主教移动到 f4,而不是用骑士占据 d4,这在这个位置上是最佳的移动。

这个错误给了 ChatGPT 巨大的优势,但是……

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

棋步 — 图片作者提供

ChatGPT 进行了一些无意义的分析,声称我的 AI 模型将其骑士固定在了女王上,并决定将一个兵移动到 a6,而不是吃掉 c3 上的骑士,而这才是这个位置的最佳移动。

看起来 ChatGPT 无法跟踪棋盘及其所有棋子,它将这场比赛与训练数据集中其他比赛混淆了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答 — 图片作者提供

ChatGPT 没有把握住这个机会,我的 AI 决定走 Qe2,然后 ChatGPT 进行了另一个无意义的分析,称我将皇后移动以连接我的车并支持 d4,但我甚至没有 d4 上的兵。这个奇怪的分析证实了它没有跟踪任何棋盘位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答和棋步 — 图片作者提供

即使有一些错误,ChatGPT 仍然有优势,我曾一度认为我的 AI 会输掉这场比赛。

然后……ChatGPT 决定走 Ba6,这是一个错误,导致整场比赛失利,并将优势转回到我的 AI。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

棋步 — 图片作者提供

经过一些无意义的分析后,ChatGPT 完全丧失了对棋盘位置的跟踪,并进入了一个非法移动的无限循环。以下是第一次非法移动的演示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答和棋步 — 图片作者提供

进入这个无限循环后,我无法再继续下棋,因为 ChatGPT 在第 15 步后无法找到任何合法的走法,游戏最终以 ChatGPT 认输结束。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 的回答 — 图片作者提供

ChessAI 获胜!因为它比 ChatGPT 更清楚自己在做什么!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

棋步 — 图片作者提供

结论

总结,ChatGPT 3.5 无法进行棋局对弈,这个大型语言模型无法跟踪棋盘并检查合法/非法的走法。ChatGPT 使用训练数据集来推断前一个对局中的下一步,但它混淆了这些对局,并没有分析实际的棋盘位置以及如何下这一位置。我们可以假设 ChatGPT 没有足够的游戏知识来做出合理的决定,即使它在开始时声称它已接受了棋规、信息和策略的训练。

国际象棋是一个具有高状态可能性和强规则的顺序决策问题,而 GPT 3.5 尚未能学会这些规则,RL 模型或搜索模型在这种类型的问题上仍然表现更好。目前我的 ChessAI 模型,一个国际象棋专家模型,仍然表现更好!

在下一篇文章中,我将与相同的 AI 进行对局,但这次对战的是 GPT 4.0。GPT 4.0 比 3.5 更强吗?它能进行完整的对局吗?让我们在下一篇文章中揭晓!

如果你想评估整场比赛:

FEN: r1bqkbnr/pp2pppp/2np4/2p5/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq — 0 4

PGN: 1. e4 c5 2. Bc4 Nc6 3. Nf3 d6 4. Nc3 Nf6 5. O-O e6 6. h3 Be7 7. d4 cxd4 8. Bf4 a6 9. Qe2 b5 10. Nxb5 axb5 11. Bxb5 Ba6 12. Bxa6 Rxa6 13. Qxa6 Qb6 14. Qd3 Qb7 15. b4 Ne5

任何问题或建议可以通过 LinkedIn 联系我:www.linkedin.com/in/octavio-b-santiago/

ChatGPT 能与领域特定情感分析机器学习模型竞争吗?

原文:towardsdatascience.com/can-chatgpt-compete-with-domain-specific-sentiment-analysis-machine-learning-models-cdcd9937b460?source=collection_archive---------4-----------------------#2023-04-25

一个使用 ChatGPT 与领域特定模型的动手比较

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Francisco Caio Lima Paiva

·

关注 发表在 Towards Data Science ·15 分钟阅读·2023 年 4 月 25 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 K. Mitch Hodge 提供,来源于 Unsplash

ChatGPT 是一个 GPT (生成式 预训练 变换器) 机器学习(ML)工具,令世界惊叹。它令人惊叹的能力让普通用户、专业人士、研究人员甚至其自身的创造者 都感到印象深刻。此外,它作为一个为通用任务训练的机器学习模型,在特定领域情况下表现出色,这一点令人印象深刻。作为一名研究人员,我对它进行情感分析(SA)的能力很感兴趣。

SA 是一种非常广泛的自然语言处理(NLP)技术。它有多个应用,因此可以用于多个领域(例如,金融、娱乐、心理学)。然而,一些领域采用特定的术语和行话(例如,金融)。因此,通用领域的机器学习模型是否可以与特定领域的 模型一样强大,仍然是一个NLP 中的开放研究问题

如果你问 ChatGPT 这个研究问题——也就是本文的标题——它会给你一个谦逊的回答(继续试试吧)。但是,哦,我亲爱的读者,我通常不会给你剧透,但你完全无法想象这个 ChatGPT 回答有多么出人意料地谦虚……

作为 AI 研究员、行业专家和爱好者,我习惯于对通用领域的 NLP 机器学习工具(例如,GloVe)进行微调,以用于特定领域的任务。这是因为大多数领域很少能找到一个开箱即用的解决方案,能够在没有一些微调的情况下表现得足够好。我将展示如何不再是这种情况。

在这篇文章中,我通过讨论以下主题来比较 ChatGPT 与一个特定领域的机器学习模型:

  1. SemEval 2017 任务 5 —— 一个特定领域的挑战

  2. 使用 ChatGPT API 给数据集打标签的代码示例

  3. 比较的裁决和结果及其可重复性细节

  4. 结论与结果讨论

  5. 额外内容:如何在实际场景中进行这种比较

注释 1这只是一个简单的实践实验,稍微揭示了这个主题, 而非 详尽的科学调查。

注释 2除非另有说明,所有图片均由作者提供。

1. SemEval 2017 任务 5 —— 一个特定领域的挑战

SemEval (语义评估) 是一个著名的 NLP 研讨会,研究团队在情感分析、文本相似性和问答任务中进行科学竞争。组织者提供由标注员(领域专家)和语言学家创建的文本数据和金标准数据集,以评估每项任务的最先进解决方案。

特别是,SemEval 的2017 年任务 5要求研究人员对金融微博和新闻标题进行情感分析,评分范围从 -1(最负面)到 1(最正面)。我们将使用那一年 SemEval 的金标准数据集来测试 ChatGPT 在特定领域任务中的表现。子任务 2 数据集(新闻标题)包含两个句子集(每个最多 30 个单词):训练集(1,142 个句子)和测试集(491 个句子)。

考虑到这些数据集,下面展示了情感评分和文本句子的分布情况。下图显示了训练集和测试集中都有双峰分布。此外,图表还显示数据集中正面句子多于负面句子。这将在评估部分提供有用的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SemEval 2017 任务 5 子任务 2(新闻标题)数据分布情感评分,考虑到训练集(左 — 1,142 个句子)和测试集(右 — 491 个句子)。

对于这个子任务,获胜的研究团队(即在测试集上排名最佳的团队)将他们的机器学习架构命名为Fortia-FBK。受这一竞赛发现的启发,我和一些同事撰写了研究文章(评估基于回归的金融文本情感分析技术),在其中我们实现了 Fortia-FBK 的版本,并评估了改进该架构的方法。

此外,我们还调查了使这一架构成为获胜架构的因素。因此,我们的实现(代码在这里)将用于与 ChatGPT 进行比较。所采用的架构(CNN+GloVe+Vader)如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

针对金融新闻领域的特定领域情感分析机器学习模型。架构是为研究文章“评估基于回归的金融文本情感分析技术”开发的。来源: 作者硕士论文(Lima Paiva, F.C.,《将情感分析融入强化学习以实现智能交易》)。

2. 使用 ChatGPT API 对数据集进行标注

关于使用 ChatGPT API 的讨论已在 Medium 上的数据合成中进行过。此外,你还可以在ChatGPT API 代码示例部分找到情感标注示例(请注意,使用该 API 并非免费)。在这个代码示例中,请考虑 SemEval 2017 任务的金标准数据集,你可以在这里获取

然后,为了同时标注多个句子,使用如下代码,其中我准备了一个完整的提示,包含来自数据框的句子,黄金标准数据集中的要标注的句子以及情感所指的目标公司。

def prepare_long_prompt(df):
  initial_txt = "Classify the sentiment in these sentences between brackets regarding only the company specified in double-quotes. The response should be in one line with format company name in normal case followed by upper cased sentiment category in sequence separated by a semicolon:\n\n"
  prompt = "\"" + df['company'] + "\"" + " [" + df['title'] + ")]"

  return initial_txt + '\n'.join(prompt.tolist())

然后,调用text-davinci-003 引擎(GPT-3 版本)的 API。在这里,我对代码进行了一些调整,以考虑提示加回答的最大字符数,总共最多 4097 个字符。

def call_chatgpt_api(prompt):
  # getting the maxium amount of tokens allowed to the response, based on the
  # api Max of 4097, and considering the length of the prompt text
  prompt_length = len(prompt)
  max_tokens = 4097 - prompt_length

  # this rule of dividing by 10 is just a empirical estimation and is not a precise rule
  if max_tokens < (prompt_length / 10):
    raise ValueError(f'Max allowed token for response is dangerously low {max_tokens} and might not be enough, try reducing the prompt size')

  response = openai.Completion.create(
    model="text-davinci-003",
    prompt=prompt,
    temperature=0,
    max_tokens=max_tokens,
    top_p=1,
    frequency_penalty=0,
    presence_penalty=0
  )

  return response.choices[0]['text']

long_prompt = prepare_long_prompt(df)
call_chatgpt_api(long_prompt)

最终,对黄金标准数据集中 1633 个(训练 + 测试集)句子进行处理,你会得到以下 ChatGPT API 标签的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 ChatGPT API 标注情感的 SemEval 2017 任务 5 子任务 2(新闻头条)黄金标准数据集示例。

2.1. ChatGPT 及其 API 在规模化时的问题

与其他 API 一样,有一些典型要求

  • 请求速率限制需要进行节流调整

  • 请求限制为 25000 tokens(即子词单元或字节对编码)

  • 每个请求的最大长度为 4096 tokens(包括提示 + 响应)

  • 成本为 0.0200 / 1 K t o k e n s (注:我在所有操作后从未花费超过 U 0.0200 / 1K tokens(注:我在所有操作后从未花费超过 U 0.0200/1Ktokens(注:我在所有操作后从未花费超过U 2)

然而,这些只是处理大多数 API 时的典型要求。此外,记住在这个领域特定的问题中,每个句子都有一个目标实体(即公司)用于情感分析。因此,我不得不进行一些调整,直到设计出一个使得能够一次标注多个句子的情感并且后续处理结果变得简单的提示模式。此外,还有其他限制影响了我之前展示的提示和代码。具体来说,我发现使用这个文本 API 处理多个句子(>1000)时存在问题。

  • 可重复性: ChatGPT 的情感评估在提示中做出很小的修改(例如,添加或删除句子中的逗号或句号)后可能会发生显著变化。

  • 一致性: 如果你没有明确指定模式响应,ChatGPT 会变得富有创意(即使你选择了非常低的随机性参数),这使得处理结果变得困难。此外,即使你指定了模式,它也可能输出不一致的格式。

  • 不匹配: 尽管它可以非常准确地识别你在句子中想要评估情感的目标实体(例如公司),但在大规模处理时,它可能会混淆结果。例如,假设你传递了 10 个包含目标公司的句子。如果这些公司出现在其他句子中或重复出现,ChatGPT 可能会混淆目标和句子情感,改变情感标签的顺序或提供少于 10 个标签。

  • 偏见: 目前,ChatGPT 偏见问题是众所周知的。而且有改进这个问题的想法。然而,在那之前,请注意你正在学习使用一个有偏见的 API。

所有这些问题都意味着需要一个学习曲线来正确使用(有偏见的)API。它需要一些微调才能得到我需要的结果。有时我不得不进行许多尝试,直到以最小的一致性达到了期望的结果。

在理想情况下,你应该一次发送尽可能多的句子,原因有二。首先,你希望尽快获得标签。其次,提示作为令牌计算成本,因此请求越少,成本越低。然而,每次请求有 4096 个令牌的限制。此外,考虑到我提到的问题,另一个显著的 API 限制存在。一次传递太多句子增加了不匹配和不一致的机会。因此,你需要不断增加和减少句子的数量,直到找到一致性和成本的最佳平衡点。如果不这样做,你会在后处理结果阶段遭遇问题。

总之,如果你需要处理成千上万的句子,先从几句开始,并且最多用 10 个提示来检查响应的可靠性。然后,逐渐增加数量以验证容量和质量,直到找到适合你的任务的最佳提示和比例。

3. 判决和比较结果

3.1. 比较的细节

在其 GPT-3 版本中,ChatGPT 不能使用数值为文本句子赋予情感(无论我尝试了多少次)。然而,专家在这个特定的黄金标准数据集中为句子情感赋予了数值评分。

因此,为了进行有效的比较,我必须:

  1. 将数据集评分分类为 正面中性负面 标签。

  2. 对领域特定的机器学习模型生成的评分做相同的操作。

  3. 定义可能的阈值范围(步长为 0.001),以确定一个类别的开始和结束。然后,根据阈值 TH,超过 +TH 的评分被视为 正面 情感,低于 -TH负面,介于两者之间的是 中性

  4. 遍历阈值范围,并在每个点评估两个模型的准确性。

  5. 按照集合(即训练或测试)来调查它们的表现,因为领域特定的模型在训练集上可能具有不公平的优势。

步骤 3 的代码如下。完整的比较代码在这里

def get_df_plot(df, th_sequence):
  temp_list = []

  for th in th_sequence:
    converted_gold_arr = np.where((df['sentiment'] <= th) & (df['sentiment'] >= -th), 0, np.sign(df['sentiment']))
    converted_model_arr = np.where((df['cnn-pred-sent'] <= th) & (df['cnn-pred-sent'] >= -th), 0, np.sign(df['cnn-pred-sent']))
    df['sent_cat_value'] = converted_gold_arr.astype(np.int64)
    df['cnn_pred_sent_cat_value'] = converted_model_arr.astype(np.int64)
    corr_gold_chatgpt = df['chatgpt_sent_value'].corr(df['sent_cat_value'])
    corr_gold_cnn = df['chatgpt_sent_value'].corr(df['cnn_pred_sent_cat_value'])
    acc_gold_chatgpt = (df['chatgpt_sent_value']==df['sent_cat_value']).mean()
    acc_gold_cnn = (df['chatgpt_sent_value']==df['cnn_pred_sent_cat_value']).mean()
    temp_list.append([th, corr_gold_chatgpt, corr_gold_cnn, acc_gold_chatgpt, acc_gold_cnn])

  return pd.DataFrame(data=temp_list, columns=['th', 'corr_gold_chatgpt', 'corr_gold_cnn', 'acc_gold_chatgpt', 'acc_gold_cnn'])

th_sequence = np.arange(0, 1.000001, 0.001)
df_plot = get_df_plot(df.copy(), th_sequence)

3.2. 判决:是的,ChatGPT 不仅可以赢,而且可以超越竞争对手

最终结果显示在下面的图表中,该图表展示了当调整阈值(x 轴)时,两个模型在对数字黄金标准数据集进行分类时的准确性(y 轴)变化。此外,训练集和测试集分别位于左右两侧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比较 ChatGPT 和领域特定机器学习模型,分别考虑训练集(左侧)和测试集(右侧)。这项评估评估了两个模型在对数字黄金标准数据集进行分类时,准确性(y 轴)如何随阈值(x 轴)的变化而变化。

首先,我必须诚实。我没想到会得到如此惊人的结果。因此,为了对 ChatGPT 公平,我复制了原始 SemEval 2017 竞赛设置,其中领域特定的机器学习模型将使用训练集进行构建。然后,实际排名和比较仅会在测试集上进行。

然而,即使在训练集中,在最有利的情况下——ChatGPT 的阈值为 0.066,而领域特定模型的阈值为 0.014——领域特定的机器学习模型的准确率最多比 ChatGPT 的最佳准确率低 2 个百分点(0.73 对 0.75)。此外,ChatGPT 在训练集和测试集中所有阈值下的准确率均优于领域特定模型。

有趣的是,两个模型的最佳阈值(0.038 和 0.037)在测试集中接近。在这个阈值下,ChatGPT 的准确率比领域特定模型高出 11 个百分点(0.66 对 0.77)。此外,ChatGPT 在阈值变化中的一致性明显优于领域特定模型。因此,可以看出 ChatGPT 的准确率下降得要少得多。

总的来说,ChatGPT 在准确性方面远远超过了领域特定的机器学习模型。另一方面,ChatGPT 还可以根据特定任务进行微调。因此,想象一下 ChatGPT 会变得多么优秀。

3.3. 调查 ChatGPT 的情感标注

我一直打算进行更深入的调查,通过分析 ChatGPT 不准确的例子,并将其与领域特定模型进行比较。然而,由于 ChatGPT 的表现远超预期,我转而只调查它未能正确判断情感的案例。

起初,我进行了一次类似的评估,但这次使用了完整的黄金标准数据集。接着,我选择了将黄金标准数值转换为正面、中性和负面标签的阈值(0.016),以获得 ChatGPT 的最佳准确率(0.75)。然后,我制作了一个混淆矩阵。图表如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左侧是一个线图,用于评估 ChatGPT 的准确性(y 轴)如何随阈值(x 轴)的变化而变化,以便对数字 Gold-Standard 完整数据集进行分类。右侧是正面、中性和负面标签的混淆矩阵,给出了最大 ChatGPT 性能的阈值为 0.016。此外,混淆矩阵包含了根据转换标签的 ChatGPT 的命中率和遗漏率的百分比。

请回忆一下我在前一节中展示的数据句子分布,其中正面句子的分数比负面句子的分数要高。在混淆矩阵中,观察到考虑到 0.016 的阈值,正面句子有 922 个(56.39%),负面句子有 649 个(39.69%),中性句子有 64 个(3.91%)。

此外,请注意 ChatGPT 在中性标签上的准确性较低。这是可以预期的,因为这些标签更容易受到阈值限制的影响。有趣的是,ChatGPT 倾向于将大多数这些中性句子分类为正面句子。然而,由于中性句子较少,这一现象可能与数据集中较高的正面情感分数有关。

另一方面,在考虑其他标签时,ChatGPT 显示出识别正面类别的能力比负面类别高出 6 个百分点(78.52%对 72.11%)。在这种情况下,我不确定这是否与每个评分范围内句子的数量有关。首先,因为每种类别的句子数量更多。其次,请观察 ChatGPT 将标签错误分类到相反方向(正面到负面或反之)的次数。再次,ChatGPT 在负面类别上的错误更多,而负面类别的句子数量较少。因此,ChatGPT 似乎在处理负面句子时比处理正面句子时更有困难。

3.4. 一些特定案例及与人类专家的比较

我选择了一些在 Gold-Standard(人类评分)和 ChatGPT 之间具有最明显特征的句子。然后,我使用了之前确定的相同阈值将数值评分转换为情感标签(0.016)。此外,ChatGPT 已经被报道表现优于人类。因此,我调查了这些差异,并给出了我的裁决,即人类还是 ChatGPT 更精确。

此外,自 2018 年以来,美国机构DARPA 已经深入研究了让 AI 决策具备解释性的重要性。值得注意的是,ChatGPT 具备这种能力:它可以解释其决策。这一能力帮助我做出了裁决。下表展示了这一检查结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

表格显示了 Gold-Standard 标签(通过使用 0.016 阈值从人类专家分数转换而来)与 ChatGPT 之间存在不匹配的句子示例。此外,我给出了我认为最好的裁决。

从我支持人类专家的案例开始。在句子 3 和 4 的情况下,ChatGPT 应该意识到,获得回款和公司联盟通常被认为在金融领域是有利的。然而,在句子 7 的情况下,我要求它解释其决定,ChatGPT 的回复是:

对 Barclays 的积极情感通过“创纪录”一词传达,这意味着公司在成功解决与监管机构的法律问题上取得了显著成就。

对于这个句子,ChatGPT 没有理解到,尽管达成创纪录的交易通常是好的,但 SEC 是一个监管机构。因此,与 SEC 达成创纪录的交易意味着 Barclays 和 Credit Suisse 必须支付创纪录的罚款。

继续讨论句子 5 和 8,这些都是非常艰难的判断。让我明确,人类在评估中是正确的。然而,ChatGPT 确实无法猜到这些。在句子 5 中,需要了解当时的情况才能理解这个句子代表了一个好的结果。对于句子 8,需要知道油价下跌与特定目标公司的股票价格下跌相关。

然后,对于句子 6,最中性的句子可以获得零情感分数,ChatGPT 解释了其决定如下:

这个句子是积极的,因为它宣布了新的投资银行首席运营官的任命,对公司来说是个好消息。

然而,这个回答很普通,没有很有洞察力,也没有解释为什么 ChatGPT 认为这位特定高管的任命是好的。因此,我在这种情况下同意了人类专家的观点。

有趣的是,我在句子 1、2、9 和 10 中对 ChatGPT 的评判是有利的。此外,仔细观察,人类专家应该更多关注目标公司或整体信息。这在句子 1 中尤其明显,专家们应当认识到虽然对 Glencore 的情感是积极的,但目标公司是 Barclays,而不是撰写报告的公司。在这方面,ChatGPT 在识别情感目标和意义方面做得更好。

4. 结论和结果讨论

从下表可以看出,实现这样的表现需要大量的财务和人力资源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比较模型的各个方面,例如参数数量、使用的词嵌入大小、成本、构建所需的研究人员数量、测试集中的最佳准确性,以及其决策是否可解释。

在这方面,尽管 ChatGPT 优于领域特定模型,但最终比较仍需要对 ChatGPT 进行领域特定任务的微调。这将有助于确定微调的性能提升是否超过了投入的成本。

此外,文本模型中最重要的因素之一是词嵌入的大小。自 SemEval 2017 版以来,这项技术已有所发展。因此,这部分的一些更新可能会显著提高领域特定模型的结果。

另一方面,随着生成文本模型和大型语言模型(LLMs)的流行,一些开源版本可以帮助组装一个有趣的未来对比。此外,像 ChatGPT 这样的 LLM 能够解释其决策是一个杰出的、可能出乎意料的成就,这可能会彻底改变这一领域。

5. 附加:如何在应用场景中进行这种对比

在不同领域的情感分析是一个独立的科学工作。然而,在合适的场景中应用情感分析的结果可能是另一个科学问题。此外,考虑到我们处理的是金融领域的句子,尝试将情感特征添加到应用智能系统中会比较方便。这正是一些研究人员一直在做的事情,我也在尝试这样做。

在 2021 年,我和一些同事发表了一篇关于如何在应用场景中使用情感分析的研究文章。在这篇文章中——在第二届 ACM 国际金融人工智能会议(ICAIF’21)上呈现——我们提出了一种将市场情绪纳入强化学习架构的高效方法。该架构的实现源代码在这里可以获得,其整体设计的一部分如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个将市场情绪纳入应用场景的强化学习架构的示例架构部分。来源: 《智能交易系统:一种情感感知的强化学习方法》。第二届 ACM 国际金融人工智能会议(ICAIF ‘21)论文集。 Lima Paiva, F. C.; Felizardo, L. K.; Bianchi, R. A. d. C. B.; Costa, A. H. R.

该架构旨在处理像 Gold-Standard 数据集中那样的数值情感分数。然而,也有技术(例如,Bullishnex 指数)可以将由 ChatGPT 生成的类别情感转换为适当的数值。应用这种转换使得可以在这样的架构中使用 ChatGPT 标记的情感。此外,这也是在这种情况下可以做的一种示例,并且是我计划在未来分析中做的事情。

5.1. 我研究领域的其他文章(NLP,RL)

使用的资源

主要参考文献

联系方式

ChatGPT 能推荐带有机器学习的电影吗

原文:towardsdatascience.com/can-chatgpt-recommend-movies-with-machine-learning-a5bc6c8fe90?source=collection_archive---------14-----------------------#2023-04-17

在推荐的背景下测试 ChatGPT 的极限的有趣旅程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Thushan Ganegedara

·

关注 发表在 Towards Data Science ·6 min read·2023 年 4 月 17 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Tech Daily提供,来源于Unsplash

最近我花了一些时间与我们亲爱的 AI 统治者 ChatGPT(开个玩笑!)一起探讨这个模型并推动其极限。我测试了电影推荐的用例。你可以在这里找到视频演示。

由数十亿参数驱动的单体 LLM,经过 RLHF 微调,彻底改变了我们对 AGI 的认知。ChatGPT 的崛起,GPT-3.5 和 GPT-4 证明了语言模型的能力和技能在过去几个月中扩展了多少。ChatGPT 在发布后仅两个月内达到 1 亿用户 是 AI 取得惊人进步的见证。

使用 ChatGPT 的电影推荐

许多人正在以创意方式使用 ChatGPT,从 从零开始制作 Flappy Bird构建网站。跟随这一趋势,我决定查看 ChatGPT 是否可以根据数据集计算一个未见过的电影的用户评分。我首先要求 ChatGPT 生成一个数据集。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

响应迅速,并生成了如上下文中解释的数据集。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

d

我将会要求 ChatGPT 进行以下操作,

预测 Jack 对电影《复仇者联盟》的评分

我的希望是 ChatGPT 使用协作过滤方法来完成此任务。可以首先创建一个评分矩阵,利用评分矩阵计算与 Jack 的用户相似度。最后,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意,我在计算评分时忽略了对《复仇者联盟》的评分为 0 的用户。以下的 Excel 表展示了这些计算。我们正在寻找的最终答案是 9。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我提出了如下问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看起来 ChatGPT 认为这应该是一个数据点,当前在数据集中缺失。我还尝试了 “一步步思考” 的技巧,但这并没有让 ChatGPT 进展太远。

接下来,我尝试使用 思路链推理 来阐述计算最终结果所需遵循的方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

成功了!这次,ChatGPT 能够按照计划执行,生成中间结果并计算最终答案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但稍等一下!最终结果是错误的。

问题 1:由于任务复杂性,ChatGPT 可能在数学上失败了

看起来 ChatGPT 得出的最终结果是错的。如果你把最后一步第 2 行的方程式复制到计算器中,你会得到 9,而不是 8.95。此外,余弦距离也毫不意外地错误。但是,考虑到 ChatGPT 只是一个语言模型,它能做到这一点仍然令人印象深刻。让我们给予它怀疑的余地,尝试找出 ChatGPT 出错的地方。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不幸的是,ChatGPT 无法解决这个问题。这是新响应的一部分内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我无法让 ChatGPT 纠正错误。但它一直承认自己犯了错误,这有点矛盾。这引出了第二个问题。

问题 #2:ChatGPT 过于奉承

ChatGPT 非常过于奉承,每次你指出它错了,它都会认为自己错了。有趣的是,即使它手头上有正确的解决方案,它也会认为自己错了 😅。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里,[0, 10, 0, 8] 是实际向量。但是 ChatGPT 认为这是错误的,并产生了其他幻觉,以摆脱它当前的困境。这几乎像是 Bing 聊天是 ChatGPT 的邪恶兄弟

经过一番对话后,我想测试一下 ChatGPT 的记忆/注意力跨度。所以我问,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ChatGPT 说,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

哦哦!如果你回顾 ChatGPT 的第一个有意义的回应,评分矩阵已经发生了变化。这就进入了大型语言模型(LLMs)中最棘手的问题之一。

问题 #3:ChatGPT 会产生幻觉

ChatGPT 的出现激励了科学界,引发了围绕 ChatGPT 的各种理念;从提高生产力到征服世界。一个观点是 ChatGPT 作为计算机程序的范式转变。在历史上,我们所熟知和喜爱的计算机程序是一组确定性的特定指令,通过这些指令我们可以达到预期的结果。ChatGPT 就像一个计算机程序,但它使用户可以使用自然语言进行交流,而不是用语法化的指令。

然而,在计算机程序中,如果一个变量脱离了上下文,那就是一个明显的错误。但对于 LLMs,它们只是凭空想象一些东西来填补空白。这在某些情况下可能是致命的。想象一下你尝试用 ChatGPT 解决账单错误,而 ChatGPT 却产生了一个登录错误。这将是一个非常混乱的用户体验。

你可以在下面找到我冒险的录像。

新的前沿

即使 ChatGPT 存在一些问题,这也不是世界末日!我仍然对 ChatGPT 比预训练的 GPT-3 更加出色感到印象深刻。所以这些模型只会变得更好。

我们已经有了GPT-4 的发布和等待列表。技术报告已经显示出令人瞩目的性能提升。例如,在小学数学问题上,GPT-3.5 达到了 57.1%,而 GPT-4 则设定了 92% 的标准。此外,GPT-4 在事实检索能力和比 ChatGPT 更少的幻觉方面表现得更好。

如果你对从定性角度比较 GPT-4 和 ChatGPT 感兴趣,我推荐这个视频

另一个进展是最近推出的能够使用自然语言进行推荐的模型。这个模型被称为 P5,并在与最先进的模型对比时表现出色。例如,P5 在顺序推荐上超越了 Bert4Rec 和 SASRec。

结论

ChatGPT 当然也不是没有缺陷。例如,ChatGPT 在简单的算术操作上失败,表现出谄媚行为,并在这个过程中产生了幻觉。但这只是开始。ChatGPT 的继任者 GPT-4 在许多方面都展现了显著的改进。此外,研究人员正在寻找利用自然语言的新方法来解决如推荐系统等新问题。

除非另有说明,所有图片均为作者提供

ChatGPT 能解决背包问题吗?

原文:towardsdatascience.com/can-chatgpt-solve-knapsack-problems-1a9a388c4caf?source=collection_archive---------10-----------------------#2023-09-21

用 ChatGPT 解决运筹学(OR)问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Guangrui Xie

·

关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 9 月 21 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Jakob Owens 提供,来自 Unsplash

自从 ChatGPT 出现以来,我一直在思考 ChatGPT 将如何影响优化和运筹学(OR)领域。关于 ChatGPT 通过高中和本科水平的数学考试的新闻,引发了我对它在解决 OR 问题方面表现的兴趣。在本文中,我将使用一个经典的 OR 问题——0/1 背包问题来进行一些测试。

背包问题

在优化和决策难题的领域,背包问题长期以来一直是经典的基准问题。它挑战我们选择一组物品,每个物品都有自己的重量和价值,以最大化总价值,同时保持在给定的重量限制内。动态规划是一种我们通常用来解决 0/1 胶囊问题的有效算法。

与 ChatGPT 的对话

我从一个很小的例子开始,并要求 ChatGPT 解决它。

我:考虑一个背包问题,其中背包的容量是 10lbs。我有 4 个物品可以选择,它们的价值分别为 $10、$40、$30、$50,重量分别为 5lbs、4lbs、6lbs、3lbs。你能为我解决这个问题吗?

ChatGPT:当然!背包问题是一个经典的优化问题,目标是最大化选择物品的总价值,同时满足所选物品的总重量不超过背包的容量。

在你的情况下,你有 4 个物品及其相应的价值和重量:

项目 1:价值 = $10,重量 = 5lbs 项目 2:价值 = $40,重量 = 4lbs 项目 3:价值 = $30,重量 = 6lbs 项目 4:价值 = $50,重量 = 3lbs

背包容量 = 10lbs

要解决这个问题,你可以使用动态规划方法。创建一个二维表格,其中行表示物品,列表示剩余的背包容量(从 0lbs 到 10lbs)。通过迭代填充表格,以找到每个物品组合和剩余容量的最大值。

以下是表格填充过程:

*容量 (lbs) 0 1 2 3 4 5 6 7 8 9 10

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

项目 1 (5lbs) 0 0 0 0 0 10 10 10 10 10 10

项目 2 (4lbs) 0 0 0 0 40 40 40 40 50 50 50

项目 3 (6lbs) 0 0 0 0 40 40 30 70 70 70 70

项目 4 (3lbs) 0 0 0 50 50 50 50 70 70 70 80*

表格显示了每个物品组合和剩余容量的最大值。最终单元格(右下角)具有 $80 的值,这是在 10lbs 的背包容量下可以获得的最大值。

要找到应该选择的物品,你可以从最后一个单元格回溯到第一个单元格,同时考虑导致最大值的决策。在这种情况下,你会选择项目 2 和项目 4,它们的总值为 $40 + $50 = $90,总重量为 4lbs + 3lbs = 7lbs,这在 10lbs 的背包容量范围内。

因此,最佳解决方案是选择项目 2 和项目 4,这样你将得到总值 $90。

显然,我们注意到 ChatGPT 能够选择正确的方法来解决这个问题,但 ChatGPT 给出的最终解答与其推理过程不一致。具体来说,动态规划(DP)表显示 $80(右下角单元格)是最佳目标值,但 ChatGPT 得出的结论是 $90 是最终答案。

为了检查哪个是正确的解决方案,我编写了一段 Python 脚本来执行 DP 算法。

import numpy as np
v = [10, 40, 30, 50]
w = [5, 4, 6, 3]
c = 10
dp = np.zeros((5,11))
for i in range(1,5):
    for j in range(1,11):
        if j < w[i-1]:
            dp[i,j] = dp[i-1,j]
        else:
            dp[i,j] = max(dp[i-1,j],dp[i-1,j-w[i-1]]+v[i-1]) 
dp[1:,1:]

打印出的 DP 表如下。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这确认了 ChatGPT 得出的结论是正确的,但 ChatGPT 给出的 DP 表是错误的。然后我继续让它纠正 DP 表。

我:我认为你的解决方案是正确的,但表格有些错误。

ChatGPT:我为任何困惑道歉。让我为你纠正表格。以下是修正后的背包问题动态规划表:

容量 (磅) 0 1 2 3 4 5 6 7 8 9 10

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

物品 1 (5 磅) 0 0 0 0 0 10 10 10 10 10 10

物品 2 (4 磅) 0 0 0 0 40 40 40 40 40 50 50

物品 3 (6 磅) 0 0 0 0 40 40 40 40 40 50 70

物品 4 (3 磅) 0 0 0 50 50 50 90 90 90 90 90*

现在,表格是正确的。右下角单元格中的值是$90,这表示在 10 磅的背包容量下可以获得的最大值。如前所述,最优解是选择物品 2 和物品 4,这将给你总价值$90。

有趣的是,ChatGPT 能够对表格进行一些修正,使右下角单元格与$90 对齐。然而,如果我们更仔细地查看表格,表格的最后一行仍然不正确。第 4 行第 7 列的单元格(对应于背包容量为 6 磅)应该是$50 而不是$90。一个容量为 6 磅的背包无法选择物品 2 和物品 4 来达到$90,因为物品 2 和物品 4 的总重量为 7 磅,超出了背包的容量。

我随后要求它编写一些代码来解决这个问题。

我:你能写一段 Python 代码来解决这个问题吗?

ChatGPT:当然可以!你可以使用动态规划在 Python 中解决 0/1 背包问题。以下是解决你提供的特定实例的 Python 代码片段:

def knapsack(values, weights, capacity):
    n = len(values)
    # Initialize a table to store the maximum values for different capacities
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(capacity + 1):
            # If the current item's weight is more than the current capacity, skip it
            if weights[i - 1] > w:
                dp[i][w] = dp[i - 1][w]
            else:
                # Choose the maximum between including and excluding the current item
                dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])

    # Backtrack to find the selected items
    selected_items = []
    i, w = n, capacity
    while i > 0 and w > 0:
        if dp[i][w] != dp[i - 1][w]:
            selected_items.append(i - 1)
            w -= weights[i - 1]
        i -= 1

    return dp[n][capacity], selected_items

# Item values and weights
values = [10, 40, 30, 50]
weights = [5, 4, 6, 3]
knapsack_capacity = 10

max_value, selected_items = knapsack(values, weights, knapsack_capacity)

print(f"Maximum value: ${max_value}")
print("Selected items:")
for i in selected_items:
    print(f"Item {i + 1} (Value: ${values[i]}, Weight: {weights[i]} lbs)")

当你运行这段代码时,它将输出最大值和应放入背包的选定物品。在你的特定情况下,它会正确选择物品 2 和物品 4,这些物品的总价值为$90,总重量为 7 磅,在背包容量为 10 磅的范围内。

这段代码清晰正确地打印出了最优解。由这段代码计算出的 DP 表也正确。

然后我进一步测试了一个更大规模的实例,一个背包容量为 500 磅且有 10 个物品可供选择的背包问题。每个物品的价值和重量都是使用以下代码随机生成的。

import numpy as np
np.random.seed(0)
w = np.random.normal(10, 3, 100)
w = [int(x) if x>= 1 else 1 for x in w]
v = np.random.normal(100, 30, 100)
v = [int(x) if x>= 10 else 10 for x in v]

dp = np.zeros((101,501))
for i in range(1,101):
    for j in range(1,501):
        if j < w[i-1]:
            dp[i,j] = dp[i-1,j]
        else:
            dp[i,j] = max(dp[i-1,j],dp[i-1,j-w[i-1]]+v[i-1]) 
dp[1:,1:]

我复制并粘贴了生成的值和重量,并要求 ChatGPT 解决它。

我:考虑一个背包问题,其中背包的容量为 500 磅。我有 100 个物品可供选择,其值由列表 [156, 59, 61, 129, 64, 158, 87, 77, 157, 144, 156, 127, 74, 157, 91, 124, 128, 95, 118, 127, 111, 67, 108, 139, 79, 95, 86, 155, 120, 112, 76, 116, 79, 100, 80, 120, 117, 93, 111, 67, 55, 113, 105, 119, 171, 128, 72, 133, 60, 86, 97, 151, 77, 75, 97, 80, 133, 67, 65, 86, 85, 157, 128, 102, 63, 125, 69, 53, 135, 109, 127, 109, 125, 80, 68, 120, 75, 79, 86, 100, 89, 58, 80, 33, 118, 51, 66, 101, 77, 146, 61, 108, 98, 64, 115, 94, 123, 124, 164, 140] 和重量由列表 [15, 11, 12, 16, 15, 7, 12, 9, 9, 11, 10, 14, 12, 10, 11, 11, 14, 9, 10, 7, 2, 11, 12, 7, 16, 5, 10, 9, 14, 14, 10, 11, 7, 4, 8, 10, 13, 13, 8, 9, 6, 5, 4, 15, 8, 8, 6, 12, 5, 9, 7, 11, 8, 6, 9, 11, 10, 10, 8, 8, 7, 8, 7, 4, 10, 8, 5, 11, 7, 10, 12, 10, 13, 6, 11, 7, 7, 8, 9, 10, 6, 12, 11, 5, 14, 15, 13, 9, 6, 13, 8, 13, 10, 12, 11, 12, 10, 15, 10, 11] 分别给出。你能为我解决这个问题吗?

ChatGPT:解决 100 个物品、容量为 500 磅的背包问题是一个计算密集型任务。然而,我可以提供给你用于解决此问题的 Python 代码,使用动态规划方法,但请注意,这可能需要一些时间来运行。

这里是使用提供的值和重量来解决背包问题的 Python 代码:

def knapsack(values, weights, capacity):
    n = len(values)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for w in range(capacity + 1):
            if weights[i - 1] > w:
                dp[i][w] = dp[i - 1][w]
            else:
                dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])

    selected_items = []
    i, w = n, capacity
    while i > 0 and w > 0:
        if dp[i][w] != dp[i - 1][w]:
            selected_items.append(i - 1)
            w -= weights[i - 1]
        i -= 1

    return dp[n][capacity], selected_items

# Item values and weights
values = [
    156, 59, 61, 129, 64, 158, 87, 77, 157, 144, 156, 127, 74, 157, 91, 124,
    128, 95, 118, 127, 111, 67, 108, 139, 79, 95, 86, 155, 120, 112, 76, 116,
    79, 100, 80, 120, 117, 93, 111, 67, 55, 113, 105, 119, 171, 128, 72, 133,
    60, 86, 97, 151, 77, 75, 97, 80, 133, 67, 65, 86, 85, 157, 128, 102, 63,
    125, 69, 53, 135, 109, 127, 109, 125, 80, 68, 120, 75, 79, 86, 100, 89, 58,
    80, 33, 118, 51, 66, 101, 77, 146, 61, 108, 98, 64, 115, 94, 123, 124, 164,
    140
]

weights = [
    15, 11, 12, 16, 15, 7, 12, 9, 9, 11, 10, 14, 12, 10, 11, 11, 14, 9, 10, 7,
    2, 11, 12, 7, 16, 5, 10, 9, 14, 14, 10, 11, 7, 4, 8, 10, 13, 13, 8, 9, 6, 5,
    4, 15, 8, 8, 6, 12, 5, 9, 7, 11, 8, 6, 9, 11, 10, 10, 8, 8, 7, 8, 7, 4, 10,
    8, 5, 11, 7, 10, 12, 10, 13, 6, 11, 7, 7, 8, 9, 10, 6, 12, 11, 5, 14, 15, 13,
    9, 6, 13, 8, 13, 10, 12, 11, 12, 10, 15, 10, 11
]

knapsack_capacity = 500

max_value, selected_items = knapsack(values, weights, knapsack_capacity)

print(f"Maximum value: {max_value}")
print("Selected items:")
for i in selected_items:
    print(f"Item {i + 1} (Value: {values[i]}, Weight: {weights[i]})")

运行这段代码将为你提供可以获得的最大值和所选物品的列表。请耐心等待,因为可能需要一些时间来执行。

这一次,它没有直接解决问题并给出答案,而是给了我一段 Python 代码来运行。代码在解决这个问题时表现很好。

结论

这是我第一次尝试理解 ChatGPT 解决复杂数学问题的能力,比如经典的 OR 问题——背包问题。对于小规模的背包问题,它似乎能够给出最佳解决方案的答案。然而,答案的质量并不总是有保障的。在推理过程中或最终答案中可能会出现错误。对于大规模实例,ChatGPT 倾向于避免直接给出最佳解,而是提供一段代码供运行。代码通常编写得很好,可以得到正确的解决方案。因此,在使用 ChatGPT 解决背包问题时,不要过于依赖它给出的最佳解直接答案,而是应运行它提供的代码进行双重检查。

ChatGPT 能写出比数据分析师更好的 SQL 吗?

原文:towardsdatascience.com/can-chatgpt-write-better-sql-than-a-data-analyst-f079518efab2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Marie Truong

·发表于Towards Data Science ·6 分钟阅读·2023 年 1 月 5 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DeepMind拍摄的照片在Unsplash

我尝试了ChatGPT,这是 GPT-3 语言模型的一个变体,专门设计用于在对话上下文中生成类似人类的文本。当然,就像大多数人一样,我也好奇:人工智能能做我的工作吗?它能做得比我好吗?

我有 2 年的数据分析师和分析工程师工作经验。根据BBC Science Focus,ChatGPT 已吸收了 570 GB 的数据。那么谁写的 SQL 更好呢?

来玩吧!

这个游戏将基于 3 个 LeetCode SQL 挑战(一个简单,两个中等)。我会先写出每个解决方案,然后将练习发给 ChatGPT,看看哪个解决方案效果最好。

我将提供每个挑战的链接,供你也尝试击败 ChatGPT。

挑战 1(简单)

这个挑战叫做客户下的最大订单数量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

这是我写的查询:

WITH layer_1 AS (
  SELECT 
    customer_number, COUNT(DISTINCT order_number) AS order_number
  FROM orders
  GROUP BY customer_number
)
SELECT customer_number 
FROM layer_1
ORDER BY order_number DESC 
LIMIT 1

它通过了,并且运行时间正确:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

现在让我们看看 ChatGPT 在这方面的表现。

这是 ChatGPT 的回答:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

ChatGPT 甚至解释了它所做的工作。我觉得这个查询的可读性不高——这也是我喜欢公共表表达式的原因之一——但让我们看看它的表现如何。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

令人印象深刻的是,它有效,但速度比我的结果慢。尽管我很高兴能做得比 ChatGPT 更好,但我希望能知道如何改进这个查询。

挑战 2(中等)

下一个挑战叫做 树节点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

我写的第一个查询是这个:

# Write your MySQL query statement below
WITH l1 AS (
SELECT 
    t.id, 
    c.id AS c_id,
    t.p_id
FROM Tree t
LEFT JOIN Tree c
ON c.p_id = t.id
), 
l2 AS (
SELECT 
    id, 
    COUNT(DISTINCT c_id) AS nb_childrens, 
    COUNT(DISTINCT p_id) AS nb_parents
FROM l1
GROUP BY id
)
SELECT id, 
    CASE 
        WHEN nb_childrens >0 AND nb_parents >0 THEN "Inner"
        WHEN nb_childrens > 0 THEN "Root"
        ELSE "Leaf"
    END AS type
FROM l2

我在提交之前运行了它,它得到了错误的结果…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

结果我没有足够注意示例,特别是第二个:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

当一个节点既是叶子又是根节点时,它应该被输出为根节点。我将 CASE WHEN 的顺序改为:

CASE 
        WHEN nb_childrens >0 AND nb_parents >0 THEN "Inner"
        WHEN nb_parents > 0 THEN "Leaf"
        ELSE "Root"
    END

而这次,它通过了!

我的查询得到了平均结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

现在是 ChatGPT 玩游戏的时候了!

ChatGPT 阅读了所有示例,并且没有在既是根节点又是叶节点的节点上犯我的错误:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

但它仍然给了我一个错误的答案:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

ChatGPT 没有按每个 id 返回一行。

所以我决定给 ChatGPT 一个提示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

而且它能够纠正它!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

我们在这个挑战中都犯了一个错误,但都能纠正它(尽管我不得不给 ChatGPT 提示)。在运行时间方面我有稍微更好的结果,所以得分给我,但差距很小!

挑战 3(中等)

最后的挑战叫做 资本收益/亏损

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

这是我写的查询:

SELECT 
  stock_name, 
  SUM(
    CASE 
      WHEN operation = "Buy" THEN -1*price 
      ELSE price
    END
    ) 
  AS  capital_gain_loss
FROM Stocks
GROUP BY stock_name

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

它通过了,但运行时间相当差,超过 90% 的玩家在这方面表现得比我好。

让我们看看 ChatGPT 是否其中之一。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

让我们尝试这个解决方案:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

再次,它得到了错误的结果。ChatGPT 将一个买入与每一个未来的卖出连接,而不仅仅是对应的一个,因此它的解决方案只在只有一对买入/卖出操作时有效。

我试图告诉 ChatGPT 纠正它的错误,而没有给它任何提示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

不幸的是,它给了我完全相同的查询。所以我抱怨了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

这一次,ChatGPT 和我使用了类似的结构,查询通过了。然而,我不确定 ChatGPT 是否真的理解了它一开始错在哪里。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

ChatGPT 的运行时间稍微比我差。

结果

 Challenge    ChatGPT 🤖      Data Analyst 👩‍💻          Winner        
 ----------- --------------- --------------------- --------------------- 
          1(22%)(62%)               Data Analyst 👩‍💻  
          2    ❌ ✅ (36%)      ❌ ✅ (54%)             Data Analyst 👩‍💻   
          3    ❌ ❌ ✅ (8%)(5%)                 Data Analyst 👩‍💻

我认为可以公平地说,我在这次 SQL 挑战中“战胜”了 ChatGPT。我仍然对它的能力感到印象深刻,并惊讶于它能够纠正自己的错误!ChatGPT 在速度上绝对超过我;它能在几秒钟内写出有效的 SQL 语法,而我需要几分钟。

但它仍然有 50%的错误。即使它成功完成了每一个挑战,我也不会担心我的工作。利益相关者从不会向分析师提出如此明确的请求和输出示例。他们带来的是业务问题,我们必须考虑如何利用现有数据来找到最佳答案。ChatGPT 能做到这一点吗?

我问了 ChatGPT 对这个问题的看法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截图作者

这就总结完了!

希望你喜欢这篇文章!如果喜欢的话,请关注我,获取更多关于 SQL 和数据分析的内容。

数据科学能找到大脚怪吗?

原文:towardsdatascience.com/can-data-science-find-bigfoot-ad0a54de5dda?source=collection_archive---------9-----------------------#2023-05-24

追踪世界上最难捉摸的神秘生物之一的分析方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Bradley Stephen Shaw

·

关注 发表在 Towards Data Science ·14 min read·2023 年 5 月 24 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Jon Sailer 提供,来源于 Unsplash

看着大脚怪研究人员在夜晚追逐——有时是字面意义上的——间接证据、声音和半隐约的阴影,通过森林寻找,确实让电视节目变得精彩纷呈。

我喜欢在闲暇时间看一些电视节目,而一档名为“探险大脚怪”的节目在许多午休时光中给我带来了乐趣。然而,这档节目并不是普通的神秘生物纪录片,因为它声称使用“数据算法”来首先确定一个目标搜索区域,然后派遣人类研究人员去追踪这个毛茸茸的对象。

显然,当我听到这种情况时,我的耳朵就会竖起来,除了打包过夜行李外,我开始思考我能做些什么来帮助搜索。所以开始吧,“帮助”!

虽然这将是一篇相当轻松的文章,我将查看一些数据,包括:

  1. 使用 Pandas Profiling 进行探索性数据分析

  2. 传统方式的探索性数据分析

  3. 使用 OPTICS 进行聚类

数据

数据来自 Timothy Renner¹,他从 Bigfoot Field Researchers Organization²那里获得了这些观察结果。

数据快速预览:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

有很多有趣的字段,例如:

  • 位置特征如目击地点的州、县以及经纬度。

  • 包含温度、云量和降水量的天气特征,但包括温度范围、降水细节和风速。

  • 像月相这样神秘的信息。

  • 对导致目击事件的完整文本描述,以及目击事件本身的详细信息。

使用 Pandas-Profiling 进行探索性数据分析

Pandas-Profiling 是探索数据的绝佳工具。只需几行代码,你就能获得一个非常有用的交互式报告,概述数据的重要特征。让我们看看它的实际效果。

# create the report
from pandas_profiling import ProfileReport
profile = ProfileReport(df)
profile.to_file(os.path.join(folder,'bfro_reports_eda.html'))

我们可以更深入地查看classification特征:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

对一个重要特征的详细拆解——稍后会有更多内容。

我们还可以可视化互动——这个视觉图解释了风速和气压之间的关系:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

Pandas-Profiling 还包括帮助我们通过各种相关性测量来理解数据的功能:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

当然,没有对缺失情况的总结,EDA 是不完整的。这里我们有几个很棒的展示——首先是按列的缺失情况图表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

… 以及缺失值之间的相关性:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

这么多有用的信息,付出如此少的努力。

传统方式的探索性数据分析

有时我们需要动手来更好地理解数据。让我们这样做,并在过程中讨论。

一个变化趋势

我对大脚怪目击事件随时间变化的趋势感兴趣。在我们继续之前,让我们快速看一下数据库中包含的报告类型。

总结 BFRO 网站³,A 类报告涉及在可以更有信心排除其他动物误解或误认的情况下的明确目击。

在任何情况下未能清晰观察到目标的目击被认为是B 类目击。这包括远距离的目击和光线条件差的目击。

C 类报告是那些高潜在不准确的报告。大多数间接报告或源头无法追溯的故事都属于这一类别。

重要的是,B 类报告并不被认为比 A 类报告更不可信或不重要——BFRO 认为这两类报告都足够可信,可以展示给公众。然而,C 类报告被保存在 BFRO 档案中,但很少公开列出;1958 年前的已发布或本地记录的事件和在非小报报纸或杂志中提到的目击事件是例外。

让我们来看看随时间变化的年度目击,并将 A 类和 B 类目击分开。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图像

随着时间的推移,整体呈现出强烈的下降趋势,同时,A 类和 B 类目击的比例保持相对一致。总体下降趋势的驱动因素可能难以确定:

  1. 改变的社会规范可能会阻止目击者报告潜在的目击——在今天,多少人相信“大脚怪”确实存在?

  2. 类似地,有多少人知道“大脚怪实地研究办公室”和它的数据库?在这些人中,有多少人愿意花时间完成提交?我敢打赌,不会太多,并且这个数字可能会随着时间的推移而减少。

  3. 数据库中报告越来越少可能有实际原因。一个主要原因是报告提交的便捷性:在一个优化为便利的数字化世界中,你会花多长时间填写详细的表单并提供准确的信息?

回到数据。值得记住的是,我们在这里讨论的是绝对数量。理想情况下,我们应该考虑一些曝光度的度量,以便获得更好的洞察:一种能指示看到“大脚怪”可能性的特征。例如,将年目击次数表示为进入森林区域的年均外出次数的一个比例,会是一个更有趣的度量。

目前,目击的绝对数量是人们外出的频率的直接函数,可能不可靠:看到更多的“大脚怪”可能是由于更多的人外出,而不是“大脚怪”数量的变化或人们发现神秘生物的能力的变化。

季节性组合

在这样的时间序列数据中,我们可以预期在规律的间隔内会有一些变化;这被称为季节性⁴。

任何大脚怪的季节性行为自然会在数据中产生季节性影响。例如,冬季的冬眠将导致冬季观察数量减少。像雪暴、降雨以及日光时数等季节性因素将影响我们实际看到大脚怪的能力。观察季节性的最大驱动因素可能是季节对潜在观察者的影响。目击事件可能会与夏季露营、季节性狩猎和浪漫的秋季森林散步有关。

现在我们可能没有足够的数据来可视化每月的季节性变化,以免噪声掩盖任何效果,所以我们将查看整体的目击分布情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

我们看到大多数目击事件发生在夏季。我们应该预期这一点,因为良好的天气和充足的日光使人们能够更远、更广泛地进入户外。作为一个喜欢温暖毯子和热茶的人,我并不感到惊讶于最少的目击事件发生在最寒冷和最黑暗的月份。也许大脚怪对茶和毯子有相同的看法。

我们看到这种季节性趋势在时间上是一致的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

不幸的是,我们没有太多关于观察时间的数据,所以无法深入探讨。我想象一下,天黑后情况会变得有点疯狂——这在电视上总是会发生的。

到目前为止,我们的探索性数据分析(EDA)帮助我们了解了报告给 BFRO 的大脚怪目击数量随着时间的推移而下降,并且绝大多数目击事件发生在夏季和秋季。这些见解主要与每个报告的时间元素相关——接下来我们将关注位置。

在北美的哪里呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 Tabea Schimpf 提供,发布在 Unsplash

让我们使用纬度和经度坐标对来了解大脚怪活动的区域。Folium⁵ 是一个用于创建地图可视化的优秀 Python 包。

使用 Folium 进行互动可视化

首先是热力图,高活动区域以红色和黄色突出显示:

import folium
from folium.plugins import HeatMap,MiniMap

# data for plotting
filter_map = (df['latitude'].notna()) & (df['longitude'].notna())
df_map = df.loc[filter_map,['latitude','longitude','observed']].copy()

# create the map
heat_map = folium.Map(
    location = [42,-97.37],
    tiles = 'OpenStreetMap',
    zoom_start = 4,
    control_scale = True
)

# add the heat to the map
HeatMap(
    data = df_map[['latitude','longitude','observed']],
    min_opacity = 0.1
).add_to(heat_map)

# add mini map
heat_map.add_child(MiniMap(position = 'bottomleft',toggle_display = True)) 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

像所有优秀的科幻电影一样,活动似乎仅限于美国(主要是下 48 州)。

华盛顿州和俄勒冈州似乎是活动的绝对重灾区;而俄亥俄州和佛罗里达州代表了最活跃的东部区域。中部地区似乎活动非常少。

我的地理知识不是很好,但我知道华盛顿州和部分俄勒冈州林木繁茂,而佛罗里达州的大部分地区野性十足(既是大沼泽地⁶ 的家园,也是佛罗里达人⁷ 的家乡)。

如果我猜的话,我会说大脚怪可能更喜欢森林或沼泽栖息地,因此华盛顿和佛罗里达似乎是合理的观察结果。到目前为止,都是合情合理的。接下来我们来探讨一些更科学的内容——通过聚类数据来识别大脚怪的热点区域。

使用 OPTICS 进行聚类

聚类是一项旨在将数据集划分为子集的工作,每个子集中的观察结果在某种程度上是相似的。

在这种情况下,我们将使用纬度和经度来识别簇——或者说大脚怪活动的热点。我们将根据各种期望的特性选择我们的聚类算法:

  • 该算法需要能够反映坐标对所隐含的物理距离——即,它必须能够使用 haversine 度量。

  • 我们需要该算法来过滤掉“噪声”:我们预期一些目击事件是随机的,因此应该落在簇之外。算法需要能够筛选出这些点并将其排除在簇之外。

  • 我不知道有多少个大脚怪热点;做出先验假设可能会使分析有偏差。因此,算法应该能够自行确定“最佳”簇数——即它应该是无监督的。

  • 理想情况下,我们还可以指定最小簇大小——即热点中的最小目击次数——以便将该簇视为高活动区域。

**排序点以识别聚类结构(OPTICS)**是一种基于密度的聚类算法,能够提取具有不同密度和形状的簇⁸。它的性质与 DBSCAN 算法类似,但设计上旨在克服在不同密度数据中检测有意义簇的问题⁹。

scikit-learn 文档¹⁰对这些概念做了很好的总结,假设你对 DBSCAN 已经有所了解:

OPTICS 算法与 DBSCAN 算法有许多相似之处,可以被认为是 DBSCAN 的推广¹⁰。

DBSCAN 和 OPTICS 之间的关键区别在于,OPTICS 算法构建了一个可达性图,这个图为每个样本分配了一个可达性 _距离,以及一个簇内的排序 _属性;这两个属性在模型拟合时分配,并用于确定簇的成员资格¹⁰。

OPTICS 生成的可达性距离允许在单个数据集中提取具有可变密度的簇¹⁰。

如果你不熟悉 DBSCAN,我在这里详细说明了它的工作原理(并解释了 haversine 距离):

## 让我们来做:使用 DBSCAN 进行空间聚类

演示如何结合自定义度量和贝叶斯优化来调整基于密度的空间聚类…

towardsdatascience.com

我们将编写一个函数来帮助我们进行聚类。这里没有什么太复杂的:

  1. 该函数将接受我们的(过滤后的)数据集、最小聚类大小的参数和季节的参数。

  2. 该函数根据season进行一些过滤。

  3. 我们初始化了 OPTICS 聚类,并进行聚类(记得转换为弧度,因为我们使用的是 haversine 距离度量)。在这种情况下,我们只改变了聚类中的最小样本数,即min_cluster_size

  4. 该函数返回一个包含结果聚类质心的数据集。我们通过计算聚类中所有点的平均纬度和平均经度来粗略计算质心,记得不要包括聚类-1,因为这是 OPTICS 分配给“噪声”观察的标签。

# function to do clustering
def get_cluster(data,cluster_size = 20,season = None):

    # get data
    if season is not None:
        d = data.loc[data['season'] == season,:].copy()
    else:
        d = data.copy()

    # cluster
    cluster = OPTICS(
        min_cluster_size = cluster_size,
        metric = 'haversine',
        algorithm = 'ball_tree',
        n_jobs = -1
    )

    cluster.fit(
        np.radians([x for x in zip(d['latitude'],d['longitude'])])
    )

    # get cluster centroids
    d['cluster'] = pd.Series(cluster.labels_)
    fields = ['latitude','longitude']
    d_agg = d.loc[d['cluster'] >= 0,:].groupby('cluster')[fields].mean()

    return d_agg

我们差不多准备好运行聚类了。在开始之前,让我们做一些粗略的计算,以帮助设置最小聚类大小。

我将集中关注过去 20 年的数据,并考虑如果某个区域有持续的活动报告,则将其视为热点。如果不按季节进行分层,我认为每年一次报告是一个好的起点——即将cluster_size设置为 20,season = None。如果我们要考虑季节性因素,则可以将其调整为反映我们上面调查的季节性分布。

这意味着:

  • 在“总体”数据集上进行的任何聚类都将具有最小聚类大小为 20。

  • 夏季、秋季、冬季和春季的最小聚类大小分别为 7、6、3 和 3。这反映了每个季节中观察到的 37%、30%、16%和 15%的数据(分别基于上面的 EDA)。

最后,是时候进行聚类和可视化了!首先是“总体”聚类结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:作者

在华盛顿州和波特兰地区,我们看到了一些聚类,正如热图所示。有趣的是,俄亥俄州并没有很多热点,佛罗里达州则没有。

让我们看看季节性聚类的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:作者

有趣!

看起来在美国西部,大脚怪主要在夏季(红点)和秋季(橙点)出现。中央地区似乎有一些春季热点(绿色)。如果你住在美国东部,你甚至可以在冬季(蓝点)看到大脚怪。

如果我们稍微放大一点,可以集中在你最有可能全年遇到大脚怪的区域:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:作者

现在我们将忽略城市化程度较高的地区似乎吸引大脚怪的事实,转而识别大脚怪经常出现的地标。

词云

我们的数据包含一个名为location_details的字段。不用猜也知道这是什么

词云将允许我们利用这个字段来了解“大脚怪”目击中最常见的地点和地标,而无需绘制频率或比例——令人耳目一新!

词云只是一个包含一组单词的图像,每个单词的大小表示其重要性或频率。词云是非常简单的展示方式,可以用最少的努力提供大量的见解。让我们使用wordcloud Python 包来生成一个词云。

from wordcloud import WordCloud

# get text
text = df.loc[:,'location_details'].str.cat(sep = ' ').lower()

# cloud
wordcloud = WordCloud(scale = 5,background_color = 'white').generate(text)

# show
plt.figure(figsize = (20,7.5))
plt.imshow(wordcloud, interpolation = 'bilinear')
plt.axis('off')
plt.show()

我们需要做的唯一数据处理是将描述列转换为一个单一的文本字段,并将所有字母转换为小写。这可以通过catlower字符串方法轻松完成。wordcloud则处理了大部分其他工作——生成一个外观出色的词云:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:作者

看起来大多数目击事件涉及某种高速公路或湖泊。显然山脉、森林和林地并不多。

总结

在这篇文章中我们涵盖了相当多的内容。让我们总结一下。

数据探索

我们看到如何使用 Pandas-Profiling 来减少许多手动的探索性数据分析任务,但也看到有时如果我们想更详细地理解数据中的某些元素,动手实践是必要的。

下次我可能会专注于在运行分析之前进行一些轻量级的预处理,因为报告最终将日期字段视为字符串而不是日期时间。这是一个误解——主题上是一致的。

我确实想知道运行时间和报告的解释性如何随着更广泛、高维的数据框的扩展而变化:更多的总结、交互和相关性计算无疑会带来计算成本;更大的相关性可视化可能会使解释变得更加困难。

可视化

Folium 使我们能够相当轻松地创建出色的交互式可视化,创建了热图并绘制了各种 OPTICS 聚类运行的结果。

虽然我在这里没有演示,但 Folium 也可以轻松创建和可视化聚类。如果我再做一次这种分析——或类似的分析——在进行更正式的聚类工作之前利用这个功能可能是有利的。

使用 OPTICS 进行聚类

尽管我没有明确展示,但我过滤掉了所有缺少纬度或经度的目击事件。在没有对坐标对的缺失进行任何分析的情况下,我不能确定这是否引入了任何偏差,如果我们在进行严肃分析时需要检查这一点。

这实际上引出了数据中的一个重要细节——坐标对的准确性。我想象一下,当你刚刚发现一个大脚怪时获取精确的 GPS 读数可能是具有挑战性的,因此很多经纬度读数可能只是参考地标的最佳估计。我们可能无法纠正这一点,因为任何形式的干预都可能引入偏差。这种位置“误差”实际上比你想象的更常见——例如,交通事故的位置可能是从最近的安全地点获取的。

现在很明显,聚类需要一些改进,尤其是因为一些聚类中心落在了相当城市化的区域。我们不仅要在 OPTICS 中调整min_cluster_size,还需要对聚类区域进行控制,以便得到一些“可搜索”的区域。我不知道 OPTICS 算法中是否有允许我们控制聚类区域的参数,因此我们需要引入某种形式的后处理——类似的内容在我上面链接的文章中有介绍。

如果你读到这里,谢谢你的阅读!希望你享受阅读的过程,就像我享受写作一样。

最后的一句话

尽管这篇文章的重点是使用数据科学的方法来寻找一个神秘生物,但请以我写作时的心态来看待它——这是一个有趣的分析。我认为大脚怪存在并在某个地方徘徊吗?不。我认为如果科学家们真的发现了一个新的动物物种,那将会很惊人。科学万岁!

布拉德

参考资料和资源

  1. 大脚怪目击数据集 — timothyrenner | data.world,根据公共领域许可证提供。

  2. 大脚怪领域研究组织(bfro.net)

  3. BFRO 数据库历史及报告分类系统

  4. 季节性 — 维基百科

  5. GitHub — python-visualization/folium: Python 数据. Leaflet.js 地图。

  6. 大沼泽地 — 维基百科

  7. 佛罗里达人 — 维基百科

  8. ML | OPTICS 聚类解释 — GeeksforGeeks

  9. OPTICS 算法 — 维基百科

  10. 2.3. 聚类 — scikit-learn 1.2.2 文档

语言模型能否自制工具?

原文:towardsdatascience.com/can-language-models-make-their-own-tools-cbc7c3777d22

LaTM、CREATOR 以及其他 LLM 工具使用的闭环框架……

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Cameron R. Wolfe, Ph.D.

·发布于Towards Data Science ·阅读时间 16 分钟·2023 年 9 月 17 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(照片由Todd Quackenbush提供,来源于Unsplash

在最近的综述中,我们探讨了通过外部工具增强大型语言模型(LLMs)的实用性。这些模型可以被教导以多种方式利用工具。然而,我们应该意识到,现有的工具跟随 LLMs 仅利用了一小部分潜在工具[3],而我们希望用 LLMs 解决的问题范围几乎是无穷无尽的! 考虑到这一点,显而易见,这种范式是有限制的——我们总是能找到需要尚不存在的工具的情景。在本综述中,我们将探讨旨在解决这一问题的最新研究,通过赋予 LLMs 创造自身工具的能力来解决这一问题。这种方法与人类生活有趣地类比,因为制造工具的能力导致了重大的技术进步。现在,我们探讨类似技术对 LLMs 进化的影响。

“根据人类进化里程碑的经验教训,一个关键的转折点是人类获得了制造自身工具以应对新兴挑战的能力。我们开始初步探索将这一进化概念应用于 LLMs 领域。” — 引自 [1]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于 [1, 2])

背景

在进一步了解工具制造的 LLMs 之前,我们需要刷新一些背景概念。我们在最近的综述中已经覆盖了许多这些概念,但我们现在会简要地再次讨论它们,以使我们对最新出版物的讨论更加全面和易于理解。

为什么我们应该使用工具?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [3, 8, 9])

在之前的概述中,我们已经了解了几种不同类型的工具,这些工具可以与 LLM 集成以改善其性能,例如:

  • 基础工具(计算器、搜索引擎等) [链接]

  • 深度学习模型 API [链接]

通过让 LLM 访问某些工具,我们可以轻松解决这些模型存在的限制,例如缺乏最新信息、无法进行简单的算术运算、产生虚假信息或在长链推理中出错。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [3])

工具提供上下文。 例如,如果 LLM 被问到关于最近几周的流行文化事件的问题,由于知识截止日期,它不太可能提供准确的答案。在某些情况下,LLM 可能会产生看似可信的错误答案,并误导用户提供不正确的信息 — 这是一个重大问题,因为许多(非技术性)LLM 用户如 ChatGPT 使用这些模型就像使用搜索引擎一样! 为了解决这个问题,我们可以提供一个工具,允许 LLM 执行搜索查询并从互联网上获取最新信息作为额外上下文;见上文。这样,LLM 可以记住更少的信息,而依赖上下文学习,通过工具提供的最新信息得出准确的最终答案。

“通过赋能 LLM 使用工具,我们可以获得更广泛和不断变化的知识库,并完成复杂的计算任务。” — 来自 [3]

在这次概述中,我们将看到一个有趣的工具作为基准使用,即 Wolfram ChatGPT 插件。ChatGPT 的插件生态系统通过 API 将 LLM 与外部工具集成。基本上,我们向 ChatGPT 提供 API 的描述,模型通过提示方法学习如何使用该工具(即调用其 API);更多详细信息 这里。要了解更多关于 Wolfram 插件的信息(它非常有用!),请查看精彩概述 这里

本概述。 存在许多不同类型或类别的工具,但我们将重点关注一种特定类型的工具 — 那些实际上由 LLM 创建的工具。通常,这些工具被格式化为独立的 Python 函数,完成对 LLM 有用的某些任务或子任务。工具通过直接提示支持代码的 LLM 生成所需的功能而创建。通过允许 LLM 创建自己的工具,解决问题的系统不再受限于固定的工具集合。我们可以随着时间的推移识别所需的功能,并使 LLM 能够自动创建任何有用的工具!

提示技术

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

语言模型以统一的格式解决许多不同的任务(来自 [10])

语言模型的通用文本到文本结构极为强大,因为它允许我们通过 i) 将问题格式化为文本提示和 ii) 从模型返回的文本中提取相关输出信息来解决许多不同的任务。然而,使用语言模型通常没有这么简单。我们提供给模型的提示的措辞和结构可以极大地改变其效果——提示工程至关重要!

最近,我们讨论了许多实用技巧和技术,以通过提示工程充分利用 LLM。

  • 实用提示工程 [link]

  • 高级提示工程 [link]

然而,有两种特别相关的提示技术——思维链(CoT) [6] 和思维程序(PoT) [7] 提示。这两种技术都旨在提高语言模型可靠解决复杂推理任务的能力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [6])

思维链。 长期以来,LLM 因无法解决基于推理的任务而受到批评。尽管这一问题已通过最近的模型变体得到缓解,但诸如 CoT 提示等技术仍能引发这些模型更好的推理能力。这怎么可能? 我们只需要向 LLM 提供将基于推理的问题分解为逐步解决的示例(即问题解决的理由或“思维链”)。这些示例直接插入 LLM 的提示中。然后,模型可以利用其上下文学习能力,在解决用户提出的问题时生成类似的理由。有趣的是,生成这样的理由会大幅提高 LLM 在基于推理的任务上的表现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [11, 12, 13])

除了普通的 CoT 提示外,还有许多变体被提出;见上文。通过将难题分解成更小的部分并逐步解决,让 LLM 解决复杂问题的理念极为强大。然而,我们可以通过几种不同的方式来实现这一点(其中一些实际上比 CoT 提示更简单)——CoT 提示并不总是我们最佳的选择

思维程序。 尽管思维 CoT 提示变体效果良好,但它们未能建模像迭代这样的概念,并且存在组成性差距,这意味着 LLM 可能正确解决了问题的每一步,但最终答案仍然可能不正确。为了缓解这些问题,最近的研究探索了程序辅助的语言模型。这些技术类似于 CoT 提示,但我们使用代码支持的 LLM(例如,Codex [14])生成包含代码和自然语言组件的混合问题解决方案——基本上是带有信息性注释的程序。然后,我们可以使用外部解释器执行 LLM 创建的程序,以得出最终答案!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源:[7])

PoT 提示的基本思想是某些想法和概念在程序中建模起来更容易。与其让 LLM 既解决子任务又从这些解决方案中生成最终答案,我们可以将部分过程委托给更可靠的系统。即,程序可以更容易地建模和解决数学方程式,执行迭代等,从而减少 LLM 的组成性差距。

工具使用的转折点

“与其让 LLM 作为工具的用户,不如让它们成为工具的创造者,以更高的准确性和灵活性解决问题。” — 来源于[2]

此时,我们可能应该确信工具是对现有语言模型的有用补充。但是,当我们将可用工具的范围扩展到 LLM 可以创建的任何工具时,会有什么可能性? 简而言之,这种方法形成了一个闭环框架,在这个框架中,LLM 被赋予了随意改进自身功能的能力。正如我们接下来会看到的,现有模型在制作自己的工具方面出乎意料地有能力,这使得它们能够随着时间的推移动态适应解决新的、困难的问题。

解耦工具制作和工具使用

我们知道,使用外部工具可以极大地提高 LLM 的解决问题能力[3]。然而,该领域的先前工作假设所需的工具已经存在并可供 LLM 使用。因此,这种方法依赖于人工制作和策划一套全面解决任何任务所需功能的工具。但是,如果 LLM 需要一个工具,而这个工具不在它的工具箱里怎么办? 现有的工具跟随方法对此类问题没有解决方案!

作为替代方法,[2]中的作者提出了一种“闭环”框架,该框架利用 LLM 自身即时构建所需工具。这个框架称为 LLMs 作为工具制造者(LaTM),它允许 LLM 不断生成解决不同复杂推理任务所需的工具;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源:[1])

两阶段工具方法。 LaTM 使用两阶段框架:

  • 工具制作: 为给定任务制作工具(即 Python 函数)

  • 工具使用: 使用现有工具来解决任务

值得注意的是,我们不必在这两个阶段使用相同的 LLM。例如,我们可以将更强大的模型(例如,GPT-4)应用于工具制作,而使用轻量且成本效益更高的模型(例如,GPT-3.5-turbo)来进行工具使用。鉴于工具制作通常需要相对于工具使用更强大的 LLM,这种方法使 LaTM 在实践中能够节省成本。我们只在工具制作阶段使用最昂贵和强大的模型,每个工具执行一次并不断重复使用于问题解决!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [1])

工具制作过程的目标是从少量任务解决示例中生成一个通用且可重用的工具 —— 以 Python 函数的形式实现。在 [2] 中,这一目标通过首先通过少量示例学习“提议”一个工具来实现。我们提供多个期望行为的演示,并提示 LLM 生成一个程序,该程序再现这些演示的输出。如果生成的程序没有错误,LaTM 使用 LLM 生成几个单元测试(即基于提供的演示)并执行这些测试以确认它们通过。最后,工具被“包装”,或通过展示其用法的提示提供;见上文。

尽管工具制作复杂且需要强大的 LLM 才能成功,但工具使用可以通过更具成本效益的模型来实现 —— 我们只是使用现有工具来解决任务! 我们可以提示 LLM 通过工具制作过程中创建的包装工具来使用工具,这些工具提供将任务转换为相关函数调用的演示。在这里,LaTM 依赖 LLM 的上下文学习能力来确定每个工具的正确使用方法;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [1])

值得注意的是,使用较小的模型进行工具使用意味着我们只在 LaTM 流程的短暂部分中使用更强大的模型 —— 工具只创建一次,解决更多问题时可以持续重复使用。创建和使用 LaTM 工具的过程可能看起来有些复杂,但实际上相当简单。下图中提供了一个创建和使用工具来解决逻辑推理问题的端到端示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [1])

添加调度器。 除了工具创建和使用之外,[2]中的作者还提出了一个调度模块,用于处理 LaTM 中新工具的即时创建和使用;详见下文。简而言之,调度器是一个用于确定传入任务是否应该创建新工具或仅使用现有工具的 LLM。通过使用这个额外的模块,我们可以轻松识别现有工具无法处理的新任务,并创建所需的任何工具,使 LaTM 能够更好地处理新任务顺序到达的流式场景。有趣的是,[2]中的作者展示了一个基于 GPT-3.5 的调度器可以以 ~95% 的准确率识别正确的工具使用方式——或是否需要新工具。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于[1])

这有效吗? LaTM 在 BigBench 提供的一小部分复杂推理任务上进行评估,其中 GPT-4 被用作工具创建者,而 GPT-3.5-turbo 被用作工具使用者。某种程度上,GPT-4 能够在大多数测试案例中创建出可行且有用的工具并不令人意外;详见下文。能力较弱的模型(例如 GPT-3.5-turbo)可以用于处理较简单的问题的工具制作,但在更复杂的领域中需要 GPT-4。进一步地,我们发现工具制作需要更长的上下文长度,因为 LaTM 在生成工具时使用了全部历史(即到目前为止生成的所有工具的示例),以提高可靠性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于[1])

当 LaTM 的性能与像链式思维提示 [4]这样的技术进行比较时,我们发现所提出的方法使现有的 LLM 能力大大增强!通过使用生成的工具,像 GPT-3.5-turbo 这样的模型可以与 GPT-4 进行类似的表现,并且远超 CoT 提示的性能;详见下文。此外,我们还发现,使用更轻量的模型作为工具使用者更为可取,甚至在某些情况下优于使用像 GPT-4 这样的强大模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于[1])

LaTM 是一个有趣的闭环框架,使 LLM 能够生成自己的工具。由于它仅在解决问题过程中的一小部分使用大型、昂贵的 LLM(例如 GPT-4),LaTM 是一种提高 LLM 性能的成本效益高的方法。我们可以以较小的模型和更低的成本,几乎匹配 GPT-4 在复杂推理任务中的表现。

纠正工具创建中的错误

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来源于[2])

在使用大型语言模型(LLMs)创建自己工具的想法的基础上,[2]的作者提出了一种新颖的框架,该框架 i) 通过文档和代码创建来使用 LLMs 创建相关工具,ii) 采用一种更简单的方法来规划如何使用工具解决问题,iii) 为工具使用过程添加了一个补充的错误处理机制,以使整个系统更具鲁棒性和准确性。 resulting technique, 被称为 CREATOR [2],与[2]中的研究并行探索。 这两篇出版物的目标是通过使所需工具的创建成为可能,从而创建更智能和适应性强的系统以解决复杂问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

问题解决方法。 CREATOR 通过一个四步过程来处理工具的创建和使用(参见上面的插图):

  1. 创建:通过文档和代码实现来创建工具。

  2. 决策:决定何时以及如何使用现有工具来解决问题。

  3. 执行:执行将工具应用于解决问题的程序。

  4. 修正:根据执行结果修改工具和决策。

之前的工作中没有修正步骤。这个组件作为一个自动化的错误处理机制,提高了系统的鲁棒性。由于[2]中的 LLMs(以及相关出版物[1]中的 LLMs)使用代码作为创建工具的媒介,我们可以轻松地检测和修正创建或使用工具时出现的错误(例如,通过堆栈跟踪或类似的方式)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

在[2]中,工具创建遵循一种上下文学习方法,该方法提供详细的说明和少量示例以指导 LLM 生成正确的工具。工具创建有两个主要组件:

  • 文档:概述有关工具的相关信息(例如,功能、目的、签名等)。

  • 实现:在代码中实现工具(参见上文)。

类似于[1],[2]中创建的工具被封装在一个函数或方法(在 Python 中)中,可以由 LLM 调用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

在决策阶段,LLM 会考虑所有工具的文档,并确定使用哪些工具以及如何使用它们来解决当前的问题。在制定了问题解决计划后,我们可以:

  • 格式化每个工具的输入。

  • 执行每个工具以获取相关输出。

  • 对工具输出执行任何需要的操作以推导出答案。

如果工具执行导致生成任何错误,我们可以简单地记录这些信息,并再次迭代四步过程,将错误作为修正现有工具的额外输入;参见上文。否则,我们可以使用生成的信息提取用户问题的最终答案。

“我们的研究揭示了利用 LLMs 作为工具创建者可以促进知识转移,且 LLMs 表现出不同的工具创建能力,从而使其能够灵活应对各种情况。” — 来自 [2]

改进的数学推理。 在 [2] 中提出的系统在数学(和表格)推理数据集 MATHTabMWP [4, 5] 上进行了评估。在所有实验中,ChatGPT(即 GPT-3.5-turbo)作为基础模型,因其代码生成和令人印象深刻的推理能力。CREATOR 与基线方法进行比较,如标准 CoT 提示 [6]、PoT 提示 [7] 和 Wolfram alpha ChatGPT 插件。当所有方法都使用 ChatGPT 作为基础模型时,我们发现 CREATOR(结合 CoT 提示)比基线方法具有更高的整体准确性,并且成功执行率也有所提高(即系统提供了有效格式的答案)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

除了这些评估,作者在 [2] 中提出了一个新的 Creation 挑战数据集,尝试通过在没有现有工具或代码包的新场景中测试问题解决能力来评估 LLM 的工具创建能力。在这一基准测试中,CREATOR 稍微超越了现有的基线。然而,当 LLM 获得关于应创建工具的实用性的文本提示时,这种性能改进会更大;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

除了在总体上匹配或超越基线性能的能力之外,CREATOR 框架在问题变得越来越困难时表现得更加稳定,而基线方法往往会恶化。CREATOR 在某些问题类别上也会出现类似的恶化,但该框架似乎更加适应并能够处理复杂问题;见下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(来自 [2])

收获

在本概述中,我们探讨了一种更具动态性和灵活性的工具使用方法,与 LLMs 结合。我们不再制定一组固定的工具供 LLM 使用,而是可以让 LLM 根据需要创建所需的工具。通过这种方法,我们不再遇到由于没有访问所需工具而导致的问题。我们可能最初会怀疑这种策略是否成功(即 LLMs 是否足够强大以创建自己的工具?),但最近的研究[1, 2]告诉我们,像 GPT-4 这样的最先进 LLMs 完全有能力创建独立的 Python 函数形式的工具,只要在工具创建中采取纠错措施。这些工具可以被使用和重复使用(即使是由较弱的 LLMs),以解决各种复杂问题。

“工具制造使 LLM 能够不断生成可以应用于不同请求的工具,以便未来的请求在解决任务时可以调用相应的 API。” — 来自 [1]

使用 LLMs(大语言模型)来创建工具是很棒的,但这与已经将 LLMs 与各种现有工具整合的并行努力有何关系? 这还有待观察。然而,我个人认为,最佳系统将会使用现有技术的混合体。已经存在许多有用的工具,并且这些工具每天都在与流行的 LLMs 集成(例如,参见 ChatGPT 插件商店)。因此,单靠 LLMs 来创建自己的工具并不合适。相反,我们可以利用现有的工具,同时也给 LLMs 所需的技能,以创造它们所缺乏的任何工具。随着时间的推移,LLMs 可用的工具套件将继续演变,使基于 AI 的问题解决系统变得更加有效。

与我联系!

非常感谢你阅读这篇文章。我是 Cameron R. WolfeRebuy的 AI 总监。我研究深度学习的经验和理论基础。如果你喜欢这个概述,请订阅我的 Deep (Learning) Focus newsletter,在这里我通过从基础到高级的相关主题概述来帮助读者理解 AI 研究。你也可以在 XLinkedIn 上关注我,或者查看我在 medium 上的 其他文章

参考文献

[1] 蔡天乐等. “大型语言模型作为工具制造者。” arXiv 预印本 arXiv:2305.17126(2023 年)。

[2] 钱成等. “CREATOR: 通过工具创建解开大型语言模型的抽象与具体推理。” arXiv 预印本 arXiv:2305.14318(2023 年)。

[3] Schick, Timo, 等. “Toolformer: 语言模型可以自学使用工具。” arXiv 预印本 arXiv:2302.04761(2023 年)。

[4] Lu, Pan, 等. “通过策略梯度进行动态提示学习,以应对半结构化数学推理。” arXiv 预印本 arXiv:2209.14610(2022 年)。

[5] Hendrycks, Dan, 等. “通过数学数据集衡量数学问题解决能力。” arXiv 预印本 arXiv:2103.03874(2021 年)。

[6] Wei, Jason, 等. “思维链提示在大型语言模型中引发推理。” arXiv 预印本 arXiv:2201.11903(2022 年)。

[7] 陈文华等. “思想提示程序:解开数值推理任务中的计算与推理。” arXiv 预印本 arXiv:2211.12588(2022 年)。

[8] Shen, Yongliang, 等. “Hugginggpt: 用 chatgpt 和 huggingface 的朋友解决 AI 任务。” arXiv 预印本 arXiv:2303.17580(2023 年)。

[9] Patil, Shishir G., 等. “Gorilla: 连接大量 API 的大型语言模型。” arXiv 预印本 arXiv:2305.15334(2023 年)。

[10] 拉菲尔,科林 等。“通过统一的文本到文本转换器探索迁移学习的极限。” 机器学习研究杂志 21.1 (2020): 5485–5551。

[11] 小岛武史 等。“大型语言模型是零样本推理器。” arXiv 预印本 arXiv:2205.11916 (2022)。

[12] 王学智 等。“自我一致性提高了语言模型中的思维链推理能力。” arXiv 预印本 arXiv:2203.11171 (2022)。

[13] 周,丹尼 等。“最少到最多提示方法在大型语言模型中的复杂推理能力。” arXiv 预印本 arXiv:2205.10625 (2022)。

[14] 陈马克 等。“评估训练有代码的大型语言模型。” arXiv 预印本 arXiv:2107.03374 (2021)。

LLM 能否取代数据分析师?构建一个 LLM 驱动的分析师

原文:towardsdatascience.com/can-llms-replace-data-analysts-building-an-llm-powered-analyst-851578fa10ce?source=collection_archive---------0-----------------------#2023-12-11

第一部分:赋能 ChatGPT 工具

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 玛丽亚·曼苏罗娃

·

关注 发表在 Towards Data Science ·19 分钟阅读·2023 年 12 月 11 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自 DALL-E 3

我认为在过去的一年里,我们每个人至少都有过一次怀疑 ChatGPT 是否(或者更准确地说何时)能够取代你的角色。我也不例外。

我们有一个共同的观点,即生成型 AI 的最新突破将对我们的个人生活和工作产生重大影响。然而,目前尚未明确我们角色的变化会如何发展。

花大量时间思考不同的未来情景及其概率可能很吸引人,但我建议一种完全不同的方法——自己动手构建原型。首先,这相当具有挑战性和趣味性。其次,这将帮助我们以更结构化的方式来看待我们的工作。第三,这将给我们提供一个实践最前沿方法——LLM 代理的机会。

在这篇文章中,我们将从简单开始,学习 LLM 如何利用工具并执行直接任务。但在接下来的文章中,我们将深入探讨不同的方法和 LLM 代理的最佳实践。

那么,旅程开始吧。

什么是数据分析?

在深入了解 LLM 之前,我们先定义什么是分析,以及我们作为分析师要做哪些任务。

我的座右铭是分析团队的目标是帮助产品团队在可用时间内基于数据做出正确的决策。这是一个很好的使命,但为了定义 LLM 驱动的分析师的范围,我们应该进一步分解分析工作。

我喜欢 Gartner 提出的框架。它识别了四种不同的数据和分析技术:

  • 描述性分析回答诸如“发生了什么?”的问题。例如,12 月份的收入是多少?这种方法包括报告任务和使用 BI 工具。

  • 诊断分析更进一步,提出诸如“为什么会发生这种情况?”的问题。例如,为什么收入比去年减少了 10%?这种技术需要对数据进行更深入的挖掘和切分。

  • 预测分析允许我们回答诸如“会发生什么?”的问题。这种方法的两个基石是预测(预测业务正常情况下的未来)和模拟(建模不同可能的结果)。

  • 规范性分析影响最终决策。常见的问题包括“我们应该关注什么?”或“我们如何能将量提高 10%?”。

通常,公司会逐步经历这些阶段。如果你的公司尚未掌握描述性分析(例如没有数据仓库、BI 工具或指标定义),那么几乎不可能开始看预测和不同的情景分析。因此,这个框架也可以展示公司的数据成熟度。

同样,当分析师从初级成长为高级时,她通常会经历这些阶段,从明确的报告任务开始,逐步发展到模糊的战略问题。因此,这个框架在个人层面也具有相关性。

如果我们回到 LLM 驱动的分析师,我们应该专注于描述性分析和报告任务。最好从基础开始。因此,我们将重点学习 LLM,以了解有关数据的基本问题。

我们已经为第一个原型定义了重点。因此,我们准备进入技术问题,讨论 LLM 代理和工具的概念。

LLM 代理和工具

当我们之前使用 LLM 时(例如,为了做主题建模这里),我们在代码中自己描述了确切的步骤。例如,让我们看看下面的链条。首先,我们要求模型确定客户评论的情感。然后,根据情感,从评论中提取提到的优点或缺点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

在这个例子中,我们明确地定义了 LLM 的行为,并且 LLM 很好地解决了这个任务。然而,如果我们构建更高层次和模糊的东西,比如一个由 LLM 驱动的分析师,这种方法就不起作用了。

如果你曾经作为分析师或与分析师合作过至少一天,你会知道分析师会收到各种不同的问题,从基础问题(如“昨天我们网站上有多少客户?”或“你能为我们明天的董事会会议做个图表吗?”)到非常高层次的问题(例如,“主要的客户痛点是什么?”或“我们应该接下来进入哪个市场?”)。不用说,描述所有可能的场景是不现实的。

然而,有一种方法可能对我们有所帮助——代理。代理的核心思想是将 LLM 用作推理引擎,可以选择下一步做什么,以及何时向客户返回最终答案。这听起来非常接近我们的行为:我们接到任务,定义所需工具,使用它们,然后在准备好时返回最终答案。

与代理相关的基本概念(我已经在上面提到过)是工具。工具是 LLM 可以调用以获取缺失信息的功能(例如,执行 SQL、使用计算器或调用搜索引擎)。工具至关重要,因为它们使你能够将 LLM 提升到一个新的水平,并与世界互动。在本文中,我们将主要关注作为工具的 OpenAI 函数。

OpenAI 已经对模型进行了微调,以便能够处理函数:

  • 你可以将包含描述的函数列表传递给模型;

  • 如果与你的查询相关,模型会返回一个函数调用——函数名称和调用它的输入参数。

你可以在文档中找到更多信息和支持函数的模型的最新列表。

使用 LLM 的函数有两个显著的用例:

  • 标记和提取——在这些情况下,函数用于确保模型的输出格式。你将得到一个结构化的函数调用,而不是通常的内容输出。

  • 工具和路由——这是一个更有趣的用例,允许你创建一个代理。

让我们从提取的更直接的用例开始,以了解如何使用 OpenAI 函数。

用例 #1: 标记与提取

你可能会想知道标记和提取之间有什么区别。这些术语很接近。唯一的区别在于模型是提取文本中呈现的信息还是标记文本提供新信息(即定义语言或情感)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

由于我们决定专注于描述性分析和报告任务,让我们使用这种方法来结构化传入的数据请求,并提取以下组件:度量标准、维度、筛选条件、时间段和期望输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

这将是一个提取示例,因为我们只需要文本中存在的信息。

OpenAI Completion API 基础示例

首先,我们需要定义函数。OpenAI 期望一个作为 JSON 的函数描述。这个 JSON 会传递给 LLM,所以我们需要告诉它所有的上下文:这个函数的作用是什么以及如何使用它。

这是一个函数 JSON 的示例。我们已经指定了:

  • 函数本身的namedescription

  • 每个参数的typedescription

  • 函数所需的输入参数列表。

extraction_functions = [
    {
        "name": "extract_information",
        "description": "extracts information",
        "parameters": {
            "type": "object",
            "properties": {
                "metric": {
                    "type": "string",
                    "description": "main metric we need to calculate, for example, 'number of users' or 'number of sessions'",
                },
                "filters": {
                    "type": "string",
                    "description": "filters to apply to the calculation (do not include filters on dates here)",
                },
                "dimensions": {
                    "type": "string",
                    "description": "parameters to split your metric by",
                },
                "period_start": {
                    "type": "string",
                    "description": "the start day of the period for a report",
                },
                "period_end": {
                    "type": "string",
                    "description": "the end day of the period for a report",
                },
                "output_type": {
                    "type": "string",
                    "description": "the desired output",
                    "enum": ["number", "visualisation"]
                }
            },
            "required": ["metric"],
        },
    }
]

在这个用例中没有必要实现函数本身,因为我们不会使用它。我们只是以结构化的方式作为函数调用获得 LLM 响应。

现在,我们可以使用标准的 OpenAI Chat Completion API 来调用函数。我们传递给 API 调用:

  • 模型——我使用了最新的 ChatGPT 3.5 Turbo,它可以与函数一起工作,

  • 消息列表——一个系统消息以设置上下文和一个用户请求,

  • 我们之前定义的函数列表。

import openai

messages = [
    {
        "role": "system",
        "content": "Extract the relevant information from the provided request."
    },
    {
        "role": "user",
        "content": "How did number of iOS users change over time?"
    }
]

response = openai.ChatCompletion.create(
    model = "gpt-3.5-turbo-1106", 
    messages = messages,
    functions = extraction_functions
)

print(response)

结果是我们得到了以下 JSON。

{
  "id": "chatcmpl-8TqGWvGAXZ7L43gYjPyxsWdOTD2n2",
  "object": "chat.completion",
  "created": 1702123112,
  "model": "gpt-3.5-turbo-1106",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "function_call": {
          "name": "extract_information",
          "arguments": "{\"metric\":\"number of users\",\"filters\":\"platform='iOS'\",\"dimensions\":\"date\",\"period_start\":\"2021-01-01\",\"period_end\":\"2021-12-31\",\"output_type\":\"visualisation\"}"
        }
      },
      "finish_reason": "function_call"
    }
  ],
  "usage": {
    "prompt_tokens": 159,
    "completion_tokens": 53,
    "total_tokens": 212
  },
  "system_fingerprint": "fp_eeff13170a"
}

记住,函数和函数调用会计入令牌限制并产生费用。

模型返回了一个函数调用而不是常见的响应:我们可以看到content是空的,而finish_reason等于function_call。在响应中,还有函数调用的输入参数:

  • metric = "number of users"

  • filters = "platform = 'iOS'"

  • dimensions = "date"

  • period_start = "2021-01-01"

  • period_start = "2021-12-31"

  • output_type = "visualisation"

模型做得很好。唯一的问题是它凭空假设了时间段。我们可以通过向系统消息添加更明确的指导来解决这个问题,例如,"从提供的请求中提取相关信息。仅提取初始请求中呈现的信息;不要添加任何其他内容。如果缺少内容,返回部分信息。"

默认情况下,模型决定是否独立使用函数(function_call = 'auto')。我们可以要求它每次返回特定的函数调用,或者根本不使用函数。

 # always calling extract_information function
response = openai.ChatCompletion.create(
    model = "gpt-3.5-turbo-1106",
    messages = messages,
    functions = extraction_functions,
    function_call = {"name": "extract_information"}
)

# no function calls
response = openai.ChatCompletion.create(
    model = "gpt-3.5-turbo-1106",
    messages = messages,
    functions = extraction_functions,
    function_call = "none"
)

我们得到了第一个使用 LLM 函数的工作程序。这很棒。然而,在 JSON 中描述函数并不是很方便。让我们讨论如何简化这个过程。

使用 Pydantic 定义函数

为了更方便地定义函数,我们可以利用Pydantic。Pydantic 是最受欢迎的数据验证 Python 库。

我们已经使用Pydantic 定义了 LangChain 输出解析器。

首先,我们需要创建一个继承自BaseModel类的类,并定义所有字段(我们函数的参数)。

from pydantic import BaseModel, Field
from typing import Optional

class RequestStructure(BaseModel):
  """extracts information"""
  metric: str = Field(description = "main metric we need to calculate, for example, 'number of users' or 'number of sessions'")
  filters: Optional[str] = Field(description = "filters to apply to the calculation (do not include filters on dates here)")
  dimensions: Optional[str] = Field(description = "parameters to split your metric by")
  period_start: Optional[str] = Field(description = "the start day of the period for a report")
  period_end: Optional[str] = Field(description = "the end day of the period for a report")
  output_type: Optional[str] = Field(description = "the desired output", enum = ["number", "visualisation"])

然后,我们可以使用 LangChain 将 Pydantic 类转换为 OpenAI 函数。

from langchain.utils.openai_functions import convert_pydantic_to_openai_function
extract_info_function = convert_pydantic_to_openai_function(RequestStructure, 
    name = 'extract_information')

LangChain 验证我们提供的类。例如,它确保函数描述被指定,因为 LLM 需要它来使用这个工具。

结果是,我们得到了相同的 JSON 以传递给 LLM,但现在我们将其表示为 Pydantic 类。

{'name': 'extract_information',
 'description': 'extracts information',
 'parameters': {'title': 'RequestStructure',
  'description': 'extracts information',
  'type': 'object',
  'properties': {'metric': {'title': 'Metric',
    'description': "main metric we need to calculate, for example, 'number of users' or 'number of sessions'",
    'type': 'string'},
   'filters': {'title': 'Filters',
    'description': 'filters to apply to the calculation (do not include filters on dates here)',
    'type': 'string'},
   'dimensions': {'title': 'Dimensions',
    'description': 'parameters to split your metric by',
    'type': 'string'},
   'period_start': {'title': 'Period Start',
    'description': 'the start day of the period for a report',
    'type': 'string'},
   'period_end': {'title': 'Period End',
    'description': 'the end day of the period for a report',
    'type': 'string'},
   'output_type': {'title': 'Output Type',
    'description': 'the desired output',
    'enum': ['number', 'visualisation'],
    'type': 'string'}},
  'required': ['metric']}}

现在,我们可以在调用 OpenAI 时使用它。让我们从 OpenAI API 切换到 LangChain,使我们的 API 调用更加模块化。

定义 LangChain 链

让我们定义一个链,以从请求中提取所需的信息。我们将使用 LangChain,因为它是 LLM 最受欢迎的框架。如果你之前没有使用过它,我推荐你在我之前的一篇文章中学习一些基础知识。

我们的链很简单。它由一个 Open AI 模型和一个包含一个变量request(用户消息)的提示组成。

我们还使用了bind函数将functions参数传递给模型。bind函数允许我们为模型指定常量参数,这些参数不是输入的一部分(例如,functionstemperature)。

from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI

model = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\
  .bind(functions = [extract_info_function])

prompt = ChatPromptTemplate.from_messages([
    ("system", "Extract the relevant information from the provided request. \
            Extract ONLY the information presented in the initial request. \
            Don't add anything else. \
            Return partial information if something is missing."),
    ("human", "{request}")
])

extraction_chain = prompt | model

现在是尝试我们的函数的时候了。我们需要使用 invoke 方法并传递一个request

extraction_chain.invoke({'request': "How many customers visited our site on iOS in April 2023 from different countries?"})

在输出中,我们得到了没有任何内容但有一个函数调用的AIMessage

AIMessage(
  content='', 
  additional_kwargs={
    'function_call': {
       'name': 'extract_information', 
       'arguments': '''{
         "metric":"number of customers", "filters":"device = 'iOS'",
         "dimensions":"country", "period_start":"2023-04-01",
         "period_end":"2023-04-30", "output_type":"number"}
        '''}
  }
)

所以,我们已经学习了如何在 LangChain 中使用 OpenAI 函数来获得结构化输出。现在,让我们转到更有趣的用例——工具和路由。

用例 #2:工具与路由

现在是使用工具并赋予我们的模型外部能力的时候了。在这种方法中,模型是推理引擎,它们可以决定使用哪些工具以及何时使用(这称为路由)。

LangChain 有一个工具的概念——代理可以用来与世界互动的接口。工具可以是函数、LangChain 链或甚至其他代理。

我们可以使用format_tool_to_openai_function轻松将工具转换为 OpenAI 函数,并不断将functions参数传递给 LLM。

定义自定义工具

让我们教会我们的 LLM 驱动的分析师计算两个指标之间的差异。我们知道 LLM 在数学上可能会出错,因此我们希望要求模型使用计算器,而不是依靠自己计算。

要定义一个工具,我们需要创建一个函数并使用@tool装饰器。

from langchain.agents import tool

@tool
def percentage_difference(metric1: float, metric2: float) -> float:
    """Calculates the percentage difference between metrics"""
    return (metric2 - metric1)/metric1*100

现在,这个函数有namedescription参数,这些参数将传递给 LLMs。

print(percentage_difference.name)
# percentage_difference.name

print(percentage_difference.args)
# {'metric1': {'title': 'Metric1', 'type': 'number'},
# 'metric2': {'title': 'Metric2', 'type': 'number'}}

print(percentage_difference.description)
# 'percentage_difference(metric1: float, metric2: float) -> float - Calculates the percentage difference between metrics'

这些参数将用于创建 OpenAI 函数规范。让我们将我们的工具转换为 OpenAI 函数。

from langchain.tools.render import format_tool_to_openai_function
print(format_tool_to_openai_function(percentage_difference))

我们得到了以下的 JSON 作为结果。它概述了结构,但缺少字段描述。

{'name': 'percentage_difference',
 'description': 'percentage_difference(metric1: float, metric2: float) -> float - Calculates the percentage difference between metrics',
 'parameters': {'title': 'percentage_differenceSchemaSchema',
  'type': 'object',
  'properties': {'metric1': {'title': 'Metric1', 'type': 'number'},
   'metric2': {'title': 'Metric2', 'type': 'number'}},
  'required': ['metric1', 'metric2']}
}

我们可以使用 Pydantic 来指定参数的模式。

class Metrics(BaseModel):
    metric1: float = Field(description="Base metric value to calculate the difference")
    metric2: float = Field(description="New metric value that we compare with the baseline")

@tool(args_schema=Metrics)
def percentage_difference(metric1: float, metric2: float) -> float:
    """Calculates the percentage difference between metrics"""
    return (metric2 - metric1)/metric1*100

现在,如果我们将新版本转换为 OpenAI 函数规范,它将包括参数描述。这样更好,因为我们可以与模型共享所有需要的上下文。

{'name': 'percentage_difference',
 'description': 'percentage_difference(metric1: float, metric2: float) -> float - Calculates the percentage difference between metrics',
 'parameters': {'title': 'Metrics',
  'type': 'object',
  'properties': {'metric1': {'title': 'Metric1',
    'description': 'Base metric value to calculate the difference',
    'type': 'number'},
   'metric2': {'title': 'Metric2',
    'description': 'New metric value that we compare with the baseline',
    'type': 'number'}},
  'required': ['metric1', 'metric2']}}

所以,我们已经定义了 LLM 可以使用的工具。让我们在实践中尝试一下。

实践中使用工具

让我们定义一个链,并将我们的工具传递给函数。然后,我们可以在用户请求上测试它。

model = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\
  .bind(functions = [format_tool_to_openai_function(percentage_difference)])

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a product analyst willing to help your product team. You are very strict to the point and accurate. You use only facts, not inventing information."),
    ("user", "{request}")
])

analyst_chain = prompt | model
analyst_chain.invoke({'request': "In April we had 100 users and in May only 95\. What is difference in percent?"})

我们得到了一个带有正确参数的函数调用,所以它正在工作。

AIMessage(content='', additional_kwargs={
    'function_call': {
      'name': 'percentage_difference', 
      'arguments': '{"metric1":100,"metric2":95}'}
  }
)

为了更方便地处理输出,我们可以使用OpenAIFunctionsAgentOutputParser。让我们将它添加到我们的链中。

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()
result = analyst_chain.invoke({'request': "There were 100 users in April and 110 users in May. How did the number of users changed?"})

现在,我们以更结构化的方式获得了输出,我们可以轻松地检索工具的参数,像result.tool_input

AgentActionMessageLog(
   tool='percentage_difference', 
   tool_input={'metric1': 100, 'metric2': 110}, 
   log="\nInvoking: `percentage_difference` with `{'metric1': 100, 'metric2': 110}`\n\n\n", 
   message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}})]
)

所以,我们可以像 LLM 请求的那样执行函数。

observation = percentage_difference(result.tool_input)
print(observation)
# 10

如果我们想从模型中获取最终答案,我们需要将函数执行结果传递回去。为此,我们需要定义一个消息列表来传递给模型观察结果。

from langchain.prompts import MessagesPlaceholder

model = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\
  .bind(functions = [format_tool_to_openai_function(percentage_difference)])

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a product analyst willing to help your product team. You are very strict to the point and accurate. You use only facts, not inventing information."),
    ("user", "{request}"),
    MessagesPlaceholder(variable_name="observations")
])

analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()
result1 = analyst_chain.invoke({
    'request': "There were 100 users in April and 110 users in May. How did the number of users changed?",
    "observations": []
})

observation = percentage_difference(result1.tool_input)
print(observation)
# 10

然后,我们需要将观察结果添加到我们的observations变量中。我们可以使用format_to_openai_functions函数以模型期望的方式格式化结果。

from langchain.agents.format_scratchpad import format_to_openai_functions
format_to_openai_functions([(result1, observation), ])

结果,我们得到了 LLM 能够理解的消息。

[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 
                                           'arguments': '{"metric1":100,"metric2":110}'}}),
 FunctionMessage(content='10.0', name='percentage_difference')]

让我们再调用一次链,将函数执行结果作为观察结果传递。

result2 = analyst_chain.invoke({
    'request': "There were 100 users in April and 110 users in May. How did the number of users changed?",
    "observations": format_to_openai_functions([(result1, observation)])
})

现在,我们得到了来自模型的最终结果,听起来很合理。

AgentFinish(
  return_values={'output': 'The number of users increased by 10%.'}, 
  log='The number of users increased by 10%.'
)

如果我们使用的是原始 OpenAI Chat Completion API,我们可以只添加另一条消息,角色设置为 tool。你可以在这里找到详细的示例。

如果我们开启调试,我们可以看到传递给 OpenAI API 的确切提示。

System: You are a product analyst willing to help your product team. You are very strict to the point and accurate. You use only facts, not inventing information.
Human: There were 100 users in April and 110 users in May. How did the number of users changed?
AI: {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}
Function: 10.0

要开启 LangChain 调试,执行以下代码并调用你的链,看看在幕后发生了什么。

import langchain
langchain.debug = True

我们尝试使用了一个工具,但让我们扩展我们的工具包,看看 LLM 如何处理它。

路由:使用多个工具

让我们在分析工具包中再添加几个工具:

  • 获取每月活跃用户

  • 使用 Wikipedia。

首先,让我们定义一个虚拟函数来计算按月份和城市过滤的观众。我们将再次使用 Pydantic 来指定函数的输入参数。

import datetime
import random

class Filters(BaseModel):
    month: str = Field(description="Month of customer's activity in the format %Y-%m-%d")
    city: Optional[str] = Field(description="City of residence for customers (by default no filter)", 
                    enum = ["London", "Berlin", "Amsterdam", "Paris"])

@tool(args_schema=Filters)
def get_monthly_active_users(month: str, city: str = None) -> int:
    """Returns number of active customers for the specified month"""
    dt = datetime.datetime.strptime(month, '%Y-%m-%d')
    total = dt.year + 10*dt.month
    if city is None:
        return total
    else:
        return int(total*random.random())

然后,让我们使用the wikipedia Python 包来允许模型查询 Wikipedia。

import wikipedia

class Wikipedia(BaseModel):
    term: str = Field(description="Term to search for")

@tool(args_schema=Wikipedia)
def get_summary(term: str) -> str:
    """Returns basic knowledge about the given term provided by Wikipedia"""
    return wikipedia.summary(term)

让我们定义一个包含模型目前知道的所有函数的字典。这个字典将帮助我们以后进行路由。

toolkit = {
    'percentage_difference': percentage_difference,
    'get_monthly_active_users': get_monthly_active_users,
    'get_summary': get_summary
}

analyst_functions = [format_tool_to_openai_function(f) 
  for f in toolkit.values()]

我对我们之前的设置做了一些更改:

  • 我稍微调整了系统提示,强制 LLM 在需要基本知识时咨询 Wikipedia。

  • 我已经将模型更改为 GPT 4,因为它更适合处理需要推理的任务。

from langchain.prompts import MessagesPlaceholder

model = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview')\
  .bind(functions = analyst_functions)

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a product analyst willing to help your product team. You are very strict to the point and accurate. \
        You use only information provided in the initial request. \
        If you need to determine some information i.e. what is the name of the capital, you can use Wikipedia."),
    ("user", "{request}"),
    MessagesPlaceholder(variable_name="observations")
])

analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()

我们可以调用我们的链条中的所有函数。让我们从一个非常直接的查询开始。

result1 = analyst_chain.invoke({
    'request': "How many users were in April 2023 from Berlin?",
    "observations": []
})
print(result1)

我们得到的结果是对 get_monthly_active_users 的函数调用,输入参数为 {'month': '2023–04–01', 'city': 'Berlin'},这看起来是正确的。模型能够找到正确的工具并解决任务。

让我们尝试将任务变得更复杂一些。

result1 = analyst_chain.invoke({
    'request': "How did the number of users from the capital of Germany\
        change between April and May 2023?",
    "observations": []
})

让我们停下来想一想我们希望模型如何推理。显然,模型没有足够的信息来直接回答,所以它需要进行一系列函数调用:

  • 调用 Wikipedia 获取德国的首都

  • 调用 get_monthly_active_users 函数两次,以获取 4 月和 5 月的 MAU

  • 调用 percentage_difference 以计算指标之间的差异。

这看起来相当复杂。让我们看看 ChatGPT 是否能够处理这个问题。

在第一次调用中,LLM 返回了一个对 Wikipedia 的函数调用,参数为 {'term': 'capital of Germany'}。到目前为止,它遵循了我们的计划。

让我们提供观察结果,看看接下来的步骤会是什么。

observation1 = toolkitresult1.tool
print(observation1)

# The capital of Germany is the  city state of Berlin. It is the seat of 
# the President of Germany, whose official residence is Schloss Bellevue. 
# The Bundesrat ("federal council") is the representation of the Federal States 
# (Bundesländer) of Germany and has its seat at the former Prussian Herrenhaus 
# (House of Lords). Though most of the ministries are seated in Berlin, 
# some of them, as well as some minor departments, are seated in Bonn, 
# the former capital of West Germany.
# Although Berlin is officially the capital of the Federal Republic of Germany,
# 8,000 out of the 18,000 total officials employed at the federal bureaucracy 
# still work in Bonn, about 600 km (370 mi) away from Berlin.

# source: https://en.wikipedia.org/wiki/Capital_of_Germany 

result2 = analyst_chain.invoke({
    'request': "How did the number of users from the capital of Germany change between April and May 2023?",
    "observations": format_to_openai_functions([(result1, observation1)])
})

模型想要执行 get_monthly_active_users,参数为 {'month': '2023–04–01', 'city': 'Berlin'}。让我们执行它,并再次将信息返回给模型。

observation2 = toolkitresult2.tool
print(observation2)
# 168

result3 = analyst_chain.invoke({
    'request': "How did the number of users from the capital of Germany change between April and May 2023?",
    "observations": format_to_openai_functions([(result1, observation1), (result2, observation2)])
})

然后,模型请求再次调用 get_monthly_active_users,参数为 {'month': '2023–05–01', 'city': 'Berlin'}。到目前为止,它表现得非常出色。让我们跟随它的逻辑。

observation3 = toolkitresult3.tool
print(observation3)
# 1046

result4 = analyst_chain.invoke({
    'request': "How did the number of users from the capital of Germany change between April and May 2023?",
    "observations": format_to_openai_functions(
      [(result1, observation1), (result2, observation2), 
      (result3, observation3)])
})

随后的结果是一个对 percentage_difference 的函数调用,参数为 {'metric1': 168, 'metric2': 1046}。让我们计算观察结果并再次调用我们的链条。希望这将是最后一步。

observation4 = toolkitresult4.tool
print(observation4)

# 523.27

result5 = analyst_chain.invoke({
    'request': "How did the number of users from the capital of Germany change between April and May 2023?",
    "observations": format_to_openai_functions(
      [(result1, observation1), (result2, observation2), 
      (result3, observation3), (result4, observation4)])
})

最终,我们从模型中得到以下响应:德国首都柏林的用户数量在 2023 年 4 月到 5 月间增加了约 523.27%。

这是针对这个问题的 LLM 调用的完整方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

在上述示例中,我们手动逐一触发了后续调用,但这可以很容易地自动化。

这是一个很棒的结果,我们能够看到 LLM 如何进行推理并利用多种工具。模型用了 5 步达成结果,但它遵循了我们最初制定的计划,因此这是一个相当合乎逻辑的路径。然而,如果你计划将 LLM 用于生产环境,请记住,它可能会犯错误,并引入评估和质量保证流程。

你可以在 GitHub 上找到完整的代码。

总结

本文教会了我们如何利用 OpenAI 函数为 LLMs 提供外部工具。我们已经考察了两个使用案例:提取以获得结构化输出和路由以使用外部信息回答问题。最终结果让我感到鼓舞,因为 LLM 能够使用三种不同的工具回答相当复杂的问题。

回到最初的问题,LLMs 是否能取代数据分析师。我们当前的原型仍然很基础,远未达到初级分析师的能力,但这只是开始。敬请关注!我们将深入探讨 LLM 代理的不同方法。下次,我们将尝试创建一个能够访问数据库并回答基本问题的代理。

参考

本文的灵感来源于 DeepLearning.AI 的“LangChain 中的函数、工具和代理”课程。

LLMs 能否替代数据分析师?使用 SQL 获取答案

原文:towardsdatascience.com/can-llms-replace-data-analysts-getting-answers-using-sql-8cf7da132259?source=collection_archive---------0-----------------------#2023-12-22

第二部分:深入探讨 LLM 代理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Mariya Mansurova

·

关注 发表在 Towards Data Science ·31 分钟阅读·2023 年 12 月 22 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 DALL-E 3 提供

上一篇文章 中,我们开始构建一个由 LLM 驱动的分析师。我们决定专注于描述性分析和报告任务,因为这些任务是分析师最常见的。大多数分析师的职业生涯从这些任务开始,大多数公司也从报告和 BI 工具开始构建分析功能。

我们的第一个原型可以使用现成的工具来回答与定义指标相关的问题,如下面的示例所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

下一步是教我们的 LLM 驱动分析师获取任何指标。分析师通常使用 SQL 来获取数据。因此,对 LLM 分析师来说,最有用的技能是与 SQL 数据库互动。

我们已经讨论了 OpenAI 功能,并了解了 LLM 如何使用工具与世界进行集成。在这篇文章中,我想专注于 LLM 代理,并详细讨论它们。我们将学习如何使用 LangChain 构建代理,并尝试不同类型的代理。

设置数据库

首先,让我们设置一个我们将要交互的数据库。我选择的是 ClickHouse。ClickHouse 是一个开源的列式 SQL 数据库管理系统,适用于在线分析处理(OLAP)。它是大数据和分析任务的良好选择。

如果你想了解更多关于 ClickHouse 的内容,请查看我的文章。不过,你可以使用任何数据库。你只需调整获取数据的函数代码即可。

安装 ClickHouse 只需一行代码。初始命令会执行 ClickHouse 团队提供的脚本,以下载适合你平台的二进制文件。然后,你需要启动一个服务器,就完成了。

curl https://clickhouse.com/ | sh # downloads appropriate binary file
./clickhouse server # starts clickhouse server

你可以通过 HTTP API 访问 ClickHouse。默认情况下,它监听 8123 端口。

CH_HOST = 'http://localhost:8123' # default address 

def get_clickhouse_data(query, host = CH_HOST, connection_timeout = 1500):
    r = requests.post(host, params = {'query': query}, 
      timeout = connection_timeout)

    return r.text

我通常检查 r.status_code = 200 以确保请求已成功完成,否则会引发错误。然而,我们将把此函数的结果传递给 LLM。因此,无论输出 DB 返回什么,都没关系,无论是否为错误,LLM 都能够妥善处理。

我为这个例子生成了合成数据。如果你想了解更多关于数据模拟的内容,可以在这里找到代码。我使用了保留曲线来为客户建模会话,考虑了账户创建以来的天数和每周的季节性。这可能现在有些复杂,因为我们还不太使用数据。但我希望在未来的原型中,我们能利用 LLM 从这些数据中获得一些见解。

我们只需要几个表来表示基本的电子商务产品数据模型。我们将处理用户列表(ecommerce.users)及其会话(ecommerce.sessions)。

让我们来看一下 ecommerce.sessions 表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

在这里,你可以看到我们为用户提供了哪些功能。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者截图

现在,我们有了可以使用的数据,准备继续深入讨论 LLM 代理的细节。

代理概述

LLM 代理的核心思想是将 LLM 作为推理引擎来定义要执行的操作集合。在经典方法中,我们硬编码了一系列操作,但使用代理时,我们给模型提供工具和任务,让它决定如何实现这些任务。

关于 LLM 代理的最基础论文之一是“ReAct: Synergizing Reasoning and Acting in Language Models”,作者是 Shunyu Yao 等。ReAct(Reasoning + Acting)方法建议结合:

  • 帮助制定计划并在出现例外情况时更新计划的推理,

  • 允许模型利用外部工具或从外部来源获取数据的操作。

这种方法在不同任务上的表现更好。下面是来自论文的一个示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自Yao 等人的论文的示例

实际上,这就是人类智能的工作方式:我们将内在的推理与任务导向的行动结合起来。假设你需要做晚餐。你将使用推理来制定计划(“客人将在 30 分钟内到达,我只有时间做意大利面”),调整计划(“本变成了素食者,我应该为他订购一些食物”)或决定委派任务(相当于外部工具,“意大利面没了,我需要问我的伴侣去买”)。同时,你会使用行动来使用一些工具(询问伴侣帮助或使用搅拌机)或获取一些信息(查找互联网了解煮意大利面所需的时间,以使其达到“ al dente”)。所以,使用类似的方法对 LLM 是合理的,因为它对人类有效(人类无疑是 AGI)。

现在,自 ReAct 以来,LLM 代理有许多不同的方法。它们在用于设置模型推理的提示、如何定义工具、输出格式、处理中间步骤的记忆等方面有所不同。

一些最受欢迎的方法包括:

  • OpenAI 函数,

  • AutoGPT,

  • BabyAGI,

  • 计划并执行的代理。

我们稍后会使用这些方法来完成我们的任务,并查看它们的工作原理及其差异。

从零开始构建代理

让我们开始构建一个代理。我们将从零开始,以了解其内部工作原理。然后,如果你不需要任何自定义,我们将使用 LangChain 的工具来加快原型制作。

LLM 代理的核心组件包括:

  • 引导模型推理的提示。

  • 模型可以使用的工具。

  • 记忆 — 一个将先前迭代传递给模型的机制。

对于 LLM 代理的第一个版本,我们将使用 OpenAI 函数作为构建代理的框架。

定义工具

让我们开始定义我们机器人的工具。让我们考虑一下我们的 LLM 驱动分析师可能需要哪些信息来回答问题:

  • 表格中的列表 — 我们可以将其放入系统提示中,以便模型了解我们拥有的数据,并且每次都不需要执行工具。

  • 表格的列列表,以便模型可以理解数据模式,

  • 表格中列的前几个值,以便模型可以查找过滤器的值,

  • SQL 查询执行的结果,以便获取实际数据。

要在 LangChain 中定义工具,我们需要对函数使用 @tool 装饰器。我们将使用 Pydantic 来指定每个函数的参数模式,以便模型知道传递给函数的内容。

我们在 上一篇文章 中详细讨论了工具和 OpenAI 功能。所以如果你需要复习这个主题,请不要犹豫去阅读它。

下面的代码定义了三个工具:execute_sqlget_table_columnsget_table_column_distr

from langchain.agents import tool
from pydantic import BaseModel, Field
from typing import Optional

class SQLQuery(BaseModel):
    query: str = Field(description="SQL query to execute")

@tool(args_schema = SQLQuery)
def execute_sql(query: str) -> str:
    """Returns the result of SQL query execution"""
    return get_clickhouse_data(query)

class SQLTable(BaseModel):
    database: str = Field(description="Database name")
    table: str = Field(description="Table name")

@tool(args_schema = SQLTable)
def get_table_columns(database: str, table: str) -> str:
    """Returns list of table column names and types in JSON"""

    q = '''
    select name, type
    from system.columns 
    where database = '{database}'
        and table = '{table}'
    format TabSeparatedWithNames
    '''.format(database = database, table = table)

    return str(get_clickhouse_df(q).to_dict('records'))

class SQLTableColumn(BaseModel):
    database: str = Field(description="Database name")
    table: str = Field(description="Table name")
    column: str = Field(description="Column name")
    n: Optional[int] = Field(description="Number of rows, default limit 10")

@tool(args_schema = SQLTableColumn)
def get_table_column_distr(database: str, table: str, column: str, n:int = 10) -> str:
    """Returns top n values for the column in JSON"""

    q = '''
    select {column}, count(1) as count
    from {database}.{table} 
    group by 1
    order by 2 desc 
    limit {n}
    format TabSeparatedWithNames
    '''.format(database = database, table = table, column = column, n = n)

    return str(list(get_clickhouse_df(q)[column].values))

值得注意的是,上面的代码使用了 Pydantic v1。在 2023 年 6 月,Pydantic 发布了 v2,这与 v1 不兼容。所以如果你看到验证错误,请检查你的版本。你可以在 文档 中找到有关 Pydantic 兼容性的更多详细信息。

我们将使用 OpenAI 的功能,需要转换我们的工具。此外,我已经将工具包保存在一个字典中。在执行工具以获取观察结果时,这将非常方便。

from langchain.tools.render import format_tool_to_openai_function

# converting tools into OpenAI functions
sql_functions = list(map(format_tool_to_openai_function, 
    [execute_sql, get_table_columns, get_table_column_distr]))

# saving tools into a dictionary for the future
sql_tools = {
    'execute_sql': execute_sql,
    'get_table_columns': get_table_columns,
    'get_table_column_distr': get_table_column_distr
}

定义一个链

我们已经为模型创建了工具。现在,我们需要定义代理链。我们将使用最新的 GPT 4 Turbo,它也经过了微调以便与这些功能一起使用。让我们初始化一个聊天模型。

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview')\
  .bind(functions = sql_functions)

下一步是定义一个由系统消息和用户问题组成的提示。我们还需要一个 MessagesPlaceholder 来设置模型将要处理的观察列表的位置。

from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

system_message = '''
You are working as a product analyst for the e-commerce company. 
Your work is very important, since your product team makes decisions based on the data you provide. So, you are extremely accurate with the numbers you provided. 
If you're not sure about the details of the request, you don't provide the answer and ask follow-up questions to have a clear understanding.
You are very helpful and try your best to answer the questions.

All the data is stored in SQL Database. Here is the list of tables (in the format <database>.<table>) with descriptions:
- ecommerce.users - information about the customers, one row - one customer
- ecommerce.sessions - information about the sessions customers made on our web site, one row - one session
'''

analyst_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_message),
        ("user", "{question}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

正如我们讨论过的,我已经将数据库中的表列表添加到提示中,以便模型对我们的数据有至少一些了解。

我们拥有所有构建块,并准备好设置代理链。输入参数包括用户消息和中间步骤(之前的消息、函数调用和观察)。我们使用 format_to_openai_function_messages 将输入参数传递到提示中,以转换成期望的格式。然后,我们将所有内容传递给 LLM,最后使用输出解析器 OpenAIFunctionsAgentOutputParser 以方便操作。

 from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser

analyst_agent = (
    {
        "question": lambda x: x["question"],
        "agent_scratchpad": lambda x: format_to_openai_function_messages(x["intermediate_steps"]),
    }
    | analyst_prompt
    | llm
    | OpenAIFunctionsAgentOutputParser()
)

我们已经定义了我们的主要代理链。让我们尝试调用它。我传递了一个空列表,因为开始时没有中间步骤。

analyst_agent.invoke({"question": "How many active customers from the United Kingdom do we have?", 
    "intermediate_steps": []})

# AgentActionMessageLog(
#    tool='execute_sql', 
#    tool_input={'query': "SELECT COUNT(DISTINCT user_id) AS active_customers_uk FROM ecommerce.sessions WHERE country = 'United Kingdom' AND active = TRUE"}, 
#    log='\nInvoking: `execute_sql` with `{\'query\': "SELECT COUNT(DISTINCT user_id) AS active_customers_uk FROM ecommerce.sessions WHERE country = \'United Kingdom\' AND active = TRUE"}`\n\n\n', 
#    message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{"query":"SELECT COUNT(DISTINCT user_id) AS active_customers_uk FROM ecommerce.sessions WHERE country = \'United Kingdom\' AND active = TRUE"}', 'name': 'execute_sql'}})]
# )

我们得到了一个 AgentActionMessageLog 对象,这意味着模型想要调用 execute_sql 函数。当模型准备好将最终答案返回给用户时,它会返回 AgentFinish 对象。

如果我们查看 tool_input,可以看到模型想要执行以下查询。

SELECT COUNT(DISTINCT user_id) AS active_customers_uk 
FROM ecommerce.sessions 
WHERE country = 'United Kingdom' AND active = TRUE

查询看起来很好,但使用了错误的列名:active 而不是 is_active。有趣的是,LLM 是否能够从这个错误中恢复并返回结果。

我们可以一步一步地手动执行这些步骤,但自动化会更方便。

  • 如果返回 AgentActionMessageLog 对象,我们需要调用一个工具,将观察结果添加到 agent_scratchpad 中,并再次调用链。

  • 如果我们得到了 AgentFinish 对象,我们可以终止执行,因为我们已经得到了最终答案。

我还会在十次迭代后添加一个断点,以避免潜在的无限循环。

from langchain_core.agents import AgentFinish

# setting initial parameters
question = "How many active customers from the United Kingdom do we have?"
intermediate_steps = []
num_iters = 0

while True:
    # breaking if there were more than 10 iterations
    if num_iters >= 10:  
        break

    # invoking the agent chain
    output = analyst_agent.invoke(
        {
            "question": question,
            "intermediate_steps": intermediate_steps,
        }
    )
    num_iters += 1

    # returning the final result if we got the AgentFinish object
    if isinstance(output, AgentFinish):
        model_output = output.return_values["output"]
        break
    # calling tool and adding observation to the scratchpad otherwise
    else:
        print(f'Executing tool: {output.tool}, arguments: {output.tool_input}')
        observation = sql_toolsoutput.tool
        print(f'Observation: {observation}')
        print()
        intermediate_steps.append((output, observation))

print('Model output:', model_output)

我在输出中添加了一些工具使用的日志,以查看执行情况。此外,你可以随时使用 LangChain 调试模式查看所有调用。

执行结果如下。

Executing tool: execute_sql, arguments: {'query': "SELECT COUNT(*) AS active_customers_uk FROM ecommerce.users WHERE country = 'United Kingdom' AND active = TRUE"}
Observation: Code: 47\. DB::Exception: Missing columns: 'active' 
while processing query: 'SELECT count() AS active_customers_uk 
FROM ecommerce.users WHERE (country = 'United Kingdom') AND (active = true)', 
required columns: 'country' 'active', maybe you meant: 'country'. 
(UNKNOWN_IDENTIFIER) (version 23.12.1.414 (official build))

Executing tool: get_table_columns, arguments: {'database': 'ecommerce', 'table': 'users'}
Observation: [{'name': 'user_id', 'type': 'UInt64'}, {'name': 'country', 'type': 'String'}, 
{'name': 'is_active', 'type': 'UInt8'}, {'name': 'age', 'type': 'UInt64'}]

Executing tool: execute_sql, arguments: {'query': "SELECT COUNT(*) AS active_customers_uk FROM ecommerce.users WHERE country = 'United Kingdom' AND is_active = 1"}
Observation: 111469

Model output: We have 111,469 active customers from the United Kingdom.

注意:不能保证代理不会对你的数据库执行 DML 操作。因此,如果在生产环境中使用,请确保 LLM 要么没有更改数据的权限,要么你的工具实现不允许这样做。

所以,模型尝试执行 SQL,但收到错误提示,没有 active 列。然后,它决定查看表的 schema,随后相应地修正了查询,并获得了结果。

这是相当不错的性能。我自己也是如此。我通常会先尝试回忆或猜测列名,只有在第一次尝试失败时才会查看文档。

但在大多数情况下,我们不需要自己编写执行代码。我们可以使用 LangChain 的 AgentExecutor 类。查看文档以了解该类的所有可能的 参数

你只有在想要自定义某些内容时,才需要编写自己的执行器。例如,添加一些条件以终止执行或逻辑来使用工具。

你可以在下面找到使用 AgentExecutor 的相同代码。

from langchain.agents import AgentExecutor

analyst_agent_executor = AgentExecutor(
    agent=analyst_agent, 
    tools=[execute_sql, get_table_columns, get_table_column_distr], 
    verbose=True,
    max_iterations=10, # early stopping criteria
    early_stopping_method='generate', 
    # to ask model to generate the final answer after stopping
)

analyst_agent_executor.invoke(
  {"question": "How many active customers from the United Kingdom do we have?"}
)

结果是我们得到了一个易于追踪的输出,且结果相同。你可以注意到 LangChain 对代理输出的格式化非常方便。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

我们从头开始构建了 LLM 代理。现在,我们了解它是如何工作的,并知道如何自定义它。然而,LangChain 提供了一个高级函数 initialize_agent,可以仅通过一次调用完成。你可以在 文档 中找到所有详细信息。

from langchain.agents import AgentType, Tool, initialize_agent
from langchain.schema import SystemMessage

agent_kwargs = {
    "system_message": SystemMessage(content=system_message)
}

analyst_agent_openai = initialize_agent(
    llm = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview'),
    agent = AgentType.OPENAI_FUNCTIONS, 
    tools = [execute_sql, get_table_columns, get_table_column_distr], 
    agent_kwargs = agent_kwargs,
    verbose = True,
    max_iterations = 10,
    early_stopping_method = 'generate'
) 

请注意,我们传递了 ChatOpenAI 模型,但未绑定任何函数。我们单独传递了工具,因此不需要将它们链接到模型。

不同的代理类型

我们从头开始构建了一个基于 OpenAI 函数的 LLM 代理。然而,还有很多其他方法。因此,我们也来尝试一下这些方法。

我们将查看 ReAct 方法(来自我们之前讨论的论文中的初始版本)以及 LangChain 提供的几种实验方法:Plan-and-execute、BabyAGI 和 AutoGPT。

ReAct 代理

让我们从查看 ReAct 代理开始。通过当前的实现,我们可以轻松地更改代理类型,并尝试论文中描述的 ReAct 方法。

最通用的 ReAct 实现是零样本 ReAct。它不适用于我们,因为它只支持单一字符串作为输入。我们的工具需要多个参数,因此我们需要使用结构化输入 ReAct

我们可以利用使用模块化框架的优势:我们只需更改一个参数agent = AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,就可以了。

system_message = system_message + '''\nYou have access to the following tools:'''

agent_kwargs = {
    "prefix": system_message
}

analyst_agent_react = initialize_agent(
    llm = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview'),
    agent = AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, 
    tools = [execute_sql, get_table_columns, get_table_column_distr], 
    agent_kwargs = agent_kwargs,
    verbose = True,
    max_iterations = 10,
    early_stopping_method = 'generate'
)

你可能会想知道如何找到可以为代理指定的参数。不幸的是,这些没有文档说明,因此我们需要深入源码来理解它。让我们一步步来讨论。

  • 我们可以看到analyst_agent_reactAgentExecutor类的一个对象。

  • 这个类有一个代理字段。在我们的例子中,它是StructuredChatAgent类的一个对象。这个类依赖于指定的代理类型。

  • 让我们找到一个StructuredChatAgent类的实现并看看它是如何工作的。在这种情况下,LangChain 创建 了一个由前缀、工具描述、格式化的指令和后缀组成的提示。

  • 你可以在代码中找到你可以作为agent_kwargs传递的所有参数的完整列表。

  • 所以,我们可以从这里覆盖默认的PREFIX值,并将其作为agent_kwargs中的prefix传递。如果你感兴趣,你可以在这里阅读默认的 ReAct 提示,并考虑如何根据你的任务对其进行调整。

如果你感兴趣,你可以使用以下调用查看最终提示。

for message in analyst_agent_react.agent.llm_chain.prompt.messages:
    print(message.prompt.template)

让我们调用我们的方法,看看结果。

analyst_agent_react.run("How many active customers from the United Kingdom do we have?")

我们可以注意到模型采用了稍微不同的推理框架。模型开始时写下思考(推理),然后转到动作(函数调用)和观察(函数调用的结果)。然后,迭代重复。最后,模型返回动作 = 最终答案

> Entering new AgentExecutor chain...
Thought: To answer this question, I need to define what is meant by 
"active customers" and then query the database for users from 
the United Kingdom who meet this criteria. I will first need to know 
the structure of the `ecommerce.users` table to understand what columns 
are available that could help identify active customers and their location.

Action:

{

“动作”: “获取表列”,

“动作输入”: {

"数据库": "电子商务",

"表": "用户"

}

}


Observation: [{'name': 'user_id', 'type': 'UInt64'}, {'name': 'country', 'type': 'String'}, {'name': 'is_active', 'type': 'UInt8'}, {'name': 'age', 'type': 'UInt64'}]
Thought:The `ecommerce.users` table contains a column named `is_active` 
which likely indicates whether a customer is active or not, and a `country` 
column which can be used to filter users by their location. Since we 
are interested in active customers from the United Kingdom, we can use 
these two columns to query the database.

Action:

{

“动作”: “执行 SQL”,

“动作输入”: {

"查询": "SELECT COUNT(*) AS active_customers_uk FROM ecommerce.users WHERE country = 'United Kingdom' AND is_active = 1"

}

}

Observation: 111469

Thought:I have the information needed to respond to the user's query.
Action:

{

“动作”: “最终答案”,

“动作输入”: “来自英国的活跃客户有 111,469 个。”

}


> Finished chain.
'There are 111,469 active customers from the United Kingdom.'

尽管模型采用了不同的路径(从理解表格模式开始,然后执行 SQL),但它得出了相同的结果。

现在,让我们继续实验方法。在 LangChain 中,有实验性的代理类型。它们尚不建议用于生产。然而,尝试使用它们并查看它们的工作效果会很有趣。

计划与执行代理

下面的代码基于来自 LangChain 厨房书的 示例

该代理遵循“计划与执行”方法,与我们之前查看的“行动”代理不同。这一方法受 BabyAGI 框架和 论文《计划与解决提示》 的启发。

这种方法的特点是代理首先尝试规划下一步,然后执行它们。

这种方法有两个组成部分:

  • 规划器 — 一个常规的大型语言模型,其主要目标是进行推理和规划,

  • 执行者 — 行动代理,是一个拥有可以用来执行的工具集合的 LLM。

这种方法的优势在于你有一个分离:一个模型专注于规划(推理),另一个模型专注于执行(行动)。它更具模块化,可能你可以使用针对特定任务微调的小型且便宜的模型。然而,这种方法也会产生更多的 LLM 调用,因此如果我们使用 ChatGPT,会更昂贵。

让我们初始化规划器和执行者。

from langchain_experimental.plan_and_execute import PlanAndExecute, load_agent_executor, load_chat_planner

model = ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview')
planner = load_chat_planner(model)
executor = load_agent_executor(model, 
    tools = [execute_sql, get_table_columns, get_table_column_distr], 
    verbose=True)

目前没有办法为执行者指定自定义提示,因为你不能将其传递给 该函数。然而,我们可以破解提示,将我们的初始系统消息添加到默认提示的开头,以提供一些关于任务的背景。

免责声明:覆盖对象字段是一种不好的做法,因为我们可能会绕过一些提示验证。我们现在这样做只是为了实验这种方法。这样的解决方案不适用于生产。

executor.chain.agent.llm_chain.prompt.messages[0].prompt.template = system_message + '\n' + \
    executor.chain.agent.llm_chain.prompt.messages[0].prompt.template

现在,是时候定义一个代理并执行我们之前询问的相同查询了。

analyst_agent_plan_and_execute = PlanAndExecute(
    planner=planner, 
    executor=executor
)
analyst_agent_plan_and_execute.run("How many active customers from the United Kingdom do we have?")

调用返回了一个错误:RateLimitError: Error code: 429 — {'error': {'message': '请求超出 gpt-4–1106-preview 组织内基于令牌使用的每分钟限制:限制 150000,要求 235832。', 'type': 'tokens_usage_based', 'param': None, 'code': 'rate_limit_exceeded'}}。看起来模型尝试向 OpenAI 发送了过多的令牌。

让我们通过查看模型的输出(可以在下面找到)来尝试理解发生了什么:

  • 首先,模型决定查看 ecommerce.usersecommerce.sessions 列,以确定“活跃”客户的标准。

  • 它意识到需要在 ecommerce.users 表中使用 is_active。然而,模型决定还应该使用会话数据来定义客户的活动。

  • 然后,模型深入挖掘试图定义ecommerce.sessions中近期活动的标准。它查看了action_datesession_durationrevenue的分布情况。

  • 最终,它将活跃客户定义为在过去 30 天内有过会话的客户,并且会话时长和收入高于某些阈值,而忽略了可以直接使用is_active。模型计划查看数据以定义这些阈值。

  • 最终错误是由于尝试加载所有客户的 user_ids,因为 ChatGPT 的令牌太多。

尽管有很多文本,我鼓励你阅读一下,看看模型是如何思考问题的。

> Entering new AgentExecutor chain...
Action:

{

“action”: “Final Answer”,

“action_input”: “有关客户的数据存储在 SQL 数据库中,特别是在名为’ecommerce.users’的表中。”

}


> Finished chain.

> Entering new AgentExecutor chain...
Thought: To determine the criteria for an "active" customer, I need 
to understand what data is available in the 'ecommerce.users' and 
'ecommerce.sessions' tables. This will help me identify which columns 
could be used to define an "active" customer based on recent purchases, 
account activity, or other engagement metrics.

Action:

{

“action”: “get_table_columns”,

“action_input”: {

"database": "ecommerce",

"table": "users"

}

}


Observation: [{'name': 'user_id', 'type': 'UInt64'}, {'name': 'country', 'type': 'String'}, {'name': 'is_active', 'type': 'UInt8'}, {'name': 'age', 'type': 'UInt64'}]
Thought:The 'ecommerce.users' table contains columns for 'user_id', 'country',
 'is_active', and 'age'. The 'is_active' column seems to directly indicate 
whether a user is considered active or not. However, to understand 
the criteria behind this designation, we need to see if there are 
any engagement metrics in the 'ecommerce.sessions' table that could 
correlate with the 'is_active' status.

Action:

{

“action”: “get_table_columns”,

“action_input”: {

"database": "ecommerce",

"table": "sessions"

}

}


Observation: [{'name': 'user_id', 'type': 'UInt64'}, {'name': 'session_id', 'type': 'UInt64'}, {'name': 'action_date', 'type': 'Date'}, {'name': 'session_duration', 'type': 'UInt64'}, {'name': 'os', 'type': 'String'}, {'name': 'browser', 'type': 'String'}, {'name': 'is_fraud', 'type': 'UInt8'}, {'name': 'revenue', 'type': 'Float32'}]
Thought:The 'ecommerce.sessions' table includes columns such as 'user_id', 
'session_id', 'action_date', 'session_duration', 'os', 'browser', 'is_fraud', 
and 'revenue'. The 'action_date' could be used to determine recent activity, 
and 'session_duration' might indicate engagement level. 'Revenue' could 
be used to identify recent purchases. To define an "active" customer, 
we could consider users with recent 'action_date', longer 'session_duration', 
and positive 'revenue'. However, to set specific criteria, we might need 
to understand the distribution of these values, such as 
the most recent 'action_date' or average 'session_duration'. 
Let's first check the distribution of 'action_date' to see how recent 
the sessions are.

Action:

{

“action”: “get_table_column_distr”,

“action_input”: {

"database": "ecommerce",

"table": "sessions",

"column": "action_date",

"n": 10

}

}

Observation: ['2023-12-25', '2023-12-26', '2023-12-27', '2023-12-18', '2023-12-28', '2023-12-21', '2023-12-20', '2023-12-04', '2023-12-19', '2023-12-14']
Thought:The distribution of 'action_date' shows recent dates, with 
the latest being '2023-12-28'. This suggests that the data includes 
very recent sessions. To define an "active" customer, we could consider 
a timeframe such as the last 30 days. However, before finalizing 
this criterion, it would be beneficial to also understand 
the distribution of 'session_duration' and 'revenue' to see if there 
are common thresholds that could further define an "active" customer. 
Let's check the distribution of 'session_duration' next.

Action:

{

“action”: “get_table_column_distr”,

“action_input”: {

"database": "ecommerce",

"table": "sessions",

"column": "session_duration",

"n": 10

}

}

Observation: [772, 860, 887, 678, 405, 671, 670, 707, 982, 907]
Thought:The distribution of 'session_duration' shows a range of values, 
with durations varying from 405 to 982 seconds. These values could 
be used to set a threshold for engagement level, but without context, 
it's difficult to determine what would be considered a high or 
low session duration. To get a better understanding, we should look 
at the distribution of 'revenue' to see if there's a common revenue 
threshold that could be used in conjunction with 'action_date' and 
'session_duration' to define an "active" customer. Let's check 
the distribution of 'revenue' next.

Action:

{

“action”: “get_table_column_distr”,

“action_input”: {

"database": "ecommerce",

"table": "sessions",

"column": "revenue",

"n": 10

}

}


Observation: [0.0, 1556.9, 7803.2, 4936.5, 619.6, 9947.7, 646.2, 3251.1, 2860.9, 4376.5]
Thought:The distribution of 'revenue' shows a range of values including zero, 
which could indicate sessions without purchases, and values up to 9947.7,
 which suggests significant purchases. To define an "active" customer, 
we could consider those with non-zero revenue within the last 30 days 
and with a session duration above a certain threshold. However, to set 
a precise threshold for 'session_duration' and 'revenue', we would need 
to analyze the data further, possibly calculating averages or percentiles. 
For now, we can propose a preliminary definition of an "active" customer
 as one who has had a session within the last 30 days, with a session duration 
and revenue above certain thresholds to be determined.

Action:

{

“action”: “Final Answer”,

“action_input”: “根据’ecommerce.users’和’ecommerce.sessions’表中可用的数据,可以初步定义一个’活跃’客户为在过去 30 天内有过会话的客户,其会话时长和收入高于某些阈值。'users’表中的’is_active’列可能已经反映了这种定义或类似定义,但需要进一步分析以设定’session_duration’和’revenue’的具体阈值。这些阈值可以通过计算数据分布的平均值或百分位数来确定。”

}


> Finished chain.

> Entering new AgentExecutor chain...
Action:

{

“action”: “get_table_columns”,

“action_input”: {

"database": "ecommerce",

"table": "users"

}

}


Observation: [{'name': 'user_id', 'type': 'UInt64'}, {'name': 'country', 'type': 'String'}, {'name': 'is_active', 'type': 'UInt8'}, {'name': 'age', 'type': 'UInt64'}]
Thought:The 'ecommerce.users' table contains a 'country' column which 
can be used to filter the customer records based on the location 
being the United Kingdom. I will write and execute an SQL query 
to retrieve the user IDs of customers located in the United Kingdom.

Action:

{

“action”: “execute_sql”,

“action_input”: {

"query": "SELECT user_id FROM ecommerce.users WHERE country = 'United Kingdom'"

}

}


Observation: 
1000001
1000011
1000021
1000029
1000044
... <many more lines...>

这是一个很好的例子,说明代理在问题上过于复杂化并进入了过多细节。人类分析师也会不时犯这样的错误。因此,看到 LLM 行为中的类似模式是很有趣的。

如果我们反思一下如何潜在地解决这个问题,有几种方法:

  • 首先,我们可以防止在尝试从数据库获取过多数据时出现问题,如果execute_sql函数的输出超过 1K 行,则返回错误。

  • 另一个我会考虑的是允许 LLM 提出后续问题,并指示它不要做出假设。

让我们继续研究启发当前方法的 BabyAGI 方法。

带工具的 BabyAGI 代理

下面的代码基于 示例 来自 LangChain 的食谱。

与之前的方法类似,我们的另一个实验方法 BabyAGI 尝试先计划然后执行。

这种方法使用检索,因此我们需要设置向量存储和嵌入模型。我使用开源且轻量级的 Chroma 进行存储和 OpenAI 嵌入。

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma

embedding = OpenAIEmbeddings()
persist_directory = 'vector_store'

vectordb = Chroma(
    persist_directory=persist_directory,
    embedding_function=embedding
)

检索允许模型长期存储所有结果,并提取和传递仅最相关的结果。如果你想了解更多关于检索的内容,请阅读 我关于 RAG 的文章(检索增强生成)。

首先,我们将创建一个待办事项链,稍后将用作我们执行器的工具。

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

todo_prompt_message = '''
You are a planner who is an expert at coming up with a todo list for 
a given objective. Come up with a todo list for this objective: {objective}
'''

todo_prompt = PromptTemplate.from_template(todo_prompt_message)
todo_chain = LLMChain(llm=OpenAI(temperature=0.1, 
    model = 'gpt-4-1106-preview'), prompt=todo_prompt)

然后,我们将创建一个指定工具和提示的代理。

from langchain.agents import AgentExecutor, Tool, ZeroShotAgent
from langchain.prompts import PromptTemplate

tools = [
    execute_sql, 
    get_table_columns, 
    get_table_column_distr,
    Tool(
        name="TODO",
        func=todo_chain.run,
        description="useful for when you need to come up with todo lists. Input: an objective to create a todo list for. Output: a todo list for that objective. Please be very clear what the objective is!",
    )
]

prefix = """
You are an AI who performs one task based on the following objective: {objective}. Take into account these previously completed tasks: {context}.

You are asked questions related to analytics for e-commerce product.
Your work is very important, since your product team makes decisions based on the data you provide. So, you are extremely accurate with the numbers you provided. 
If you're not sure about the details of the request, you don't provide the answer and ask follow-up questions to have a clear understanding.
You are very helpful and try your best to answer the questions.

All the data is stored in SQL Database. Here is the list of tables (in the format <database>.<table>) with descriptions:
- ecommerce.users - information about the customers, one row - one customer
- ecommerce.sessions - information about the sessions customers made on our web site, one row - one session
"""

suffix = """Question: {task}
{agent_scratchpad}"""

prompt = ZeroShotAgent.create_prompt(
    tools,
    prefix=prefix,
    suffix=suffix,
    input_variables=["objective", "task", "context", "agent_scratchpad"],
)

llm = OpenAI(temperature=0.1)
llm_chain = LLMChain(llm=llm, prompt=prompt)
tool_names = [tool.name for tool in tools]
analyst_agent_babyagi = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)
analyst_agent_babyagi_executor = AgentExecutor.from_agent_and_tools(
    agent=analyst_agent_babyagi, tools=tools, verbose=True
)

最后一步是定义 BabyAGI 执行器并运行它。

from langchain_experimental.autonomous_agents import BabyAGI
baby_agi = BabyAGI.from_llm(
    llm=llm,
    vectorstore=vectordb,
    task_execution_chain=analyst_agent_babyagi_executor,
    verbose=True,
    max_iterations=10
)
baby_agi("Find, how many active customers from the United Kingdom we have.")

再次,模型未能返回结果,因为它无法遵循工具的输入模式。

而且,令人惊讶的是,模型决定不使用待办事项功能来创建待办列表,而是直接跳入 SQL 查询。然而,第一个查询不正确。模型尝试恢复并调用get_table_columns函数以获取列名,但未能遵循模式。

让我们查看日志。

*****TASK LIST*****

1: Make a todo list

*****NEXT TASK*****

1: Make a todo list

> Entering new AgentExecutor chain...
Thought: I need to find out how many active customers from the United Kingdom 
we have
Action: execute_sql
Action Input: SELECT COUNT(*) FROM ecommerce.users WHERE country = 'UK' AND active = 1
Observation: Code: 47\. DB::Exception: Missing columns: 'active' while processing query: 
'SELECT count() FROM ecommerce.users WHERE (country = 'UK') AND (active = 1)', 
required columns: 'country' 'active', maybe you meant: 'country'. 
(UNKNOWN_IDENTIFIER) (version 23.12.1.414 (official build))

Thought: I need to get the columns of the ecommerce.users table
Action: get_table_columns
Action Input: ecommerce.users

所以,我们看到另一个问题,这在未使用 OpenAI 函数的代理中非常常见——它们无法遵循结构。

带工具的 AutoGPT 代理

下面的代码基于 示例 来自 LangChain 的食谱。

让我们看看另一种实验方法——使用 LangChain 框架实现的 AutoGPT

再次,我们需要为中间步骤设置向量存储。

embedding = OpenAIEmbeddings()
from langchain.vectorstores import Chroma
persist_directory = 'autogpt'

vectordb = Chroma(
    persist_directory=persist_directory,
    embedding_function=embedding
)

在这种情况下,我们再次无法向模型指定任何提示。让我们尝试在没有任何具体指导的情况下使用它。但我们将添加 get_tables 工具,以便模型可以查看所有可用的表。我希望这能帮助模型编写正确的 SQL 查询。

@tool()
def get_tables() -> str:
    """Returns list of tables in the format <database>.<table>"""

    return ['ecommerce.users', 'ecommerce.sessions']

让我们创建一个 AutoGPT 代理。这只需一个函数调用即可完成。然后,我们执行它,看看它是如何工作的。

 from langchain_experimental.autonomous_agents import AutoGPT

analyst_agent_autogpt = AutoGPT.from_llm_and_tools(
    ai_name="Harry",
    ai_role="Assistant",
    tools= [execute_sql, get_table_columns, 
        get_table_column_distr, get_tables],
    llm=ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview'),
    memory=vectordb.as_retriever(),
)

analyst_agent_autogpt.chain.verbose = True

analyst_agent_autogpt.run(["Find how many active customers from the United Kingdom we have."])

模型能够给出正确的答案:“来自英国的活跃客户数量是 111,469。”

阅读提示是有趣的,因为我们使用了默认提示。你可以通过 analyst_agent_autogpt.chain.prompt 访问它。

System: You are Harry, Assistant
Your decisions must always be made independently without seeking user 
assistance.
Play to your strengths as an LLM and pursue simple strategies with 
no legal complications.
If you have completed all your tasks, make sure to use the "finish" command.

GOALS:

1\. Find how many active customers from the United Kingdom we have.

Constraints:
1\. ~4000 word limit for short term memory. Your short term memory is short, 
so immediately save important information to files.
2\. If you are unsure how you previously did something or want to recall 
past events, thinking about similar events will help you remember.
3\. No user assistance
4\. Exclusively use the commands listed in double quotes e.g. "command name"

Commands:
1\. execute_sql: execute_sql(query: str) -> str - Returns the result of SQL query execution, args json schema: {"query": {"title": "Query", "description": "SQL query to execute", "type": "string"}}
2\. get_table_columns: get_table_columns(database: str, table: str) -> str - Returns list of table column names and types in JSON, args json schema: {"database": {"title": "Database", "description": "Database name", "type": "string"}, "table": {"title": "Table", "description": "Table name", "type": "string"}}
3\. get_table_column_distr: get_table_column_distr(database: str, table: str, column: str, n: int = 10) -> str - Returns top n values for the column in JSON, args json schema: {"database": {"title": "Database", "description": "Database name", "type": "string"}, "table": {"title": "Table", "description": "Table name", "type": "string"}, "column": {"title": "Column", "description": "Column name", "type": "string"}, "n": {"title": "N", "description": "Number of rows, default limit 10", "type": "integer"}}
4\. get_tables: get_tables() -> str - Returns list of tables in the format <database>.<table>, args json schema: {}
5\. finish: use this to signal that you have finished all your objectives, args: "response": "final response to let people know you have finished your objectives"

Resources:
1\. Internet access for searches and information gathering.
2\. Long Term memory management.
3\. GPT-3.5 powered Agents for delegation of simple tasks.
4\. File output.

Performance Evaluation:
1\. Continuously review and analyze your actions to ensure you are 
performing to the best of your abilities.
2\. Constructively self-criticize your big-picture behavior constantly.
3\. Reflect on past decisions and strategies to refine your approach.
4\. Every command has a cost, so be smart and efficient. Aim to complete 
tasks in the least number of steps.

You should only respond in JSON format as described below 
Response Format: 
{
    "thoughts": {
        "text": "thought",
        "reasoning": "reasoning",
        "plan": "- short bulleted\n- list that conveys\n- long-term plan",
        "criticism": "constructive self-criticism",
        "speak": "thoughts summary to say to user"
    },
    "command": {
        "name": "command name",
        "args": {
            "arg name": "value"
        }
    }
} 
Ensure the response can be parsed by Python json.loads
System: The current time and date is Sat Dec 16 18:39:49 2023
System: This reminds you of these events from your past:
[]

所以,我们可以看到这个模型通过向量存储具有长期记忆。此外,它还具备全面的推理,包括思考、理由、计划、批评和总结。

让我们看看模型的推理(我只过滤了模型的回答)。所以,它遵循了与之前成功的代理相同的方法。

{
    "thoughts": {
        "text": "To find the number of active customers from the United Kingdom, I need to identify the relevant database and table that contains customer information. Once I have that, I can look for columns that might indicate customer activity and country. The first step is to list all the available tables.",
        "reasoning": "Understanding the structure of the database is essential before I can execute a query to find the active customers. Listing tables is a logical first step to identify where customer data might be stored.",
        "plan": "- Use the get_tables command to list all available tables.\n- Identify the table that likely contains customer information.\n- Use get_table_columns to understand the structure of the customer table.\n- Execute an SQL query to count active UK customers.",
        "criticism": "I need to ensure that I'm efficient with the commands I use, aiming to gather the necessary information in as few steps as possible.",
        "speak": "I am now going to list all the available tables to find where customer information is stored."
    },
    "command": {
        "name": "get_tables",
        "args": {}
    }
}

{
    "thoughts": {
        "text": "The 'ecommerce.users' table is likely to contain information about the customers. To find the number of active customers from the United Kingdom, I need to understand the structure of this table, specifically looking for columns that might indicate whether a customer is active and their country.",
        "reasoning": "Knowing the structure of the 'users' table will help me determine which columns to use in an SQL query to count active UK customers.",
        "plan": "- Use the get_table_columns command to get the structure of the 'ecommerce.users' table.\n- Identify columns that indicate customer activity and country.\n- Formulate an SQL query to count active UK customers based on the identified columns.",
        "criticism": "I must continue to be efficient with my commands, ensuring that each step brings me closer to the goal without unnecessary actions.",
        "speak": "I will now retrieve the structure of the 'ecommerce.users' table to identify the relevant columns for active customers and their country."
    },
    "command": {
        "name": "get_table_columns",
        "args": {
            "database": "ecommerce",
            "table": "users"
        }
    }
}

{
    "thoughts": {
        "text": "With the structure of the 'ecommerce.users' table known, I can see that there are columns for 'country' and 'is_active', which are necessary to identify active customers from the United Kingdom.",
        "reasoning": "The 'country' column will allow me to filter customers by their country, and the 'is_active' column will allow me to filter customers by their activity status. With this information, I can construct an SQL query to count the number of active customers from the UK.",
        "plan": "- Execute an SQL query using the 'execute_sql' command to count the number of active customers from the United Kingdom.\n- The query will count rows where 'country' is 'United Kingdom' and 'is_active' is true (1).",
        "criticism": "I need to ensure that the SQL query is correctly formulated to retrieve the exact information needed without any errors.",
        "speak": "I am now ready to execute an SQL query to count the number of active customers from the United Kingdom."
    },
    "command": {
        "name": "execute_sql",
        "args": {
            "query": "SELECT COUNT(*) FROM ecommerce.users WHERE country = 'United Kingdom' AND is_active = 1"
        }
    }
}

{
    "thoughts": {
        "text": "I have successfully executed the SQL query and obtained the number of active customers from the United Kingdom, which is 111,469.",
        "reasoning": "The goal was to find the number of active customers from the UK, and the SQL query has provided this information.",
        "plan": "- Since the goal has been achieved, the next step is to use the 'finish' command to signal that I have completed all my objectives.",
        "criticism": "There is no need for further action or commands as the objective has been met.",
        "speak": "I have found the number of active customers from the United Kingdom and am ready to finish the task."
    },
    "command": {
        "name": "finish",
        "args": {
            "response": "The number of active customers from the United Kingdom is 111,469."
        }
    }
} 

总体来说,AutoGPT 看起来是一种可行的方法。然而,我仍然相信,目前构建代理的最可靠方法是通过 OpenAI 的函数。

我们需要从头开始构建一切吗?

我们在构建集成了 SQL 数据库的代理时度过了一段美好的学习时光。然而,我必须提到 LangChain 有自己实现的 SQL 代理——SQLDatabaseChain

这种方法使用 SQL Alchemy 与数据库进行交互。因此,我们需要安装clickhouse-sqlalchemy包以连接到 ClickHouse。

pip install clickhouse-sqlalchemy

我们可以建立与数据库的连接并初始化工具包。

uri = 'clickhouse+native://localhost/ecommerce'
db = SQLDatabase.from_uri(uri)
toolkit = SQLDatabaseToolkit(db=db, llm=OpenAI(temperature=0))

工具包是与某个主题相关的有用工具的集合。你可以在文档中找到许多示例。

我们可以看到工具包中拥有的工具列表。这里有用于进行 SQL 查询或获取与数据库相关信息的工具。

toolkit.get_tools()

然后,我们可以快速创建并运行一个基于 OpenAI 函数的代理。

agent_executor = create_sql_agent(
    llm=ChatOpenAI(temperature=0.1, model = 'gpt-4-1106-preview'),
    toolkit=toolkit,
    verbose=True,
    agent_type=AgentType.OPENAI_FUNCTIONS
)

agent_executor.run("How many active customers from the United Kingdom do we have?")

我们在这边没有多大麻烦就得到了正确的答案。

> Entering new AgentExecutor chain...

Invoking: `sql_db_list_tables` with ``

sessions, users
Invoking: `sql_db_schema` with `users`

CREATE TABLE users (
 user_id UInt64, 
 country String, 
 is_active UInt8, 
 age UInt64
) ENGINE = Log

/*
3 rows from users table:
user_id country is_active age
1000001 United Kingdom 0 70
1000002 France 1 87
1000003 France 1 88
*/
Invoking: `sql_db_query` with `SELECT COUNT(*) FROM users WHERE country = 'United Kingdom' AND is_active = 1`

[(111469,)]We have 111,469 active customers from the United Kingdom.

> Finished chain.
'We have 111,469 active customers from the United Kingdom.'

我们可以使用langchain.debug = True来查看使用了什么提示。

System: You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct clickhouse query 
to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, 
always limit your query to at most 10 results.
You can order the results by a relevant column to return the most interesting 
examples in the database.
Never query for all the columns from a specific table, only ask for 
the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the below tools. Only use the information returned 
by the below tools to construct your final answer.
You MUST double check your query before executing it. If you get 
an error while executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) 
to the database.

If the question does not seem related to the database, just return 
"I don't know" as the answer.

Human: How many active customers from the United Kingdom do we have? 
AI: I should look at the tables in the database to see what I can query.  
Then I should query the schema of the most relevant tables.

因此,我们有了一个相当方便且有效的 SQL 分析实现。如果你不需要任何自定义更改,你可以直接使用 LangChain 的实现。

此外,你可以稍作调整,例如,通过将提示传递给create_sql_agent函数(文档)。

摘要

今天,我们学习了如何创建不同类型的代理。我们实现了一个完全从零开始的 LLM 驱动代理,可以处理 SQL 数据库。然后,我们利用高层次的 LangChain 工具,通过几个函数调用实现了相同的结果。

所以,现在我们的 LLM 驱动分析师可以使用你的数据库中的数据并回答问题。这是一个重大改进。我们可以将 SQL 数据库代理作为工具添加到我们的 LLM 驱动分析师中。这将是我们的第一个技能。

代理现在可以回答与数据相关的问题并自行工作。然而,分析工作的基石是合作。因此,在接下来的文章中,我们将添加记忆功能和学习代理,以便提出后续问题。敬请关注!

非常感谢你阅读这篇文章。我希望它对你有所启发。如果你有任何后续问题或评论,请在评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值