大型语言模型已使许多任务变得简单多了,例如开发聊天机器人、语言翻译、文本摘要等。过去在为摘要任务编写模型时,我们总会遇到性能问题。现在,我们可以使用大型语言模型(LLM)轻松完成这些任务。例如,最先进的大型语言模型可以处理整个书籍的上下文窗口。但在处理非常大的文档时进行摘要,仍然存在一些限制。
大型文档总结中LLM的局限性上下文限制在大型语言模型(简称LLM)中指的是模型能处理的标记数。每个模型都有自己的上下文长度,也称为最大标记数或标记限制。例如,标准GPT-4模型的上下文长度为128,000个标记。超过这个数量的标记将被忽略,导致信息丢失。一些最新的LLM的上下文限制可以达到100万个标记。然而,随着上下文限制的增加,LLM会遭受近期效应和首因效应(即近期效应和首因效应)等限制。我们还可以讨论缓解这些效应的方法。
- Primacy effect 在大语言模型中是指模型更倾向于最早出现的信息。
- Recency effect 是指模型强调最近处理的信息。
这两种效应会使模型对输入数据中的特定部分产生偏好,可能会忽略序列中间的重要内容。
第二个问题是成本。我们可以通过拆分文本来解决第一个问题(即上下文限制),但是我们不能直接将整本书传递给模型,这会花费很多钱。例如,比如有一本书有100万个token,直接将其传递给GPT4模型,我们的总成本大约是90美元(提示和完成的token)。我们需要找到一种折中的方法,以考虑价格、上下文限制以及书的完整内容来概括文本。
在这次教程里,你将学习如何在考虑价格和模型的限制下总结整本书。我们开始吧。
使用LangChain和OpenAI概括长文档 配置环境要跟随本教程,您需要:
(注:原文未列出具体要求)
- 已安装Python
- 一个IDE(如VS Code)
安装依赖项,请打开终端窗口并输入命令:
请运行以下命令来安装所需的软件包:
pip install langchain openai tiktoken fpdf2 pandas
这条命令会安装所有必需的依赖包。
加载书你将使用查尔斯·狄更斯的小说《大卫科波菲尔》,这本书是公开可获取的。让我们用LangChain提供的PyPDFLoader
加载这本书。
从langchain.document_loaders导入PyPDFLoader
# 加载PDF文件
loader = PyPDFLoader("David-Copperfield.pdf")
pages = loader.load_and_split()
它会加载整本书,但我们只对正文感兴趣。我们可以跳过前言和开场白等页面。
# 去掉开头和结尾部分
pages = pages[6:1308]
# 合并页面,并将制表符替换成空格
text = ' '.join([page.page_content.replace('\t', ' ') for page in pages])
现在我们已经有了内容,让我们打印前200个字符的内容吧。
text[0:200]
预处理
让我们从文本中去掉像不可见字符、多余空格等等不必要的内容。
import re
def 清理文本(text):
# 移除特定短语 'Free eBooks at Planet eBook.com' 及其周围的空白
cleaned_text = re.sub(r'\s*Free eBooks at Planet eBook\.com\s*', '', text, flags=re.DOTALL)
# 移除多余的空格
cleaned_text = re.sub(r' +', ' ', cleaned_text)
# 移除 'David Copperfield' 之前或之后的不可打印字符
cleaned_text = re.sub(r'(David Copperfield )?[\x00-\x1F]', '', cleaned_text)
# 将换行符替换为空格
cleaned_text = cleaned_text.replace('\n', ' ')
# 移除连字符周围的空白
cleaned_text = re.sub(r'\s*-\s*', '', cleaned_text)
return cleaned_text
result=清理文本(text)
清洗数据后,我们可以开始进行总结了。
导入OpenAI API// 如果需要注释,可以使用“//”或“/ /”格式。API等术语保持英文,符合中文技术圈的习惯。
在使用OpenAI API之前,我们需要在这里设置并提供凭证信息。
import os
# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = "your-openai-key-here"
在那里输入你的API密钥,它会自动设置环境变量。
我们来看看这本书里有多少个token:
from langchain导入OpenAI
llm = OpenAI()
Tokens = llm.get_num_tokens(clean_text)
print(f"这本书里有{Tokens}个token")
这本书有超过466,000个词汇单元,如果我们直接全部输入给语言模型,成本会很高。因此,为了降低成本,我们将使用K-means聚类算法来提取书中的重要片段。
注意:使用K-means聚类算法的决定是受到数据达人Greg Kamradt的教程(https://www.youtube.com/watch?v=qaPMdcCqtWk&t=870s&ab_channel=GregKamradt%28DataIndy%29)的启发。
为了得到书中的重点,我们先把它分成几部分吧。
将内容分成单独的文档我们将使用LangChain的SemanticChunker
工具将书拆分成多个文档。
从 langchain_experimental.text_splitter 导入 SemanticChunker
从 langchain_openai.embeddings 导入 OpenAIEmbeddings
text_splitter = SemanticChunker(
OpenAIEmbeddings(), breakpoint_threshold_type="interquartile"
)
docs = text_splitter.create_documents([clean_text])
SemanticChunker
接收两个参数,分别是嵌入模型和breakpoint_threshold_type
。嵌入模型生成的嵌入用于根据语义将文本分割成不同的段落。breakpoint_threshold_type
决定了文本在哪些点上应该被分割成不同的段落,依据语义相似度。
注意:通过处理这些较小的、语义上相似的块,我们旨在减少在我们的大型语言模型(LLM)中的近期效应和初始效应。这一策略使我们的模型能够更好地处理每个小的上下文,从而实现更加平衡的解释和响应生成。
获取每个文档的嵌入现在,我们来获取每个生成文档的嵌入表示吧。你会用OpenAI
的默认方法来获取这些嵌入。
import numpy as np
import openai
def get_embeddings(text):
response = openai.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data
# 获取文档内容的嵌入表示
embeddings=get_embeddings([doc.page_content for doc in docs])
get_embeddings
方法可以得到所有文档的嵌入表示。
请注意:text-embedding-3-small
方法是由 OpenAI 特别发布,被认为更便宜且更快。
接下来,我们将文档内容列表及其嵌入转换成pandas的DataFrame,以便更方便地进行数据处理和分析。
import pandas as pd
content_list = [doc.page_content for doc in docs] # Extract page content from each document
df = pd.DataFrame(content_list, columns=['page_content']) # Create a DataFrame with the content list
vectors = [embedding.embedding for embedding in embeddings] # Extract embeddings from each embedding object
array = np.array(vectors) # Convert the list of vectors to a numpy array
embeddings_series = pd.Series(list(array)) # Convert the array to a pandas Series
df['embeddings'] = embeddings_series # Add the embeddings to the DataFrame
使用Faiss来做高效的聚簇
现在,我们将文档向量转换为与Faiss兼容的格式,之后使用K-means将其聚成50个簇,并为文档之间的相似度搜索创建一个Faiss索引。
**<code>import numpy as np \
import faiss \
<em>//如果不是 float32 类型,转换为 float32</em> \
array = array.astype('float32') \
num_clusters = 50 \
<em>//向量维度</em> \
dimension = array.shape[1] \
<em>//使用 Faiss 训练 K-means</em> \
kmeans = faiss.Kmeans(dimension, num_clusters, niter=20, verbose=True) \
kmeans.train(array) \
<em>//直接访问聚类的中心</em> \
centroids = kmeans.centroids \
<em>//为原始数据集创建索引</em> \
index = faiss.IndexFlatL2(dimension) \
<em>//将原始数据集添加进索引</em> \
index.add(array)</code></strong>
这些文档会被分成50个组,通过这种K-means算法。
注意:选择K-means聚类的原因在于,每个聚类将具有相似的内容和上下文,因为该聚类内的所有文档都具有相关的嵌入,我们将选择离中心最近的那个。
选择要导入的文档我们就挑选最重要的文档。为此,我们只会选择最接近簇中心的向量,从每个簇中进行挑选。
D, I = index.search(centroids, 1) # 寻找最接近的中心点
这段代码使用了索引上的搜索方法,找到每个质心最近的那个文档。它返回两个数组:D
,包含每个质心到其最近文档的距离;I
,包含这些最近文档的索引号。搜索方法中的第二个参数1
指定了只为每个质心找到最近的那个文档。
现在我们需要对选定的文档编号进行排序,因为这些文档是按照书里的顺序排列的。
将 sorted_array 设置为 np.sort(I, axis=0) 的结果。
将 sorted_array 平铺。
从 sorted_array 中提取 docs 的文档。
获取每个文档的概要,
下一步是使用GPT-4模型为每个文档生成摘要以节省成本。在使用GPT-4之前,我们先来定义一下这个模型。
model = ChatOpenAI(temperature=0, model="gpt-4") # 这里设置了聊天模型为gpt-4,温度为0
先定义好提示,做个提示模板出来,然后通过LangChain传给模型。
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
提示 = ChatPromptTemplate.from_template("""
你将一次接一次地收到一本书的不同段落,并对它们进行总结。请对以下文本进行总结。你的结果必须详细,并至少包含两段落。在总结时,直接进入文本的叙述或描述,不需要使用类似 '在这一段中' 的引言。直接描述主要事件、角色和主题,将文本中的精髓和重要细节以流畅的方式呈现出来。目标是提供一个统一的内容视图,使内容像自然过渡到总结中一样流畅地继续下去。
段落:
```{text}```
摘要:
"""
)
这个提示模板将帮助模型更有效地总结文件。
接下来的步骤是使用LCEL(LangChain表达语言)来定义一个这样的LangChain。
chain = ( 提示 | 模型 | StrOutputParser() )
汇总链使用StrOutputParser用以解析结果。此外,还有其他解析器可以进一步了解。
你可以最后应用定义的链到每个文档上,从而得到摘要。
from tqdm import tqdm
final_summary = ""
for doc in tqdm(extracted_docs, desc="Processing documents"):
# 获取新的摘要。
new_summary = chain2.invoke({"text": doc.page_content})
# 更新摘要列表:移除最早的一个摘要并添加新的摘要到最后:
final_summary+=new_summary
上述代码对每个文档依次应用链式操作,并将每个摘要添加到final_summary
。
下一步是将摘要保存为PDF文件并整理好。
从fpdf导入FPDF
class PDF(FPDF):
def header(self):
# 选择Arial粗体15
self.set_font('Arial', 'B', 15)
# 向右移动
self.cell(80)
# 带边框的标题
self.cell(30, 10, '摘要', 1, 0, 'C')
# 换行
self.ln(20)
def footer(self):
# 距底部1.5厘米处
self.set_y(-15)
# 选择Arial斜体8
self.set_font('Arial', 'I', 8)
# 页面编号
self.cell(0, 10, '第%s页' % self.page_no(), 0, 0, 'C')
# 创建PDF对象并添加一页
pdf = PDF()
pdf.add_page()
pdf.set_font("Arial", size=12)
# 确保'last_summary'文本被正确地处理成UTF-8格式
# 请将'last_summary'替换为你的实际文本变量
# 请确认你的文本已经转换为utf-8编码的字符串
last_summary_utf8 = last_summary.encode('latin-1', 'replace').decode('latin-1')
pdf.multi_cell(0, 10, last_summary_utf8)
# 将PDF保存为文件's_output1.pdf'
pdf_output_path = "s_output1.pdf"
pdf.output(pdf_output_path)
那么这里就有了这本书的PDF全文。
结论部分在这次教程里,我们探讨了如何使用大型语言模型(LLMs)来总结像整本书这样的大量文本的复杂情况,并解决了上下文限制和成本方面的挑战。我们学习了预处理文本并实施一种结合语义分块和K-means聚类策略的步骤,以有效地管理模型的上下文限制。通过高效地使用聚类方法,我们有效地提取了关键段落,减少了直接处理大量文本的负担。这种方法不仅通过减少处理令牌的数量显著降低了成本,还减轻了大型语言模型的近期效应和首要效应,确保了对所有文本段落的均衡考虑。
人们对于通过大型语言模型(LLM)的API开发AI应用程序表现出极大的热情,其中向量数据库起到了关键作用,通过提供上下文嵌入的高效存储和检索。MyScaleDB 是一个专为AI应用程序设计的向量数据库,充分考虑了成本、准确性和速度。它友好的SQL界面允许开发人员无需学习新技能就能开始开发AI应用程序。
如果你想与我们更深入地讨论,欢迎大家来分享你的想法和反馈。
共同学习,写下你的评论
评论加载中...
作者其他优质文章