【AI大模型应用开发】3. RAG初探 - 动手实现一个最简单的RAG应用

news2024/11/25 7:09:05

0. 什么是RAG

大模型也不是万能的,也有局限性。

  • LLM 的知识不是实时的
  • LLM 可能不知道你私有的领域/业务知识

RAG(Retrieval Augmented Generation)顾名思义:通过检索的方法来增强生成模型的能力。你可以把这个过程想象成开卷考试。让 LLM 先翻书,再回答问题。

1. RAG基本流程

在这里插入图片描述 看图就很容易理解RAG的流程了:

(1)私有知识通过切分、向量化保存到向量数据库中,供后续使用

(2)用户提问时,将用户提问用同样的方式向量化,然后去向量数据库中检索

(3)检索出相似度最高的k个切分段落

(4)将检索结果和用户的提问放到Prompt模板中,组装成一个完整的Prompt

(5)组装好的Prompt给大模型,让大模型生成回答

理想状态下,大模型是完全依赖检索出的文档片段进行组织答案的

简化一下,可以看出RAG有两大过程:

  1. 加载文档,生成向量数据库
  2. 查询向量数据库,询问大模型得到答案

下面我们一步步拆解,深入了解下RAG的流程和实现RAG所需的基本模块。

2. 向量数据库的生成

2.1 文档加载与分块

首先加载我们私有的知识库。这里以加载PDF文件为例。Python提供了加载PDF的一些库,这里用 pdfminer。

  • 安装 pdfminer:
bash
复制代码
pip install pdfminer.six
  • 先看代码:
python
复制代码
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer

class PDFFileLoader():
    def __init__(self, file) -> None:
        self.paragraphs = self.extract_text_from_pdf(file, page_numbers=[0,3])
        i = 1
        for para in self.paragraphs[:3]:
            print(f"========= 第{i}段 ==========")
            print(para+"\n")
            i += 1
    
    def getParagraphs(self):
        return self.paragraphs

    ################################# 文档的加载与切割 ############################
    def extract_text_from_pdf(self, filename, page_numbers=None):
        '''从 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:
                continue
            for element in page_layout:
                if isinstance(element, LTTextContainer):
                    full_text += element.get_text() + '\n'
        
        # 段落分割
        lines = full_text.split('。\n')
        for text in lines:
            buffer = text.replace('\n', ' ')
            
            if buffer:
                paragraphs.append(buffer)
                buffer = ''
                row_count = 0
                
        if buffer:
            paragraphs.append(buffer)
        return paragraphs

PDFFileLoader("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
  • 代码解释

(1)我们首先定义了一个 PDFFileLoader 的类,接收一个PDF文件路径。然后类内部调用extract_text_from_pdf去解析PDF文件并分段。

(2)extract_text_from_pdf中前半部分代码是利用 extract_pages 按页提取出PDF文件中的文字,然后组装成 full_text

(3)extract_text_from_pdf中后半部分代码是将 full_text 进行段落划分。

说明:因为每个PDF提取出来的文字格式可能不同,有的每一行后面都带有"\n\n",有的不带有"\n\n",有的每一行中的单词都粘在一起…,各种各样,所以PDF文字划分和段落分割的算法都无法做到完美适应所有PDF。本文重点不再这,所以粗暴地根据"。\n"划分了段落。实际应用中这里你应该按照你的PDF文件去进行调试和分割,段落划分这几行代码不能直接用。

可以简单看下我为什么能如此粗暴的划分段落:通过extract_pages提取出来的文本如下:

'如何向 ChatGPT 提问以获得高质量答案:提示\n技巧工程完全指南\n\n介绍\n\n我很高兴欢迎您阅读我的最新书籍《The Art of Asking ChatGPT for High-Quality Answers: A complete \n\nGuide to Prompt Engineering Techniques》。本书是一本全面指南,介绍了各种提示技术,用于从\n\nChatGPT中生成高质量的答案。\n\n我们将探讨如何使用不同的提示工程技术来实现不同的目标。ChatGPT是一款最先进的语言模型,能够生成\n\n类似人类的文本。然而,理解如何正确地向ChatGPT提问以获得我们所需的高质量输出非常重要。而这正是\n本书的目的。\n\n无论您是普通人、研究人员、开发人员,还是只是想在自己的领域中将ChatGPT作为个人助手的人,本书都\n是为您编写的。我使用简单易懂的语言,提供实用的解释,并在每个提示技术中提供了示例和提示公式。通\n\n过本书,您将学习如何使用提示工程技术来控制ChatGPT的输出,并生成符合您特定需求的文本。\n\n在整本书中,我们还提供了如何结合不同的提示技术以实现更具体结果的示例。我希望您能像我写作时一\n\n样,享受阅读本书并从中获得知识。\n\n \n\n

与原文对比,大体上按"。\n"来分割能与实际段落比较接近,所以本例我就先这样干了。这实际是不能用于实际项目的:

在这里插入图片描述

  • 分割结果(打印前三段):

在这里插入图片描述

2.2 创建向量数据库

本文以 chromadb 向量数据库为例进行实操。

  • 安装向量数据库chromadb
python
复制代码
pip install chromadb
2.2.1 创建过程

(1)创建一个向量数据库类。该类add_documents函数用来添加数据,它需要三个参数:

  • 文档的向量
  • 文档的原文
  • 文档的id
python
复制代码
import chromadb
from chromadb.config import Settings

class MyVectorDBConnector:
    def __init__(self, collection_name, embedding_fn):
        chroma_client = chromadb.Client(Settings(allow_reset=True))

        # 为了演示,实际不需要每次 reset()
        chroma_client.reset()

        # 创建一个 collection
        self.collection = chroma_client.get_or_create_collection(name=collection_name)
        self.embedding_fn = embedding_fn

    def add_documents(self, documents):
        '''向 collection 中添加文档与向量'''
        self.collection.add(
            embeddings=self.embedding_fn(documents),  # 每个文档的向量
            documents=documents,  # 文档的原文
            ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id
        )

    def search(self, query, top_n):
        '''检索向量数据库'''
        results = self.collection.query(
            query_embeddings=self.embedding_fn([query]),
            n_results=top_n
        )
        return results

(2)文档的向量怎么来?可以通过OpenAI的embeddings接口计算得到:

python
复制代码
from openai import OpenAI
import os
# 加载环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEY

client = OpenAI()

def get_embeddings(texts, model="text-embedding-3-small"):
    '''封装 OpenAI 的 Embedding 模型接口'''
    print(texts)
    print(model)
    data = client.embeddings.create(input=texts, model=model).data
    print(data)
    return [x.embedding for x in data]

(3)调用接口,创建向量数据库

python
复制代码
# 创建一个向量数据库对象
vector_db = MyVectorDBConnector("demo", get_embeddings)
# 向向量数据库中添加文档
vector_db.add_documents(pdf_loader.getParagraphs())

(4)测试查询

python
复制代码
user_query = "什么是角色提示?"
results = vector_db.search(user_query, 3) # 3是指查询出最相近的3块文本
for para in results['documents'][0]:
    print(para+"\n\n")
2.2.2 运行结果

(1)通过OpenAI的embeddings接口计算得到的文本向量

在这里插入图片描述

(2)查询结果,查找出最相近的3块文本

在这里插入图片描述

2.2.3 踩坑
2.2.3.1 坑一:NoneType object is not iterable

在这里插入图片描述

  • 原因:传入的分块有空字符的情况。

不知道这种情况为什么会导致NoneType的错误,可能是OpenAI向量化时对特殊字符进行了去除?

在这里插入图片描述

  • 解决方法:保证分块中没有全是特殊字符的分块即可。
2.2.3.2 坑二:Number of embeddings 9 must match number of ids 10

在这里插入图片描述

  • 原因:可以看下下面的代码,上面的错误指的是embeddings是9个值,而ids有10个值。这是因为在解决坑一时,将里面最后那个空的文档分块去掉了,没去生成embeddings。
python
复制代码
self.collection.add(
    embeddings=self.embedding_fn(documents),  # 每个文档的向量
    documents=documents,  # 文档的原文
    ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id
)
  • 解决方法:保证documents和embeddings的数组大小长度一致。

以上两个坑总体的解决方案代码,看下里面修改的部分(注释部分),在段落分割部分就把异常的分块去掉,从源头上保证documents的正常以及后面documents和embeddings数组大小一致:

python
复制代码
# 段落分割
lines = full_text.split('。\n')
for text in lines:
    buffer = text.strip(' ').replace('\n', ' ').replace('[', '').replace(']', '') ## 1. 去掉特殊字符
    if len(buffer) < 10: ## 2. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用
        continue
    if buffer:
        paragraphs.append(buffer)
        buffer = ''
        row_count = 0
        
if buffer and len(buffer) > 10: ## 3. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用
    paragraphs.append(buffer)
return paragraphs

注意:文档分块不一定是按段落分。

3. Prompt模板

上面我们已经拿到了检索回来的相关文档。下面我们写一个Prompt模板用来组装这些文档以及用户的提问。

python
复制代码
def build_prompt(prompt_template, **kwargs):
    '''将 Prompt 模板赋值'''
    prompt = prompt_template
    for k, v in kwargs.items(): 
        if isinstance(v,str):
            val = v
        elif 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

prompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

已知信息:
__INFO__

用户问:
__QUERY__

请用中文回答用户问题。
"""

注意以上最重要的提示词,要求大模型完全按照给定的文本回答问题:

你的任务是根据下述给定的已知信息回答用户问题。 确保你的回复完全依据下述已知信息。不要编造答案。 如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

4. 使用大模型得到答案

4.1 封装OpenAI接口

python
复制代码
def get_completion(prompt, model="gpt-3.5-turbo-1106"):
    '''封装 openai 接口'''
    messages = [{"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0,  # 模型输出的随机性,0 表示随机性最小
    )
    return response.choices[0].message.content

4.2 组装Prompt

python
复制代码
prompt = build_prompt(prompt_template, info=results['documents'][0], query=user_query)
print(prompt)
  • 运行结果

在这里插入图片描述

4.3 使用大模型得到答案

python
复制代码
response = get_completion(prompt)
print(response)
  • 运行结果

在这里插入图片描述

5. 总结

至此,我们已经实现了RAG的基本流程。总结下流程:

  • 离线部分,可提前生成好

    (1)文档加载与分块

    (2)分块数据灌入向量数据库

  • 在线部分

    (3)解析用户提问,用户提问向量化

    (4)查询向量数据库,得到最相似的k个文本块

    (5)使用得到的k个文本块和用户提问组装Prompt模板

    (6)询问大模型得到最终答案

5.1 封装RAG

我们将RAG流程封装一下,createVectorDB完成离线部分,创建出向量数据库和灌入数据。chat完成在线部分。

python
复制代码
class RAG_Bot:
    def __init__(self, n_results=2):
        self.llm_api = get_completion
        self.n_results = n_results

    def createVectorDB(self, file):
        print(file)
        pdf_loader = PDFFileLoader(file)
        # 创建一个向量数据库对象
        self.vector_db = MyVectorDBConnector("demo", get_embeddings)
        # 向向量数据库中添加文档,灌入数据
        self.vector_db.add_documents(pdf_loader.getParagraphs())

    def chat(self, user_query):
        # 1. 检索
        search_results = self.vector_db.search(user_query,self.n_results)
        
        # 2. 构建 Prompt
        prompt = build_prompt(prompt_template, info=search_results['documents'][0], query=user_query)
        
        # 3. 调用 LLM
        response = self.llm_api(prompt)
        return response
  • 使用
python
复制代码
rag_bot = RAG_Bot()
rag_bot.createVectorDB("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
response = rag_bot.chat("什么是角色提示?")
print("response=====================>")
print(response)

5.2 完整代码

python
复制代码
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer


class PDFFileLoader():
    def __init__(self, file) -> None:
        self.paragraphs = self.extract_text_from_pdf(file, page_numbers=[0,3])
        i = 1
        for para in self.paragraphs:
            print(f"========= 第{i}段 ==========")
            print(para+"\n")
            i += 1
    
    def getParagraphs(self):
        return self.paragraphs

    ################################# 文档的加载与切割 ############################
    def extract_text_from_pdf(self, filename, page_numbers=None):
        '''从 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:
                continue
            for element in page_layout:
                if isinstance(element, LTTextContainer):
                    full_text += element.get_text() + '\n'
        
        # 段落分割
        lines = full_text.split('。\n')
        for text in lines:
            buffer = text.strip(' ').replace('\n', ' ').replace('[', '').replace(']', '') ## 1. 去掉特殊字符
            if len(buffer) < 10: ## 2. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用
                continue
            if buffer:
                paragraphs.append(buffer)
                buffer = ''
                row_count = 0
                
        if buffer and len(buffer) > 10: ## 3. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用
            paragraphs.append(buffer)
        return paragraphs

# pdf_loader = PDFFileLoader("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")

from openai import OpenAI
import os
# 加载环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEY

client = OpenAI()

def get_embeddings(texts, model="text-embedding-3-small"):
    '''封装 OpenAI 的 Embedding 模型接口'''
    data = client.embeddings.create(input=texts, model=model).data
    return [x.embedding for x in data]

import chromadb
from chromadb.config import Settings

class MyVectorDBConnector:
    def __init__(self, collection_name, embedding_fn):
        chroma_client = chromadb.Client(Settings(allow_reset=True))

        # 为了演示,实际不需要每次 reset()
        chroma_client.reset()

        # 创建一个 collection
        self.collection = chroma_client.get_or_create_collection(name=collection_name)
        self.embedding_fn = embedding_fn

    def add_documents(self, documents):
        '''向 collection 中添加文档与向量'''
        self.collection.add(
            embeddings=self.embedding_fn(documents),  # 每个文档的向量
            documents=documents,  # 文档的原文
            ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id
        )

    def search(self, query, top_n):
        '''检索向量数据库'''
        results = self.collection.query(
            query_embeddings=self.embedding_fn([query]),
            n_results=top_n
        )
        return results

# # 创建一个向量数据库对象
# vector_db = MyVectorDBConnector("demo", get_embeddings)
# # 向向量数据库中添加文档
# vector_db.add_documents(pdf_loader.getParagraphs())

# user_query = "什么是角色提示?"
# results = vector_db.search(user_query, 3)
# for para in results['documents'][0]:
#     print(para+"\n\n")

def build_prompt(prompt_template, **kwargs):
    '''将 Prompt 模板赋值'''
    prompt = prompt_template
    for k, v in kwargs.items(): 
        if isinstance(v,str):
            val = v
        elif 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

prompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

已知信息:
__INFO__

用户问:
__QUERY__

请用中文回答用户问题。
"""

########################### 大模型接口封装 #############################

def get_completion(prompt, model="gpt-3.5-turbo-1106"):
    '''封装 openai 接口'''
    messages = [{"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0,  # 模型输出的随机性,0 表示随机性最小
    )
    return response.choices[0].message.content

# prompt = build_prompt(prompt_template, info=results['documents'][0], query=user_query)
# print(prompt)

# response = get_completion(prompt)
# print(response)


##################################  基于向量检索的 RAG ##################
class RAG_Bot:
    def __init__(self, n_results=2):
        self.llm_api = get_completion
        self.n_results = n_results

    def createVectorDB(self, file):
        print(file)
        pdf_loader = PDFFileLoader(file)
        # 创建一个向量数据库对象
        self.vector_db = MyVectorDBConnector("demo", get_embeddings)
        # 向向量数据库中添加文档,灌入数据
        self.vector_db.add_documents(pdf_loader.getParagraphs())

    def chat(self, user_query):
        # 1. 检索
        search_results = self.vector_db.search(user_query,self.n_results)
        
        # 2. 构建 Prompt
        prompt = build_prompt(prompt_template, info=search_results['documents'][0], query=user_query)
        print("prompt===================>")
        print(prompt)
        
        # 3. 调用 LLM
        response = self.llm_api(prompt)
        return response


rag_bot = RAG_Bot()
rag_bot.createVectorDB("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
response = rag_bot.chat("什么是角色提示?")
print("response=====================>")
print(response)

6. 思考

RAG 是一个增强大模型垂直领域能力和减少幻觉的通用方法论,所以了解其原理和流程对实现出效果较好的大模型应用非常有用。

但是上面也可以看到,它也限制了大模型使用其自身的知识库去回答问题,只能够用给定的文本回复问题。这就导致这个RAG应用的通用性大大降低。

另外,从RAG流程中也可以看到要想实现的效果好,也是困难重重:

(1)预处理: 首先文本分割的块要恰到好处

  • 文本分割的粒度太小,查找到的参考文本较少
  • 文本颗粒度太大,参考文本太多,消耗token,同时也会带入更多的干扰信息,导致大模型出现幻觉的概率增加

(2)有些问题的回答是需要依赖上下文的,怎样将上下文所在的文本块都找出来也不容易

(3)召回正确性:召回文档的相关性也对结果比较重要。查找出的文档虽然与用户提问的向量值比较相似,但某些时候,最相似的并不一定是与问题答案相关的

(4)大模型本身的能力对结果也比较重要

目前针对以上各个困难都有非常多的研究,还在快速发展阶段,未形成一套通用、效果好的方法论。

后面可以针对这部分进行深入探索和学习,关注和整理当下最新的RAG调优方法。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
在这里插入图片描述

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

在这里插入图片描述

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

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

相关文章

TCP协议在物联网中的实战

一、TCP协议介绍 网上对TCP协议介绍众多&#xff0c;本人按照自己的理解简单介绍一下。 TCP&#xff08;Transmission Control Protocol&#xff0c; 传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输控制层通信协议。 1.1 协议机制 1.1.1 三次握手 &…

基于Springboot的民宿管理平台

基于SpringbootVue的民宿管理平台设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 民宿信息 后台登录 后台首页 用户管理 商家管理 民宿信息管理 房间类型管理 …

C. Assembly via Remainders

C.通过余数组装 每次测试时限&#xff1a;2 秒 每次测试的内存限制&#xff1a;256 兆字节 输入&#xff1a;标准输入 输出&#xff1a;标准输出 思路分析 我们用arr数组存放输入的数据,用brr代表代表答案数组,我们可以看到答案的第一位是都是arr的首位1,然后我们观察可…

【研发管理】产品经理知识体系-组合管理

导读&#xff1a;新产品开发的组合管理是一个重要的过程&#xff0c;它涉及到对一系列新产品开发项目进行策略性选择、优先级排序、资源分配和监控。这个过程旨在确保企业能够最大化地利用有限的资源&#xff0c;以实现其战略目标。 目录 1、组合管理、五大目标 2、组合管理的…

Numerical Analysis(byRichard.L..Burden)【pdf高清英文原版】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

Linux下top命令指标说明

目录 Linux下top命令指标说明1. 概览2. CPU利用率3. 内存利用率4. 进程信息 Linux下top命令指标说明 在Linux系统中&#xff0c;top 命令是一个用于实时监视系统运行状态的工具。通过 top 命令&#xff0c;我们可以了解系统的负载情况、CPU利用率、内存使用情况以及各个进程的…

k8s环境部署gpu以及CUDA兼容性分析

本文记录和学习在实用gpu搭建k8s支持上层应用时的功能实践和遇到的问题。 1. 基础概念 CUDA本质上就是NVIDIA专为通用高性能并行计算设计的一套计算平台和编程模型&#xff0c;换句话使用GPU并行编程的规范方法&#xff0c;所以CUDA在软件层面包含了众多库&#xff0c; 那这里…

《QT实用小工具·五十一》带动画的 CheckBox

1、概述 源码放在文章末尾 该项目实现了带动画效果的多选框&#xff0c;鼠标放在上面或者选中都会呈现炫酷的动画效果&#xff0c;demo演示如下&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef LINEARCHECKBOX_H #define LINEARCHECKBOX_H#include <QCheckBox> …

图像处理1,灰度,data,for循环批处理图片,图片属性查看,图片单通道查看,椒盐噪声的生成,滤波处理,图像分割

图像处理1 灰度处理data库的使用for循环批处理图像对图像属性的查看图片类型图片尺寸图片宽度图像高度通道数总像素个数最大像素值最小像素值&#xff0c;像素平均值图像点像素值 for循环分别显示图像rgb通道椒盐噪声的生成中值滤波处理高斯模糊处理图像切割 灰度处理 from sk…

多国语言免费在线客服系统源码,网站在线客服系统,网页在线客服软件在线聊天通讯平台

详情介绍 多国语言免费在线客服系统源码,网站在线客服系统,网页在线客服软件在线聊天通讯平台 新款在线客服系统全开源无加密:多商户、国际化多语言、智能机器人、自动回复、语音聊天、 文件发送、系统强力防黑加固、不限坐席、国际外贸、超多功能 支持手机移动端和PC网页…

如何从Mac电脑恢复任何删除的视频

Microsoft Office是包括Mac用户在内的人们在世界各地创建文档时使用的最佳软件之一。该软件允许您创建任何类型的文件&#xff0c;如演示文稿、帐户文件和书面文件。您可以使用 MS Office 来完成。所有Microsoft文档都可以在Mac上使用。大多数情况下&#xff0c;您处理文档&…

私有开源LLM实例的三个考虑因素

原文地址&#xff1a;three-considerations-for-private-open-source-llm-instances 2024 年 4 月 29 日 在生产应用中使用商业 LLM APIs 会带来明确且经过充分研究的风险。因此&#xff0c;企业越来越多地转向利用开源的私有托管LLM实例&#xff0c;并通过RAG技术进行增强。 介…

RCE学习

从最近的xyctf中&#xff0c;最大的感受就是自己的rce基础并不牢固&#xff0c;所以马上来恶补一下 漏洞成因 php和其他语言有很多能够执行系统命令或执行其他php代码的函数&#xff0c;因为开发者的使用不当&#xff0c;使得用户能够控制传递给执行命令的函数的参数&#xf…

【C++题解】1300. 小明暑假的零花钱

问题&#xff1a;1300. 小明暑假的零花钱 类型&#xff1a;多分支结构 题目描述&#xff1a; 小明同学的妈妈在期末考试之后决定根据小明的考试成绩奖励小明不同的暑假零花钱&#xff0c;如果考试成绩在90 分以上&#xff08;包括 90 分&#xff09;&#xff0c;零花钱是成绩…

clang:在 Win10 上编译 MIDI 音乐程序

先从 Microsoft C Build Tools - Visual Studio 下载 1.73GB 安装 "Microsoft C Build Tools“ 访问 Swift.org - Download Swift 找到 Windows 10&#xff1a;x86_64 下载 swift-5.10-RELEASE-windows10.exe 大约490MB 建议安装在 D:\Swift\ &#xff0c;安装后大约占…

【Linux系统编程】32.线程同步、锁的使用、互斥锁管理

目录 线程同步 锁的使用 注意事项 pthread_mutex_init 注意 参数mutex 参数attr 返回值 动态初始化 静态初始化 pthread_mutex_destroy 参数mutex 返回值 pthread_mutex_lock 参数mutex 返回值 pthread_mutex_unlock 参数mutex 返回值 pthread_mutex_trylo…

JAVA面试之MQ

如何保证消息的可靠传输&#xff1f;如果消息丢了怎么办 数据的丢失问题&#xff0c;可能出现在生产者、MQ、消费者中。 &#xff08;1&#xff09;生产者发送消息时丢失&#xff1a; ①生产者发送消息时连接MQ失败 ②生产者发送消息到达MQ后未找到Exchange(交换机) ③生产者发…

Python | Leetcode Python题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; class Solution:def minPathSum(self, grid: List[List[int]]) -> int:if not grid or not grid[0]:return 0rows, columns len(grid), len(grid[0])dp [[0] * columns for _ in range(rows)]dp[0][0] grid[0][0]for i in range(1, r…

WPF基础应用

WPF参考原文 MVVM介绍 1.常用布局控件 1.1 布局控件 WPF&#xff08;Windows Presentation Foundation&#xff09;提供了多种布局容器来帮助开发者设计用户界面&#xff0c;以下是一些常用的布局&#xff1a; Grid: Grid是最常用的布局容器之一&#xff0c;它允许你通过定…

计算机网络-408考研

后续更新发布在B站账号&#xff1a;谭同学很nice http://【计算机408备考-什么是计算机网络&#xff0c;有什么特点&#xff1f;】 https://www.bilibili.com/video/BV1qZ421J7As/?share_sourcecopy_web&vd_source58c2a80f8de74ae56281305624c60b13http://【计算机408备考…