简易指南:迅速构建个性化RAG(Retrieval-Augmented Generation)应用

前面的章节,我们已经完成了可用的基于知识库回答的ai助手,尽管RAG容易上手,但是要真正掌握其精髓却颇有难度,实际上,建立一个的有效的RAG系统不仅仅是将文档放入向量数据库并叠加一个llm模型那么简单,这种方式知识时而有效而已。比如我们问些复杂点问题:

可以看llm的回答的确是相当不如意,提示词的内容并不全面。

在揭开解决方案的神秘面纱之前,我们先来探索一下这个问题的核心。想象一下,你有一个巨大的图书馆,里面有数十亿本书,而你的任务是在这个图书馆中找到与你的研究主题最相关的资料。这就是RAG(Retrieval-Augmented Generation)模型的工作——在大规模的文本海洋中进行语义搜索。

为了在这个巨大的图书馆中快速找到答案,我们使用了一种叫做向量搜索的技术。这就像是在一个多维空间中,把每本书的内容压缩成一个小小的向量,然后通过计算这些向量与你的查询向量之间的距离(比如用余弦相似度),来找到最接近的那本书。但是,这里有个小问题。当我们把书的内容压缩成向量时,就像是把一个丰富多彩的故事变成了黑白照片,总会有一些细节丢失。所以,有时候即使是最接近的三本书,也可能遗漏了一些关键的线索。如果那些排名靠后的书里藏着宝藏般的信息,我们该怎么办呢?

一个直观的想法是,把更多的书带回家(增加top_k值),然后把它们一股脑儿地交给我们的“大语言模型”。但是,我们真正关心的是召回率,也就是“我们找到了多少真正相关的书”。召回率并不在乎我们带回了多少本书,它只关心我们是否找到了所有相关的书。理论上,如果我们把图书馆里的每一本书都带回家,我们就能达到完美的召回率。然而,现实是残酷的。我们的“大语言模型”就像是一个只能装下有限信息的背包,我们称之为上下文窗口。即使是最先进的模型,比如Anthropic的Claude,它的背包可以装下100K Token(可以想象成100K个信息块),我们还是不能把所有的书都塞进去。可以参考下面这个图:

图表达的意思是如果信息被放置在上下文窗口的中间位置,那么模型回忆或检索这些信息的能力会降低,其效果甚至不如这些信息从未被提供给模型。这里的“上下文窗口”指的是模型在处理语言时能够考虑的文本范围,通常是一个固定长度的序列。

这种现象可能是因为在上下文窗口中间的信息相比于靠近窗口开始或结束位置的信息,更容易被后续输入的信息所覆盖或干扰,从而导致模型在需要时难以准确地回忆起这些信息。这表明,在设计或使用大型语言模型时,信息在上下文中的位置可能会影响模型的性能,特别是对于需要长期依赖或记忆的任务。

举个例子:

假设我们有一个大型语言模型(LLM),它的上下文窗口长度为10个句子。我们想要模型根据一段对话来回答问题。对话内容如下:

  1. 小明说:“我昨天去了图书馆。”

  2. 小华问:“你借了什么书?”

  3. 小明回答:“我借了一本关于历史的书。”

  4. 小华又问:“那本书是关于哪个时期的?”

  5. 小明说:“是关于古罗马的。”

  6. 小华说:“听起来很有趣。”

  7. 小明补充:“是的,书中有很多关于罗马帝国的细节。”

  8. 小华问:“你打算什么时候还书?”

  9. 小明回答:“下周三。”

  10. 小华说:“我可能也会去借那本书。”

现在,我们要求模型回答问题:“小明借的书是关于什么的?”

如果我们将这个问题放在上下文窗口的中间(例如,在第5句和第6句之间),模型可能会因为后续的对话内容(如小华对书的兴趣、还书日期等)而分散注意力,导致它回忆起小明借的书是关于古罗马的能力降低。相比之下,如果问题紧跟在第3句或第5句之后,模型可能更容易直接关联到小明借的书的内容,因为它还没有被后续的对话内容所干扰。

这个例子说明了在上下文窗口中间存储的信息可能会受到后续信息的干扰,从而影响模型回忆这些信息的能力。这也强调了在设计交互式或连续对话系统时,合理安排信息在上下文中的位置对于提高模型性能的重要性。

LLM(大型语言模型)的回忆能力指的是它从其上下文窗口内的文本中检索信息的能力。研究表明,随着我们在上下文窗口中放置更多的令牌(tokens),LLM的回忆能力会下降。当我们过度填充上下文窗口时,LLM也更不可能遵循指令——因此,过度填充上下文窗口是一个糟糕的想法。

我们可以通过增加向量数据库返回的文档数量来提高检索回忆率,但我们不能在不损害LLM回忆能力的情况下将这些文档传递给LLM。

解决这个问题的方法是,通过检索大量文档来最大化检索回忆率,然后通过最小化传递给LLM的文档数量来最大化LLM的回忆能力。为了做到这一点,可以采用以下方案:

1. 使用合适的切分器

在前面的例子中,我使用了

# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=30)

RecursiveCharacterTextSplitter:此文本拆分器是推荐用于通用文本的拆分器。它通过一个字符列表参数化,并尝试按顺序在这些字符上拆分,直到块足够小。默认的字符列表是[“\n\n”, “\n”, " ", “”]。这样做的效果是尽可能长时间地保持所有段落(然后是句子,然后是单词)在一起,因为这些通常看起来是最具有语义相关性的文本部分。它不仅基于一系列预设的字符进行拆分,而且能够递归地处理文本,确保按照给定的chunk_size(块大小)和chunk_overlap(块重叠)参数优化拆分结果。这种方法特别适用于需要保持文本内在结构和语义连贯性的场景,如处理段落、句子等结构化文本。

合理地分割文档需要考虑以下因素:

  • 块大小:块的大小应该适中,既要足够小以适应模型的上下文长度限制,又要足够大以包含足够的信息。通常,块的大小在几百个单词左右。
  • 语义完整性:分割时应尽量保持每个块内的语义完整性。这意味着应该避免将一个句子或一个概念分割到两个不同的块中。
  • 重复内容:在分割时应该避免在不同的块中重复相同的内容,除非这是文档结构的一部分(例如,法律文件中的条款可能会在多个部分重复)。
  • 文档结构:考虑文档的结构,如章节、子章节、段落等,可以帮助确定分割点。通常,可以在章节或段落边界处进行分割。
  • 关键词和实体:在分割时,可以考虑文档中的关键词和实体,确保它们不会被分割到不同的块中,以便在检索时能够准确地匹配到相关信息。

总之,合理地分割文档是构建高效、准确的RAG AI助手的关键步骤之一。通过考虑文档的内容、结构和模型的限制,可以创建出既高效又准确的文档分割策略。这个过程是不断调试的过程。为了更加贴合中文文档的格式,我们可以尝试使用重写过的切分器:

from langchain.text_splitter import CharacterTextSplitter
import re
from typing import List#该方案出自qanyting开源项目class ChineseTextSplitter(CharacterTextSplitter):def __init__(self, pdf: bool = False, sentence_size: int = 100, **kwargs):super().__init__(**kwargs)self.pdf = pdfself.sentence_size = sentence_sizedef split_text(self, text: str) -> List[str]:if self.pdf:text = re.sub(r"\n{3,}", r"\n", text)text = re.sub('\s', " ", text)text = re.sub("\n\n", "", text)text = re.sub(r'([;;.!?。!??])([^”’])', r"\1\n\2", text)  # 单字符断句符text = re.sub(r'(.{6})([^"’”」』])', r"\1\n\2", text)  # 英文省略号text = re.sub(r'(\…{2})([^"’”」』])', r"\1\n\2", text)  # 中文省略号text = re.sub(r'([;;!?。!??]["’”」』]{0,2})([^;;!?,。!??])', r'\1\n\2', text)# 如果双引号前有终止符,那么双引号才是句子的终点,把分句符\n放到双引号后,注意前面的几句都小心保留了双引号text = text.rstrip()  # 段尾如果有多余的\n就去掉它# 很多规则中会考虑分号;,但是这里我把它忽略不计,破折号、英文双引号等同样忽略,需要的再做些简单调整即可。ls = [i for i in text.split("\n") if i]for ele in ls:if len(ele) > self.sentence_size:ele1 = re.sub(r'([,,.]["’”」』]{0,2})([^,,.])', r'\1\n\2', ele)ele1_ls = ele1.split("\n")for ele_ele1 in ele1_ls:if len(ele_ele1) > self.sentence_size:ele_ele2 = re.sub(r'([\n]{1,}| {2,}["’”」』]{0,2})([^\s])', r'\1\n\2', ele_ele1)ele2_ls = ele_ele2.split("\n")for ele_ele2 in ele2_ls:if len(ele_ele2) > self.sentence_size:ele_ele3 = re.sub('( ["’”」』]{0,2})([^ ])', r'\1\n\2', ele_ele2)ele2_id = ele2_ls.index(ele_ele2)ele2_ls = ele2_ls[:ele2_id] + [i for i in ele_ele3.split("\n") if i] + ele2_ls[ele2_id + 1:]ele_id = ele1_ls.index(ele_ele1)ele1_ls = ele1_ls[:ele_id] + [i for i in ele2_ls if i] + ele1_ls[ele_id + 1:]id = ls.index(ele)ls = ls[:id] + [i for i in ele1_ls if i] + ls[id + 1:]return ls

对比效果,几乎全做到了按照段落去切分

2. 对检索到的文档进行重新排序,并只为我们的LLM保留最相关的文档——为了做到这一点,我们使用重新排序(reranking)技术。

重排序模型,是能够针对一个查询和文档对,输出它们的相似度分数。我们利用这个分数对文档按照与查询的相关性进行重新排序。简单理解的意思就是:对embedding检索器出来的chunks再次通过重排序模型rerank按照分数排序后,筛选出相似度最高的chunks作为提示词输入。这也叫两阶段检索系统。

rerank模型与embedding模型的区别
embedding模型rerank模型
检索原理1、把文档A向量化2、把问题B向量化3、对比问题B与文档A的向量值,检索出值与B问题类似的文档,并得出分数1、将查询和某个文档直接输入到Transformer中,进行一整个推理步骤,并最终生成一个相似度分数。
优点检索速度快准确性高,1v1的vip服务,能更准确理解上下文的意思
缺点准确性低,将高维的文本数据压缩到较低维度的向量空间中,这无疑导致了信息的丢失。此外,由于查询是在收到后才知道的,对查询的上下文一无所知(我们是在用户提出查询之前就已经创建了嵌入)。检索速度慢
总结:Embedding模型可以提供有用的信息(粗排),但Rerankers模型通过考虑更多的上下文信息、用户意图和复杂的特征交互,能够提供更精确的排序结果(细排)。在实际应用中,这两种模型通常是互补的,结合使用可以提高整个信息检索系统的性能。

rerank模型有:

模型名称Reranking平均
bge-reranker-base57.7857.78
bge-reranker-large59.6959.69
bce-reranker-base_v160.0660.06

模型可以在魔搭社区下载

代码示例

import os
from typing import Listimport nltk
from langchain_community.document_loaders import UnstructuredWordDocumentLoader
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pypinyin import pinyin, Stylefrom LlmClient import LlmClient
from RerankModel import RerankerModel
from configs import rerank_model_path, embedding_path, filepath
from splitter.chinese_text_splitter import ChineseTextSplitternltk_data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'zhipu_chat/nltk_data')
nltk.data.path.insert(0, nltk_data_path)# 创建嵌入模型
embeddings = HuggingFaceEmbeddings(model_name=embedding_path)# 获取文件名
file_name = os.path.basename(filepath)
# 将文件名转换为拼音
pinyin_names = pinyin(file_name, style=Style.NORMAL)
# 生成数据库id
kb_id = ''.join([item[0] for item in pinyin_names]).replace('.', '_')faiss_index_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), kb_id, 'faiss_index')def merge_splits(docs) -> List:new_docs = []for doc in docs:if not new_docs:new_docs.append(doc)else:last_doc = new_docs[-1]if len(last_doc.page_content) + len(doc.page_content) < 200:last_doc.page_content += '\n' + doc.page_contentelse:new_docs.append(doc)splitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n", "。", "!", "!", "?", "?", ";", ";", "……", "…", "、", ",", ",", " ", ""],chunk_size=400,chunk_overlap=100,)end_docs = splitter.split_documents(new_docs)return end_docsif os.path.exists(faiss_index_path):print("Index loaded from:", faiss_index_path)index = FAISS.load_local(folder_path=faiss_index_path, embeddings=embeddings, allow_dangerous_deserialization=True)
else:loader = UnstructuredWordDocumentLoader(filepath)# 创建文本分割器text_splitter = ChineseTextSplitter()# 分割文本splits = loader.load_and_split(text_splitter)# 再次分割处理splits = merge_splits(splits)# 创建索引index = FAISS.from_texts(texts=[doc.page_content for doc in splits],embedding=embeddings)# 保存索引index.save_local(folder_path=faiss_index_path)print("Index saved to:", faiss_index_path)# 基于问题检索出类似的文档段落,喂给llm,llm经过推理后获取答案
llm_client = LlmClient()
while True:user_input = input("请输入文字,按回车键确认:")# 检查用户是否想要退出if user_input.lower() == 'exit':print("程序退出。")break# 执行相似性搜索,并返回与给定查询最相似的前k个结果。doc_score = index.similarity_search_with_score(user_input, k=30)for doc, score in doc_score:doc.metadata['score'] = scoredocs = [doc for doc, score in doc_score]retrieval_documents = sorted(docs, key=lambda x: x.metadata['score'], reverse=True)reranker_model = RerankerModel(rerank_model_path)scores = reranker_model.score_pairs([(user_input, doc.page_content) for doc in retrieval_documents])for doc, score in zip(retrieval_documents, scores):doc.metadata['reranker_score'] = score.tolist()# 排序rerank_documents = sorted(retrieval_documents, key=lambda x: x.metadata['reranker_score'], reverse=True)# 删除分数小于0.35的文档rerank_documents = [doc for doc in rerank_documents if doc.metadata['reranker_score'] > 0.35]# 只拿前面7个rerank_documents = retrieval_documents[: 7]# 调用llm优化提示词llm_client.query(prompt=';'.join(doc.page_content for doc in rerank_documents),user_input=user_input)# 调用llm回答llm_client.query(prompt=';'.join(doc.page_content for doc in rerank_documents),user_input=user_input)

下面我们再来问问:提供了那些岗位?

在这里插入图片描述

大模型&AI产品经理如何学习

求大家的点赞和收藏,我花2万买的大模型学习资料免费共享给你们,来看看有哪些东西。

1.学习路线图

在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己整理的大模型视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

在这里插入图片描述

在这里插入图片描述

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方图片前往获取

3.技术文档和电子书

这里主要整理了大模型相关PDF书籍、行业报告、文档,有几百本,都是目前行业最新的。
在这里插入图片描述

4.LLM面试题和面经合集

这里主要整理了行业目前最新的大模型面试题和各种大厂offer面经合集。
在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
在这里插入图片描述

1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

心觉:赚钱是修行最快的一种方式

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松搞定人生挑战&#xff0c;实现心中梦想&#xff01; 挑战日更写作152/1000(完整记录在下面) 公门洞开纳百川 众心逐梦越千山 号召引领潜力绽 心觉潜意识无间 人生就是一场体…

【网络】P2P打洞原理

本文首发于 ❄️慕雪的寒舍 1. 引入 如果你折腾过NAS或者BT下载等等玩意&#xff0c;可能听说过“P2P打洞”这一技术名词。简单来说&#xff0c;P2P打洞可以让我们直接在外网访问内网的设备&#xff0c;从而让没有公网IP的家庭设备也能获得“公网直连”的速度。 比如绿联、极…

ES 根据条件删除文档

随着业务量的增多&#xff0c;es中数据越来越多&#xff0c;但有些数据其实后期并无业务用途&#xff0c;可直接做物理删除&#xff0c;程序里做兼容&#xff0c;但历史每个月的索引里的数据需要处理这部分冗余数据。 es提供_delete_by_query 根据查询条件进行删除的操作&…

呼入的电话通过http接口转接(mod_cti基于FreeSWITCH)

文章目录 前言联系我们配置流程1.呼入路由配置2.呼入安全配置3.配置生效规则4. 动作解析动作说明接口返回说明 5.创建拨号方案并启用 前言 呼叫流程&#xff1a;任意手机呼叫指定的号码&#xff0c;进入到中间件中&#xff0c;然后通过接口转接到对应的坐席分机中。接口作用&a…

警惕!血脂偏高,这些身体信号你不可不知!

在快节奏的现代生活中&#xff0c;高血脂&#xff0c;这个看似“沉默的杀手”&#xff0c;正悄然威胁着越来越多人的健康。它不像感冒发烧那样有明显的症状&#xff0c;却能在不知不觉中侵蚀血管&#xff0c;增加心血管疾病的风险。今天&#xff0c;我们就来揭开高血脂的神秘面…

探索最佳无代码低代码工具:加速 Web 应用开发

Web 应用无处不在。 从用户友好的在线表单到功能强大的企业级解决方案&#xff0c;Web 应用的多样性和复杂性不断增长。 随着低代码无代码技术的发展&#xff0c;构建一个 Web 应用的门槛正在大大降低。 对于刚踏入 Web 开发领域的人员来说&#xff0c;正确的低代码/无代码工…

【AI大模型】提示词(Prompt)全面解析

文章目录 前言前置准备&#xff08;非常重要&#xff09;一、Prompt 提示词介绍1.1 Prompt 的重要性 二、Prompt 提示词元素构成与实践2.1 关键字2.2 上下文2.3 格式要求2.4 实践示例 三、Prompt 提示词编写原理3.1 清晰性3.2 具体性3.3 适应性 四、Prompt 提示词编写常用的分隔…

游戏开发设计模式之装饰模式

目录 装饰模式在游戏开发中的具体应用案例是什么&#xff1f; 如何在Unity中实现装饰模式以动态扩展游戏对象的功能&#xff1f; 装饰模式与其他设计模式&#xff08;如适配器模式、代理模式&#xff09;相比&#xff0c;有哪些优势和劣势&#xff1f; 优势 劣势 与适配器…

错误使用 gretna_GUI_PreprocessInterface>RunBtn_Callback

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

【IC设计】跨时钟异步处理系列——单比特跨时钟

文章目录 建立时间和保持时间单比特信号的跨时钟处理慢时钟域的信号传输到快时钟域打两拍 快时钟域的信号传输到慢时钟域方案一 脉冲展宽同步 (打拍打拍&#xff0c;进行或)代码原理图 方案二 脉冲电平检测双触发器同步边沿检测代码原理图 建立时间和保持时间 所谓的建立时间或…

spring boot(学习笔记第十九课)

spring boot(学习笔记第十九课) Spring boot的batch框架&#xff0c;以及Swagger3(OpenAPI)整合 学习内容&#xff1a; Spring boot的batch框架Spring boot的Swagger3&#xff08;OpenAPI&#xff09;整合 1. Spring boot batch框架 Spring Batch是什么 Spring Batch 是一个…

<数据集>TACO垃圾识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6004张 标注数量(xml文件个数)&#xff1a;6004 标注数量(txt文件个数)&#xff1a;6004 标注类别数&#xff1a;18 标注类别名称&#xff1a;[Crankshaft, Centrifugal_body, Washer_container, Circlip_containe…

基于Java的高校学生工作系统的设计与实现(论文+源码)_kaic

摘 要 本系统为高校学生工作管理系统&#xff0c;系统能够为高校提供便捷的学生信息管理功能。该系统采用 Java 语言编写&#xff0c;系统采用MVC架构进行设计&#xff0c;通过Servlet和JSP等技术实现前后端数据交互和页面呈现&#xff1b;采用 Mysql作为后台数据库&#xff0…

出现Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are requiredProperty报错

目录&#xff1a; bug Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are requiredProperty报错解决方法 bug Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are requiredProperty 报错 在一个springboot demo启动的时候出现以下错误 &#xff0c;…

2024升级zblog小程序开源源码/基于uniapp开发的(支持微信小程序、百度小程序、安卓APP)

源码简介&#xff1a; 2024最新zblog多端小程序开源源码&#xff0c;它是基于uniapp开发的&#xff0c;它是针对和支持微信小程序、百度小程序和安卓APP哦&#xff01;百度百科小程序源码下载。 这个基于uniapp开发的zblog多端小程序开源源码&#xff0c;听说对收录和SEO都有…

Chrome 中的 RCE 会在 JIT 编译器中产生不正确的副作用

此类漏洞通常是“一键式”攻击的起点,当受害者访问恶意网站时,该漏洞会危害受害者的设备。Chrome 中的渲染器 RCE 允许攻击者危害 Chrome 渲染器进程并执行任意代码。但是,渲染器进程的权限有限,因此攻击者需要将此类漏洞与第二个“沙盒逃逸”漏洞串联起来:Chrome 浏览器进…

ssm基于微信小程序的付费自习室系统源码调试讲解

本项目包含程序源码数据库LW调试部署环境&#xff0c;文末可获取一份本项目的java源码和数据库参考。 系统的选题背景和意义 选题背景&#xff1a; 随着社会的发展和教育竞争的加剧&#xff0c;越来越多的学生和职场人士需要一个安静、舒适、专注的学习环境。然而&#xff0c;传…

STM32学习记录-08-USART串口

1 通信接口 通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统 通信协议:制定通信的规则,通信双方按照协议规则进行数据收发 USART:TX数据发送、RX数据接收 I2C:SCL时钟、SDA数据 SPI:SCLK时钟、MOSI主机输出、MISO主机输入、CS片选 CAN:CAN_H、CAN_L一对差分数据…

Reactor 模式的 Java 实现(feat. Scalable IO in Java - Doug Lea)

原文地址&#xff1a;http://hscarb.github.io/java/20240827-reactor-java.html Reactor 模式的 Java 实现&#xff08;feat. Scalable IO in Java - Doug Lea&#xff09; 1. 背景 Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想&#xff0c;大…

【计算机网络】计算机网络的分层结构

为什么要分层&#xff1f;为什么要制定协议&#xff1f; 计算机网络功能复杂→采用分层结构&#xff0c;将诸多功能合理地划分在不同层次→对等层之间制定协议&#xff0c;以实现功能。