知识库

知识库

image-20230905153722770

准备数据

  • 遍历文件清洗数据,load文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    from langchain.docstore.document import Document
      
      
    data = re.sub(r'!', "!\n", data)
    data = re.sub(r':', ":\n", data)
    data = re.sub(r'。', "。\n", data)
    data = re.sub(r'\r', "\n", data)
    data = re.sub(r'\n\n', "\n", data)
    data = re.sub(r"\n\s*\n", "\n", data)
      
      
    docs.append(Document(page_content=data, metadata={"source": file}))
      
      
      
      
            _, ext = os.path.splitext(file_path)
            if ext.lower() == '.pdf':
                #pdf
                with pdfplumber.open(file_path) as pdf:
                    data_list = []
                    for page in pdf.pages:
                        print(page.extract_text())
                        data_list.append(page.extract_text())
                    data = "\n".join(data_list)
            elif ext.lower() == '.txt':
                # txt
                with open(file_path, 'rb') as f:
                    b = f.read()
                    result = chardet.detect(b)
                with open(file_path, 'r', encoding=result['encoding']) as f:
                    data = f.read()
    
  • 分批文本块

    1
    2
    3
    4
    5
    6
    7
    8
    from langchain.text_splitter import CharacterTextSplitter
      
    # chunk_size : 文本分割的滑窗长度
    # chunk_overlap:重叠滑窗长度 保留一些重叠可以保持文本块之间的连续性,做一个承上启下
    text_splitter = CharacterTextSplitter(
                chunk_size=int(settings.librarys.rtst.size), chunk_overlap=int(settings.librarys.rtst.overlap), separator='\n')
      
    doc_texts = text_splitter.split_documents(docs)
    
  • embedding 文本数据的数字表示。这种数字表示很有用,因为它可以用来查找相似的文档

    1
    2
    3
    embeddings = HuggingFaceEmbeddings(model_name='')
    embeddings.client = sentence_transformers.SentenceTransformer("moka-ai/m3e-base", device="cuda")
      
    
  • VectorStore

    向量数据库存放文档和embedding后的vertor

    FAISS高效相似性搜索和密集向量聚类的内存库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from langchain.vectorstores.faiss import FAISS as Vectorstore
      
    docs = []
    texts = [d.page_content for d in doc_texts]
    metadatas = [d.metadata for d in doc_texts]
      
    # 新增
    vectorstore_new = Vectorstore.from_texts(texts, embeddings, metadatas=metadatas)
    # 合并
    vectorstore.merge_from(vectorstore_new)
      
    # 加载
    vectorstore_old = Vectorstore.load_local(
            'memory/default', embeddings=embeddings)
      
    # 保存
    vectorstore_old.save_local('memory/default')
    

获取doc

query 向量化,匹配库里向量,查找与之相似度最高的文本块。用户提出的问题与查找到的相关知识将被组合并被LLM处理,最终返回用户所需要的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 加载
embeddings = HuggingFaceEmbeddings(model_name='')
embeddings.client = sentence_transformers.SentenceTransformer("moka-ai/m3e-base", device="cuda")

vectorstore_old = Vectorstore.load_local(
        'memory/default', embeddings=embeddings)

embedding = get_vectorstore(memory_name).embedding_function(s)
# 选出相似count个scores,indices
scores, indices = vectorstores[memory_name].index.search(np.array([embedding], dtype=np.float32),int(cunnrent_setting.count))

docs = []
for j, i in enumerate(indices[0]):
   if i == -1:
			continue
	if scores[0][j] > 260: continue
	# 获取对应id的doc和相邻step的上下文,去掉chunk_overlap部分,组合成新文章
	docs.append(get_doc(i, scores[0][j], step, memory_name))

作为system提示词:学习以下文段, 用中文回答用户问题。如果无法从中得到答案,忽略文段内容并用中文回答用户问题。

垂直邻域LLM

参考

由于在训练时训练语料的限制,最终产生的LLM往往只具备通用知识,而不具备特定垂直领域的知识,尤其是企业内部信息。

目前情况

  • 如果构建具备特定垂直领域知识的LLM,需要将特定垂直领域的知识作为新的语料来微调通用大模型,不仅耗费大量算力,而且每次信息的更新都需要重新进行模型训练,还无法保证结果的准确性
  • 可以将特定垂直领域的知识作为提示(prompt)输入给通用大模型,由此得到准确的结果。但由于LLM对提示词的长度有限制,其可以获取的信息非常有限,难以记住全部的知识信息,因此无法回答垂直领域的问题。
  • 训练会造成通用能力降低,只能回答垂直领域问题。

思考

  • 真正垂直领域大模型的做法,应该从Pre-Train做起。SFT只是激发原有大模型的能力,预训练才是真正知识灌输阶段,让模型真正学习领域数据知识,做到适配领域。但目前很多垂直领域大模型还停留在SFT阶段。
  • 领域大模型在行业领域上效果是优于通用大模型即可,不需要“即要又要还要”
  • 不应该某些垂直领域大模型效果不如ChatGPT,就否定垂直领域大模型。有没有想过一件可怕的事情,ChatGPT见的垂直领域数据,比你的领域大模型见的还多。但某些领域数据,ChatGPT还是见不到的
  • 纯文本只能用于模型的预训练,真正可以进行后续问答,需要的是指令数据。当然可以采用一些人工智能方法生成一些指数据,但为了保证事实性,还是需要进行人工校对的。高质量SFT数据,才是模型微调的关键。
  • 外部知识主要是为了解决模型幻觉、提高模型回复准确。同时,采用外部知识库可以快速进行知识更新,相较于模型训练要快非常多。

解决

  • 大模型+知识库,这是目前最简单的办法,构建领域知识库,利用大模型的In Context Learning(基于上下文学习)的能力,通过检索在问答中增强给模型输入的上下文,让大模型可以准确回答特定领域和企业的问题;但是这种方式对准确检索能力要求很高,另外如果模型本身不具备领域知识,即使有准确上下文,也难以给出正确答案。
  • PEFT(参数高效的微调),通过P-Tuning或者LoRA等方式对模型进行微调,使其适应领域问题,比如一些法律和医疗的开源模型。但是这种方式微调的模型一般效果不会好,这是因为PEFT并不是用来让模型学会新的知识,而是让模型在特定任务表现更好的方式。类似Prefix Tuning、P-Tuning和LoRA等技术在某种程度上是等价的,目的是让模型适应特定任务,并不是让模型学会新的知识。因此,想通过PEFT方式让模型学会新的知识是南辕北辙。
  • 全量微调,这是另外一种比较流行的方式,在某个基座模型的基础上,对模型进行全量微调训练,使其学会领域知识。理论上,全量微调是最佳方式,基座模型已经学会了通用的“世界知识”,通过全量微调可以增强它的专业能力。但是实际上,如果语料不够,知识很难“喂”给模型。现在模型训练的方式,并不存在让模型记住某一本书的方法。其次是,如果拿到的不是预训练好的基座模型,而是经过SFT甚至RLHF的模型,在这些模型的基础上进行训练时,就会产生灾难性遗忘的问题。再者这种方法对算力的要求还是比较高的。
  • 从预训练开始定制,这种方式应该是构建垂直领域大模型最有效的方法,从一开始词表的构建,到训练语料的配比,甚至模型的结构都可以进行定制。然后严格遵循OpenAI的Pretrain–>SFT–>RLHF三段训练方法,理论上可以构建出一个优秀的领域大模型。但是这种方法需要的费用极高,除了预训练,还需要考虑模型的迭代,一般的企业根本无力承受。

需要澄清的概念

  • 垂直领域和特定任务

    很多需求是希望大模型去完成一些特定任务,而并不需要大模型具备专业领域知识。比如文本分类、文本生成等,这些都是特定任务。如果任务和专业领域无关,那么其实并不需要一个垂直领域的大模型。只要对模型进行PEFT微调,甚至是研究一个更好的prompt方式,就可以让模型处理特定任务时表现更好即可。在需要一些知识注入的帮助,一般可以通过外挂知识库的形式进行。除非对专业领域有很高要求,例如医学论文,法律条文解读,需要模型本身具备很强的领域知识,否则都不需要对模型本身进行微调。

  • 垂直领域”系统”和”模型”

    很多垂直领域需求是一个”系统”而并非一定是一个模型。如果能够利用通用的领域模型,加上其他增强技术构建出一个能够适应特定领域问题的系统,那么也是满足要求的,例如ChatLaw这个开源的项目中,其实也综合使用了其他模型(KeywordLLM)来增强垂直领域能力。

技巧

  • 学习长文本,分段summary,LLM生成答案摘要,通过另一个model(Bart)进行扩写
  • 经常变化知识,用知识库