部分內容參考:https://zhuanlan.zhihu.com/p/164502624
近年來,NLP自然語言處理、推薦系統,以及計算機視覺已成爲目前工業界算法崗的主流方向,無論在哪個領域,對“Embedding”這個詞概念的理解都是每個龐大知識體系的基石。
“Embedding”直譯是嵌入式、嵌入層。
看到這個翻譯的時候是不是一臉懵圈?什麼叫嵌入?意思是牢固地或深深地固定?那麼它能把什麼嵌入到什麼呢?
很開心地告訴你,它能把萬物嵌入萬物,是溝通兩個世界的橋樑,是打破次元壁的蟲洞!
用數學的話來說:“它是單射且同構的(看到這麼好的性質是不是很激動!)”
簡單來說,我們常見的地圖就是對於現實地理的Embedding,現實的地理地形的信息其實遠遠超過三維,但是地圖通過顏色和等高線等來最大化表現現實的地理信息。
通過它,我們在現實世界裏的文字、圖片、語言、視頻就能轉化爲計算機能識別、能使用的語言,且轉化的過程中信息不丟失。
怎麼理解Embedding
首先,我們有一個one-hot編碼的概念。
假設,我們中文,一共只有10個字,那麼我們用0-9就可以表示完。
比如,這十個字就是“小普喜歡星海灣的朋友”
其分別對應“0-9”,如下:
那麼,其實我們只用一個列表就能表示所有的對話。
例如:
或者:
但是,經過one-hot編碼把上面變成:
即:把每一個字都對應成一個十個(樣本總數/字總數)元素的數組/列表,其中每一個字都用唯一對應的數組/列表對應,數組/列表的唯一性用1表示。
那問題來了,費老大勁整這個幹嘛呢?有什麼優勢?
很明顯,計算簡單嘛,稀疏矩陣做矩陣計算的時候,只需要把1對應位置的數相乘求和就行,也許你心算都能算出來;而一維列表,你能很快算出來?
何況這個列表還是一行,如果是100行、1000行或1000列呢?所以,one-hot編碼的優勢就體現出來了,計算方便快捷、表達能力強。
然而,缺點也隨着來了。
比如:中文大大小小簡體繁體常用不常用有十幾萬,然後一篇文章100W字,你要表示成100W X 10W的矩陣???
這是它最明顯的缺點:過於稀疏時,過度佔用資源。
比如:其實我們這篇文章,雖然100W字,但是其實我們整合起來,有99W字是重複的,只有1W字是完全不重複的。
那我們用100W X 10W的豈不是白白浪費了99W X 10W的矩陣存儲空間。
那怎麼辦???
這時,Embedding層就出現了!
假設:我們有一個2 x 6的矩陣,然後乘上一個6 x 3的矩陣後,變成了一個2 x 3的矩陣。
先不管它什麼意思,這個過程,我們把一個A中的12個元素的矩陣變成C中6個元素的矩陣,直觀上,大小是不是縮小了一半?
對!!!Embedding層,在某種程度上,就是用來降維的,降維的原理就是矩陣乘法。
假如我們有一個100W X10W的矩陣,用它乘上一個10W X 20的矩陣,我們可以把它降到100W X 20,瞬間量級降了10W/20=5000倍!!!
這就是嵌入層的一個作用——降維。
接着,既然可以降維,當然也可以升維。
爲什麼要升維?
這張圖,如果要你在10米開外找出四處不同!是不是太困難了!(小普這就叫復聯的鷹眼來幫我!)當然,目測這是不可能完成的。
但是讓你在一米外,也許你一瞬間就發現鼻子是不同的,然後再走近半米,你又發現右下角元寶也是不同的。再走近20釐米,又發現耳朵也不同,最後,在距離屏幕10釐米的地方,終於發現第四個不同的地方在眼睛的高光。
但是,其實無限靠近並不代表認知度就高了,比如,你只能距離屏幕1釐米遠的地方找,找出四處不同,小普怕不是要被讀者打死了。
由此可見,距離的遠近會影響我們的觀察效果。
同理也是一樣的,低維的數據可能包含的特徵是非常籠統的,我們需要不停地拉近拉遠來改變我們的感受,讓我們對這幅圖有不同的觀察點,找出我們要的"茬"。
Embedding的又一個作用體現了:對低維的數據進行升維時,可能把一些其他特徵給放大了,或者把籠統的特徵給分開了。
同時,這個Embedding是一直在學習在優化的,就使得整個拉近拉遠的過程慢慢形成一個良好的觀察點。
比如:小普來回靠近和遠離屏幕,發現45釐米是最佳觀測點,這個距離能10秒就把4個不同點找出來了。
因此它就是作爲這個橋樑的存在,讓我們手頭的東西可伸可縮,變成我們希望的樣子。
語義理解中Embedding意義
從詞向量說起
從字面本身計算語義相關性是不夠的 - 不同字,同義:「快樂」vs.「高興」 - 同字,不同義:「上馬」vs.「馬上」 所以我們需要一種方法,能夠有效計算詞與詞之間的關係,詞向量(Word Embedding)應運而生詞向量的基本原理:用一個詞上下文窗口表示它自身
詞向量的不足
- 同一個詞在不同上下文中語義不同:我從「馬上」下來 vs. 我「馬上」下來- https://nlp.stanford.edu/projects/glove/
- https://fasttext.cc/
基於整個句子,表示句中每個詞,那麼同時我們也就表示了整個句子
所以,句子、篇章都可以向量化
Sentence Transformer
向量相似度計算¶
from dotenv import load_dotenv
import numpy as np
from numpy import dot
from numpy.linalg import norm
from langchain.embeddings import OpenAIEmbeddings
import warnings
warnings.filterwarnings("ignore")
load_dotenv()
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)
model = OpenAIEmbeddings(model='text-embedding-ada-002')
query = "業績增長"
documents = [
"小明,我們公司三季度接了個大單",
"小明,我們公司今天三季度同比增長60%",
"小明,我們公司第二季度,有一筆交易被認定的死賬",
"小明,公司CEO信心滿滿,下一季度再創新高",
"小明,公司計劃要擴招300人",
]
query_vec = model.embed_query(query)
doc_vecs = model.embed_documents(documents)
print("Cosine distance:") # 越大越相似
print(cos_sim(query_vec, query_vec))
for vec in doc_vecs:
print(cos_sim(query_vec, vec))
print("\nEuclidean distance:") # 越小越相似
print(l2(query_vec, query_vec))
for vec in doc_vecs:
print(l2(query_vec, vec))
Cosine distance: 1.0 0.8063100782497249 0.8492772395611916 0.7755446349946937 0.8278780825719825 0.8181322596289557 Euclidean distance: 0.0 0.6223984603937824 0.549040545750145 0.6700080074227571 0.5867229626118574 0.6031048671185539
基於相似度聚類¶
from langchain.embeddings import OpenAIEmbeddings
from sklearn.cluster import KMeans, DBSCAN
texts = [
"這個接口返回的參數不對",
"幫我添加一個下載按鈕",
"我需要在window上也能用",
"接口報500錯誤",
"接口反應超時了",
"服務掛了",
"我需要新增一個新的配置",
"這個可以取消不用做了",
"我們做一個適配的接口吧",
"系統上報一個監控到的錯誤日誌"
]
model = OpenAIEmbeddings(model='text-embedding-ada-002')
X = []
for t in texts:
embedding = model.embed_query(t)
X.append(embedding)
# clusters = KMeans(n_clusters=3, random_state=42, n_init="auto").fit(X)
clusters = DBSCAN(eps=0.55, min_samples=2).fit(X)
for i, t in enumerate(texts):
print("{}\t{}".format(clusters.labels_[i], t))
0 這個接口返回的參數不對 -1 幫我添加一個下載按鈕 -1 我需要在window上也能用 0 接口報500錯誤 0 接口反應超時了 -1 服務掛了 -1 我需要新增一個新的配置 -1 這個可以取消不用做了 -1 我們做一個適配的接口吧 -1 系統上報一個監控到的錯誤日誌
本地部署¶
# %pip install sentence_transformers
from langchain.embeddings import HuggingFaceBgeEmbeddings
model_name = "BAAI/bge-large-zh-v1.5"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
model = HuggingFaceBgeEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
Downloading pytorch_model.bin: 100%|██████████| 1.30G/1.30G [02:45<00:00, 7.87MB/s] Downloading (…)nce_bert_config.json: 100%|██████████| 52.0/52.0 [00:00<?, ?B/s] Downloading (…)cial_tokens_map.json: 100%|██████████| 125/125 [00:00<?, ?B/s] Downloading (…)cec2c/tokenizer.json: 100%|██████████| 439k/439k [00:00<00:00, 474kB/s] Downloading (…)okenizer_config.json: 100%|██████████| 394/394 [00:00<00:00, 140kB/s] Downloading (…)bb65dcec2c/vocab.txt: 100%|██████████| 110k/110k [00:00<00:00, 55.3MB/s] Downloading (…)5dcec2c/modules.json: 100%|██████████| 229/229 [00:00<?, ?B/s]
query = "前瞻性"
documents = [
"我們需要藉助最近流行的起來的GPT幫我們提升工作效率",
"你按照這個guide page一步一步走就可以了",
"我們一起來探索下,看看這個新的開源框架能不能幫助到我們",
"Hugging Face上有很多新的開源模型,我們可以基於我們環境來嘗試試驗下",
"Langchain上的開源工具也很多,我們的選擇性很大,加油",
]
query_vec = model.embed_query(query)
doc_vecs = model.embed_documents(documents)
print("Cosine distance:") # 越大越相似
print(cos_sim(query_vec, query_vec))
for vec in doc_vecs:
print(cos_sim(query_vec, vec))
Cosine distance: 0.9999999999999999 0.22479291416184247 0.12025486693514557 0.2333528960497604 0.2213879507146927 0.1755505059722227
- Sentence Transformer: https://www.sbert.net/index.html
- BGE Embedding: https://huggingface.co/BAAI