教程如何使用 langChain 和矢量数据库将我们自己的文档与 huggingface结合使用
在之前的笔记本中,我们了解了如何使用矢量数据库为 Hugging Face 语言模型创建丰富的提示。在这一篇中,我们整合了 LangChain,以便我们可以对考虑到我们信息的语言模型进行查询。
信息可以是我们自己的文档,也可以是业务知识数据库中包含的任何内容。
我已经准备好笔记本,以便它可以与不同的 Kaggle 数据集一起使用,这样就可以轻松地使用不同的数据集进行不同的测试。
数据已从 Pandas DataFrame 加载,使用 LangChain 的 document_loaders 库中的 DataFrameLoader 函数。
1.安装并加载库。 #
首先,我们需要安装必要的 Python 包。
langchain:使用大型语言模型构建应用程序的革命性框架。
sentence_transformers:创建我们要存储在向量数据库中的嵌入所必需的。
chromadb:这是我们的向量数据库。ChromaDB 易于使用且开源,可能是用于存储嵌入的最常用的向量数据库。
!pip install -q chromadb==0.4.22
!pip install -q langchain==0.1.4
!pip install -q sentence_transformers==2.3.0
2. 加载数据集 #
如您所见,笔记本已准备好使用不同的数据集。只需取消注释要使用的数据集的行即可。
由于我们在空闲且有限的空间中工作,并且只能使用有限的 GB 内存,因此我使用变量 MAX_NEWS 限制了要使用的新闻数量。
包含新文本的字段的名称存储在变量 DOCUMENT 中,元数据存储在 TOPIC 中
我使用了 kotartemiy/topic-labeled-news-dataset https://www.kaggle.com/datasets/kotartemiy/topic-labeled-news-dataset
Artem Burgara(2020 年)R 与 Python:主题标签新闻数据集,检索于 2023 年 12 月,来自 https://www.kaggle.com/discussions/general/46091。
但您可以使用其他数据集,我鼓励您至少尝试以下其中一个:
https://www.kaggle.com/datasets/gpreda/bbc-news
https://www.kaggle.com/datasets/deepanshudalal09/mit-ai-news-published-till-2023
连接到 Kaggle。
2.1 Conecting to Kaggle #
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
!pip install -q kaggle
import os
#This directory should contain you kaggle.json file with your key
os.environ['KAGGLE_CONFIG_DIR'] = '/content/drive/MyDrive/kaggle'
!kaggle datasets download -d kotartemiy/topic-labeled-news-dataset
#!kaggle datasets download -d gpreda/bbc-news
Dataset URL: https://www.kaggle.com/datasets/kotartemiy/topic-labeled-news-dataset
License(s): CC0-1.0
Downloading topic-labeled-news-dataset.zip to /content
85% 8.00M/9.45M [00:01<00:00, 12.7MB/s]
100% 9.45M/9.45M [00:01<00:00, 8.75MB/s]
import numpy as np
import pandas as pd
import zipfile
# Define the path to your zip file
file_path = '/content/topic-labeled-news-dataset.zip'
#file_path = '/content/bbc-news.zip'
with zipfile.ZipFile(file_path, 'r') as zip_ref:
zip_ref.extractall('/content/drive/MyDrive/kaggle')
news = pd.read_csv('/content/drive/MyDrive/kaggle/labelled_newscatcher_dataset.csv', sep=';')
MAX_NEWS = 1000
DOCUMENT="title"
TOPIC="topic"
#news = pd.read_csv('/content/drive/MyDrive/kaggle/bbc_news.csv')
#MAX_NEWS = 500
#DOCUMENT="description"
#TOPIC="title"
#Because it is just a course we select a small portion of News.
subset_news = news.head(MAX_NEWS)
news.head(2)
topic | link | domain | published_date | title | lang | |
---|---|---|---|---|---|---|
0 | SCIENCE | https://www.eurekalert.org/pub_releases/2020-0… | eurekalert.org | 2020-08-06 13:59:45 | A closer look at water-splitting’s solar fuel … | en |
1 | SCIENCE | https://www.pulse.ng/news/world/an-irresistibl… | pulse.ng | 2020-08-12 15:14:19 | An irresistible scent makes locusts swarm, stu… | e |
3. 从数据框创建文档 #
我们将从 pandas DataFrame 加载数据。但是,LangChain 通过 document_loader 库支持多种数据源,例如 Word 文档、Excel 文件、纯文本、SQL 等。
我们还导入了 Chroma 库,用于将嵌入保存在 ChromaDB 数据库中。
from langchain.document_loaders import DataFrameLoader
from langchain.vectorstores import Chroma
首先,我们创建加载器,指示数据源和 DataFrame 中列的名称,我们在其中存储我们可以视为文档的内容,即我们想要传递给模型的信息,以便它在响应中考虑到它。
df_loader = DataFrameLoader(subset_news, page_content_column=DOCUMENT)
## Then, we use the loader to load the document.
df_document = df_loader.load()
display(df_document[:2])
[Document(page_content="A closer look at water-splitting's solar fuel potential", metadata={'topic': 'SCIENCE', 'link': 'https://www.eurekalert.org/pub_releases/2020-08/dbnl-acl080620.php', 'domain': 'eurekalert.org', 'published_date': '2020-08-06 13:59:45', 'lang': 'en'}),
Document(page_content='An irresistible scent makes locusts swarm, study finds', metadata={'topic': 'SCIENCE', 'link': 'https://www.pulse.ng/news/world/an-irresistible-scent-makes-locusts-swarm-study-finds/jy784jw', 'domain': 'pulse.ng', 'published_date': '2020-08-12 15:14:19', 'lang': 'en'})]
4.创建嵌入 #
首先,我们导入几个库。
CharacterTextSplitter:我们将使用它对不同块中包含的信息进行分组。
HuggingFaceEmbeddings:它将以我们将存储在数据库中的格式创建嵌入。
from langchain.text_splitter import CharacterTextSplitter
#from langchain.embeddings import HuggingFaceEmbeddings
正如我上面所说,我们使用 CharacterTextSplitter 将数据拆分成可管理的块,以向量形式存储。没有确切的方法可以做到这一点,更多的块意味着更详细的上下文,但会增加向量存储的大小。
没有神奇的数字可以告知。重要的是要考虑到,块大小越大,模型将拥有的上下文就越多,但我们的向量存储的大小也会增加。
text_splitter = CharacterTextSplitter(chunk_size=250, chunk_overlap=10)
texts = text_splitter.split_documents(df_document)
display(texts[:2])
[Document(page_content="A closer look at water-splitting's solar fuel potential", metadata={'topic': 'SCIENCE', 'link': 'https://www.eurekalert.org/pub_releases/2020-08/dbnl-acl080620.php', 'domain': 'eurekalert.org', 'published_date': '2020-08-06 13:59:45', 'lang': 'en'}),
Document(page_content='An irresistible scent makes locusts swarm, study finds', metadata={'topic': 'SCIENCE', 'link': 'https://www.pulse.ng/news/world/an-irresistible-scent-makes-locusts-swarm-study-finds/jy784jw', 'domain': 'pulse.ng', 'published_date': '2020-08-12 15:14:19', 'lang': 'en'})]
我们加载库来从 HuggingFace 创建预训练模型,从而从句子创建嵌入。
from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings
embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
5. 使用 Chroma 创建索引 #
这里我们创建嵌入索引。使用文档和上面创建的嵌入函数。
directory_cdb = '/content/drive/MyDrive/chromadb'
chroma_db = Chroma.from_documents(
texts, embedding_function, persist_directory=directory_cdb
)
6. LangChain创建链 #
最后,是时候使用 LangChain 创建我们的链了。这很简单。我们所做的就是给它一个检索器和一个模型,以便使用从检索器获得的结果进行调用。
现在我们将从 langchain 模块导入 RetrievalQA 和 HuggingFacePipeline 类。
from langchain.chains import RetrievalQA
from langchain.llms import HuggingFacePipeline
#from transformers import pipeline
现在我们创建检索器对象,负责返回 ChromaDB 数据库中包含的数据。
retriever = chroma_db.as_retriever()
我使用 Fugging Face 的两个模型测试了笔记本。
第一个是 dolly-v2-3b,最小的 Dolly 模型。它有 30 亿个参数,对于我们的样本来说已经足够了,而且比 GPT2 效果好得多。它是一个文本生成模型,因此可以生成稍微更有想象力的响应。
第二个是 t5 模型。这是一个 text2text 生成。因此它将产生更简洁明了的响应。
只需确保测试两者,如果需要,可以从 Hugging Face 中选择其他模型。
model_id = "databricks/dolly-v2-3b" #a good textgeneration model for testing
task="text-generation"
#model_id = "google/flan-t5-large" #Nice text2text model
#task="text2text-generation"
我们使用 HuggingFacePipeline 类为特定的 Hugging Face 语言模型创建管道。让我们分解一下代码:
model_id:这是您要使用的 Hugging Face 语言模型的 ID。它通常由模型名称和版本组成。
task:此参数指定您要使用语言模型执行的任务。它可以是“文本生成”、“文本到文本生成”、“问答”或模型支持的其他任务。
model_kwargs:允许您提供特定于所选模型的其他参数。在本例中,它将“temperature”设置为 0(表示确定性输出)并将“max_length”设置为 256,这将生成的文本的最大长度限制为 256 个标记。
pipeline_kwargs:允许您提供与管道相关的额外信息。
#If you want to test the T5 model remove the return_full_text parameter in pipeline_kwargs,
#also I recommend to add model_kwargs and change the temperature.
# model_kwargs={
# "temperature": 0,
# "max_length": 256
# },
hf_llm = HuggingFacePipeline.from_model_id(
model_id=model_id,
task=task,
pipeline_kwargs={
"max_new_tokens": 256,
"repetition_penalty":1.1,
"return_full_text":True
},
)
词汇表中已添加特殊标记,请确保对相关的词嵌入进行微调或训练。
我们正在设置 document_qa,一个 RetrievalQA 对象,我们将使用它来运行问题。
stuff 类型是我们可以拥有的最简单的链类型。我从检索器中获取文档并使用语言模型获取响应。
document_qa = RetrievalQA.from_chain_type(
llm=hf_llm, retriever=retriever, chain_type='stuff'
)
是时候调用链并获取响应了!
#Sample question for newscatcher dataset.
response = document_qa.invoke("Can I buy a Toshiba laptop?")
#Sample question for BBC Dataset.
#response = document_qa.run("Who is going to meet boris johnson?")
display(response)
{'query': 'Can I buy a Toshiba laptop?',
'result': ' No, Toshiba laptops have been discontinued.\n\n'}
6.1使用 LangChain 的新 LCEL 架构 #
LangChain 建议使用 LCEL(LangChain 表达语言)而不是 Chains。我在笔记本中使用了这两种方法,但请注意,LCEL 是推荐的方法。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
template = """Answer the question based on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| hf_llm
| StrOutputParser()
)
chain.invoke("Can I buy a Toshiba laptop?")
'Answer: No, Toshiba officially done with making laptops\n\n'
7. 检查句子之间的余弦距离 #
奖励部分:您可能知道,向量数据库通过测量嵌入之间的距离来找到与用户问题最相关的信息。
下面是一个简短的示例来说明其工作原理。
embedding_s1 = embedding_function.embed_query(
"I would like to eat more vegetables and exercise every day")
embedding_s2 = embedding_function.embed_query(
"I will try to maintain a healthier lifestyle.")
embedding_s3 = embedding_function.embed_query(
"I prefer to play football")
无论句子长度如何,所有嵌入的长度都相同。
print(f""" embedding_s1 = {len(embedding_s1)}
embedding_s2 = {len(embedding_s2)}
embedding_s3 = {len(embedding_s3)}""")
embedding_s1 = 384
embedding_s2 = 384
embedding_s3 = 384
# 5 first positions of embedding_s1.
print(embedding_s1[:5])
print(embedding_s2[:5])
print(embedding_s3[:5])
[-0.034526657313108444, 0.031680285930633545, -0.03841095045208931, 0.07523446530103683, -0.04117880389094353]
[-0.009032496251165867, 0.014947249554097652, 0.06308788061141968, 0.06641353666782379, 0.03385470062494278]
[0.01728087104856968, -0.019022813066840172, 0.011131912469863892, -0.003836166812106967, 0.03133479878306389]
导入 SKLEARN 来测量余弦距离
!pip install -q scikit-learn==1.2.2
from sklearn.metrics.pairwise import cosine_similarity
embedding_s1_2d = np.array(embedding_s1).reshape(1, -1)
embedding_s2_2d = np.array(embedding_s2).reshape(1, -1)
embedding_s3_2d = np.array(embedding_s3).reshape(1, -1)
print(embedding_s1_2d[0][:5])
[-0.03452671 0.03168032 -0.03841094 0.07523444 -0.04117881]
S1 句子与 S2 句子的相似度比与 S3 句子的相似度更高。这是因为前几句谈论的是更健康的生活方式。
print(cosine_similarity(embedding_s1_2d, embedding_s2_2d))
print(cosine_similarity(embedding_s1_2d, embedding_s3_2d))
print(cosine_similarity(embedding_s2_2d, embedding_s3_2d))
[[0.54180279]]
[[0.16314688]]
[[0.25131365]]
8. Print embeddings #
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# Perform PCA for 2D visualization
PCA_model = PCA(n_components = 2)
embeddings_sentences=[]
embeddings_sentences.append(embedding_s1)
embeddings_sentences.append(embedding_s2)
embeddings_sentences.append(embedding_s3)
PCA_model.fit(embeddings_sentences)
embeddings_coord = PCA_model.transform(embeddings_sentences)
print(embeddings_coord)
[[-0.46251847 -0.44190478]
[-0.31148544 0.50339061]
[ 0.77400391 -0.06148583]]
import matplotlib.pyplot as plt
x, y = zip(*embeddings_coord)
# Name the points
names = ['s1', 's2', 's3']
# Colors
colors = ['red', 'green', 'blue']
for i, name in enumerate(names):
plt.scatter(x[i], y[i], marker='o', color=colors[i], label=name)
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()
10. 结论 #
这不是一个很长的笔记本,但内容丰富。
我们使用了一个矢量数据库来存储来自 Kaggle 数据集的信息。我们通过检索器将其合并到 LangChain 链中,现在我们可以向几个 Hugging Face 语言模型查询数据集中包含的信息。
而且你也已经了解了 LCEL!!!
请不要在这里停下来。
笔记本准备使用两个数据集。用它进行测试。如果你有时间,可以从 Kaggle 中选择其他数据集。
在 Hugging Face 上找到另一个模型并进行比较。
我们从 Dataframe 加载数据,尝试从 .txt 文件上传数据。