大模型之RAG-基于向量检索的理论与实战,对比关键字检索方案

前言

RAG系列的讲解,我们之前和大家分享了RAG的流程、文档切分、基于关键字检索的方案。

在关键字检索的认识与实战一文中,我们讲到了基于关键字检索的局限性:关键字检索可能会受到一些问题的影响,例如同义词、拼写错误等,这可能会导致一些相关的文档被漏掉或者一些不相关的文档被检索到。

今天再来和大家一起分享基于向量检索的方案与实战,再结合关键字检索方案做一下多维度的对比。 让我们对RAG的实现方案能够加深一些理解,在面对不同场景中,选择合适的方案。

image.png

向量检索的定义与原理

什么是向量

向量是一种有大小和方向的数学对象。它可以表示为从一个点到另一个点的有向线段。例如,二维空间中的向量可以表示为 (𝑥,𝑦)(𝑥,𝑦),表示从原点 (0,0)(0,0) 到点 (𝑥,𝑦)(𝑥,𝑦) 的有向线段。

image.png

以此类推,我可以用一组坐标 (𝑥0,𝑥1,…,𝑥𝑁−1)(𝑥0,𝑥1,…,𝑥𝑁−1) 表示一个 𝑁𝑁 维空间中的向量,𝑁𝑁 叫向量的维度。

文本向量(Text Embeddings)

  1. 将文本转成一组 𝑁𝑁 维浮点数,即文本向量又叫 Embeddings
  2. 向量之间可以计算距离,距离远近对应语义相似度大小

image.png

文本向量是怎么得到的

  1. 构建相关(正立)与不相关(负例)的句子对儿样本
  2. 训练双塔式模型,让正例间的距离小,负例间的距离大

image.png

向量间的相似度计算

image.png

我们用检索关键词和一组文本的样例来看下效果

余弦距离和欧氏距离的核心逻辑

python代码解读复制代码def cos_sim(a, b):'''余弦距离 -- 越大越相似'''return dot(a, b)/(norm(a)*norm(b))def l2(a, b):'''欧氏距离 -- 越小越相似'''x = np.asarray(a)-np.asarray(b)return norm(x)def get_embeddings(texts, model="text-embedding-ada-002", dimensions=None):'''封装 OpenAI 的 Embedding 模型接口'''if model == "text-embedding-ada-002":dimensions = Noneif dimensions:data = client.embeddings.create(input=texts, model=model, dimensions=dimensions).dataelse:data = client.embeddings.create(input=texts, model=model).datareturn [x.embedding for x in data]# query = "国际争端"# 且能支持跨语言
query = "global conflicts"documents = ["联合国就苏丹达尔富尔地区大规模暴力事件发出警告","土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判","日本岐阜市陆上自卫队射击场内发生枪击事件 3人受伤","国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营","我国首次在空间站开展舱外辐射生物学暴露实验",
]

执行并输出结果

python代码解读复制代码query_vec = get_embeddings([query])[0]
doc_vecs = get_embeddings(documents)print("Query与自己的余弦距离: {:.2f}".format(cos_sim(query_vec, query_vec)))
print("Query与Documents的余弦距离:")
for vec in doc_vecs:print(cos_sim(query_vec, vec))print()print("Query与自己的欧氏距离: {:.2f}".format(l2(query_vec, query_vec)))
print("Query与Documents的欧氏距离:")
for vec in doc_vecs:print(l2(query_vec, vec))

我们来看下执行的效果:

python代码解读复制代码Query与自己的余弦距离: 1.00
Query与Documents的余弦距离:
0.7622749944010915(越大越相似)
0.7563038106493584
0.7426665802579038
0.7079273699608006
0.7254355321045072Query与自己的欧氏距离: 0.00
Query与Documents的欧氏距离:
0.6895288502682277(越小越相似)
0.6981349637998769
0.7174028746492277
0.7642939833636829
0.7410323668625171

向量数据库

向量数据库(Vector Database),也叫矢量数据库,主要用来存储和处理向量数据。

再结合刚才我们对向量定义的描述,图像、文本和音视频这种非结构化数据都可以通过某种变换或者嵌入学习转化为向量数据存储到向量数据库中,从而实现对图像、文本和音视频的相似性搜索和检索。

这意味着您可以使用向量数据库根据语义或上下文含义查找最相似或相关的数据,而不是使用基于精确匹配或预定义标准查询数据库的传统方法。也就是我们提到的关键字检索的局限性。
在这里插入图片描述

向量数据库的特点

这里我们为了方便使用向量数据库完成向量检索的方案,简单介绍下向量数据库的特点:

向量数据库的主要特点是高效存储与检索。利用索引技术和向量检索算法能实现高维大数据下的快速响应。向量数据库也是一种数据库,除了要管理向量数据外,还是支持对传统结构化数据的管理。实际使用时,有很多场景会同时对向量字段和结构化字段进行过滤检索,这对向量数据库来说也是一种挑战。

严格来说数据向量化本不属于向量数据库,但是数据向量化又是一项很重要的工作,为了流程的完整性暂且放进去。区别与传统数据库主要有以下几个地方不相同:数据向量化,向量检索和相似度计算。

chromadb的简单介绍

之所以介绍一下chromadb,下面我们的实战demo就是基于chromadb来实现。

Chroma的目标是帮助用户更加便捷地构建大模型应用,更加轻松的将知识(knowledge)、事实(facts)和技能(skills)等我们现实世界中的文档整合进大模型中。

Chroma提供的工具:

  • 存储文档数据和它们的元数据:store embeddings and their metadata
  • 嵌入:embed documents and queries
  • 搜索: search embeddings

流向量数据库功能对比

由于大模型的火热,现在市面上的向量数据库众多,主流的向量数据库对比如下所示:

向量数据库URLGitHub StarLanguage
chromagithub.com/chroma-core…7.4KPython
milvusgithub.com/milvus-io/m…21.5KGo/Python/C++
pineconewww.pinecone.io/
qdrantgithub.com/qdrant/qdra…11.8KRust
typesensegithub.com/typesense/t…12.9KC++
weaviategithub.com/weaviate/we…6.9KGo

表格引用自:一文全面了解向量数据库的基本概念、原理、算法、选型

一个基于文档向量检索的RAG实战例子

我们再回顾RAG的基本流程,对照如下例子,大家就可以更好理解了

RAG系统搭建的基本流程

  1. 准备对应的垂域资料
  2. 文档的读取解析,进行文档切分
  3. 将分割好的文本灌入检索引擎(向量数据库)
  4. 封装检索接口
  5. 构建流程:Query -> 检索 -> Prompt -> LLM -> 回复
  6. 文档加载
python代码解读复制代码def extract_text_from_pdf(filename,page_numbers=None,min_line_length=10):"""从 PDF 文件中(按指定页码)提取文字"""paragraphs = []buffer = ''full_text = ''# 提取全部文本for i, page_layout in enumerate(extract_pages(filename)):# 如果指定了页码范围,跳过范围外的页if page_numbers is not None and i not in page_numbers:continuefor element in page_layout:if isinstance(element, LTTextContainer):full_text += element.get_text() + '\n'# 按空行分隔,将文本重新组织成段落lines = full_text.split('\n')for text in lines:if len(text) >= min_line_length:buffer += (' '+text) if not text.endswith('-') else text.strip('-')elif buffer:paragraphs.append(buffer)buffer = ''if buffer:paragraphs.append(buffer)return paragraphs
  1. 文档切割(交叠切割防止问题的答案跨两个片段,使上下文更完整)
python代码解读复制代码def split_text(paragraphs,chunk_size=300,overlap_size=100):"""按指定 chunk_size 和 overlap_size 交叠割文本"""sentences = [s.strip() for p in paragraphs for s in sent_tokenize(p)]chunks = []i= 0while i < len(sentences):chunk = sentences[i]overlap = ''prev_len = 0prev = i - 1# 向前计算重叠部分while prev >= 0 and len(sentences[prev])+len(overlap) <= overlap_size:overlap = sentences[prev] + ' ' + overlapprev -= 1chunk = overlap+chunknext = i + 1# 向后计算当前chunkwhile next < len(sentences) and len(sentences[next])+len(chunk) <= chunk_size:chunk = chunk + ' ' + sentences[next]next += 1chunks.append(chunk)i = nextreturn chunks
  1. 向量化(这里使用openai的向量化模型)
python代码解读复制代码def get_embedding(text, model="text-embedding-ada-002"):"""封装 OpenAI 的 Embedding 模型接口"""return openai.Embedding.create(input=[text], model=model)['data'][0]['embedding']
  1. 灌入向量库(使用chromadb)
python代码解读复制代码def __init__(self, name="demo"):self.chroma_client = chromadb.Client(Settings(allow_reset=True))self.chroma_client.reset()self.name = nameself.collection = self.chroma_client.get_or_create_collection(name=name)def add_documents(self, documents):self.collection.add(embeddings=[get_embedding(doc) for doc in documents],documents=documents,metadatas=[{"source": self.name} for _ in documents],ids=[f"id_{i}" for i in range(len(documents))])
  1. 检索向量数据库
python代码解读复制代码def search(self, query, top_n):"""检索向量数据库"""results = self.collection.query(query_embeddings=[get_embedding(query)],n_results=top_n)return results['documents'][0]
  1. 将检索数据带入提示词
python代码解读复制代码def build_prompt(template=prompt_template, **kwargs):"""将 Prompt 模板赋值"""prompt = templatefor k, v in kwargs.items():if isinstance(v, str):val = velif isinstance(v, list) and all(isinstance(elem, str) for elem in v):val = '\n'.join(v)else:val = str(v)prompt = prompt.replace(f"__{k.upper()}__", val)return prompt
  1. 调用大模型
python代码解读复制代码def get_completion(prompt, context, model="gpt-3.5-turbo"):"""封装 openai 接口"""messages = context + [{"role": "user", "content": prompt}]response = openai.ChatCompletion.create(model=model,messages=messages,temperature=0,  # 模型输出的随机性,0 表示随机性最小)return response.choices[0].message["content"]

向量检索与关键字检索的对比

维度向量检索关键字检索
检索方式基于文档和查询之间的相似度计算来进行检索。文档和查询通常被表示为高维空间中的向量,通过计算它们之间的相似度来确定最相关的文档。是通过匹配查询中的关键字与文档中的关键字来进行检索。当用户输入一个查询时,系统会在文档集合中查找包含这些关键字的文档,并将它们返回给用户。
表示方式需要将文档和查询转换成向量形式,这通常通过词袋模型或词嵌入等技术实现,其中每个维度代表一个词汇项,而向量的值通常表示词频或TF-IDF权重。不需要对文档和查询进行特殊的表示,而是直接基于文档中的关键字与查询中的关键字进行匹配。
匹配精度通常能够提供更精确的匹配,因为它考虑了文档和查询之间的语义相似度,而不仅仅是关键字的匹配。可能会受到一些问题的影响,例如同义词、拼写错误等,这可能会导致一些相关的文档被漏掉或者一些不相关的文档被检索到。
使用范围通常在需要考虑语义相似度的情况下使用,例如在自然语言处理领域中的文档检索、语义搜索等方面。更适合简单的检索场景,例如在搜索引擎中用户输入关键字进行网页检索。

总的来说,向量检索更适合处理复杂的语义匹配问题,而关键字检索则更适合简单的关键字匹配需求

总结

8921551d5b8de132f1425831f724536aee3a256f.jpg 本文的分享结束,也代表着我们对向量检索的RAG如何实现,向量化以及向量数据库,同时用一个实战的例子讲解了向量检索的RAG如何完成。

再结合之前的文章我们对于关键字检索的RAG的讲解,我们能够充分的了解RAG的两种实现方式,以及他们之间的对比。

RAG的核心在于检索增强,而检索增强的主要手段是知识库,我们引入外部知识库可以是ES类似的关键字检索,也可以是Chroma类似的向量检索。

RAG已经是AIGC当中热门又尤为重要的一个方向,希望我们对于我们提高我们大模型的性能有所帮助。

大模型资源分享

针对所有自学遇到困难的同学,我为大家系统梳理了大模型学习的脉络,并且分享这份LLM大模型资料:其中包括LLM大模型书籍、640套大模型行业报告、LLM大模型学习视频、LLM大模型学习路线、开源大模型学习教程等。😝有需要的小伙伴,可以扫描下方二维码免费领取↓↓↓

在这里插入图片描述

一、全套 AGI 大模型学习路线

AI 大模型时代的精彩学习之旅:从根基铸就到前沿探索,牢牢掌握人工智能核心技能!

在这里插入图片描述

二、640 套 AI 大模型报告合集

此套涵盖 640 份报告的精彩合集,全面涉及 AI 大模型的理论研究、技术实现以及行业应用等诸多方面。无论你是科研工作者、工程师,还是对 AI 大模型满怀热忱的爱好者,这套报告合集都将为你呈上宝贵的信息与深刻的启示。

在这里插入图片描述

三、AI 大模型经典 PDF 书籍

伴随人工智能技术的迅猛发展,AI 大模型已然成为当今科技领域的一大热点。这些大型预训练模型,诸如 GPT-3、BERT、XLNet 等,凭借其强大的语言理解与生成能力,正在重塑我们对人工智能的认知。而以下这些 PDF 书籍无疑是极为出色的学习资源。
在这里插入图片描述
在这里插入图片描述

阶段 1:AI 大模型时代的基础认知

  • 目标:深入洞悉 AI 大模型的基本概念、发展历程以及核心原理。

  • 内容

    • L1.1 人工智能概述与大模型起源探寻。
    • L1.2 大模型与通用人工智能的紧密关联。
    • L1.3 GPT 模型的辉煌发展历程。
    • L1.4 模型工程解析。
    • L1.4.1 知识大模型阐释。
    • L1.4.2 生产大模型剖析。
    • L1.4.3 模型工程方法论阐述。
    • L1.4.4 模型工程实践展示。
    • L1.5 GPT 应用案例分享。

阶段 2:AI 大模型 API 应用开发工程

  • 目标:熟练掌握 AI 大模型 API 的运用与开发,以及相关编程技能。

  • 内容

    • L2.1 API 接口详解。
    • L2.1.1 OpenAI API 接口解读。
    • L2.1.2 Python 接口接入指南。
    • L2.1.3 BOT 工具类框架介绍。
    • L2.1.4 代码示例呈现。
    • L2.2 Prompt 框架阐释。
    • L2.2.1 何为 Prompt。
    • L2.2.2 Prompt 框架应用现状分析。
    • L2.2.3 基于 GPTAS 的 Prompt 框架剖析。
    • L2.2.4 Prompt 框架与 Thought 的关联探讨。
    • L2.2.5 Prompt 框架与提示词的深入解读。
    • L2.3 流水线工程阐述。
    • L2.3.1 流水线工程的概念解析。
    • L2.3.2 流水线工程的优势展现。
    • L2.3.3 流水线工程的应用场景探索。
    • L2.4 总结与展望。

阶段 3:AI 大模型应用架构实践

  • 目标:深刻理解 AI 大模型的应用架构,并能够实现私有化部署。

  • 内容

    • L3.1 Agent 模型框架解读。
    • L3.1.1 Agent 模型框架的设计理念阐述。
    • L3.1.2 Agent 模型框架的核心组件剖析。
    • L3.1.3 Agent 模型框架的实现细节展示。
    • L3.2 MetaGPT 详解。
    • L3.2.1 MetaGPT 的基本概念阐释。
    • L3.2.2 MetaGPT 的工作原理剖析。
    • L3.2.3 MetaGPT 的应用场景探讨。
    • L3.3 ChatGLM 解析。
    • L3.3.1 ChatGLM 的特色呈现。
    • L3.3.2 ChatGLM 的开发环境介绍。
    • L3.3.3 ChatGLM 的使用示例展示。
    • L3.4 LLAMA 阐释。
    • L3.4.1 LLAMA 的特点剖析。
    • L3.4.2 LLAMA 的开发环境说明。
    • L3.4.3 LLAMA 的使用示例呈现。
    • L3.5 其他大模型介绍。

阶段 4:AI 大模型私有化部署

  • 目标:熟练掌握多种 AI 大模型的私有化部署,包括多模态和特定领域模型。

  • 内容

    • L4.1 模型私有化部署概述。
    • L4.2 模型私有化部署的关键技术解析。
    • L4.3 模型私有化部署的实施步骤详解。
    • L4.4 模型私有化部署的应用场景探讨。

学习计划:

  • 阶段 1:历时 1 至 2 个月,构建起 AI 大模型的基础知识体系。
  • 阶段 2:花费 2 至 3 个月,专注于提升 API 应用开发能力。
  • 阶段 3:用 3 至 4 个月,深入实践 AI 大模型的应用架构与私有化部署。
  • 阶段 4:历经 4 至 5 个月,专注于高级模型的应用与部署。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146659.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode题练习与总结:回文链表--234

一、题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#x…

书籍阅读—影响力(一):如何让你的提议或要求被别人采纳?90%的人都会试的一种方法

问题背景 比方说&#xff0c;我们遇到一个这样的问题&#xff0c;大家参加了一个演讲&#xff0c;主办方希望每个人都写总结然后给到他&#xff0c;这样有助于参与者加深对课堂内容的理解&#xff0c;以及主办方也可以了解到这一次的演讲是否开得有意义。所以主办方这边就发送…

如何使用智能代码编辑器改变编程体验

你是否曾经在深夜加班时&#xff0c;望着屏幕上密密麻麻的代码&#xff0c;感到无比疲惫&#xff1f;或者在处理复杂项目时&#xff0c;被繁琐的代码管理和调试过程折磨得头痛不已&#xff1f;如果是这样&#xff0c;那么你可能还没有发现编程世界中的一个秘密武器——智能代码…

软考高级:逻辑地址和物理地址转换 AI解读

一、题目 设某进程的段表如下所示&#xff0c;逻辑地址&#xff08; &#xff09;可以转换为对应的物理地址。 A. &#xff08;0&#xff0c;1597&#xff09;、&#xff08;1&#xff0c;30&#xff09;和&#xff08;3&#xff0c;1390&#xff09; B. &#xff08;0&…

【设计模式-备忘录】

备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;用于保存对象的内部状态&#xff0c;以便在将来某个时间可以恢复到该状态&#xff0c;而不暴露对象的内部实现细节。备忘录模式特别适合在需要支持撤销&#xff08;Undo&#xff09;操作的应…

Anthropic介绍Contextual Retrieval

人工智能模型要想在特定环境中发挥作用&#xff0c;往往需要获取背景知识。 例如&#xff0c;客户支持聊天机器人需要了解具体的业务&#xff0c;而法律分析机器人则需要了解大量的过往案例。 开发人员通常使用检索增强生成&#xff08;RAG&#xff09;来增强人工智能模型的知…

LEAN 赋型唯一性(Unique Typing)之 Church-Rosser 定理 (Church-Rosser Theorem)及 赋型唯一性的证明

有了并行K简化的概念及其属性&#xff0c;以及其在LEAN类型理论中的相关证明&#xff0c;就可以证明&#xff0c;在K简化下的Church-Rosser 定理。即&#xff1a; 其过程如下&#xff1a; 证明如下&#xff1a; 其中的 lemma 4.9 和 4.10 &#xff0c;及 4.8 是 这整个证明过程…

ImportError: /lib/x86 64-linux-gnu/libm.so.6:version GLIBc 2.29‘ not found

一、概述 在编译时出现一些问题&#xff0c;在网上搜索之后&#xff0c;对问题进行整理记录。 二、具体解决方法 &#xff08;一&#xff09;问题 如图所示&#xff0c;在编译过程中出现如下的问题。 &#xff08;二&#xff09;问题分析 通过在网络查询&#xff0c;在github…

后端-navicat查找语句(单表与多表)

表格字段设置如图 语句&#xff1a; 1.输出 1.输出name和age列 SELECT name,age from student 1.2.全部输出 select * from student 2.where子语句 1.运算符&#xff1a; 等于 >大于 >大于等于 <小于 <小于等于 ! <>不等于 select * from stude…

传统软件在定制化方面有哪些优势,SaaS 软件如何克服这一劣势?

一、传统软件在定制化优势 传统软件在定制化方面的优势主要体现在以下几个方面&#xff1a; 个性化需求满足&#xff1a;传统软件可以根据客户的特定需求进行个性化定制&#xff0c;提供定制化的解决方案&#xff0c;满足客户的业务流程和功能需求。灵活性和扩展性&#xff1a…

使用 Vue 3、Vite 和 TypeScript 的环境变量配置

使用 Vue 3、Vite 和 TypeScript 的环境变量配置 在开发现代前端应用时&#xff0c;环境变量是一个非常重要的概念。它可以帮助我们根据不同的环境&#xff08;开发、测试、生产&#xff09;配置不同的行为&#xff0c;比如 API 请求地址、调试选项等。在 Vue 3、Vite 和 Type…

一个.NET开发且功能强大的Windows远程控制系统

项目介绍 SiMayRemoteMonitorOS是一个基于Windows的远程控制系统&#xff0c;完全采用C#.NET开发&#xff0c;遵循AGPL-3.0开源协议。 核心功能 远程桌面&#xff1a;基于逐行扫描算法&#xff0c;提供流畅的远程桌面体验&#xff0c;支持多屏幕切换&#xff0c;以及全屏监控…

【C++】类和对象(下):再探构造函数、类型转换、static成员、友元、内部类、匿名对象、拷贝对象时编译器的优化

这篇博文是C中类和对象的最后一些知识&#xff0c;包括再探构造函数、类型转换、static成员、友元、内部类、匿名对象、拷贝对象时编译器的优化这些知识点。 1.再探构造函数 之前我们实现构造函数时&#xff0c;初始化成员变量主要是使用函数体内赋值&#xff0c;构造函数初始化…

neo4j:ubuntu环境下的安装与使用

一、neo4j安装 1. 下载安装包 进入网站&#xff1a;https://neo4j.com/deployment-center/#community 在上图中选择下载即可&#xff08;社区版免费&#xff09; 注意&#xff1a;neo4j的版本要和电脑安装的jdk版本对应&#xff0c;jdk版本使用java --version查看&#xff1a;…

计算机视觉的应用34-基于CV领域的人脸关键点特征智能提取的技术方法

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用34-基于CV领域的人脸关键点特征智能提取的技术方法。本文主要探讨计算机视觉领域中人脸关键点特征智能提取的技术方法。详细介绍了基于卷积神经网络模型进行人脸关键点提取的过程&#xff0c;包括使…

长列表加载性能优化

一、长列表优化概述 列表是应用开发中最常见的一类开发场景&#xff0c;它可以将杂乱的信息整理成有规律、易于理解和操作的形式&#xff0c;便于用户查找和获取所需要的信息。应用程序中常见的列表场景有新闻列表、购物车列表、各类排行榜等。随着信息数据的累积&#xff0c;特…

基于SpringBoot的漫画网设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

Java 每日一刊(第14期):抽象类和接口

“抽象是所有能力的精髓。” 前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内容&#xff1a; 抽象类接口抽象类和接口的区别什么时候用抽象类&#xff0c;什么时候用接口抽象类可以实现接口接口中的常量其实是 public static final标记…

C语言图形编程:构建视觉效果与应用

引言 在计算机科学的领域中&#xff0c;C语言凭借其简洁、高效以及对底层硬件的强大控制能力&#xff0c;一直是系统级编程的首选语言之一。尽管近年来出现了许多高级语言&#xff0c;但C语言仍然在多个领域占据着重要地位&#xff0c;特别是在图形编程方面。本文将深入探讨如…

粒子向上持续瀑布动画效果(直接粘贴到记事本改html即可)

代码&#xff1a; 根据个人喜好修改即可 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>宽粒子向上…