如何使用你的垂直领域补充 LLM 的能力

LLM 的局限性

1. LLM 的知识不是实时的

2.LLM 可能不知道你的私有领域及业务知识

如何增强模型的能力

既然模型的知识不是最新的,那我们能不能扩展他的知识,比如将数据库中的数据检索给他来增强它的能力? 答案是完全可以,这种通过检索的方法来增强生成模型的能力就是 RAG (Retrieval Augmented Generation)

如何做一个RAG?

1.将相关信息先进行检索,数据来源可以是你的数据,也可以是 ES 等检索引擎

2.构建 Prompt 将用户的问题以及检索到的信息喂给 LLM

3.LLM 根据已知的信息进行回复

接下来为了说明 RAG 的作用先上一个例子:

这里先封装一个 AIHelper

from openai import OpenAI


class AIHelper:
    PROMPT_TEMPLATE = """
        你是一个问答机器人。
        你的任务是根据下述给定的已知信息回答用户问题。

        用户问:
        {query}

        如果已知信息不包含用户问题的答案,或者已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。
        请不要输出已知信息中不包含的信息或答案。
        请用中文回答用户问题。
        """

    def __init__(self):
        self.llm = OpenAI(
            api_key="your key"
        )

    def get_completion(self, prompt, model="gpt-3.5-turbo-1106"):
        response = self.llm.chat.completions.create(
            model=model,
            messages=[
                {"role": "user", "content": prompt}
            ],
            temperature=0,
            max_tokens=1024,
            top_p=1,
            frequency_penalty=0,
            presence_penalty=0
        )
        return response.choices[0].message.content

    def build_prompt(self, **kwargs):
        return self.PROMPT_TEMPLATE.format(**kwargs)


if __name__ == "__main__":
    ai_helper = AIHelper()
    prompt = ai_helper.build_prompt(query="llama2 有多少参数?")
    res = ai_helper.get_completion(prompt)
    print(res)

运行上面的代码结果是

没错,gpt3.5 发布的时候 llama2 还没有发布,所以无法回答你的问题

接下来我们把 llama2 的知识喂给他

假如我们从数据库或者搜索引擎检索到了这些信息:

本文来自DataLearner官方博客:重磅!Meta发布LLaMA2,最高700亿参数,在2万亿tokens上训练,各项得分远超第一代LLaMA~完全免费可商用! | 数据学习者官方网站(Datalearner)

根据官方的介绍,Meta和Microsoft准备将LLaMA2引入到Azure公有云以及Windows本地上。目前已经在AzureAI上开放了LLaMA2系列的6个模型供大家使用。

LLaMA2模型的参数范围从70亿到700亿不等,在超过2万亿tokens数据集上训练。官方对齐微调的结果称为LLaMA2-Chat系列,专门针对场景优化。

接下来我们修改一下代码

from openai import OpenAI


class AIHelper:
    PROMPT_TEMPLATE = """
        你是一个问答机器人。
        你的任务是根据下述给定的已知信息回答用户问题。
        
        已知信息:
        {context}

        用户问:
        {query}

        如果已知信息不包含用户问题的答案,或者已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。
        请不要输出已知信息中不包含的信息或答案。
        请用中文回答用户问题。
        """

    def __init__(self):
        self.llm = OpenAI(
            api_key="your key",
        )

    def get_completion(self, prompt, model="gpt-3.5-turbo-1106"):
        response = self.llm.chat.completions.create(
            model=model,
            messages=[
                {"role": "user", "content": prompt}
            ],
            temperature=0,
            max_tokens=1024,
            top_p=1,
            frequency_penalty=0,
            presence_penalty=0
        )
        return response.choices[0].message.content

    def build_prompt(self, **kwargs):
        return self.PROMPT_TEMPLATE.format(**kwargs)


if __name__ == "__main__":
    ai_helper = AIHelper()
    knowledge = """
    本文来自DataLearner官方博客:重磅!Meta发布LLaMA2,最高700亿参数,在2万亿tokens上训练,各项得分远超第一代LLaMA~完全免费可商用! | 数据学习者官方网站(Datalearner)
根据官方的介绍,Meta和Microsoft准备将LLaMA2引入到Azure公有云以及Windows本地上。目前已经在AzureAI上开放了LLaMA2系列的6个模型供大家使用。
LLaMA2模型的参数范围从70亿到700亿不等,在超过2万亿tokens数据集上训练。官方对齐微调的结果称为LLaMA2-Chat系列,专门针对场景优化。"""
    prompt = ai_helper.build_prompt(query="llama2 有多少参数?", context=knowledge)
    res = ai_helper.get_completion(prompt)
    print(res)

再次运行上面的代码:

没错,现在 LLM 可以根据我们给他的信息来回答问题了。

原理就是这么简单

如何搭建一个知识体系并根据用户的query来检索得到最匹配的知识喂给大模型才是关键所在

如何搭建知识体系

搭建知识体系,说白了就是搭建一个检索系统,你可以用最暴力的方式,直接通过数据库中的模糊查询来取到匹配的数据,当然数据库不支持分词,最好是配合 ElasticSearch(简称ES) 这样的工具做一个关键词检索会比较好。将你数据库中的数据灌入到 ES 就能使用关键词搜索了

接下来假设你已经具备了这么个 ES 服务,我们通过代码示例来看看如何与 LLM 结合在一起为用户提供一个垂直领域的问答机器人

先封装一个 ESClient 用来检索数据

class EsClient:
    def __init__(self):
        self.client = Elasticsearch(
            hosts=['服务地址'],
            http_auth=('用户名', '密码')
        )
        self.index_name = "test"

    def create_index(self, index_name):
        self.client.indices.delete(index=index_name)
        if not self.client.indices.exists(index=index_name):
            self.client.indices.create(index=index_name)
        return index_name

    def save(self, paragraph_text):
        index_name = self.create_index(self.index_name)
        actions = [
            {
                "_index": index_name,
                "_source": {
                    "keywords": self.to_keywords_zh(para),
                    "text": para,
                    "id": f"doc_{i}"
                }
            }
            for i, para in enumerate(paragraph_text)
        ]

        helpers.bulk(self.client, actions)

    @staticmethod
    def to_keywords_zh(input_string):
        word_tokens = jieba.cut_for_search(input_string)
        stop_words = set(stopwords.words('chinese'))
        filtered_words = [w for w in word_tokens if not w in stop_words]
        return ' '.join(filtered_words)

    def search(self, query_string, top_n=3):
        response = self.client.search(
            index=self.index_name,
            query={
                "match": {
                    "keywords": self.to_keywords_zh(query_string)
                }
            },
            size=top_n
        )
        return {hit['_source']['id']: {'text': hit['_source']['text'], 'rank': i} for i, hit in
                enumerate(response['hits']['hits'])}

上面代码用到了 jieba, elasticsearch7 库,可以通过 pip 自行安装

ESClient 提供了 save 方法,可以将文本灌入 ES 服务,search 方法提供了搜索功能

接下来就可以这么玩了:

if __name__ == "__main__":
    ai_helper = AIHelper()
    esclient = EsClient()
    query="llama2 有多少参数?"
    query_res = esclient.search(query, 4)
    context="\n\n".join(v['text'] for k, v in query_res.items()
    prompt = ai_helper.build_prompt(query=query, context=context)
    res = ai_helper.get_completion(prompt)
    print(res)

没错就是这么简单

向量数据库

ES 数据库是通过关键词去匹配搜索结果的,如果关键词换成 英文:how manay parameters does llama2 have, 那 ES 里面可能就出不了结果了。

这个时候 向量数据库 就该登场了。

什么是向量数据库?

解释这个问题之前我们需要先了解什么是向量

通常,我们将一个文本转成一组浮点数,每个下标 i,对应一个维度,假如这个数组长度是 1000,那我们就把这个数组想象成为一个 1000 维空间的一个点,这个过程用英文来表示叫 Embeddings

既然是空间的一个点,那么点和点之间是可以计算距离的,距离越近代表语义越相似。

如何向量化?

这又需要用到模型了,毕竟机器是不知道 parameter 和 参数 的意思是一样的,所以需要大量的数据进行训练,让这种相似的语义的词生成的向量之间的距离比较接近

提供一个向量化函数参考:

def get_embeddings(texts, model="text-embedding-ada-002", dimensions=None):
    '''封装 OpenAI 的 Embedding 模型接口'''
    if model == "text-embedding-ada-002":
        dimensions = None
    if dimensions:
        data = client.embeddings.create(
            input=texts, model=model, dimensions=dimensions).data
    else:
        data = client.embeddings.create(input=texts, model=model).data
    return [x.embedding for x in data]
如何计算向量之间的距离?

两种方法:余弦距离,欧氏距离

余弦距离就是夹角大小,夹角越小余弦值越大,也就是越相似

欧式距离是直线距离,直线越短越相似。

还是看代码:

import numpy as np
from numpy import dot
from numpy.linalg import norm


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)

接下来测试一下

# query = "国际争端"

query = "global conflicts"

documents = [
    "联合国就苏丹达尔富尔地区大规模暴力事件发出警告",
    "土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判",
    "日本岐阜市陆上自卫队射击场内发生枪击事件 3人受伤",
    "国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营",
    "我国首次在空间站开展舱外辐射生物学暴露实验",
]

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("Query与自己的欧氏距离: {:.2f}".format(l2(query_vec, query_vec)))
print("Query与Documents的欧氏距离:")
for vec in doc_vecs:
    print(l2(query_vec, vec))

运行后可以看到,向量化后,检索就不受语言不同的影响了

不管是 global conflicts 还是 国际争端 都能匹配上 documents 中的前两条

解释清楚了向量,那么向量数据库就是存储这些向量的数据库了

下面模拟一个向量数据库与 LLM 结合的例子

import chromadb
from chromadb.config import Settings


class VectorDBHelper:
    def __init__(self, collection_name, embedding_fn):
        self.chroma_client = chromadb.Client(Settings(allow_reset=True))
        self.collection = self.chroma_client.get_or_create_collection(
            name=collection_name
        )
        self.embedding_fn = embedding_fn

    def save(self, documents):
        embeddings = self.embedding_fn(documents)
        self.collection.add(
            documents=documents,
            embeddings=embeddings,
            ids=[f"doc_{i}" for i in range(len(documents))]
        )

    def search(self, query, top_n):
        embeddings = self.embedding_fn([query])
        results = self.collection.query(
            query_embeddings=embeddings,
            n_results=top_n
        )
        res = zip(results['ids'][0], results['documents'][0])
        return {id: {'text': text, 'rank': i} for i, (id, text) in enumerate(res)}

向量数据库工具类 VectorDBHelper,同样提供了两个方法,save 会将文本向量化并存到数据库中,search 会根据 query 搜索最匹配的记录返回

最终例子:

if __name__ == "__main__":
    ai_helper = AIHelper()
    
    vec_db= VectorDBHelper("test", embedding_fn=get_embeddings)
    query="llama2 有多少参数?"
    query_res = vec_db.search(query, 4)
    context="\n\n".join(v['text'] for k, v in query_res.items()
    prompt = ai_helper.build_prompt(query=query, context=context)
    res = ai_helper.get_completion(prompt)
    print(res)

当然向量数据库并不是替代 ES 这类关键词数据库,向量数据库的问题是不够精准,在一些医学领域比如:非小细胞肺癌 会把 小细胞肺癌 查出来并放在第一位,这种场景 ES 反而效果更好。实际使用过程中往往是结合两者,将向量数据库及 ES 搜索得到的结果进行重拍返回最优结果。

关于这部分有兴趣可以关注私聊。

文中可能有疏漏之处欢迎指正

  • 41
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值