天天看點

Sentence-BERT實戰

引言

本文主要介紹了SBERT作者提供的官方子產品的使用實戰。

通過Sentence-BERT了解句子表示

Sentence-BERT(下文簡稱SBERT)用于擷取固定長度的句向量表示。它擴充了預訓練的BERT模型(或它的變種)來擷取句子表示。

SBERT常用于句子對分類、計算句子間的相似度等等任務。

在了解SBERT的細節之前,我們先看下如何使用預訓練的BERT模型來計算句子表示。

計算句子表示

考慮句子​

​Paris is a beautiful city​

​,假設我們要計算該句子的向量表示。首先,我們需要分詞并增加特殊标記:

tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP] ]
      

接着,我們把這些标記清單喂給預訓練的BERT模型,它會傳回每個标記的單詞表示:

Sentence-BERT實戰

我們已經得到了每個單詞的表示,那我們如何得到整個句子的表示呢?我們知道​

​[CLS]​

​​标記儲存了整個句子的壓縮表示。是以我們可以使用該标記對應的向量作為句子表示:

但是這樣做會有一個問題,就是這種句子表示是不精确的,尤其是我們直接使用未經微調的預訓練的BERT。是以,除了這種方式,我們可以使用池化政策。即,我們通過池化所有标記的表示來作為句子表示。

池化可分為平均池化和最大池化。平均池化就是取所有單詞表示向量之和的均值,而最大池化則是取​

​[CLS]​

​标記的輸出來表示整個句子。

Sentence-BERT實戰

上面介紹的都是取最後一個編碼器層的輸出進行計算。其實還有其他方法,比如取第一個編碼器和最後一個編碼器輸出之和、以及取倒數第二個編碼器層的輸出等。

下面我們來看下SBERT。

了解SBERT

SBERT也不是從頭開始訓練的,它是基于預訓練的BERT模型(或變種),然後進行微調擷取句子表示。

也就是說,SBERT基本上是一個預訓練的BERT模型,并為擷取句子表示而微調。

為了微調與訓練的BERT模型來獲得句子表示,SBERT使用孿生(Siamese)網絡和三重态(Triplet)網絡,其有助于微調得更快和擷取精确的句子表示。

SBERT使用孿生網絡來處理涉及句子對輸入的任務。并且使用三重态網絡來實作三重态損失目标函數。

帶有孿生網絡的SBERT

SBERT使用孿生網絡架構來對句子對任務進行微調。

首先,我們會看到SBERT是如何完成句子對分類任務的,然後我們會學習SBERT是如何用于句子對回歸任務的。

SBERT用于句子對分類任務

假設我們有一個資料集包含句子對以及二分類标簽,該标簽顯示這兩個句子是相似(1)還是不相似(0)。

Sentence-BERT實戰

現在,我們看看如何用上面的資料集基于孿生網絡來為句子對分類任務微調預訓練的BERT模型。首先看看資料集中的第一對句子:

Sentence 1: I completed my assignment
Sentence 2: I completed my homework       

我們需要判斷給定的句子對是相似的(1)還是不相似的(0)。首先,還是老操作:

Tokens 1 = [ [CLS], I completed, my, assignment, [SEP]]
Tokens 2 = [ [CLS], I, completed, my, homework, [SEP]]      

接着,我們把這些标記喂給預訓練的BERT模型(後面如果沒有特殊說明的話,簡稱為BERT模型)然後獲得每個标記的向量表示。我們知道了SBERT使用孿生網絡。孿生網絡其實就是兩個共享權重的相同的網絡。是以這裡我們使用兩個完全相同的BERT模型。

Sentence-BERT實戰

我們把句子1的那些标記清單喂給第一個BERT,把句子2的那些表示清單喂給另一個BERT,然後計算這兩個句子的表示向量。

為了計算一個句子的表示向量,我們這裡使用平均或最大池化。在SBERT中預設使用平均池化。在應用池化政策之後,我們有了給定句子對的句子表示,如下所示:

Sentence-BERT實戰

代表句1的句子表示;代表句2的句子表示。現在,我們把它們以及它們的元素之差的結果拼接起來,然後乘以一個權重,如下:

注意權重的次元是,其中是句子嵌入的次元;是類别數量。下面,我們把這個結果輸入一個Softmax函數,傳回給定句子對相似的機率:

上面的過程可以用下圖描述。首先呢,我們把句子對輸入到BERT模型,然後通過池化政策得到句子表示,接着拼接這兩個句子表示并乘以一個全球你在,最後經過Softmax函數得到相似機率。

Sentence-BERT實戰

我們通過最小化交叉熵損失來訓練上面的網絡,同時更新權重。這樣,我們就可以使用SBERT來完成句子對的分類任務。

SBERT用于句子對回歸任務

假設我們有一個資料集包含句子對以及它們的相似度值:

Sentence-BERT實戰

我們看看如何基于上面的資料集使用孿生網絡來為句子對回歸任務微調BERT模型。在該任務中,我們的目标是預測兩個給定句子間的語義相似度。同樣看看資料集中第一對句子:

Sentence 1: How old are you
Sentence 2: What is your age      

現在我們需要計算這兩個句子之間的相似度。我們對句子進行一些預處理:

Tokens 1 = [ [CLS], How, old, are, you, [SEP]]
Tokens 2 = [ [CLS], What, is, your, age, [SEP]]      

然後把這些标記清單輸入到BERT模型,并獲得每個标記的向量表示。此任務也是基于孿生網絡,是以我們有兩個一樣的BERT模型。我們把句子1喂給第一個BERT模型,把句子2喂給第二個BERT模型,然後計算模型輸出的标記表示的均值(池化)。

令代表句子1的表示;代表句子2的表示。然後我們通過餘弦相似計算這兩個向量表示的相似度:

Sentence-BERT實戰

整個過程如上圖所示。這裡我們通過最小化均方誤差損失來訓練該網絡。這樣,我們就可以使用SBERT來做句子對的回歸任務。

帶有三重态網絡的SBERT

假設我們有三個句子,一個Anchor句子,一個正(positive)樣本和一個負(negative)樣本句子:

  • Anchor句子:Play the game
  • Positive 句子:He is playing the game
  • Negative 句子:Don’t play the game

我們的任務是一個表示讓Anchor句子和正樣本句子之間的相似度很高,同時Anchor句子和負樣本之間的相似度很低。因為我們有三個句子,此時,SBERT使用三重态網絡架構。

首先,還是對句子進行預處理,然後喂給三個BERT模型,并通過池化得到每個句子的表示:

Sentence-BERT實戰

在上圖中,我們用分别表示anchor,positive和negative句子的句向量。下面,我們通過最小化下面的三重态目标函數來訓練該網絡:

其中,表示距離名額,我們使用歐幾裡得距離。表示間隔margin,用于保證正樣本句向量至少比負樣本句向量距離Anchor句向量要近。

如下圖所示,我們分别輸入anchor,positive,negative句子到BERT模型,并通過池化得到句向量。然後,訓練模型去最小化三重态損失函數。最小化該損失函數確定anchor和positive的相似度要大于和negative的相似度。

Sentence-BERT實戰

SBERT的作者提供了​

​sentence-transformers​

​包來開源他們的代碼。

探索sentence-transformers包

理論千遍不如實操一遍。

首先我們通過下面的指令安裝這個工具:

pip install -U sentence-transformers      

SBERT的作者釋出了他們預訓練的SBERT模型。所有預訓練的模型可以在這裡找到:https://public.ukp.informatik.tu-darmstadt.de/reimers/sentence-transformers/v0.2/ ,可惜沒有中文版的。

我們可以返現這些預訓練的模型以​

​bert-base-nli-cls-token​

​​,​

​bert-base-nli-mean-token​

​​,​

​roberta-base-nli-max-tokens​

​​,​

​distilbert-base-nli-mean-tokens​

​這樣的方式命名。我們來看下是啥意思:

  • ​bert-base-nli-cls-token​

    ​​ 是以預訓練BERT-base模型在NLI資料集上進行微調的SBERT模型,并且該模型使用​

    ​[CLS]​

    ​标記的輸出作為句子表示
  • ​bert-base-nli-mean-token​

    ​是以預訓練BERT-base模型在NLI資料集上進行微調的SBERT模型,并且該模型使用均值池化政策計算句子表示
  • ​roberta-base-nli-max-tokens​

    ​ 是以預訓練RoBERTa-base模型在NLI資料集進行微調的SBERT模型,并且該模型使用均值池化政策計算句子表示
  • ​distilbert-base-nli-mean-tokens​

    ​是以預訓練DistilBERT-base模型在NLI資料集上進行微調的SBERT模型,并且該模型使用均值池化政策計算句子表示

這樣,我們說預訓練的SBERT模型,其實基本就是說我們有一個預訓練的BERT模型然後使用孿生/三重态網絡架構微調它。

那麼下面我們就來看看如何使用預訓練的SBERT模型。

使用SBERT計算句子表示

首先,我們從​

​sentence_transformers​

​​中引入​

​SentenceTransformer​

​子產品:

from sentence_transformers import      

下載下傳并加載預訓練的SBERT:

model = SentenceTransformer('bert-base-nli-mean-tokens')      

定義我們需要計算句子表示的句子:

sentence = 'Peking is a beautiful city'      

使用預訓練的SBERT模型的​

​encode​

​函數計算句子表示:

sentence_representation = model.encode(sentence)      

現在,我們來看看該句子表示的次元:

print(sentence_representation.shape)      
(768,)      

嗯,768維。這樣我們就使用預訓練的SBERT模型得到了固定長度的句子表示。

計算句子相似度

首先引入需要的包:

import scipy
from sentence_transformers import SentenceTransformer,      

下載下傳并加載預訓練的SBERT模型:

model = SentenceTransformer('bert-base-nli-mean-tokens')      

定義一個句子對:

sentence1 = 'It was a great day' 
sentence2 = 'Today was awesome'      

計算該句子對中每個句子的句子表示:

sentence1_representation = model.encode(sentence1)
sentence2_representation = model.encode(sentence2)      

接着計算這兩個句子表示之間的餘弦相似度:

cosin_sim = util.pytorch_cos_sim(sentence1_representation,sentence2_representation)
print(cosin_sim)      
tensor([[0.9313]])      

我們可以看到相似度有0.93。

加載自定義模型

除了使用官方預定義的模型外,我們也可以使用我們自己的模型。假設我們有一個預訓練的ALBERT模型。現在,我們看看如何使用該預訓練的ALBERT模型來獲得句子表示。

首先,導入必要的子產品:

from sentence_transformers import models,SentenceTransformer      

現在,定義我們的詞嵌入模型,它可以傳回輸入句子中每個标記的表示向量。我們使用預訓練的ALBERT作為詞嵌入模型:

word_embedding_model = models.Transformer('albert-base-v2')      

接下來,我們定義池化模型來對所有标記表示進行池化操作。

我們首先設定池化政策,​

​pooling_mode_mean_tokens = True​

​表示我們使用均值池化來計算定長的句子表示:

pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(),
                               pooling_mode_mean_tokens=True,
                               pooling_mode_cls_token=False,
                               pooling_mode_max_tokens=False)      

好,下面我們使用詞嵌入和池化模型來定義SBERT:

model = SentenceTransformer(modules=[word_embedding_model, pooling_model])      

我們可以像下面這樣使用該模型計算句子表示:

model.encode('Transformers are awesome')      

該段代碼會傳回一個768維的向量,代表這個句子的句向量。

通過SBERT查找相似句子

假設我們有一個電子商務網站,假設在我們的資料庫中有很多訂單相關的問題,比如How to cancel my order?, *Do you provide a refund?*等等。現在當新問題進來時,我們的目标是找到與新問題最相似的問題。

我們看看如何基于SBERT實作這個需求。

首先,導包:

from sentence_transformers import SentenceTransformer, util
import numpy as      

加載預訓練的SBERT模型:

model = SentenceTransformer('bert-base-nli-mean-tokens')      

定義我們的問題資料庫:

master_dict = [
                 'How to cancel my order?',
                 'Please let me know about the cancellation policy?',
                 'Do you provide refund?',
                 'what is the estimated delivery date of the product?', 
                 'why my order is missing?',
                 'how do i report the delivery of the incorrect items?'
                 ]      

定義新問題:

inp_question = 'When is my product getting delivered?'      

計算新問題的句子表示:

inp_question_representation = model.encode(inp_question, convert_to_tensor=True)      

然後計算資料庫中所有問題的句子表示(實際應該事先計算好):

master_dict_representation = model.encode(master_dict, convert_to_tensor=True)      

現在,計算新問題和資料庫中所有問題的餘弦相似度:

similarity = util.pytorch_cos_sim(inp_question_representation, master_dict_representation)      
print('The most similar question in the master dictionary to given input question is:',master_dict[np.argmax(similarity)])      
The most similar question in the master dictionary to given input question is: what is the estimated delivery date of the product?      

參考

  1. Getting Started with Google BERT

繼續閱讀