【NLP】文本相似度的BERT度量方法
作者?| James Briggs
編譯 | VK
來(lái)源 | Towards Data Science
這篇文章討論的是關(guān)于BERT的序列相似性。
NLP的很大一部分依賴于高維空間中的相似性。通常,一個(gè)NLP解決方案需要一些文本,處理這些文本來(lái)創(chuàng)建一個(gè)大的向量/數(shù)組來(lái)表示該文本。
這是高維的魔法。
句子的相似性是一個(gè)最清楚的例子,說(shuō)明了高維魔法是多么強(qiáng)大。
邏輯是這樣的:
把一個(gè)句子,轉(zhuǎn)換成一個(gè)向量。
把其他許多句子,轉(zhuǎn)換成向量。
找出它們之間的距離(歐幾里德)或余弦相似性。
我們現(xiàn)在就有了一個(gè)句子間語(yǔ)義相似性的度量!
當(dāng)然,我們希望更詳細(xì)地了解正在發(fā)生的事情,并用Python實(shí)現(xiàn)它!所以,讓我們開(kāi)始吧。
BERT
BERT,正如我們已經(jīng)提到的,是NLP的MVP。其中很大一部分歸功于BERT將單詞的意思嵌入到密集向量的能力。
我們稱之為密集向量,因?yàn)橄蛄恐械拿總€(gè)值都有一個(gè)值,并且有一個(gè)成為該值的原因-這與稀疏向量相反,例如one-hot編碼向量,其中大多數(shù)值為0。
BERT擅長(zhǎng)創(chuàng)建這些密集向量,每個(gè)編碼器層輸出一組密集向量。
對(duì)于BERT-base,這將是一個(gè)包含768維的向量,這768個(gè)值包含我們對(duì)單個(gè)token的數(shù)字表示,我們可以使用它作為上下文詞嵌入。
我們可以把這些張量轉(zhuǎn)換成輸入序列的語(yǔ)義表示。然后,我們可以采用相似性度量并計(jì)算不同序列之間的相似性。
最簡(jiǎn)單和最常用的提取張量是最后的隱藏狀態(tài)。
當(dāng)然,這是一個(gè)相當(dāng)大的張量,是512x768維,因?yàn)橛?12個(gè)token,我們需要一個(gè)向量來(lái)應(yīng)用我們的相似性度量。
要做到這一點(diǎn),我們需要把最后一個(gè)隱藏態(tài)張量轉(zhuǎn)換成768維的向量。
創(chuàng)建向量
為了把最后一個(gè)隱藏態(tài)張量轉(zhuǎn)換成向量,我們使用了平均池運(yùn)算。
這512個(gè)token中的每一個(gè)都有各自的768個(gè)值。這個(gè)池操作將取所有token嵌入的平均值,并將它們壓縮到一個(gè)768向量空間中,從而創(chuàng)建一個(gè)“句子向量”。
我們不需要考慮填充token(我們不應(yīng)該包括它)。
代碼
這是理論和邏輯-但我們?nèi)绾卧诂F(xiàn)實(shí)中應(yīng)用這一點(diǎn)?
我們將概述兩種方法-簡(jiǎn)單方法和稍微復(fù)雜一點(diǎn)的方法。
簡(jiǎn)單—Sentence-Transformers
對(duì)于我們來(lái)說(shuō),實(shí)現(xiàn)我們剛剛介紹的所有內(nèi)容的最簡(jiǎn)單方法是通過(guò)Sentence-Transformers庫(kù)——它將這個(gè)過(guò)程的大部分內(nèi)容封裝成幾行代碼。
首先,我們使用pip install sentence-transformers來(lái)安裝sentence-transformers。這個(gè)庫(kù)使用HuggingFace的Transformer,所以我們可以在這里找到 sentence-transformers模型:https://huggingface.co/sentence-transformers
我們將使用bert-base-nli-mean-tokens模型,它實(shí)現(xiàn)了我們到目前為止討論的相同邏輯。
(它還使用128個(gè)輸入token,而不是512個(gè))。
讓我們創(chuàng)建一些句子,初始化我們的模型,并對(duì)句子進(jìn)行編碼:
Write?a?few?sentences?to?encode?(sentences?0?and?2?are?both?similar): sentences?=?["Three?years?later,?the?coffin?was?still?full?of?Jello.","The?fish?dreamed?of?escaping?the?fishbowl?and?into?the?toilet?where?he?saw?his?friend?go.","The?person?box?was?packed?with?jelly?many?dozens?of?months?later.","He?found?a?leprechaun?in?his?walnut?shell." ] Initialize?our?model: from?sentence_transformers?import?SentenceTransformermodel?=?SentenceTransformer('bert-base-nli-mean-tokens') HBox(children=(HTML(value=''),?FloatProgress(value=0.0,?max=405234788.0),?HTML(value='')))Encode?the?sentences: sentence_embeddings?=?model.encode(sentences) sentence_embeddings.shape (4,?768)很好,我們現(xiàn)在有四個(gè)句子嵌入-每個(gè)包含768維。
現(xiàn)在我們要做的是取這些嵌入,找出它們之間的余弦相似性。所以對(duì)于第0句:
Three years later, the coffin was still full of Jello.
我們可以通過(guò)以下方法找到最相似的句子:
from?sklearn.metrics.pairwise?import?cosine_similarity 讓我們計(jì)算第0句的余弦相似度: cosine_similarity([sentence_embeddings[0]],sentence_embeddings[1:] ) array([[0.33088642,?0.7218851?,?0.55473834]],?dtype=float32) 這些相似之處可以解釋為:| 1 | "The fish dreamed of escaping the fishbowl and into the toilet where he saw his friend go." | 0.3309 |
| 2 | "The person box was packed with jelly many dozens of months later." | 0.7219 |
| 3 | "He found a leprechaun in his walnut shell." | 0.5547 |
復(fù)雜-Transformer和PyTorch
在進(jìn)入第二種方法之前,值得注意的是,它與第一種方法做了相同的事情,但有點(diǎn)復(fù)雜。
使用這種方法,我們需要自己創(chuàng)建句子嵌入。為此,我們執(zhí)行平均池操作。
https://youtu.be/jVPd7lEvjtg
此外,在平均池操作之前,我們需要?jiǎng)?chuàng)建last_hidden_state,如下所示:
from?transformers?import?AutoTokenizer,?AutoModel import?torch First?we?initialize?our?model?and?tokenizer: tokenizer?=?AutoTokenizer.from_pretrained('sentence-transformers/bert-base-nli-mean-tokens') model?=?AutoModel.from_pretrained('sentence-transformers/bert-base-nli-mean-tokens') Then?we?tokenize?the?sentences?just?as?before: sentences?=?["Three?years?later,?the?coffin?was?still?full?of?Jello.","The?fish?dreamed?of?escaping?the?fishbowl?and?into?the?toilet?where?he?saw?his?friend?go.","The?person?box?was?packed?with?jelly?many?dozens?of?months?later.","He?found?a?leprechaun?in?his?walnut?shell." ]#?初始化字典來(lái)存儲(chǔ) tokens?=?{'input_ids':?[],?'attention_mask':?[]}for?sentence?in?sentences:#?編碼每個(gè)句子并添加到字典new_tokens?=?tokenizer.encode_plus(sentence,?max_length=128,truncation=True,?padding='max_length',return_tensors='pt')tokens['input_ids'].append(new_tokens['input_ids'][0])tokens['attention_mask'].append(new_tokens['attention_mask'][0])#?將張量列表重新格式化為一個(gè)張量 tokens['input_ids']?=?torch.stack(tokens['input_ids']) tokens['attention_mask']?=?torch.stack(tokens['attention_mask']) We?process?these?tokens?through?our?model: outputs?=?model(**tokens) outputs.keys() odict_keys(['last_hidden_state',?'pooler_output'])The?dense?vector?representations?of?our?text?are?contained?within?the?outputs?'last_hidden_state'?tensor,?which?we?access?like?so: embeddings?=?outputs.last_hidden_state embeddings tensor([[[-0.0692,??0.6230,??0.0354,??...,??0.8033,??1.6314,??0.3281],[?0.0367,??0.6842,??0.1946,??...,??0.0848,??1.4747,?-0.3008],[-0.0121,??0.6543,?-0.0727,??...,?-0.0326,??1.7717,?-0.6812],...,[?0.1953,??1.1085,??0.3390,??...,??1.2826,??1.0114,?-0.0728],[?0.0902,??1.0288,??0.3297,??...,??1.2940,??0.9865,?-0.1113],[?0.1240,??0.9737,??0.3933,??...,??1.1359,??0.8768,?-0.1043]],[[-0.3212,??0.8251,??1.0554,??...,?-0.1855,??0.1517,??0.3937],[-0.7146,??1.0297,??1.1217,??...,??0.0331,??0.2382,?-0.1563],[-0.2352,??1.1353,??0.8594,??...,?-0.4310,?-0.0272,?-0.2968],...,[-0.5400,??0.3236,??0.7839,??...,??0.0022,?-0.2994,??0.2659],[-0.5643,??0.3187,??0.9576,??...,??0.0342,?-0.3030,??0.1878],[-0.5172,??0.3599,??0.9336,??...,??0.0243,?-0.2232,??0.1672]],[[-0.7576,??0.8399,?-0.3792,??...,??0.1271,??1.2514,??0.1365],[-0.6591,??0.7613,?-0.4662,??...,??0.2259,??1.1289,?-0.3611],[-0.9007,??0.6791,?-0.3778,??...,??0.1142,??0.9080,?-0.1830],...,[-0.2158,??0.5463,??0.3117,??...,??0.1802,??0.7169,?-0.0672],[-0.3092,??0.4833,??0.3021,??...,??0.2289,??0.6656,?-0.0932],[-0.2940,??0.4678,??0.3095,??...,??0.2782,??0.5144,?-0.1021]],[[-0.2362,??0.8551,?-0.8040,??...,??0.6122,??0.3003,?-0.1492],[-0.0868,??0.9531,?-0.6419,??...,??0.7867,??0.2960,?-0.7350],[-0.3016,??1.0148,?-0.3380,??...,??0.8634,??0.0463,?-0.3623],...,[-0.1090,??0.6320,?-0.8433,??...,??0.7485,??0.1025,??0.0149],[?0.0072,??0.7347,?-0.7689,??...,??0.6064,??0.1287,??0.0331],[-0.1108,??0.7605,?-0.4447,??...,??0.6719,??0.1059,?-0.0034]]],grad_fn=<NativeLayerNormBackward>) embeddings.shape torch.Size([4,?128,?768])在生成密集向量嵌入之后,我們需要執(zhí)行平均池操作來(lái)創(chuàng)建單個(gè)向量編碼(句子嵌入)。
為了實(shí)現(xiàn)這個(gè)平均池操作,我們需要將嵌入張量中的每個(gè)值乘以其各自的掩碼值,這樣我們就可以忽略非實(shí)數(shù)token。
To?perform?this?operation,?we?first?resize?our?attention_mask?tensor: attention_mask?=?tokens['attention_mask'] attention_mask.shape torch.Size([4,?128]) mask?=?attention_mask.unsqueeze(-1).expand(embeddings.size()).float() mask.shape torch.Size([4,?128,?768]) mask tensor([[[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],...,[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.]],[[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],...,[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.]],[[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],...,[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.]],[[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],[1.,?1.,?1.,??...,?1.,?1.,?1.],...,[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.],[0.,?0.,?0.,??...,?0.,?0.,?0.]]])上面的每個(gè)向量表示一個(gè)單獨(dú)token的掩碼——現(xiàn)在每個(gè)token都有一個(gè)大小為768的向量,表示它的attention_mask狀態(tài)。然后將兩個(gè)張量相乘: masked_embeddings?=?embeddings?*?mask masked_embeddings.shape torch.Size([4,?128,?768]) masked_embeddings tensor([[[-0.0692,??0.6230,??0.0354,??...,??0.8033,??1.6314,??0.3281],[?0.0367,??0.6842,??0.1946,??...,??0.0848,??1.4747,?-0.3008],[-0.0121,??0.6543,?-0.0727,??...,?-0.0326,??1.7717,?-0.6812],...,[?0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000],[?0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000],[?0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000]],[[-0.3212,??0.8251,??1.0554,??...,?-0.1855,??0.1517,??0.3937],[-0.7146,??1.0297,??1.1217,??...,??0.0331,??0.2382,?-0.1563],[-0.2352,??1.1353,??0.8594,??...,?-0.4310,?-0.0272,?-0.2968],...,[-0.0000,??0.0000,??0.0000,??...,??0.0000,?-0.0000,??0.0000],[-0.0000,??0.0000,??0.0000,??...,??0.0000,?-0.0000,??0.0000],[-0.0000,??0.0000,??0.0000,??...,??0.0000,?-0.0000,??0.0000]],[[-0.7576,??0.8399,?-0.3792,??...,??0.1271,??1.2514,??0.1365],[-0.6591,??0.7613,?-0.4662,??...,??0.2259,??1.1289,?-0.3611],[-0.9007,??0.6791,?-0.3778,??...,??0.1142,??0.9080,?-0.1830],...,[-0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000],[-0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000],[-0.0000,??0.0000,??0.0000,??...,??0.0000,??0.0000,?-0.0000]],[[-0.2362,??0.8551,?-0.8040,??...,??0.6122,??0.3003,?-0.1492],[-0.0868,??0.9531,?-0.6419,??...,??0.7867,??0.2960,?-0.7350],[-0.3016,??1.0148,?-0.3380,??...,??0.8634,??0.0463,?-0.3623],...,[-0.0000,??0.0000,?-0.0000,??...,??0.0000,??0.0000,??0.0000],[?0.0000,??0.0000,?-0.0000,??...,??0.0000,??0.0000,??0.0000],[-0.0000,??0.0000,?-0.0000,??...,??0.0000,??0.0000,?-0.0000]]],grad_fn=<MulBackward0>)然后我們沿著軸1將剩余的嵌入項(xiàng)求和: summed?=?torch.sum(masked_embeddings,?1) summed.shape torch.Size([4,?768])然后將張量的每個(gè)位置上的值相加: summed_mask?=?torch.clamp(mask.sum(1),?min=1e-9) summed_mask.shape torch.Size([4,?768]) summed_mask tensor([[15.,?15.,?15.,??...,?15.,?15.,?15.],[22.,?22.,?22.,??...,?22.,?22.,?22.],[15.,?15.,?15.,??...,?15.,?15.,?15.],[14.,?14.,?14.,??...,?14.,?14.,?14.]])最后,我們計(jì)算平均值: mean_pooled?=?summed?/?summed_mask mean_pooled tensor([[?0.0745,??0.8637,??0.1795,??...,??0.7734,??1.7247,?-0.1803],[-0.3715,??0.9729,??1.0840,??...,?-0.2552,?-0.2759,??0.0358],[-0.5030,??0.7950,?-0.1240,??...,??0.1441,??0.9704,?-0.1791],[-0.2131,??1.0175,?-0.8833,??...,??0.7371,??0.1947,?-0.3011]],grad_fn=<DivBackward0>)一旦我們有了密集向量,我們就可以計(jì)算每個(gè)向量之間的余弦相似性——這和我們以前使用的邏輯是一樣的:
from?sklearn.metrics.pairwise?import?cosine_similarity 讓我們計(jì)算第0句的余弦相似度: #?將PyTorch張量轉(zhuǎn)換為numpy數(shù)組 mean_pooled?=?mean_pooled.detach().numpy()#?計(jì)算 cosine_similarity([mean_pooled[0]],mean_pooled[1:] ) array([[0.33088905,?0.7219259?,?0.55483633]],?dtype=float32)These?similarities?translate?to:| 1 | "The fish dreamed of escaping the fishbowl and into the toilet where he saw his friend go." | 0.3309 |
| 2 | "The person box was packed with jelly many dozens of months later." | 0.7219 |
| 3 | "He found a leprechaun in his walnut shell." | 0.5548 |
我們返回了幾乎相同的結(jié)果-唯一的區(qū)別是索引3的余弦相似性從0.5547移到了0.5548,這是一個(gè)微小的差異。
以上就是介紹如何使用BERT測(cè)量句子的語(yǔ)義相似性的全部?jī)?nèi)容—使用sentence-transformers ,PyTorch和transformers兩種方法實(shí)現(xiàn)。
兩種方法的完整筆記本:https://github.com/jamescalam/transformers/blob/main/course/similarity/04_sentence_transformers.ipynb和https://github.com/jamescalam/transformers/blob/main/course/similarity/03_calculating_similarity.ipynb。
感謝閱讀!
參考引用
N. Reimers, I. Gurevych, Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks (2019), Proceedings of the 2019 Conference on Empirical Methods in NLP
往期精彩回顧適合初學(xué)者入門人工智能的路線及資料下載機(jī)器學(xué)習(xí)及深度學(xué)習(xí)筆記等資料打印機(jī)器學(xué)習(xí)在線手冊(cè)深度學(xué)習(xí)筆記專輯《統(tǒng)計(jì)學(xué)習(xí)方法》的代碼復(fù)現(xiàn)專輯 AI基礎(chǔ)下載機(jī)器學(xué)習(xí)的數(shù)學(xué)基礎(chǔ)專輯黃海廣老師《機(jī)器學(xué)習(xí)課程》課件合集 本站qq群851320808,加入微信群請(qǐng)掃碼: 與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的【NLP】文本相似度的BERT度量方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 我利用这几个神器来拯救自己奇丑无比的py
- 下一篇: 母狼高斯普和狐狸