暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

如何使用 LangChain 和 OpenAI 对大型文档摘要总结

墨奇科技 2024-10-31
902

大型语言模型(LLM)简化了诸多任务,例如聊天机器人开发、语言翻译和文本摘要。以往,文本摘要模型的性能问题一直是挑战。如今,利用LLM 可以轻松实现高质量的摘要。例如,一些先进的 LLM 已具备处理整本书上下文窗口的能力。然而,对超长文档进行摘要时,LLM 仍存在一些局限性。


LLM 的上下文长度(或上下文限制)是指模型可处理的 token 数量。每个模型都有其自身的上下文长度,也称为最大 token 数或 token 限制。例如,标准 GPT-4 模型的上下文长度为 128,000 个 token,超出此限制的信息将丢失。部分先进 LLM 的上下文长度可达 1,000,000 个token。然而,随着上下文长度的增加,LLM会受到首要性效应和最近性效应的影响。本文将探讨缓解这些效应的方法。

LLM 中的首要性效应指模型更重视序列开头的信息,而最近性效应指模型更强调最近处理的信息。这两种效应都会导致模型偏向输入数据的特定部分, 忽略序列中的关键信息。

成本是另一个问题。虽然可以通过拆分文本来解决上下文长度限制,但直接将整本书输入模型成本高昂。例如,一本包含 1,000,000 个 token 的书籍,直接使用 GPT-4 模型进行处理,总成本约为 90 美元(包含提示和完成 token)。因此,需要在价格、上下文限制和书籍完整上下文之间寻求平衡,找到一种有效的方法来进行文本摘要。

本文将介绍如何在考虑模型成本和上下文限制的情况下,使用 LangChain 和 OpenAI 整本书进行摘要。

1. 设置环境

使用如下命令安装依赖环境:

    pip install langchain openai tiktoken fpdf2 pandas

    2. 加载书籍

    本示例中将使用查尔斯·狄更斯的书《大卫·科波菲尔德》。接下来使用LangChain 提供的 PyPDFLoader 实用程序加载书籍。

      from langchain.document_loaders import PyPDFLoader


      # 加载书籍
      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]

          3. 预处理

          首先需要从文本中删除不必要的内容,如不可打印字符、额外的空格等,完成数据清理。

            import re
            def clean_text(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)
            # 删除不可打印字符,可选择在“大卫·科波菲尔德”之前
            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
            clean_text=clean_text(text)

            4. 加载 OpenAI API

            配置 OpenAI,并提供认证信息:

              import os
              os.environ["OPENAI_API_KEY"] = "your-openai-key-here"

              接下来查看下本书的 token 数量。

                from langchain import OpenAI
                llm = OpenAI()
                Tokens = llm.get_num_tokens(clean_text)
                print (f"We have {Tokens} tokens in the book")

                该书包含超过 466,000 个标记。直接将所有标记输入 LLM 成本高昂。为降低成本,我们将采用 K-means 聚类算法提取书中的关键片段。

                注:K-means 聚类算法的使用受数据专家 Greg Kamradt 教程的启发。

                为提取关键内容,首先将书籍分割成若干文本块。

                5. 将内容拆分为文档

                接下来使用 LangChain 的 `SemanticChunker` 实用程序将书的内容拆分为文档。

                  from langchain_experimental.text_splitter import SemanticChunker
                  from langchain_openai.embeddings import OpenAIEmbeddings
                  text_splitter = SemanticChunker(
                  OpenAIEmbeddings(), breakpoint_threshold_type="interquartile"
                  )
                  docs = text_splitter.create_documents([clean_text])

                  `SemanticChunker` 接收两个参数,第一个是嵌入模型。该模型生成的嵌入用于基于语义拆分文本。第二个参数是`breakpoint_threshold_type`,它根据语义相似性确定文本应该在哪些点上拆分为不同的块。

                  注意:通过处理这些较小的、语义相似的块,我们旨在最小化LLM中的最近性和首要性效应。这种策略使我们的模型能够更有效地处理每个小的上下文,确保更平衡的解释和响应生成。

                  6. 找到每个文档的 embedding

                  现在我们将使用 OpenAI 默认的方式获取每篇生成文档的 embedding。

                    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]
                    )

                    7. 重新排列数据

                    接下来,将文档内容和它们的 embeddding 列表转换为 pandas DataFrame,以便更轻松地处理和分析数据。

                      import pandas as pd
                      content_list = [doc.page_content for doc in docs]
                      df = pd.DataFrame(content_list, columns=['page_content'])
                      vectors = [embedding.embedding for embedding in embeddings]
                      array = np.array(vectors)
                      embeddings_series = pd.Series(list(array))
                      df['embeddings'] = embeddings_series

                      8. 使用 Faiss 进行高效聚类

                      现在,我们将文档向量转换为与 Faiss 兼容的格式,使用 K-means 将它们聚类为 50 个组,然后创建 Faiss 索引以在文档之间进行高效的相似性搜索。

                        import numpy as np
                        import faiss
                        # 如果尚未转换为float32,请将其转换为float32
                        array = array.astype('float32')
                        num_clusters = 50
                        # 向量维度
                        dimension = array.shape[1]
                        # 使用Faiss训练KMeans
                        kmeans = faiss.Kmeans(dimension, num_clusters, niter=20, verbose=True)
                        kmeans.train(array)
                        # 直接访问质心
                        centroids = kmeans.centroids
                        # 为原始数据集创建新索引
                        index = faiss.IndexFlatL2(dimension)
                        # 将原始数据集添加到索引中
                        index.add(array)

                        注意:选择 K-means 聚类的原因是每个聚类将具有相似的内容或相似的上下文,因为该聚类中的所有文档都具有相关的嵌入,并且我们将选择最接近核心的文档。

                        9. 导出文档

                        现在,我们将从每个聚类中选择最接近聚类中心的文档,以此代表该聚类的核心内容。

                          D, I = index.search(centroids, 1)

                          以下代码使用 similarity_search_by_vector 方法,查找与每个聚类中心向量最接近的文档。该方法返回两个数组:D 包含每个文档到其最近聚类中心的距离,I 包含这些最近文档的索引。

                          由于文档是按书籍顺序排列的,我们需要对选定的文档索引进行排序以保持文本的原始顺序。

                            sorted_array = np.sort(I, axis=0)
                            sorted_array=sorted_array.flatten()
                            extracted_docs = [docs[i] for i in sorted_array]

                            10. 获取每个文档的摘要

                            现在使用 GPT-4 模型获取每个文档的摘要,以节省成本。首先我们来定义模型。

                              model = ChatOpenAI(temperature=0,model="gpt-4")

                              定义提示并使用 LangChain 创建提示模板,将其传递给模型。

                                from langchain_core.output_parsers import StrOutputParser
                                from langchain_openai import ChatOpenAI
                                from langchain_core.prompts import ChatPromptTemplate
                                prompt = ChatPromptTemplate.from_template("""
                                You will be given different passages from a book one by one. Provide a summary of the following text. Your result must be detailed and atleast 2 paragraphs. When summarizing, directly dive into the narrative or descriptions from the text without using introductory phrases like 'In this passage'. Directly address the main events, characters, and themes, encapsulating the essence and significant details from the text in a flowing narrative. The goal is to present a unified view of the content, continuing the story seamlessly as if the passage naturally progresses into the summary.


                                Passage:


                                ```{text}```
                                SUMMARY:
                                """
                                )

                                接下来使用 LangChain Expression Language(LCEL)定义 LangChain 的链。

                                  chain= (
                                  prompt
                                  | model
                                  |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

                                    11. 将摘要保存为 PDF

                                    接下来我们将摘要格式化并以 PDF 格式保存。

                                      from fpdf import FPDF


                                      class PDF(FPDF):
                                      def header(self):
                                      # 选择Arial粗体15号字体
                                      self.set_font('Arial', 'B', 15)
                                      # 向右移动
                                      self.cell(80)
                                      # 带边框的标题
                                      self.cell(30, 10, 'Summary', 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, 'Page %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保存到文件
                                      pdf_output_path = "s_output1.pdf"
                                      pdf.output(pdf_output_path)

                                      至此,我们在PDF格式中获得了整本书的完整摘要。

                                      结论

                                      本教程探讨了如何利用 LLM 摘要大型文本(例如整本书籍),并解决了上下文限制和成本挑战。我们学习了文本预处理步骤,并结合语义分块和 K-means 聚类策略,有效地解决了模型的上下文长度限制问题。高效的聚类方法提取了关键段落,减少了直接处理大规模文本的开销,显著降低了成本。此外,该方法还减轻了 LLM 固有的首要性效应和最近性效应,确保所有文本段落得到均衡考虑。

                                      目前,通过 LLM API 开发 AI 应用备受关注,其中向量数据库在提供高效的上下文嵌入存储和检索方面发挥着关键作用。MyScaleDB 是一款专为 AI 应用设计的向量数据库,兼顾成本、准确性和速度。其 SQL 友好界面使开发者无需学习新的技术即可快速构建 AI 应用。

                                      了解墨奇科技 点击更多资讯



                                      如果喜欢,点个在看 ↓

                                      文章转载自墨奇科技,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                      评论