容器中的⼤模型(一)| 三行命令,大模型让Excel直接回答问题

撰文:宋文欣,智领云科技联合创始人兼CTO

武汉大学计算机系本科及硕士,美国纽约州立大学石溪分校计算机专业博士。曾先后就职于Ask.com和EA(电子艺界)。在Ask.com期间,担任大数据部门技术负责人及工程经理,使用Hadoop集群处理实时搜索数据,形成全球规模领先的Search Ads Arbitrage用户;在EA期间,担任数字平台部门高级研发经理,从无到有组建EA数据平台团队,建设公司大数据平台,为EA全球工作室提供数据能力支持。

2016年回国联合创立智领云科技有限公司,组建智领云技术团队,开发了BDOS大数据平台操作系统。

01 简介

在快速发展的数据处理与分析领域中,大型语言模型(LLM)正引领潮流,展现出超越传统文本应用的突破性功能。其中一个值得关注且潜力巨大的应用领域是利用 LLMs 解读和推理表格数据。本文将深入探讨如何利用 LLMs对表格数据进行查询,这不仅是一项专业任务,更蕴含着巨大的潜力,它有望革新我们与结构化数据集交互的方式。

在我们的探索中,两项创新技术发挥着关键作用:LlamaIndex 和 LocalAI 。LlamaIndex 在我们的研究过程中扮演着核心角色,它遵循了前沿论文“Rethinking Tabular Data Understanding with Large Language Models” 及“Chain-of-Table: Evolving Tables in the Reasoning Chain for Table Understanding”的指导原则。LlamaIndex 巧妙地实现了这些论文中的策略,将理论转化为实践。与此相辅相成的是 LocalAI ,它提供了一个无缝衔接的本地环境,使得用户可以便捷地在本地启动 LLM 并与之互动。这种协同效应不仅让高级数据查询方法更加普及易用,也将 LLMs的可用性推向了新的高度。

本文将深入浅出地介绍 LLM如何用于查询表格数据。我们将展示如何使用 LocalAI 提供的 Docker 镜像来启动本地 LLM,并展示如何将其与 OpenAI 兼容的 API 服务进行对接。这不仅仅是理论探讨,我们将提供实战演练,全程指导您在 Docker 容器中搭建整套系统,确保其既适用于仅配备 CPU 的机器,也能适应拥有 GPU 设备的环境。

02 理论背景

表格数据领域,其结构化且复杂的特性,为 LLM 带来了独特的挑战和机遇。在本节中,我们将基于两篇论文的理论基础进行深入探讨,这为我们的实践探索奠定了基础。

Rethinking Tabular Data Understanding with Large Language Models

这篇论文是理解 LLM在解释表格数据领域的能力和局限的基础。它从以下三个核心角度开展论述:

1. 对结构扰动的鲁棒性:研究表明,当表格结构发生变化时,LLM 的性能显著下降。这一发现至关重要,它强调了需要一个即使面对表格格式变化,仍能保持准确性的鲁棒模型。

2. 文本推理与符号推理:将文本和符号推理进行比较分析,结果表明在处理表格数据时,文本推理略胜一筹。然而,两种方法的优势会根据具体任务的不同而有所变化,在应用这些推理方法时需要根据实际任务进行考量。

3. 通过推理路径提升性能:最重要的贡献可能是提出了融合多个推理路径的方法。通过融合文本和符号推理,并采用混合自洽机制,模型实现了最先进的性能。这种方法不仅提高了准确性,还为大型语言模型中更复杂的表格处理范式铺平了道路。

备注:结构扰动的鲁棒性是评价一个结构设计是否优良的重要指标。即一个结构在受到一些外部或内部的扰动时,能否保持其原有的功能或性能的能力。扰动可以是一些不确定的因素,如参数变化、环境变化、噪声干扰等。

Chain-of-Table: Evolving Tables in the Reasoning Chain for Table Understanding

这篇论文介绍了创新概念:链式表格( Chain-of-Table ),彻底改变了 LLM 与表格数据交互的方式:

1. 推理链中的表格数据:文中明确提出将表格数据纳入推理链中。这种方法与传统依赖上下文的方法截然不同,它提供了一种利用表格结构化特性的全新方式。

2. 迭代式表格演化:该框架引导 LLM 迭代地生成操作并更新表格,从而有效地创建了 “表格推理链”。这种动态演化使模型能够根据先前的结果规划后续操作,反映出更类似人类的推理过程。

3. 结构化中间结果的动态传递:这种方法的一个有趣方面是,通过不断演进的表格来承载和传递中间结果的结构化信息。这一特性不仅使得推理流程更为透明,同时也有助于提升预测的可靠性和准确性。

理论与实践结合

这些相关论文共同构建了一个坚实的理论框架,指导我们在查询表格数据中实际应用 大型语言模型(LLMs) 。它们揭示了处理表格数据的细微差别,强调了对模型良好的适应性、复杂的推理能力以及对表格结构化格式敏感的需求。随着我们探究的不断深入,这些理论洞见在后续部分将成为我们使用 LlamaIndex 和 LocalAI 进行实践演示的根基。

03 技术背景

LlamaIndex 及其 Llama Packs

LlamaIndex 是构建大型语言模型应用程序必不可少的多功能“数据框架”。它简化了从各种来源和格式获取数据的过程,包括 API、PDF、文档和 SQL。该框架擅长使用索引和图表结构化数据,确保与 LLM 无缝兼容。它的一项主要特征是拥有先进的检索和查询界面,允许用户向 LLM 输入提示词,并接收到富含上下文的响应。

Llama Packs 补充了 LlamaIndex,它作为社区驱动的中心,提供了一系列预打包模块,以加速 LLM 应用程序的开发。这些模块设计广泛适用于多种应用场景,从创建 Streamlit 应用程序到在简历中实现高级检索和结构化数据提取。Llama Packs 的一项关键特性是其赋予用户的灵活性,用户不仅可以即插即用地导入现成的模块,还可以根据具体需求和偏好定制自己的模块。

LocalAI 框架

LocalAI 的目标是在本地运行开源基础大模型服务,然后提供与 OpenAI 完全兼容的API,为那些寻求本地推理能力但是又想保持使用OpenAI灵活性的用户提供了一种独特的解决方案。LocalAI 功能包括运行LLMs、生成图像、制作音频,其应用范围十分广泛。此外,LocalAI 还支持多系列模型和架构,无需 GPU 也能高效运行。

04 实际演示

本篇文章可以使用本机环境和阿里云/AWS 为主要演示环境进行呈现。

在Mac电脑(无GPU配置)运行

1.将此存储库克隆到您的本机:

1.git clone https://github.com/LinkTime-Corp/llm-in-containers.git

2.cd llm-in-containers/tabular-data-analysis

2.若需要使用 OpenAI 的模型进行推理,将您的OpenAI API密钥设置到conf/config.json的“OPENAI_API_KEY”中:

如果用户的OpenAI 未进行订阅,这里则需要修改model为:”OPENAI_API_MODEL”: “gpt-3.5-turbo”

3.下载本地模型。如果您遇到wget命令的问题,您可以手动从此链接(https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q6_K.gguf)下载模型并保存在’models’目录中。

bash download-models.sh

4.启动演示:

注:需要启动 Docker Desktop`

bash run.sh

5.访问 http://localhost:8501/ 上的用户界面

在阿里云/AWS服务器运行

我们在阿里云和AWS上都测试过这个流程,在公有云上的过程中,1-4步和上面设置一样,请确认服务器上已安装Docker引擎。

如果服务器上配置有GPU,在第四步的命令中我们可以指定使用GPU来运行LLM:

bash run.sh -gpu

和本地运行不一样的是,在云上运行的时候我们需要设置防火墙规则之后才能访问界面。

5.配置防火墙规则

例如,对于阿里云,可参考https://help.aliyun.com/zh/simple-application-server/user-guide/manage-the-firewall-of-a-server

6.访问 http://{云CPU实例的IP地址}:8501 上的用户界面

可以在云服务商工作台处查看服务器的IP地址,或者在连接服务器后使用如下命令查看IP地址:

curl ip.sb

随后在浏览器中访问 http://{云CPU实例的IP地址}:8501 访问web ui.

浏览器访问Web UI 

现在让我们通过上传一些示例数据来使用Web UI:

1.访问提供的链接(https://github.com/ppasupat/WikiTableQuestions/releases/download/v1.0.2/WikiTableQuestions-1.0.2-compact.zip)下载一组示例CSV文件。解压下载的文件。在演示中,我们将使用位于“WikiTableQuestions/csv/200-csv/11.csv”的文件。

2. 进入UI后,首先上传一个CSV文件,比如刚刚解压的文件。选择“LLM类型”来处理您的查询。您可以在“ChatGPT”和“Local_LLM”之间进行选择。

3.选择引擎来查询您的表格数据文件。有两个选项:“MixSelfConsistency[1]”和“ChainOfTable[2]”。

4. 选择完这些选项后,您现在可以根据CSV文件中的数据提问了。例如,“谁赢得了1972年的最佳男演员奖?”点击“查询”按钮提交您的问题,并从选择的LLM获得答案。

5. 在UI的侧边栏上,有一个查看LLM推理跟踪的选项。这个功能可以让您看到LLM逐步处理您的问题的过程,从而提供了如何得出答案的见解。

05 代码结构说明

该演示的代码[3]主要包括以下几个文件:

  • main.py[4]:该文件包含用户界面(UI)的代码。
  • backend.py[5]:负责处理选择LLM和查询引擎的逻辑,并与LLMs进行交互。
  • constants.py[6]:定义了代码库中使用的所有常量。

06 查询引擎的运作机制

在演示中,我们使用了一个名为“11.csv”的CSV文件,该文件来自“WikiTableQuestions/csv/200-csv”示例数据集。该CSV文件包含在下表中显示的结构化表格数据。让我们来探索一下查询引擎对于问题“哪位提名者在1972年获得了奥斯卡最佳男演员奖?”是如何回答的。

“MixSelfConsistency” 查询引擎

“MixSelfConsistency” 引擎通过循环遍历两种不同的可配置迭代次数的查询路径来运行。这两个路径分别被称为“文本推理”和“符号推理”。

“文本推理”路径

这个路径很直接。它将 CSV 文件的内容直接整合到提示词中,从而形成一个综合查询,然后输入给LLM。我们执行了三次这个流程,得到了结果列表:

['Gene Hackman', 'Gene Hackman', 'Gene Hackman'].

“符号推理”路径

这条路径使用LlamaIndex的PandasQueryEngine。该查询引擎将CSV文件加载到pandas数据帧中,然后根据给定的问题生成pandas指令以获取结果。在演示中,我们得到了三条 pandas 指令,每条指令对应一次迭代运行。

第一次运行:

df[(df['Award'] == 'Academy Awards, 1972') & (df['Category'] == 'Best Actor') & (df['Result'] == 'Won')]['Nominee']

第二次运行

df[(df['Award'] == 'Academy Awards, 1972') & (df['Category'] == 'Best Actor') & (df['Result'] == 'Won')]['Nominee'].iloc[0]

第三次运行

df[(df['Award'] == 'Academy Awards, 1972') & (df['Category'] == 'Best Actor') & (df['Result'] == 'Won')]['Nominee']

因此,结果列表为:

[    '2    Gene Hackman\nName: Nominee, dtype: object',    
     'Gene Hackman',
    '2    Gene Hackman\nName: Nominee, dtype: object'
]

自洽性聚合

最后一步是将从”文本推理”和”符号推理”生成的合并表中统计每个推理结果出现的次数,然后返回出现次数最多的那个推理结果,即为答案。在我们的演示中,出现计数最高并因此被判定为最有可能的答案的推理结果是”Gene Hackman”。

优点和缺点

“MixSelfConsistency”查询引擎通过融合文本推理和符号推理,并利用混合自洽性机制,提高了准确性。但是,对这些查询路径进行多次迭代运算可能会导致整体响应时间变长。

“ChainOfTable” 查询引擎 

“ChainOfTable”引擎最初采用一系列操作将原始表格修改成可以直接处理查询的格式。随后,它将经过修改的表格和问题结合起来创建一个提示词,以便 LLMs 能够推导出最终答案。这个最后阶段类似于“MixSelfConsistency”引擎中的”文本推理”路径。

表格操作链 

让我们深入了解一下它构建操作链的方法。在每次迭代中,引擎都会提示 LLM 下一步的操作,同时考虑当前的表格状态和之前的操作历史。可能的操作包括: 

1. f_add_column():当表格需要额外的推断数据来准确响应查询时,使用这个操作添加新列。

2. f_select_row():当只有特定行与问题相关时,使用这个操作来隔离并专注于特定行。

3. f_select_column():与f_select_row()类似,使用这个操作将关注点缩小到回答查询所需的特定列上。

4. f_group_by():对于涉及具有相同值及其计数项的查询,使用这个操作可根据这些项进行分组,从而提高数据表述的清晰度。

5. f_sort_by():如果查询涉及某列中数据的顺序或排名,使用这个操作会根据问题的上下文对该项进行排序。

演示案例

回到我们的演示,让我们重新审视一下查询:“哪位提名者在1972年获得了奥斯卡最佳男主角奖?”。作为回应,”ChainOfTable”引擎在第一次迭代时执行了操作f_select_row([‘row 3’])。此操作导致创建了一个新的表,结构如下:

最终的查询变得直接了当,直接得出了最终答案:“Gene Hackman。”

优点和缺点

“ChainOfTable”引擎创建了一系列操作,将原始表格转化为与最终问题更加吻合的版本。这种方法显著提高了对那些无法从原始表格中立即得出答案的查询的准确性,需要一系列表格操作来澄清问题。然而,这个过程要求每次与 LLM的交互都要在提示中包含当前表格的内容。这种方法可能会影响 LLMs 的性能,特别是在处理大型表格时,因为数据的大小直接影响处理负载。

性能比较

在我们的查询引擎演示中,每次执行查询都会提供两个关键信息:查询引擎的响应和生成该响应所需的时间。通过使用演示中的 CSV 文件进行实验,我们发现当使用 ChatGPT 推理时,”MixSelfConsistency “引擎往往比 “ChainOfTable “引擎更快、更准确。

需要注意的是,这些结果并不是通过系统的基准测试或对两种查询引擎的全面比较得出的。我们提到的结果完全基于我们有限的实验。因此,这些结果应被视为初步观察结果,而非最终结论。

我们鼓励对这一领域感兴趣的个人以我们的演示为起点,进行更广泛的比较或基准测试。

07 使用体会

与LLMs的交互

在本次演示的实现过程中,一个关键部分是与用于查询的 LLMs 的 API 建立连接。这包括设置与 ChatGPT 的 OpenAI API 的建立连接以及与本地 LLMs 的类似 API 建立连接。以下是代码的第一部分:

self.openai_llm = OpenAI(model=OPENAI_API_MODEL)
self.local_llm = OpenAILike(
   api_base=API_BASE,
   api_key=API_KEY,
   model=MODEL_NAME,
   is_chat_model=True,
   is_function_calling_model=True,
   context_window=3900,
   timeout=MAC_M1_LUNADEMO_CONSERVATIVE_TIMEOUT,
)

此外,设置 ServiceContext 对于 LLMs 也非常重要,特别是对于本地 LLMs 。本地 LLMs 使用的是 Huggingface 的本地 embed_model(来自此文档https://docs.llamaindex.ai/en/stable/module_guides/models/embeddings.html,本地 embed_model 指的是 “BAAI/bge-large-en” ):

if llm_type == GPT_LLM:
   chosen_llm = self.openai_llm
   embed_model = OpenAIEmbedding(embed_batch_size=10)
   service_context = ServiceContext.from_defaults(
       chunk_size=1024, llm=chosen_llm, embed_model=embed_model)
else:
   chosen_llm = self.local_llm
   service_context = ServiceContext.from_defaults(
       chunk_size=1024, llm=chosen_llm, embed_model="local")
   set_global_service_context(service_context)

上述实现在功能上是可行的,但在灵活性和切换不同 LLM(如 OpenAI 和本地 LLM )的易用性方面并不理想。在理想的设置中,类似 OpenAI 或 OpenAILike 的类应能够与 OpenAI 模型和本地 LLMs 建立连接。这可以通过简单地指定这些兼容 API 的 api_base 和 api_key 来实现。

正如 LlamaIndex 所解释的那样,OpenAILike类是对 OpenAI 模型的轻量级封装。其目的是确保与提供 OpenAI 兼容 API 的第三方工具兼容。然而,目前 LlamaIndex 限制了使用自定义模型的 OpenAI 类的使用,主要是因为需要从模型名称中推断出某些元数据。

这个限制说明未来需要优化实现过程,让用户更容易集成和切换不同 LLMs,无论是 OpenAI 还是本地 LLMs。

OpenAI兼容的API服务

选择使用与 OpenAI 兼容的 API 服务作为应用程序和 LLMs 之间的中介是一个战略性的决定,旨在提高模块化和增强灵活性。这种方法允许在不更改应用程序代码的情况下无缝更换 LLMs,从而减轻了直接集成可能出现的兼容性问题。这样的设置确保应用程序对它们交互的具体 LLMs 保持中立,从而便于更容易地进行更新和维护。

在选择合适的 API 服务的过程中,我们最初的选择是 GPT4All Rest API。众所周知,GPT4All是一个很好的工具,通过在标准硬件上启用对 LLMs 的使用,使其更加大众化。然而,现在 GPT4All Rest API 与当前的 OpenAI API 规范不兼容,因此我们无法切换后端的 LLM 服务。随后,我们评估了 LocalAI ,它被证明与 LlamaIndex OpenAILike 类兼容并且能够有效地运行。这种兼容性对我们来说十分重要,LocalAI 符合当前的规范,并能够与我们的框架平滑集成。

管理Docker镜像的大小

选择在Docker容器中运行我们的演示是由Docker为LLM应用提供的诸多优势驱动的,包括增强的可移植性、可重现性、可扩展性、安全性、资源效率和简化的部署流程。然而,在构建我们的演示的Docker镜像过程中,我们注意到安装PyTorch后镜像大小显著增加。为了解决这个问题并减小CPU实例的Docker镜像大小,我们选择直接从官方的wheel安装PyTorch,下载地址为 http://download.pytorch.org/whl/cpu:

pip install --no-cache-dir -r requirements.txt \    --extra-index-url https://download.pytorch.org/whl/cpu

此方法显著减少了压缩后的镜像大小,仅为 435.06 MB,而GPU实例的压缩后大小则变得更大了,为 5.38 GB。对于那些希望用 CPU 实例构建镜像的人来说,采用这种策略尤为有效,这一策略在功能和效率之间取得了平衡。

08 链接

本文Github 链接:

https://github.com/LinkTime-Corp/llm-in-containers/tree/main/tabular-data-analysis

博客原文:

https://blog.gopenai.com/enhancing-tabular-data-analysis-with-llms-78af1b7a6df9

[1]MixSelfConsistency

https://github.com/run-llama/llama-hub/tree/main/llama_hub/llama_packs/tables/mix_self_consistency

[2]ChainOfTable

https://github.com/run-llama/llama-hub/tree/main/llama_hub/llama_packs/tables/chain_of_table

[3]演示代码

https://github.com/LinkTime-Corp/llm-in-containers/tree/main/tabular-data-analysis/src

[4]main.py

https://github.com/LinkTime-Corp/llm-in-containers/blob/main/tabular-data-analysis/src/main.py

[5]backend.py

https://github.com/LinkTime-Corp/llm-in-containers/blob/main/tabular-data-analysis/src/backend.py

[6]constants.py

https://github.com/LinkTime-Corp/llm-in-containers/blob/main/tabular-data-analysis/src/constants.py