[自然語言處理] #7 ChatGPT 專業知識小幫手 實作教學
建立客製化的專業知識ChatGPT小幫手
上一篇:[自然語言處理] #6 如何與ChatGPT合作-寫廣告篇
[前言]
好久沒有寫實作教學,拜Chatgpt所賜,現在看到的聊天機器人已經變得越來越多,現在常見的社團裡面三不五時就有人提供用api串好的Line Chatbot,這是很有趣的畫面,我不會認為這樣的現象不好,畢竟openAI開放api就是要讓人使用的,但很多的聊天機器人只是單純的把api串起來,完全沒有附加價值…,作為自然語言工程師其實覺得很讓人問號,在講下去就離題太遠了,讓我們拉回來,今天本篇會教給大家如何一步步建立專業Domain知識的聊天機器人,有問題或是講解不好的地方,還請各位讀者留言指教~
對於想要just give me code的朋友可以參考此notion-qa,使用LangChain將全部串完(只需要API的費用~),想要知道更多的朋友可以往下看~
目錄
- 背景說明、問題介紹❓
- 步驟說明📑
- 實作教學📓
- 結語💬
背景說明、問題介紹 ❓
將思考角度轉個彎,找出適合的解法解決問題
隨著聊天機器人的普及,帶給了很多企業一道曙光,好像可以作出以前做不出來的聊天機器人,但同樣也帶來一些隱憂跟問題:
- 回答的答案不一定正確:儘管ChatGPT已經經過大量訓練,但它仍然無法避免由於語言模型的限制而產生誤解,進而產生回答錯誤的情況。
- 回答的答案跟公司內部的資訊不同:但每一個領域,一定有自己的規範或是產品等(Ex. 不同公司的產品、不同國家的法律)是跟大眾的資訊不同的,由於ChatGPT是基於大量的資料而建構出來的,因此它學習到的知識,跟特定領域的答案,很高機率是不同。
通常為了解決以上問題,普遍上有兩種不同的做法:
- 使用特定domain的資料進行Finetune 語言模型(chatgpt、ada etc.)
- 限縮回答的知識來源,採用知識餵養的方式 [0629更新](這個做法被稱為retrieval augmented generation, RAG)
但是以我來推薦的話,我會推薦用第二種方式,第一種方式"可能"會是一個治本的做法,但是要Finetune大型語言模型LLM資料還是需要有一定的量體,通常一般的公司或是小團體不太可能會有這些資料,儘管只是Finetune,而且還有可能越訓練在其他的效果更差,而使用第二種方法,則是利用LLM對於資料彙整以及通順回答的強大,避開生成式AI的弱點!
不要跟他拚拳,我們切他中路!
步驟說明 📑
建構一個ChatGPT專業知識小幫手,通常可以簡化為以下四個步驟:
1. 資料整理
2. 建立索引
3. 問題匹配
4. 設計Prompt並餵養資料讓ChatGPT回答
讓我們後面一一跟讀者說明。
整理資料
這個步驟是非常重要的,因為本次製作的聊天機器人所使用的資料決定了其回答問題的準確性和可靠性還有覆蓋度。作法上可以將資料整理的像是FAQ,基本上會建議資料保持以下三種欄位:
- 標題(optional輔助用):內文標題,有些應用上可以拿出來呈現給使用者看問題可能跟那些標題文本有關,也可以當做餵養ChatGPT的資料來源。
- 內文:要餵養ChatGPT的主要資料來源,讓ChatGPT從中找出使用者問題的答案。
- 索引內容:最多技巧的欄位,主要用來跟使用者輸入的問題進行匹配,去索引資料的欄位。
為什麼要額外有索引內容這個欄位呢?
其實是因為阿,有些場景他的問題跟從內文上是看不出關係的,或是希望有額外的索引方式ex. 時間、作者、問的問題、標題等,把他跟內文合在一起,一起拿去索引可以有高的coverage。
建立索引方式
建立索引是將整理好的資料進行分類、歸納和整理的過程。這個步驟的目的是將資料結構化,並建立一個能夠快速搜尋資料的索引系統,來針對user的輸入進行相似度匹配,以提高聊天機器人的回答速度和效率還有準確度。
這一步驟有很多建立索引的方式,像是傳統Information Retrieval上有用關鍵詞彙做的TFIDF、BM25或是用doc_to_vec、embedding的做法,根據選的檢索的方法不同會先建立embedding或是索引,對於整個聊天機器人來說,效果影響(速度、準確度、價格)最大的地方就是在這裡,因為越準確的方法通常其速度就越慢價格成本越高,就像是資源分配的三角。
為什麼看到很多人都是用Embedding來做啊?
因為以目前來說,Embedding的做法抓出正確答案準確度以及覆蓋度綜合來說最高,所以大部分的做法都是用Embedding的方式來做。
問題匹配
當用戶提出問題時,會根據前一步建立索引的方式,會把問題進行向量化然後與我們先前處理好的資料進行相似度比對,讓系統能夠快速且盡可能的從索引中找到相關的資訊。
設計Prompt並餵養資料讓ChatGPT回答
需要設計好一個Prompt在有限的長度內,將資料插入到Prompt中,讓ChatGPT能夠進行答案的找尋,並且組織成問題的答案,還有輸出格式的規範。
基本上看到這裡,假如對於上面的技術都了解的朋友,我相信已經可以自己用各種套件建構出自己的聊天機器人了。但還不知道怎麼做的朋友,繼續看我下一段落的教學~ 😃
實作教學 📓
整理資料
我是使用新聞網站的資料做為範例,你們可以使用自己的資料,整理成下面的格式即可。
import pandas as pd
df = pd.read_csv('news.csv')
建立索引方式
本次索引的方式是用Sentence-transformers,因為我openai的Quota用完了而且不想花錢XD,假如想要使用openAI的embedding,需要使用到自己的api token。(如何取得api token)
sentence-transformers version:
#sentence_transformers version
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-mpnet-base-v2')
embeddings = model.encode(df['內文索引'])
df['context_embedding'] = embeddings.tolist()
print(len(df['context_embedding'][0]))
print(type(df['context_embedding'][0]))
print(df['context_embedding'][0])
openai embedding version:
#openai version
import openai
openai.api_key = openai_api_key
#使用自己的api key 如何產生請參考https://platform.openai.com/account/api-keys
def embedding(sent):
response = openai.Embedding.create(
input=sent,
model="text-embedding-ada-002" #也可以選用其他的model但ada就夠用了
)
embeddings = response['data'][0]['embedding']
return embeddings
context_embeddings = []
for context_index in df['內文索引']:
context_embedding = embedding(context_index)
context_embeddings.append(context_embedding)
df['context_embedding'] = context_embeddings
這邊會搭配一個高維快速索引樹spotify/annoy,為什麼會需要他,是因為當資料的維度很大的時候,像是用sentence-transformers的文件embedding會有768維,假如是用openAI的則會有1536維,這在做向量運算的時候其實很慢,而annoy是用樹的方式建立index犧牲了些許的準確度,但是大大增加了速度。
from annoy import AnnoyIndex
distance_spec = 'angular'
f = 768 # Length of item vector that will be indexed
df['num'] = range(df.shape[0])
context_index = AnnoyIndex(f, distance_spec)
for num, vec in zip(df['num'], df['context_embedding']):
context_index.add_item(num, vec)
context_index.build(2) # 2 trees 資料很少,兩棵樹就夠了示意一下。
context_index.save('index.ann') 存起來下次用
這裡需要注意的是,annoy需要給他一個數字做維索引的編號num。
問題匹配
這邊就是跟資料embedding一樣將問題去做embedding
input_text = '美國財政部長是誰?'
input_embed = model.encode([input_text])
index_doc_count = 2 #怕索引不準,就多抓幾筆就好,有自信可以只抓一筆XD
num_id = context_index.get_nns_by_vector(input_embed[0],index_doc_count)
paragraph = df[df['num'].isin(num_id)]['內文']
print(paragraph)
設計Prompt並餵養資料讓ChatGPT回答
接下來就是根據您的使用場景去設計Prompt來讓ChatGPT去回答。
def ask_chatgpt(data,question):
prompt_template = '請只根據以下資料"{}",不要用條列式回答我的話,{},假如資料裡面沒有答案,回答"根據您提供的資料,我不知道答案。""'.format(','.join(data),question)
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt_template},
]
)
return completion
res = ask_chatgpt(paragraph,input_text)
print(res['choices'][0]['message']['content'])
結語 💬
好久沒有寫這麼長的教學文章了,感覺真的很不適應,雖然有用到CreatiMind.AI輔助,但還是有點累XD,本篇教學文章從資料的前處理
、到索引的方式
、還有高維度資料如何快速索引
、到限縮資料範圍給ChatGPT進行回答確保ChatGPT回答正確的答案,希望有幫助到您。
最後我也想補充一句,我們學習不是為了要重複造輪子,而是更加了解問題的根本,然後根據我們的狀況學以致用!