日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【NLP】文本相似度的BERT度量方法

發(fā)布時(shí)間:2025/3/12 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【NLP】文本相似度的BERT度量方法 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者?| 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) 這些相似之處可以解釋為: IndexSentenceSimilarity
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: IndexSentenceSimilarity
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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。