C#使用詞嵌入向量與向量資料庫為大語言模型(LLM)賦能長期記憶實現私域問答機器人落地

a1010發表於2023-05-09

 

本文將探討如何使用c#開發基於大語言模型的私域聊天機器人落地。大語言模型(Large Language Model,LLM 這裡主要以chatgpt為代表的的文字生成式人工智慧)是一種利用深度學習方法訓練的能夠生成人類語言的模型。這種模型可以處理大量的文字資料,並學習從中獲得的模式,以預測在給定的文字上下文中最可能出現的下一個詞。 在一般場景下LLM可以理解使用者提出的問題並生成相應的回答。然而由於其訓練時的資料限制LLM無法處理特定領域的問題。因此我們需要探索一種方法讓LLM能夠獲取並利用長期記憶來提高問答機器人的效果。

這裡我們主要是用到了詞嵌入向量表示以及對應的向量資料庫持久化儲存,並且透過相似度計算得到長期記憶用於模型對特定領域的特定問題進行作答。詞嵌入是自然語言處理(NLP)中的一個重要概念,它是將文字資料轉換成數值型的向量,使得機器可以理解和處理。詞嵌入向量可以捕獲詞語的語義資訊,如相似的詞語會有相似的詞嵌入向量。而向量資料庫則是一種專門用來儲存和檢索向量資料的資料庫,它可以高效地對大量的向量進行相似性搜尋。

 

 

目標:如何利用C#,詞嵌入技術和向量資料庫,使LLM實現長期記憶,以落地私域問答機器人。基於以上目的,我們需要完成以下幾個步驟,從而實現將大語言模型與私域知識相結合來落地問答機器人。

 

一、私域知識的構建與詞嵌入向量的轉換

 

首先我們應該收集私域知識的文字語料,透過清洗處理得到高質量的語意文字。接著我們將這些文字透過呼叫OpenAI的詞嵌入向量介面轉化為詞嵌入向量表示的陣列

這裡我們以ChatGLM為例,ChatGLM是清華大學開源的文字生成式模型,其模型開源於2023年。所以在ChatGPT的知識庫中並不會包含相關的領域知識。當直接使用ChatGPT進行提問時,它的回答是這樣的

 由於只是演示這裡我們只准備一條關於chatglm的知識。透過呼叫openai的介面,將它轉化成詞嵌入向量

原始語料:ChatGLM是一個開源的清華技術成果轉化的公司智譜AI研發的支援中英雙語的對話機器人它支援中英雙語問答的對話語言模型,基於 General Language Model (GLM) 架構,具有 62 億引數。結合模型量化技術,使用者可以在消費級的顯示卡上進行本地部署(INT4 量化級別下最低只需 6GB 視訊記憶體)。ChatGLM-6B 使用了和 ChatGLM 相同的技術,針對中文問答和對話進行了最佳化。經過約 1T 識別符號的中英雙語訓練,輔以監督微調、反饋自助、人類反饋強化學習等技術的加持,62 億引數的 ChatGLM-6B 已經能生成相當符合人類偏好的回答。

  接著準備好一個openai的開發者key,我們將這段文字轉化成詞嵌入,這裡我使用Betalgo.OpenAI.GPT3這個Nuget包,具體程式碼如下:

var embeddings = await new OpenAiOptions() { ApiKey = key }.Embeddings.CreateEmbedding(new EmbeddingCreateRequest()
            {
                InputAsList = inputs.ToList(),
                Model = OpenAI.GPT3.ObjectModels.Models.TextEmbeddingAdaV2
            });
return embeddings.Data.Select(x => x.Embedding).ToList();

  這裡的inputs就是你的句子陣列,由於這個介面可以一次處理多條句子,所以這裡可以傳入句子陣列來實現批處理。

接著這裡會返回詞嵌入向量結果,類似如下的list<double>:

[-0.0020597207,-0.012355088,0.0037828966,-0.032127112,-0.04815184,0.016633095,-0.01277577,........]

  

二、對詞嵌入向量的理解和使用

接著我們需要使用一個向量資料庫,這裡由於只是演示,我就是用elasticsearch這樣的支援向量儲存的搜尋引擎來儲存。這裡我使用NEST作為操作ES的包

首先我們構建一個對應的實體用於讀寫ES,這裡的向量維度1536是openai的詞嵌入向量介面的陣列長度,如果是其他詞嵌入技術,則需要按需定義維度

    public class ChatGlmVector
    {
        public ChatGlmVector()
        {
            Id = Id ?? Guid.NewGuid().ToString();
        }
        [Keyword]
        public string Id { get; set; }

        [Text]
        public string Text { get; set; }

        [DenseVector(Dimensions = 1536)]
        public IList<double> Vector { get; set; }
    }

    接著我們使用NEST建立一個索引名(IndexName)並儲存剛才得到的文字和向量表示,這裡的item就是上文的ChatGlmVector例項。

if (!elasticClient.Indices.Exists(IndexName).Exists)
  elasticClient.Indices.Create(IndexName, c => c.Map<ChatGlmVector>(m => m.AutoMap()));
await elasticClient.IndexAsync(item, idx => idx.Index(IndexName));

三、使用者問題的處理與相似度計算

  使用者問題的處理和知識處理相似,將使用者問題轉化成詞嵌入向量。這裡主要講一下如何基於ES做相似度搜尋,以下是原始的請求es的json表示

POST /my_index/_search
{
  "size": 3,    // 返回前3個最相似的文件
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "functions": [
        {
          "script_score": {
            "script": {
              "source": "def cosineSim = cosineSimilarity(params.queryVector, 'vector'); if (cosineSim > 0.8) return cosineSim; else return 0;",
              "params": {
                "queryVector": [1.0, 2.0, 3.0]   // 要查詢的向量
              }
            }
          }
        }
      ],
      "boost_mode": "replace"
    }
  }
}

  我們在c#中使用NEST的表示可以透過如下程式碼來完成,這裡我們以0.8作為一個閾值來判斷相似度最低必須高於這個數字,否則可以判斷使用者問題與知識沒有關聯性。當然這個值可以根據實際情況調整。

var scriptParams = new Dictionary<string, object>
{
    {"queryVector", new double[]{1.0, 2.0, 3.0}}
};

var script = new InlineScript("def cosineSim = cosineSimilarity(params.queryVector, 'vector'); if (cosineSim > 0.8) return cosineSim; else return 0;")
{
    Params = scriptParams
};

var searchResponse = client.Search<object>(s => s
    .Size(3)
    .Query(q => q
        .FunctionScore(fs => fs
            .Query(qq => qq
                .MatchAll()
            )
            .Functions(fu => fu
                .ScriptScore(ss => ss
                    .Script(sc => script)
                )
            )
            .BoostMode(FunctionBoostMode.Replace)
        )
    )
);

四、構建精巧的prompt與OpenAI的chat介面的使用

  這裡我們就可以透過一些混合一些提示+長記憶+使用者問題作為完整的prompt餵給chatgpt得到回答

            return (await GetOpenAIService().ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest()
            {
                Messages=new List<ChatMessage>() {
                ChatMessage.FromUser("你是一個智慧助手,你需要根據下面的事實依據回答問題。如果使用者輸入不在事實依據範圍內,請說\"抱歉,這個問題我不知道。\""),
ChatMessage.FromUser($"事實依據:{這裡需要從ES查詢出相似度最高的文字作為LLM的長期記憶}"),
ChatMessage.FromUser($"使用者輸入:{這裡是使用者的原始問題}")
                },
                Model = OpenAI.GPT3.ObjectModels.Models.ChatGpt3_5Turbo
            })).Choices.FirstOrDefault().Message;

  當我們使用新的提示詞提問後,chatgpt就可以準確的告訴你相關的回答:

 寫在最後

  ChatGPT的出現已經徹底改變了這個世界,作為一個開發人員,我們能做的只能儘量跟上技術的腳步。在這個結合C#、詞嵌入技術和向量資料庫將大語言模型成功應用到私域問答機器人的案例中只是大語言模型落地的冰山一角,這僅僅是開始,我們還有許多可能性等待探索........

相關文章