余弦相似度是计算两个向量夹角角度的
cos
\cos
cos 值,取值范围在 -1 和 1 之间。如果两个向量的方向完全一致,那么它们的余弦相似度为 1;如果两个向量的方向完全相反,那么它们的余弦相似度为 -1;如果两向量是垂直(正交)的,那么它们的余弦相似度为 0。其公式如下:
cos
(
θ
)
=
A
⃗
⋅
B
⃗
∥
A
⃗
∥
∥
B
⃗
∥
\cos(\theta) = \frac{\vec A \cdot \vec B}{\|\vec A\| \|\vec B\|}
cos(θ)=∥A∥∥B∥A⋅B
A
⃗
\vec A
A 和
B
⃗
\vec B
B 分别是两个向量,
θ
\theta
θ 是两个向量的夹角。而
∥
A
⃗
∥
\|\vec A\|
∥A∥ 和
∥
B
⃗
∥
\|\vec B\|
∥B∥ 分别是向量
A
⃗
\vec A
A 和
B
⃗
\vec B
B 的长度(模长)。因为 OpenAI 的 Embedding 模型返回的是单位向量,即向量的长度为 1,所以我们不需要计算模长,而它们的夹角就是两个向量的点积。
cos
(
θ
)
=
A
⃗
⋅
B
⃗
1
⋅
1
=
A
⃗
⋅
B
⃗
\cos(\theta) = \frac{\vec A \cdot \vec B}{1 \cdot 1} = \vec A \cdot \vec B
cos(θ)=1⋅1A⋅B=A⋅B
import os
import pandas
MAX_LEN =2048defsplit_text(text, max_length=2048):
paragraphs = text.split("\n")
result =[]
current_paragraph =""for paragraph in paragraphs:iflen(current_paragraph)+len(paragraph)> max_length:
result.append(current_paragraph)
current_paragraph = paragraph
else:
current_paragraph +="\n"+ paragraph
if current_paragraph:
result.append(current_paragraph)return result
deffind_md_files(directory):
result =[]for root, dirs, files in os.walk(directory):forfilein files:iffile.endswith(".md"):
result.append(os.path.join(root,file))return result
if __name__ =="__main__":
df = pandas.DataFrame(columns=["file","content"])forfilein find_md_files("."):withopen(file)as f:
text = f.read()for c in split_text(text, MAX_LEN):
df.loc[len(df)]=[file, c]
df.to_csv("output.csv", index=False)
然后将这些段落传入 Embedding 模型,得到每个段落的向量。这里我没有使用异步,这是为了避免触发 API 的速率限制。为了演示方便,我只是将数据保存在 csv 文件中,实际使用时,我们可以将数据保存到 Pinecone,Milvus 等向量数据库中。
import openai
import pandas
openai.api_key =""
openai.api_base =""
openai.api_type ="azure"
openai.api_version ="2023-03-15-preview"
model ="text-embedding-ada-002"defget_embedding(text):
response = openai.Embedding.create(input=text, engine="text-embedding-ada-002")
embedding = response["data"][0]["embedding"]assertlen(embedding)==1536return embedding
defmain():
df = pandas.read_csv("output.csv")
embeddings =[get_embedding(text)for text in df["content"]]
df["embedding"]= embeddings
df.to_csv("docs.csv", index=False)if __name__ =="__main__":import time
star = time.time()
main()print(f"Time taken: {time.time()- star}")
现在我们获取到了相关的信息,接下来我们将相关的信息和问题一起传入 GPT,让它生成回答。这里因为我用的是 GPT-3,他对 system 的内容没有那么看重,所以我用了 user 的身份来传入最开始我们设定的 prompt,并手动编写了一个回答来强化 GPT 对于我们的提示的理解。这句话和上面生成查询语句的请求一样,并没有放到 history 中。但我们有将 GPT 的回答放进去。
history.append({"role":"user","content": user_input})
massage =[{"role":"user","content": prompt_prefix.format(sources=top_content)},{"role":"assistant","content":"好的,我只会根据以上提供的资料提供的内容回答问题,我不会回答不使用资源的内容。",},]+ history
res = get_chat_answer(massage)print(res["content"])
history.append(res)print("-"*50, end="\n\n")