0-前序
Bert已经是相当6了,但在STS(语义文本相似性)任务中,需要将两个句子都输入到网络中,也就是说要过模型,这样计算量就大了。如下是文本相似性,并不是语义。
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')
inputs1 = tokenizer('今天的天气真好啊,暖和和的', return_tensors='pt')
inputs2 = tokenizer('今天天气真暖和啊', return_tensors='pt')
inputs3= tokenizer('今天天气真差劲啊', return_tensors='pt')
outputs1 = model(**inputs1)
outputs2 = model(**inputs2)
outputs3 = model(**inputs3)
outputs1 =outputs1.pooler_output
outputs2 =outputs2.pooler_output
outputs3 =outputs3.pooler_output
import torch
from sklearn.metrics.pairwise import cosine_similarity
torch.nn.functional.cosine_similarity(outputs2, outputs3, dim=-1)
cosine_similarity(outputs2.detach().numpy(), outputs3.detach().numpy())
#array([[0.98333544]], dtype=float32)
#277356808 Q group
为了简单说明问题,没有一个batch输入(一个batch需要设置padding,长度不一致),总之需要过模型。
Bert计算两两句子之间的相似性(STS-B)所用label是1~5,因此与上面稍微不同,需要将两个句子同时输入,然后得到score,如果是1w个句子呢?两两之间的相似度计算需要65h(paper说的),那么对此任务,可能就不是那么计算了。
因此本文提出sentence Bert也就是SBERT,使用暹罗siamese和三元组网络结构来得到语义上有意义的句子嵌入,可以使用余弦相似性进行比较。这就减少了时间,而且保持了acc。
semantically meaningful 是指语义相似的句子在向量空间是接近的。(而不是上面的文本最接近)
上述两个句子相似性,1w个,需要Bert推理1w*(1w-1)/2接近5kw了,同样对于QA问题更耗时,单个query可能需要50h。一个常见的处理聚类和语义搜索的方法是将每个句子映射到同一个向量空间。通用的方法是将Bert 输出层(也就是上面代码例子)取平均,或者使用CLS的输出。但此embedding相当差,(下面将逐步说明)
Siamese网络结构输入的sentence获得定长向量,然后使用余弦相似度即可计算相似性。上面的1w个句子之间的相似性可在5s解决(得到embedding,0.01s计算相似性),而QA缩短到一个请求几秒。在7个STS任务重SBERT取得最好的结果。Bert网络结构一个最大的缺点是没有独立的句子embedding得到,一个规避的方法是通过平均词向量,或者使用CLS的输出(上述代码就是这个,如下为证):并不是直接的输出,而是经过线性结构和tanh激活。
pooler_output : Last layer hidden-state of the first token of the sequence (classification token) after further processing through the layers used for the auxiliary pretraining task. E.g. for BERT-family of models, this returns the classification token after processing through a linear layer and a tanh activation function.
1-SBERT模型
SBERT对Bert增加了一个pooling操作,这样就能得到固定长度的sentence embedding。pooling方法 1)CLS token输出;2)向量平均;3)向量最大 对比才能知道效果。为了微调Bert,构建Siamese和triplet 网络更新权重,使得语义近似的embedding相近,可以用余弦相似性来计算。
分类目标函数:拼接embedding u 和v 以及|u-v|并与W相乘,o = softmax(Wt(u, v, |u − v|)) 如下左图,下右目标函数为MSE
Siamese network structure 孪生网络结构,其实就一个网络,只不过两个输入而已,两个输入分别得到u和v,
Triplet Objective Function :给定锚点(anchor)句子a,一个正向句子p,一个负向句子n,triplet loss 调整网络损失使得a和p的距离小于a和n的距离,如下FaceNet论文中的图:
数学上需要min(max(||sa − sp|| − ||sa − sn|| + ε, 0)), sa,sp,sn分别是a,p,n的embedding,||.||是距离,ε是边际margin,ε确保sp至少与sa更接近,相比于sn,距离可以用欧式距离,ε在实验中为1.
采用57w句子对SNLI进行预训练,label为contradiction, eintailment ,neural,微调 3-way softmax- classifier objective function for one epoch ,batch size 16,Adam优化器,lr=2e-5
Avg Bert embedding 54.81,CLS-token 29.19, 前者是直接平均,而不是SBERT,SBERT 70+
pooling策略,也就是上面左图中的pooling,取MEAN效果最好,而不是MAX,也不是CLS
concat策略,证实(u,v,|u-v|)效果最好。
2-实际代码
from sentence_transformers import SentenceTransformer
model=SentenceTransformer("distiluse-base-multilingual-cased-v1")
sentence_embeddings =model.encode(["今天的天气真好啊,暖和和的","今天天气真暖和啊","今天天气真差劲啊"]
cosine_similarity(sentence_embeddings[0].reshape(1, -1), sentence_embeddings[1].reshape(1, -1))
#array([[0.9464222]], dtype=float32)
cosine_similarity(sentence_embeddings[0].reshape(1, -1), sentence_embeddings[2].reshape(1, -1))
#array([[0.8281469]], dtype=float32)#这个差距就明显了,而上面Bert CLS差距不明显
##Q Group 277356808
总结:本文创新点引用了triplet学习方法,使语义近似的词向量也相近。 孪生网络不算创新。concat只能算作trick。pooling是仍旧是词向量的平均,这点与Avg bert embedding一样,但目标函数不同(loss不同),是用的triplet,这是核心关键。
时间点节约在上面代码中的encode方法可以同时输入多个inputs
愿我们终有重逢之时,而你还记得我们曾经讨论的话题