Chat Towards Data Science|如何用个人数据知识库构建 RAG 聊天机器人?(上)
生成式人工智能时代,开发者可以借助大语言模型(LLM)开发更智能的应用程序。然而,由于有限的知识,LLM 非常容易出现幻觉。检索增强生成(RAG)https://zilliz.com/use-cases/llm-retrieval-augmented-generation 通过为 LLM 补充外部知识,有效地解决了这一问题。
在 Chat Towards Data Science 博客系列中,我们将详细介绍如何使用个人的数据知识库构建 RAG 聊天机器人。本文是该系列的第一部分,将为大家介绍如何创建一个用于 Towards Data Science https://towardsdatascience.com/ 网站的聊天机器人,如何利用网页抓取数据、创建存储在 Zilliz Cloud https://zilliz.com.cn/ 上的知识库。
01.使用 BeautifulSoup4 抓取网页数据
所有机器学习(ML)项目的第一步都是收集所需的数据。本项目中,我们使用网页抓取技术来收集知识库数据。用 requests
库获取网页并使用 BeautifulSoup4.从网页中提取信息、解析 HTML 信息并提取段落。
导入 BeautifulSoup4 和 Requests 库进行网页抓取
运行 pip install beautifulsoup4 sentence-transformers
安装 BeautifulSoup 和 Sentence Transformers。在数据抓取部分只需要导入requests和 BeautifulSoup。接下来,创建一个 dictionary,其中包含我们要抓取的 URL 格式。在本示例中,我们只从 Towards Data Science 抓取内容,同理也可以从其他网站抓取。
现在,用以下代码所示的格式从每个存档页面获取数据:
import requests from bs4 import BeautifulSoup urls = { 'Towards Data Science': '<https://towardsdatascience.com/archive/{0}/{1:02d}/{2:02d}>' }
此外,我们还需要两个辅助函数来进行网页抓取。第一个函数将一年中的天数转换为月份和日期格式。第二个函数从一篇文章中获取点赞数。
天数转换函数相对简单。写死每个月的天数,并使用该列表进行转换。由于本项目仅抓取 2023 年数据,因此我们不需要考虑闰年。如果您愿意,可以根据不同的年份进行修改每个月天数。
点赞计数函数统计 Medium 上文章的点赞数,单位为 “K” (1K=1000)。因此,在函数中需要考虑点赞数中的单位“K”。
def convert_day(day): month_list = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] m = 0 d = 0 while day > 0: m += 1 d = day day -= month_list[m-1] return (m, d) def get_claps(claps_str): if (claps_str is None) or (claps_str == '') or (claps_str.split is None): return 0 split = claps_str.split('K') claps = float(split[0]) return int(claps*1000) if len(split) == 2 else int(claps)
解析 BeautifulSoup4 的网页抓取响应
现在已经设置好必要的组件,可以进行网页抓取。为了避免在过程中遇到 429 错误(请求过多),我们使用 time 库,在发送请求之间引入延迟。此外,用 sentence transformers 库从 Hugging Face 获取 embedding 模型—— MiniLM 模型。
如前所述,我们只抓取了 2023 年的数据,所以将年份设置为 2023。此外,只需要从第 1 天(1 月 1 日)到第 244 天(8 月 31 日)的数据。根据设定的天数进行循环,每个循环在第一次调用time.sleep()
之前会首先设置必要的组件。我们会把天数转换成月份和日期,并转成字符串,然后根据 urls 字典组成完整的 URL,最后发送请求获取 HTML 响应。
获取 HTML 响应之后,使用 BeautifulSoup 进行解析,并搜索具有特定类名(在代码中指示)的div
元素,该类名表示它是一篇文章。我们从中解析标题、副标题、文章 URL、点赞数、阅读时长和回应数。随后,再次使用requests
来获取文章的内容。每次通过请求获取文章内容后,都会再次调用time.sleep()
。此时,我们已经获取了大部分所需的文章元数据。提取文章的每个段落,并使用我们的 HuggingFace 模型获得对应的向量。接着,创建一个字典包含该文章段落的所有元信息。
import time from sentence_transformers import SentenceTransformer model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2") data_batch = [] year = 2023 for i in range(1, 243): month, day = convert_day(i) date = '{0}-{1:02d}-{2:02d}'.format(year, month, day) for publication, url in urls.items(): response = requests.get(url.format(year, month, day), allow_redirects=True) if not response.url.startswith(url.format(year, month, day)): continue time.sleep(8) soup = BeautifulSoup(response.content, 'html.parser') articles = soup.find_all("div","postArticle postArticle--short js-postArticle js-trackPostPresentation js-trackPostScrolls") for article in articles: title = article.find("h3", class_="graf--title") if title is None: continue title = str(title.contents[0]).replace(u'\\\\xA0', u' ').replace(u'\\\\u200a', u' ') subtitle = article.find("h4", class_="graf--subtitle") subtitle = str(subtitle.contents[0]).replace(u'\\\\xA0', u' ').replace(u'\\\\u200a', u' ') if subtitle is not None else '' article_url = article.find_all("a")[3]['href'].split('?')[0] claps = get_claps(article.find_all("button")[1].contents[0]) reading_time = article.find("span", class_="readingTime") reading_time = int(reading_time['title'].split(' ')[0]) if reading_time is not None else 0 responses = article.find_all("a", class_="button") responses = int(responses[6].contents[0].split(' ')[0]) if len(responses) == 7 else (0 if len(responses) == 0 else int(responses[0].contents[0].split(' ')[0])) article_res = requests.get(article_url) time.sleep(8) paragraphs = BeautifulSoup(article_res.content, 'html.parser').find_all("[class*=\\\\"pw-post-body-paragraph\\\\"]") for i, paragraph in enumerate(paragraphs): embedding = model.encode([paragraph.text])[0].tolist() data_batch.append({ "_id": f"{article_url}+{i}", "article_url": article_url, "title": title, "subtitle": subtitle, "claps": claps, "responses": responses, "reading_time": reading_time, "publication": publication, "date": date, "paragraph": paragraph.text, "embedding": embedding })
最后一步是使用 pickle 处理文件。
filename = "TDS_8_30_2023" with open(f'{filename}.pkl', 'wb') as f: pickle.dump(data_batch, f)
数据呈现
数据可视化十分有用。下面是在 Zilliz Cloud 中数据的样子。请注意其中的 embedding,这些数据表示了文档向量,也就是我们根据文章段落生成的向量。
02.将 TDS 数据导入到向量数据库中
获取数据后,下一步是将其导入到向量数据库中。在本项目中,我们使用了一个单独的 notebook 将数据导入到 Zilliz Cloud,而不是从 Towards Data Science 进行网页抓取。
要将数据插入 Zilliz Cloud,需按照以下步骤进行操作:
-
连接到 Zilliz Cloud
-
定义 Collection 的参数
-
将数据插入 Zilliz Cloud
设置 Jupyter Notebook
运行 pip install pymilvus python-dotenv
来设置 Jupyter Notebook 并启动数据导入过程。用 dotenv
库来管理环境变量。对于pymilvus
包,需要导入以下模块:
-
utility
用于检查集合的状态 -
connections
用于连接到 Milvus 实例 -
FieldSchema
用于定义字段的 schema -
CollectionSchema
用于定义 collection schema -
DataType
字段中存储的数据类型 -
Collection
我们访问 collection 的方式
然后,打开之前 pickle 的数据,获取环境变量,并连接到 Zilliz Cloud。
import pickle import os from dotenv import load_dotenv from pymilvus import utility, connections, FieldSchema, CollectionSchema, DataType, Collection filename="TDS_8_30_2023" with open(f'{filename}.pkl', 'rb') as f: data_batch = pickle.load(f) zilliz_uri = "your_zilliz_uri" zilliz_token = "your_zilliz_token" connections.connect( uri= zilliz_uri, token= zilliz_token )
设置 Zilliz Cloud 向量数据库并导入数据
接下来,需要设置 Zilliz Cloud。我们必须创建一个 Collection 来存储和组织从 TDS 网站抓取的数据。需要两个常量:dimension(维度)和 collection name(集合名称),dimension 是指我们的向量具有的维度数。在本项目中,我们使用 384 维的 MiniLM 模型。
Milvus 的全新 Dynamic schema https://milvus.io/docs/dynamic_schema.md#Dynamic-Schema 功能允许我们仅为 Collection 设置 ID 和向量字段,无需考虑其他字段数量和数据类型。注意,需要记住保存的特定字段名称,因为这对于正确检索字段至关重要。
DIMENSION=384 COLLECTION_NAME="tds_articles" fields = [ FieldSchema(name='id', dtype=DataType.VARCHAR, max_length=200, is_primary=True), FieldSchema(name='embedding', dtype=DataType.FLOAT_VECTOR, dim=DIMENSION) ] schema = CollectionSchema(fields=fields, enable_dynamic_field=True) collection = Collection(name=COLLECTION_NAME, schema=schema) index_params = { "index_type": "AUTO_INDEX", "metric_type": "L2", "params": {"nlist": 128}, } collection.create_index(field_name="embedding", index_params=index_params)
Collection 有两种插入数据的选项:
-
遍历数据并逐个插入每个数据
-
批量插入数据
在插入所有数据之后,重要的是刷新集合以进行索引并确保一致性,导入大量数据可能需要一些时间。
for data in data_batch: collection.insert([data]) collection.flush()
03.查询 TDS 文章片段
一切准备就绪后,就可以进行查询了。
获取 HuggingFace 模型并设置 Zilliz Cloud 查询
注意,必须获取 embedding 模型并设置向量数据库以查询 Towards Data Science 知识库。这一步使用了一个单独的笔记本。我们将使用dotenv
库来管理环境变量。此外,还需要使用 Sentence Transformers 中的 MiniLM 模型。这一步中,可以重用 Web Scraping 部分提供的代码。
import os from dotenv import load_dotenv from pymilvus import connections, Collection zilliz_uri = "your_zilliz_uri" zilliz_token = "your_zilliz_token" from sentence_transformers import SentenceTransformer model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2")
执行向量搜索查询
连接到向量数据库并执行搜索。在本项目中,我们将连接到一个 Zilliz Cloud 实例,并检索之前创建的集合 tds_articles
,用户要先输入他们的查询问题。
接下来,使用 Hugging Face 的 embedding 模型对查询进行编码。这个过程将用户的问题转换为一个 384 维的向量。然后,使用这个编码后的查询向量来搜索向量数据库。在搜索过程中,需要指定进行 ANN 查询字段(anns_field
)、索引参数、期望的搜索结果数量限制以及我们想要的输出字段(output fields)。
之前,我们用了 Milvus 的 Dynamic Schema 特性来简化字段 Schema 定义流程。搜索向量数据库时,包括所需的动态字段在搜索结果中是必要的。这个特定的场景涉及请求paragraph
字段,其中包含文章中每个段落的文本。
connections.connect(uri=zilliz_uri, token=zilliz_token) collection = Collection(name="tds_articles") query = input("What would you like to ask Towards Data Science's 2023 publications up to September? ") embedding = model.encode(query) closest = collection.search([embedding], anns_field='embedding', param={"metric_type": "L2", "params": {"nprobe": 16}}, limit=2, output_fields=["paragraph"]) print(closest[0][0]) print(closest[0][1])
比如,我在应用中查询大语言模型相关的信息,返回了以下两个回答。尽管这些回答提到了“语言模型”并包含一些相关信息,但它们没有提供关于大型语言模型的详细解释。第二个回答在语义上相似,但是不足够接近我们想要的内容。
04.给向量数据库知识库添加内容
到目前为止,我们使用 Zilliz Cloud 作为向量数据库在 TDS 文章上创建了一个知识库。虽然能够轻松地检索语义上相似的搜索结果,但还没有达到我们的期望。下一步是通过加入新的框架和技术来增强我们的结果。
05.总结
本教程介绍了如何基于 Towards Data Science 文章构建聊天机器人。我们演示了网页爬取的过程,创建了知识库,包括将文本转换成向量存储在 Zilliz Cloud 中。然后,我们演示了如何提示用户进行查询,将查询转化为向量,并查询向量数据库。
不过,虽然结果在语义上相似,但并不完全符合我们的期望。在本系列的下一篇中,我们将探讨使用 LlamaIndex 来优化查询。除了这里讨论的步骤之外,大家也可以结合 Zilliz Cloud 尝试替换模型、合并文本或使用其他数据集。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
POSIX 真的不适合对象存储吗?
最近,留意到 MinIO 官方博客的一篇题为“在对象存储上实现 POSIX 访问接口是坏主意”的文章,作者以 S3FS-FUSE 为例分享了通过 POSIX 方式访问 MinIO 中的数据时碰到了性能方面的困难,性能远不如直接访问 MinIO。在对结果进行分析时,作者认为是 POSIX 本身存在的缺陷导致的性能问题。这个结论与我们既有经验有一定出入。 我们知道 POSIX 是一个有用而且广泛应用的标准,遵循它而开发的程序可以保证不同操作系统之间的兼容性和可移植性。各行各业中常用的业务系统和应用程序,大多遵循 POSIX 标准。 随着云计算、大数据、人工智能等技术的发展和数据存储量的攀升,本地化应用也逐渐产生对对象存储等弹性存储的需求,MinIO 等对象存储虽然提供了各种语言的 SDK,但许多传统应用很难甚至无法修改代码去适配对象存储的访问接口,这促使很多存储产品在对象存储的基础上去实现 POSIX 接口来满足这样的刚性需求。 业内在对象存储上实现 POSIX 接口的产品有很多,比如 Ceph、JuiceFS、Weka 等,它们都有广泛的用户群和大量的成功案例,在性能方面也都有不错的表现...
- 下一篇
MathLabTool 更新公告(231025)
MathLabTool 是一款数学仿真、图形化工具,可为数学相关的实验、仿真、图形化等各类需求,提供便捷工具。 可以为数据绘制各类 2D、3D 图形,视频、图像处理,显示实时串口数据图形,封装各类算法。 https://www.oschina.net/p/mathlabtool 更新功能列表(231025): 增加感知机接口。 var test_data = [ [1, 2, 1], [2, 3, 1], [2, 1, 1], [2, 6, -1], [3, 5, -1] ]; var data = { eta: 0.5, max_iter_num: 0, row: test_data.length, col: test_data[0].length, data: test_data }; var res = mlt_perceptron(data); mlt_page_console_log(res);
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6