NLP(十一):sentence_BERT


一、引言

https://zhuanlan.zhihu.com/p/351678987

在自然語言處理NLP中,文本分類、聚類、表示學習、向量化、語義相似度或者說是文本相似度等等都有非常重要的應用價值。這些任務都是為了能從復雜的文本中,通過采用ML/DL的方法,學習到本文深層次的語義表示,從而更好地服務於下游任務(分類、聚類、相似度等)。這里筆者將這些統一概括為智能語義計算。

二、具體應用場景

  • 文本語義向量化:文本語義嵌入,通過D L模型的學習,將文本隱含語義由數字向量表示出來,提供給其他任務進行訓練或計算。
  • 語義相似度:如智能客服回答,客戶的一個新問題是否與已知問題在語義空間上相似或接近,是否能夠用已知問題的答案,對客戶進行回復。
  • 文本聚類:如一批無標簽的文本進行歸類。
  • 文本復述:給定一個文本查找與它表述相同的版本,例如簡化句子的表述。
  • 雙文本挖掘:在語料庫中查找平行句子對的過程,例如翻譯。
  • 語義搜索:尋找語義相似相近的文本。
  • 語義檢索並排序:尋找語義相近的前K個文本(召回),並對這K個文本再進行語義相似度排序。
  • 雙文本評分及分類:通過模型計算一對文本的評分(文檔與摘要是否匹配、不同語言翻譯是否正確)或分類。

本文我們將主要關注於語義相似度Semantic Textual Similarity這個子任務,對比相關模型,並在有應用價值的數據上進行實驗。

 

三、雙塔SBERT模型的介紹

塔式結構在深度學習模型中是比較常見的,比較著名的是微軟的DSSM(Deep Structured Semantic Models )基於深度網絡的語義模型,其結構如下。

我對dssm模型的理解,該模型通過一層一層堆疊的網絡提取隱含語義。通過semantic特征向量(128維)的兩兩余弦計算,得到各文本之間的相似度R(q, d)。最后優化相似度與樣本的距離。其中128維的semantic feature,即為通過該模型學習到的對應文本的隱含語義。

而SBert與dssm思想上比較類似,都是通過獨立的子模塊進行高維信息抽取,通過各文本特征向量的余弦距離,來表征文本語義相似度。 Bert(Bidirectional Encoder Representation from Transformers)及其變種在NLP中大殺四方,不多介紹了。sbert結構圖如下。

SBERT模型結構圖

semantic feature(上圖的U、V)在實際生產上非常有意義,它能夠預先通過sbert計算得到。然后,通過向量搜索引擎處理這些向量,檢索到最相似語義的文本。這種方式能非常快速實現海量相似文本的查詢、排序,而無需再進行高延遲的模型預測。類似的,推薦系統也是預先學習到用戶、商品的表征向量,從而進行召回、排序、推薦等任務。


四、模型實驗

采用sentence-transformer庫進行模型搭建與實驗。該框架提供了一種簡便的方法來計算句子和段落的向量表示(也稱為句子嵌入)。這些模型基於諸如BERT / RoBERTa / XLM-RoBERTa等模型,並且經過專門調整以優化向量表示,以使具有相似含義的句子在向量空間中接近。

實驗數據

數據集為QA_corpus,訓練數據10w條,驗證集和測試集均為1w條。實例如下:

[('為什么我無法看到額度', '為什么開通了卻沒有額度', 0),
('為啥換不了', '為兩次還都提示失敗呢', 0),
('借了錢,但還沒有通過,可以取消嗎?', '可否取消', 1),
('為什么我申請額度輸入密碼就一直是那個頁面', '為什么要輸入支付密碼來驗證', 0),
('今天借 明天還款可以?', '今天借明天還要手續費嗎', 0),
('你好!今下午咱沒有扣我款?', '你好 今天怎么沒有扣款呢', 1),
('所借的錢是否可以提現?', '該筆借款可以提現嗎!', 1),
('不是邀請的客人就不能借款嗎', '一般什么樣得人會受邀請', 0),
('人臉失別不了,開不了戶', '我輸入的資料都是正確的,為什么總說不符開戶失敗?', 0),
('一天利息好多錢', '1萬利息一天是5元是嗎', 1)]

 

1、實驗代碼

# 先安裝sentence_transformers
from sentence_transformers import SentenceTransformer

# Define the model. Either from scratch of by loading a pre-trained model
model = SentenceTransformer('distiluse-base-multilingual-cased')
# distiluse-base-multilingual-cased 蒸餾得到的,官方預訓練好的模型

# 加載數據集
def load_data(filename):
    D = []
    with open(filename, encoding='utf-8') as f:
        for l in f:
            try:
                text1, text2, label = l.strip().split(',')
                D.append((text1, text2, int(label)))
            except ValueError:
                _
    return D

train_data = load_data('text_matching/input/train.csv')
valid_data = load_data('text_matching/input/dev.csv')
test_data  = load_data('text_matching/input/test.csv')

from sentence_transformers import SentenceTransformer, SentencesDataset 
from sentence_transformers import InputExample, evaluation, losses
from torch.utils.data import DataLoader

# Define your train examples.
train_datas = []
for i in train_data:
    train_datas.append(InputExample(texts=[i[0], i[1]], label=float(i[2])))

# Define your evaluation examples
sentences1,sentences2,scores = [],[],[]
for i in valid_data:
    sentences1.append(i[0])
    sentences2.append(i[1])
    scores.append(float(i[2]))

evaluator = evaluation.EmbeddingSimilarityEvaluator(sentences1, sentences2, scores)


# Define your train dataset, the dataloader and the train loss
train_dataset = SentencesDataset(train_datas, model)
train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)

# Define your train dataset, the dataloader and the train loss
train_dataset = SentencesDataset(train_datas, model)
train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)

# Tune the model
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=3, warmup_steps=100, 
          evaluator=evaluator, evaluation_steps=200, output_path='./two_albert_similarity_model')

 

2、向量相似度的測評:

# Define your evaluation examples
sentences1,sentences2,scores = [],[],[]
for i in test_data:
    sentences1.append(i[0])
    sentences2.append(i[1])
    scores.append(float(i[2]))

evaluator = evaluation.EmbeddingSimilarityEvaluator(sentences1, sentences2, scores)
model.evaluate(evaluator)

>>> 0.68723840499831

3、模型准確度的測評:

'''
Evaluate a model based on the similarity of the embeddings by calculating the accuracy of 
identifying similar and dissimilar sentences. The metrics are the cosine similarity as well 
as euclidean and Manhattan distance The returned score is the accuracy with a specified metric.
'''
evaluator = evaluation.BinaryClassificationEvaluator(sentences1, sentences2, scores)
model.evaluate(evaluator)

>>> 0.8906211331111515

4、測試模型獲取向量

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('./two_albert_similarity_model')

# Sentences are encoded by calling model.encode()
emb1 = model.encode('什么情況導致評估不過')
emb2 = model.encode("個人信用怎么評估出來的")
print(emb1)
print(emb2)

cos_sim = util.pytorch_cos_sim(emb1, emb2)
print("Cosine-Similarity:", cos_sim)
emb1:[ 2.98918579e-02 -1.61327735e-01 -2.11362451e-01 -8.07176754e-02 8.48087892e-02 -1.54550061e-01 -1.11961491e-01 -7.36757461e-03 。。。 -1.64973773e-02 -6.62902296e-02 7.76088312e-02 -5.86621352e-02]
emb2:[-0.00474379 0.01176221 -0.12961781 0.03897651 -0.08082788 0.02274037 -0.01527447 -0.03132218 0.04967966 -0.11125126 0.03260884 -0.08910057。。。 0.04023521 0.07583613 -0.01950659 -0.04246246 0.03055439 0.0451214] Cosine-Similarity: tensor([[-0.0515]])

5、模型向量召回

from tqdm import tqdm
import numpy as np
import faiss                   # make faiss available

ALL = []
for i in tqdm(test_data):
    ALL.append(i[0])
    ALL.append(i[1])
ALL = list(set(ALL))

DT = model.encode(ALL)
DT = np.array(DT, dtype=np.float32)

# https://waltyou.github.io/Faiss-Introduce/
index = faiss.IndexFlatL2(DT[0].shape[0])   # build the index
print(index.is_trained)
index.add(DT)                  # add vectors to the index
print(index.ntotal)

6、查詢最相似的文本

k = 10                          # we want to see 10 nearest neighbors
aim = 220
D, I = index.search(DT[aim:aim+1], k) # sanity check
print(I)
print(D)
print([ALL[i]for i in I[0]])

[[ 220 4284 3830 2112 1748 639 5625 6062 1515 1396]]
[[0. 0.04789903 0.04982989 0.07678283 0.08252098 0.08306326
0.08532818 0.11053496 0.11116458 0.11140325]]
['4500元一天要多少息', '借一萬一天利息多少錢', '萬元日利息多少', '代五萬元,一天的息是多少', '一萬元的日息是多少?', '一萬元每天多少錢利息', '1千元日息是多少', '一天利息好多錢', '10000塊日利息是多少', '借1萬一天多少錢利息啊']

7、先獲取特征再查詢最相似的文本

query = [model.encode('1萬元的每天利息是多少')]
query = np.array(query, dtype=np.float32)
D, I = index.search(query, 10) # sanity check
print(I)
print(D)
print([ALL[i]for i in I[0]])

[[3170 1476 639 2112 1826 3193 1396 4332 5360 1748]]
[[0.03987426 0.05244656 0.05858241 0.05872107 0.07376505 0.08907703
0.0905426 0.09842405 0.10062639 0.10626719]]
['20000每天利息多少', '1萬元日利息多少', '一萬元每天多少錢利息', '代五萬元,一天的息是多少', '1萬元日息是多少啊!', '100000元一天的利息是5000嗎', '借1萬一天多少錢利息啊', '借一萬八,那一天是多少利息', '28000的日息是多少', '一萬元的日息是多少?']

五、與其他模型的對比

模型 loss acc
DSSM 0.7613157 0.6864
ConvNet 0.6872447 0.6977
ESIM 0.55444807 0.736
ABCNN 0.5771452 0.7503
BiMPM 0.4852 0.764
DIIN 0.48298636 0.7694
DRCN 0.6549849 0.7811
SBERT(distiluse-base-multilingual-cased) 0.6872384 0.8906 - 0.91

SBERT模型的准確率提升很多。

其他模型代碼參考 terrifyzhao/text_matching

六、更先進的模型

Bert-flow

Keyword-BERT

七、參考材料

SentenceTransformers Documentation​www.sbert.net圖標terrifyzhao/text_matching 文本匹配模型​github.com

 八、具體代碼

from sentence_transformers import SentenceTransformer, SentencesDataset
from sentence_transformers import InputExample, evaluation, losses
from torch.utils.data import DataLoader
import pandas as pd
from tqdm import tqdm

model = SentenceTransformer('distiluse-base-multilingual-cased')

train_data = pd.read_csv(r"train.csv", sep="\t")
train_data.sample(frac=1)
val_data = pd.read_csv(r"val.csv", sep="\t")
val_data.sample(frac=1)
test_data = pd.read_csv(r"test.csv", sep="\t")

def get_input():
    train_datas = []
    _y = train_data["y"]
    _s1 = train_data["s1"]
    _s2 = train_data["s2"]
    for s1, s2, l in tqdm(zip(_s1, _s2, _y)):
        train_datas.append(InputExample(texts=[s1, s2], label=float(l)))
    return train_datas

train_datas = get_input()

def eval_examples():
    sentences1, sentences2, scores = [], [], []
    for s1, s2, l in tqdm(zip(val_data["s1"], val_data["s2"], val_data["y"])):
        sentences1.append(s1)
        sentences2.append(s2)
        scores.append(float(l))
    return sentences1, sentences2, scores

sentences1, sentences2, scores = eval_examples()


# Define your train dataset, the dataloader and the train loss
train_dataset = SentencesDataset(train_datas, model)
train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=64)
train_loss = losses.CosineSimilarityLoss(model)

evaluator = evaluation.BinaryClassificationEvaluator(sentences1, sentences2, scores)
# Tune the model
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=50, warmup_steps=100,
          evaluator=evaluator, evaluation_steps=300, output_path='./two_albert_similarity_model')
model.evaluate(evaluator)

# Define your evaluation examples


def test_examples():
    sentences1, sentences2, scores = [], [], []
    for s1, s2, l in tqdm(zip(test_data["s1"], test_data["s2"], test_data["y"])):
        sentences1.append(s1)
        sentences2.append(s2)
        scores.append(float(l))
    return sentences1, sentences2, scores

sentences1, sentences2, scores = test_examples()

evaluator = evaluation.EmbeddingSimilarityEvaluator(sentences1, sentences2, scores)
print(model.evaluate(evaluator))

evaluator = evaluation.BinaryClassificationEvaluator(sentences1, sentences2, scores)
print(model.evaluate(evaluator))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM