一种基于滑动窗口扩展上下文的RAG(检索增强生成)优化实现方案实践

RAG(检索增强生成)是一种结合了检索(通常是知识库或数据库)和生成模型(大语言模型)的技术,目的是在生成文本的时候能够参考相关的外部知识。这样,即使生成模型在训练时没有看到某些信息,它也能在生成时通过检索到的知识来生成更加准确和丰富的回答,这篇文章实现一种基于动态上下文窗口的方案,能够处理大规模文档,保留重要的上下文信息,提升检索效率,同时保持灵活性和可配置性。

](https://link.juejin.cn/?target=https%3A%2F%2Fu.jd.com%2FV8pkqFY)

先看效果

演示效果

演示效果

整体方案

整体方案在于文档预处理阶段实现满足上下文窗口的原始文本分块,文档检索阶段实现文本的三次检索,下面逐一进行说明。测试文章来自 大语言模型的安全问题探究-提示词攻击

文档预处理过程

小文本块拆分

50 token大小(可根据自身文档之间的组织规律动态调整粒度)对文本做首次分割

ini复制代码# 小文档块大小
BASE_CHUNK_SIZE = 50
# 小块的重叠部分大小
CHUNK_OVERLAP = 0
def split_doc(
    doc: List[Document], chunk_size=BASE_CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP, chunk_idx_name: str
):
    data_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        # 使用了 tiktoken 来确保分割不会在一个 token 的中间发生
        length_function=tiktoken_len,
    )
    doc_split = data_splitter.split_documents(doc)
    chunk_idx = 0
    for d_split in doc_split:
        d_split.metadata[chunk_idx_name] = chunk_idx
        chunk_idx += 1
    return doc_split

下面示例显示了前 7 个分块信息,结果如下:

css

复制代码[Document(page_content='LLM 安全专题 提示词', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 0}),Document(page_content='是指在训练或与⼤型语⾔模型(Claude,ChatGPT等)进⾏交互时,提供给模', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 1}),Document(page_content='型的输⼊⽂本。通过给定特定的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 2}),Document(page_content='提示词,可以引导模型⽣成特定主题或类型的⽂本。在⾃然语⾔处理(NLP)任务中,提', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 3}),Document(page_content='示词充当了问题或输⼊的⻆⾊,⽽模型的输出是对这个问题的回答或完成的任务。关于', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 4}),Document(page_content='怎样设计好的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 5}),Document(page_content='Prompt,查看Prompt专题章节内容就可以了,我不在这⾥过多阐述,个⼈⽐较感兴趣针对', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 6}),...]
添加窗口

以步长为 3,窗口大小为 6,将上述步骤的小块匹配到不同的上下文窗口。

ini复制代码# 步长定义了窗口移动的速度,具体来说,它是上一个窗口中第一个块和下一个窗口中第一个块之间的距离
WINDOW_STEPS = 3
# 窗口大小直接影响到每个窗口中的上下文信息量,窗口大小= BASE_CHUNK_SIZE * WINDOW_SCALE
WINDOW_SCALE = 6
def add_window(
    doc: Document, window_steps=WINDOW_STEPS, window_size=WINDOW_SCALE, window_idx_name: str
):
    window_id = 0
    window_deque = deque()

    for idx, item in enumerate(doc):
        if idx % window_steps == 0 and idx != 0 and idx < len(doc) - window_size:
            window_id += 1
        window_deque.append(window_id)

        if len(window_deque) > window_size:
            for _ in range(window_steps):
                window_deque.popleft()

        window = set(window_deque)
        item.metadata[f"{window_idx_name}_lower_bound"] = min(window)
        item.metadata[f"{window_idx_name}_upper_bound"] = max(window)

下面示例显示了前 7 个增加窗口信息后的分块内容,结果如下

css

复制代码[Document(page_content='LLM 安全专题 提示词', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 0, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 0}),Document(page_content='是指在训练或与⼤型语⾔模型(Claude,ChatGPT等)进⾏交互时,提供给模', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 1, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 0}),Document(page_content='型的输⼊⽂本。通过给定特定的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 2, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 0}),Document(page_content='提示词,可以引导模型⽣成特定主题或类型的⽂本。在⾃然语⾔处理(NLP)任务中,提', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 3, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 1}),Document(page_content='示词充当了问题或输⼊的⻆⾊,⽽模型的输出是对这个问题的回答或完成的任务。关于', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 4, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 1}),Document(page_content='怎样设计好的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 5, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 1}),Document(page_content='Prompt,查看Prompt专题章节内容就可以了,我不在这⾥过多阐述,个⼈⽐较感兴趣针对', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 6, 'large_chunks_idx_lower_bound': 1, 'large_chunks_idx_upper_bound': 2}),Document(page_content='Prompt的攻击,随着⼤语⾔模型的⼴泛应⽤,安全必定是⼀个⾮常值', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'small_chunk_idx': 7, 'large_chunks_idx_lower_bound': 1, 'large_chunks_idx_upper_bound': 2}),...]

中等文本块

以小文本块 3 倍(可动态配置),即150 token大小对文本做二次分割,形成中等文本块

ini复制代码# 中等大小的文档块大小=BASE_CHUNK_SIZE * CHUNK_SCALE
CHUNK_SCALE = 3
def merge_metadata(dicts_list: dict):
    merged_dict = {}
    bounds_dict = {}
    keys_to_remove = set()

    for dic in dicts_list:
        for key, value in dic.items():
            if key in merged_dict:
                if value not in merged_dict[key]:
                    merged_dict[key].append(value)
            else:
                merged_dict[key] = [value]

    for key, values in merged_dict.items():
        if len(values) > 1 and all(isinstance(x, (int, float)) for x in values):
            bounds_dict[f"{key}_lower_bound"] = min(values)
            bounds_dict[f"{key}_upper_bound"] = max(values)
            keys_to_remove.add(key)

    merged_dict.update(bounds_dict)

    for key in keys_to_remove:
        del merged_dict[key]

    return {
        k: v[0] if isinstance(v, list) and len(v) == 1 else v
        for k, v in merged_dict.items()
    }

def merge_chunks(doc: Document, scale_factor=CHUNK_SCALE, chunk_idx_name: str):
    merged_doc = []
    page_content = ""
    metadata_list = []
    chunk_idx = 0

    for idx, item in enumerate(doc):
        page_content += item.page_content
        metadata_list.append(item.metadata)
        if (idx + 1) % scale_factor == 0 or idx == len(doc) - 1:
            metadata = merge_metadata(metadata_list)
            metadata[chunk_idx_name] = chunk_idx
            merged_doc.append(
                Document(
                    page_content=page_content,
                    metadata=metadata,
                )
            )
            chunk_idx += 1
            page_content = ""
            metadata_list = []

    return merged_doc

下面示例显示了前 3 个中等分块信息,结果如下:

css

复制代码[Document(page_content='LLM 安全专题 提示词是指在训练或与⼤型语⾔模型(Claude,ChatGPT等)进⾏交互时,提供给模型的输⼊⽂本。通过给定特定的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 0, 'small_chunk_idx_lower_bound': 0, 'small_chunk_idx_upper_bound': 2, 'medium_chunk_idx': 0}),Document(page_content='提示词,可以引导模型⽣成特定主题或类型的⽂本。在⾃然语⾔处理(NLP)任务中,提示词充当了问题或输⼊的⻆⾊,⽽模型的输出是对这个问题的回答或完成的任务。关于怎样设计好的', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'large_chunks_idx_lower_bound': 0, 'large_chunks_idx_upper_bound': 1, 'small_chunk_idx_lower_bound': 3, 'small_chunk_idx_upper_bound': 5, 'medium_chunk_idx': 1}),Document(page_content='Prompt,查看Prompt专题章节内容就可以了,我不在这⾥过多阐述,个⼈⽐较感兴趣针对Prompt的攻击,随着⼤语⾔模型的⼴泛应⽤,安全必定是⼀个⾮常值得关注的领域。提示攻击', metadata={'source': './data/一文带你了解提示攻击.pdf', 'page': 0, 'large_chunks_idx_lower_bound': 1, 'large_chunks_idx_upper_bound': 2, 'small_chunk_idx_lower_bound': 6, 'small_chunk_idx_upper_bound': 8, 'medium_chunk_idx': 2}),...]

文档检索过程

检索器声明

首先声明一个检索器,用于检索文档,这里将 BM25 检索器和嵌入式检索器组合成一个集成检索器,用于检索和评估文档相似度。下面是一些需要相关知识:

  • BM25 是一种基于词袋模型的检索方法,它通过考虑单词在文档中的频率和在整个文档集合中的逆文档频率来计算文档之间的相似度

  • 嵌入式检索器通常使用预训练的嵌入模型(本案例使用 OpenAI 的 text-embedding-ada-002 模型)将文档转换为密集向量,然后通过计算这些向量之间的相似度来评估文档之间的相似性

  • emb_filter: 用于在嵌入式检索过程中过滤结果。例如,可以根据某些标准排除不相关的文档

  • k: 这是一个整数,表示要返回的最匹配的前几个结果数量

  • weights: 包含两个权重值,分别用于 BM25 检索器和嵌入式检索器在集成检索中的权重。

    ini复制代码def get_retriever(
        self,
        docs_chunks,
        emb_chunks,
        emb_filter=None,
        k=2,
        weights=(0.5, 0.5),
    ):
        bm25_retriever = BM25Retriever.from_documents(docs_chunks)
        bm25_retriever.k = k
    
        emb_retriever = emb_chunks.as_retriever(
            search_kwargs={
                "filter": emb_filter,
                "k": k,
                "search_type": "mmr",
            }
        )
        return MyEnsembleRetriever(
            retrievers={"bm25": bm25_retriever, "chroma": emb_retriever},
            weights=weights,
        )
    
检索相关文档

文档检索通过采用多阶段(三次)的方式进行

  • 第一阶段:小分块检索 使用小文档块(docs_index_small)和小嵌入块(embedding_chunks_small)初始化一个检索器(first_retriever),使用这个检索器检索与查询相关的文档,并将结果存储在 first 变量中,对检索到的文档 ID 进行清理和过滤,确保它们是相关的,并存储在 ids_clean 变量中。

  • 第二阶段:移动窗口检索 针对每个唯一的源文档,使用小文档块检索与该源文档相关的所有文档块。使用包含这些文档块的新检索器(second_retriever),再次进行检索,以进一步缩小相关文档的范围,将检索到的文档添加到 docs 列表中。

  • 第三阶段:中等分块检索 使用过滤条件从中等文档块(docs_index_medium)检索相关文档,使用包含这些文档块的新检索器(third_retriever)进行检索。从检索到的文档中选择前 third_num_k 个文档,存储在 third 变量中,清理文档的元数据,删除不需要的内容,将最终检索到的文档按文件名分类,并存储在 qa_chunks 字典中。

    ini复制代码def get_relevant_documents(
        self,
        query: str,
        num_query: int,
        *,
        run_manager: Optional[CallbackManagerForChainRun] = None,
    ) -> List[Document]:
        # 第一次检索,小分块信息
        first_retriever = self.get_retriever(
            docs_chunks=self.docs_index_small.documents,
            emb_chunks=self.embedding_chunks_small,
            emb_filter=None,
            k=self.first_retrieval_k,
            weights=self.retriever_weights,
        )
        first = first_retriever.get_relevant_documents(
            query, callbacks=run_manager.get_child()
        )
        ids_clean = self.get_relevant_doc_ids(first, query)
        qa_chunks = {}
        if ids_clean and isinstance(ids_clean, list):
            source_md5_dict = {}
            for ids_c in ids_clean:
                if ids_c < len(first):
                    if ids_c not in source_md5_dict:
                        source_md5_dict[first[ids_c].metadata["source_md5"]] = [
                            first[ids_c]
                        ]
            if len(source_md5_dict) == 0:
                source_md5_dict[first[0].metadata["source_md5"]] = [first[0]]
            num_docs = len(source_md5_dict.keys())
            third_num_k = max(
                1,
                (
                    int(
                        (
                            MAX_LLM_CONTEXT
                            / (BASE_CHUNK_SIZE * CHUNK_SCALE)
                        )
                        // (num_docs * num_query)
                    )
                ),
            )
    
            for source_md5, docs in source_md5_dict.items():
                second_docs_chunks = self.docs_index_small.retrieve_metadata(
                    {
                        "source_md5": (IndexerOperator.EQ, source_md5),
                    }
                )
                # 第二次检索
                second_retriever = self.get_retriever(
                    docs_chunks=second_docs_chunks,
                    emb_chunks=self.embedding_chunks_small,
                    emb_filter={"source_md5": source_md5},
                    k=self.second_retrieval_k,
                    weights=self.retriever_weights,
                )
                second = second_retriever.get_relevant_documents(
                    query, callbacks=run_manager.get_child()
                )
                docs.extend(second)
                docindexer_filter, chroma_filter = self.get_filter(
                    self.num_windows, source_md5, docs
                )
                third_docs_chunks = self.docs_index_medium.retrieve_metadata(
                    docindexer_filter
                )
                # 第三次检索
                third_retriever = self.get_retriever(
                    docs_chunks=third_docs_chunks,
                    emb_chunks=self.embedding_chunks_medium,
                    emb_filter=chroma_filter,
                    k=third_num_k,
                    weights=self.retriever_weights,
                )
                third_temp = third_retriever.get_relevant_documents(
                    query, callbacks=run_manager.get_child()
                )
                third = third_temp[:third_num_k]
                for doc in third:
                    mtdata = doc.metadata
                    mtdata["page_content"] = None
                file_name = third[0].metadata["source"].split("/")[-1]
                if file_name not in qa_chunks:
                    qa_chunks[file_name] = third
                else:
                    qa_chunks[file_name].extend(third)
        return qa_chunks
    

整个过程是一个分层的检索过程,首先在小文档块中进行粗略检索,然后在特定的源文档中进行更精确的检索,最后在中等文档块中进行最终的检索。这种分层的方法有助于提高检索的效率和准确性,因为它允许系统在更小的文档集上进行更精确的检索,从而减少了在大文档集上进行复杂检索所需的计算量。

该方案的优势

处理大规模文档

由于知识库通常包含大量的文档,直接在这么大的文档集合上进行检索是非常耗时的。通过将文档分割成更小的块(chunk_small),小块更容易被索引和检索。

保留上下文信息

通过为小块添加窗口信息(add_window),可以确保在检索时不会丢失重要的上下文信息。这是因为有些信息可能分布在多个小块中,单独检索一个小块可能会遗漏这些信息,窗口机制确保在检索时考虑到足够的上下文,从而生成更准确的回答。

提升检索效率

通过将相邻的小块合并成中等大小的块(chunk_medium),在保留细粒度特性的同时增加了更大范围的上下文信息。这有助于提升检索的效率和准确性,因为中等大小的块既不会像大块那样导致检索效率下降,也不会像小块那样缺乏足够的上下文信息。

灵活性和可配置性

由于整个流程是模块化的,可以根据具体应用的需求灵活地配置每个步骤的参数(如块的大小、窗口的大小和步长等),以达到最佳的性能和效果平衡。

支持多种检索策略

由于有了不同大小和包含窗口信息的文档块,可以根据查询的需要选择最适合的块来进行检索。对于需要广泛上下文的查询,可以使用包含更多上下文信息的中等大小或大块来进行检索,对于需要快速响应的查询,可以使用小块来提高检索速度。

整体方案在保证生成质量的同时,实现高效处理,在实际应用中效果明显。

不足之处

因为检索阶段经过三次检索,加之使用本地矢量数据库使用 Chroma[1],整体的响应速率还需进一步改善。

读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用

对于0基础小白入门:

如果你是零基础小白,想快速入门大模型是可以考虑的。

一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。

资源分享

图片

大模型AGI学习包

图片

图片

资料目录

  1. 成长路线图&学习规划
  2. 配套视频教程
  3. 实战LLM
  4. 人工智能比赛资料
  5. AI人工智能必读书单
  6. 面试题合集

人工智能\大模型入门学习大礼包》,可以扫描下方二维码免费领取

1.成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

图片

2.视频教程

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩

图片

3.LLM

大家最喜欢也是最关心的LLM(大语言模型)

图片

人工智能\大模型入门学习大礼包》,可以扫描下方二维码免费领取

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Dockerfile构建镜像 使用docker-compose 一键部署IM项目

本文讲解&#xff1a;使用Dockerfile构建镜像 & 使用docker-compose 一键部署IM项目。 im项目地址&#xff1a;xzll-im &#xff0c;欢迎志同道合的开发者 一起 维护&#xff0c;学习&#xff0c;欢迎star &#x1f604; 1、Dockerfile编写与镜像构建&容器运行 Dockerf…

大语言模型(LLM)LangChain介绍

LangChain是一个利用大语言模型的能力开发各种下游应用的开源框架&#xff0c;它的核心理念是为各种大语言模型应用实现通用的接口&#xff0c;简化大语言模型应用的开发难度&#xff0c;主要的模块示意图为&#xff1a; Index&#xff1a;提供了各类文档导入、文本拆分、文本向…

夏天到了,用这两款软件,悄悄惊艳所有人!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 夏天来了&#xff0c;又到了“露肉”的季节&#xff0c;或许大家会为了身材烦恼&#xff0c;即便有运动意愿却苦于健身计划和时间上安排&#xff0c;也没有合适的免费软件。 别担心&a…

1-爬虫基础知识(6节课学会爬虫)

1-爬虫基础知识&#xff08;6节课学会爬虫&#xff09; 1.什么是爬虫2.爬取的数据去哪了3.需要的软件和环境4.浏览器的请求&#xff08;1&#xff09;Url&#xff08;2&#xff09;浏览器请求url地址&#xff08;3&#xff09;url地址对应的响应 5.认识HTTP/HTTPS5.1 http协议之…

餐饮火锅加盟网站pbootcms模板源码

模板介绍 如果您正在创建火锅店、餐饮店或加盟网站&#xff0c;小编推荐您下载这款餐饮火锅加盟网站pbootcms模板源码&#xff0c;整站源码响应式自适应的设计&#xff0c;可以让您自由编辑且适应任何终端浏览器&#xff0c;节约您的建站时间成本。 模板截图 源码下载 餐饮火…

生成式人工智能和机器人技术是否即将取得最后的突破?

了解生成式人工智能与机器人技术的融合如何彻底改变从医疗保健到娱乐等行业 想象一下这样一个世界&#xff0c;机器人可以谱写交响乐、画出杰作、写出小说。这种创造力与自动化的迷人融合&#xff0c;由 生成式人工智能&#xff0c;不再是梦想&#xff1b;它正在以重大方式重塑…

前后端分离的后台管理系统开发模板(带你从零开发一套自己的若依框架)上

前言&#xff1a; 目前&#xff0c;前后端分离开发已经成为当前web开发的主流。目前最流行的技术选型是前端vue3后端的spring boot3&#xff0c;本次。就基于这两个市面上主流的框架来开发出一套基本的后台管理系统的模板&#xff0c;以便于我们今后的开发。 前端使用vue3ele…

go Channel 原理 (一)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

Postman设置请求间自动保存返回参数,方便后续请求调用,减少复制粘贴

postman中常常出现&#xff1a;有两个请求&#xff0c;一个请求首先获取验证码或者token&#xff0c;再由得到的验证码或token编写body发送另一个请求。如何设置两个请求间自动关联相关数据呢&#xff1f; 通过环境存储全局变量 现在有两个请求如下图&#xff0c;生成验证码是…

代理IP如何助力旅游信息聚合?

在数字化时代&#xff0c;旅游信息聚合对于提升服务质量、优化用户体验起着至关重要的作用。随着在线旅游预订的普及&#xff0c;旅游信息的采集、整合和呈现成为了一个复杂而关键的过程。在这个过程中&#xff0c;代理IP技术以其独特的优势&#xff0c;为旅游信息聚合提供了强…

服务器硬件以及RAID配置

目录 一、RAID磁盘阵列原理&#xff08;嘎嘎重要&#xff09; 1、RAID的概述 2、常用的RAID 2.1、RAID 0 2.2、RAID 1 2.3、RAID 5 2.5、RAID 10 3、阵列卡介绍 二、建立软件RAID磁盘阵列 1、添加硬盘 2、使用fdisk分区&#xff0c;类型为fd 3、mdata命令使用参数 …

CXL:拯救NVMe SSD缓存不足设计难题-2

LMB提出了基于CXL协议的内存扩展框架和内核模块。该方案利用CXL内存扩展器作为物理DRAM源&#xff0c;旨在提供一个统一的内存分配接口&#xff0c;使PCIe和CXL设备都能方便地访问扩展的内存资源。通过这个接口&#xff0c;NVMe驱动和CUDA的统一内存内核驱动可以直接高效地访问…

探索人工智能和LLM对未来就业的影响

近年来&#xff0c;人工智能&#xff08;AI&#xff09;迅猛发展&#xff0c;引发了人们的兴奋&#xff0c;同时也引发了人们对就业未来的担忧。大型语言模型&#xff08;LLM&#xff09;就是最新的例子。这些强大的人工智能子集经过大量文本数据的训练&#xff0c;以理解和生成…

【贡献法】2262. 字符串的总引力

本文涉及知识点 贡献法 LeetCode2262. 字符串的总引力 字符串的 引力 定义为&#xff1a;字符串中 不同 字符的数量。 例如&#xff0c;“abbca” 的引力为 3 &#xff0c;因为其中有 3 个不同字符 ‘a’、‘b’ 和 ‘c’ 。 给你一个字符串 s &#xff0c;返回 其所有子字符…

【Arduino】实验使用ESP32控制可编程继电器制作跑马灯(图文)

今天小飞鱼实验使用ESP控制继电器&#xff0c;为了更好的掌握继电器的使用方法这里实验做了一个跑马灯的效果。 这里用到的可编程继电器&#xff0c;起始原理并不复杂&#xff0c;同样需要ESP32控制针脚输出高电平或低电平给到继电器&#xff0c;继电器使用这个信号控制一个电…

Linux 网络:网卡 promiscuous 模式疑云

文章目录 1. 前言2. 问题场景3. 问题定位和分析4. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 问题场景 调试 Marvell 88E6320 时&#xff0c;发现 eth0 出人意料的进入了 promis…

【吊打面试官系列-MyBatis面试题】MyBatis 与 Hibernate 有哪些不同?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 与 Hibernate 有哪些不同&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 与 Hibernate 有哪些不同&#xff1f; 1、Mybatis 和 hibernate 不同&#xff0c;它不完全是一个 ORM 框架&#xff0c;因…

grpc学习golang版( 四、多服务示例 )

系列文章目录 第一章 grpc基本概念与安装 第二章 grpc入门示例 第三章 proto文件数据类型 第四章 多服务示例 第五章 多proto文件示例 第六章 服务器流式传输 第七章 客户端流式传输 第八章 双向流示例 文章目录 一、前言二、定义proto文件三、编写server服务端四、编写Client客…

盘点全球Top10大云计算平台最热门技能证书

小李哥花了一年半时间终于考下全球10大云的77张认证&#xff0c;今天盘点下各个云的热门证书&#xff0c;希望能帮到非CS专业转IT和刚刚入行云计算的小伙伴。 排名取自23年Yahoo云计算市场份额排名报告&#xff0c;我会从云平台、证书价格、证书热门程度做推荐。 1️⃣亚马逊云…

MathType7.6永久破解激活码注册码 包含安装包下载

MathType是一款强大的数学公式编辑器&#xff0c;它能够帮助用户轻松编辑各种复杂的数学公式和符号。无论是学生、教师还是科研人员&#xff0c;MathType都能提供专业、精确的数学公式编辑服务。 在学习和工作中&#xff0c;我们常常会遇到需要编写数学公式的情况。然而&#x…