当前位置:   article > 正文

NLP-文本匹配任务原理与代码实践总结_sentence transformer原理

sentence transformer原理

目录

1. 文本匹配任务原理

1.1. 文本匹配的定义

2. 文本匹配的原理

2.1. 表示

2.2 匹配

2. 代码实践

2.1. 方法之SpaCy

2.2. 方法之Sentence Transformers

2.2.1. 得到句子的Embedding向量

2.2.2. 根据句子的embeding向量,计算句子的距离,有三种方法。

2.3. 方法之TFHub Universal Sentence Encoder

2.4. 方法之TF-IDF

2.5.  CountVectorizer

2.6. Jaccard Index--杰卡德系数计算

总结

参考资料


1. 文本匹配任务原理

文本匹配是自然语言处理(NLP)领域中的一项重要任务,它可以用于很多应用,比如信息检索、机器翻译、对话系统等。文本匹配的目的是判断两个文本是否具有一定的相似度或者关系,通常通过计算它们之间的相似度来进行判断。本文将介绍NLP中的文本匹配任务,并结合具体的原理和代码讲解。

1.1. 文本匹配的定义

文本匹配是指在NLP领域中,判断两个文本之间的相似度或者关系的任务。文本匹配任务通常包括以下三种类型:

  1. 文本相似度计算:判断两个文本之间的相似度,例如文本分类、信息检索等;
  2. 文本匹配:判断两个文本是否具有匹配关系,例如问答系统、对话系统等;
  3. 文本对齐:将两个文本进行对齐,例如机器翻译、文本摘要等。

2. 文本匹配的原理

文本匹配任务通常包括两个步骤:表示和匹配。表示是将文本转化成向量表示,匹配是计算两个文本向量之间的相似度。

2.1. 表示

将文本表示成向量的方法有很多,常见的方法包括:

  1. 基于词袋模型的表示:将文本中的所有单词都提取出来,并将它们转化成一个向量表示;
  2. 基于TF-IDF的表示:将词袋模型中每个单词的重要性加权,得到一个更准确的向量表示;
  3. 基于word2vec的表示:将每个单词表示成一个向量,将文本中所有单词的向量取平均得到整个文本的向量表示。

2.2 匹配

计算文本之间相似度的方法也有很多,常见的方法包括:

  1. 余弦相似度:计算两个向量的夹角余弦值,值越大表示越相似;
  2. 欧几里得距离:计算两个向量之间的欧几里得距离,值越小表示越相似;
  3. 曼哈顿距离:计算两个向量之间的曼哈顿距离,值越小表示越相似。

2. 代码实践

在做自然语言处理的过程中,现在智能对话比较火,例如智能客服,智能家电,智能音箱等,我们需要获取用户说话的意图,方便做出正确的回答,这里面就涉及到句子相似度计算的问题,那么我们就来了解一下怎么样来用 Python 实现文本匹配任务中句子相似度的计算。

下面的大多数库应该是语义相似性比较的不错选择。您可以使用这些库中的预训练模型生成单词或句子向量,从而跳过直接单词比较。

2.1. 方法之SpaCy

参考文献

Linguistic Features · spaCy Usage Documentation

需要下载模型

要使用 en_core_web_md,请使用 python -m spacy download en_core_web_md 进行下载。要使用 en_core_web_lg,请使用 python -m spacy download en_core_web_lg。 大型模型大约为 830mb 左右,而且速度很慢,因此中型模型是一个不错的选择。

python -m spacy download en_core_web_lg

代码

  1. import spacy
  2. nlp = spacy.load("en_core_web_lg")
  3. doc1 = nlp(u'the person wear red T-shirt')
  4. doc2 = nlp(u'this person is walking')
  5. doc3 = nlp(u'the boy wear red T-shirt')
  6. print(doc1.similarity(doc2))
  7. print(doc1.similarity(doc3))
  8. print(doc2.similarity(doc3))

结果

  1. 0.7003971105290047
  2. 0.9671912343259517
  3. 0.6121211244876517

2.2. 方法之Sentence Transformers

GitHub - UKPLab/sentence-transformers: Multilingual Sentence & Image Embeddings with BERT

Semantic Textual Similarity — Sentence-Transformers documentation

安装包sentence-transformers

pip install -U sentence-transformers

所需的所有模型可以在​​​​​​Models - Hugging Face查看,

也可以通过Pretrained Models — Sentence-Transformers documentation查看。

2.2.1. 得到句子的Embedding向量

1. 是利用sentence_transformers包。

代码:

  1. from sentence_transformers import SentenceTransformer, util
  2. model = SentenceTransformer('distilbert-base-nli-mean-tokens')
  3. sentences = [
  4. 'the person wear red T-shirt',
  5. 'this person is walking',
  6. 'the boy wear red T-shirt'
  7. ]
  8. sentence_embeddings = model.encode(sentences)
  9. for sentence, embedding in zip(sentences, sentence_embeddings):
  10. print("Sentence:", sentence)
  11. print("Embedding:", embedding)
  12. print("")

输出:

  1. Sentence: the person wear red T-shirt
  2. Embedding: [ 1.31643847e-01 -4.20616418e-01 ... 8.13076794e-01 -4.64620918e-01]
  3. Sentence: this person is walking
  4. Embedding: [-3.52878094e-01 -5.04286848e-02 ... -2.36091137e-01 -6.77282438e-02]
  5. Sentence: the boy wear red T-shirt
  6. Embedding: [-2.36365378e-01 -8.49713564e-01 ... 1.06414437e+00 -2.70157874e-01]

2. 利用transformers 和Torch

可以看出sentence_transformers包其实是transformers包和torch包的高级方法。它做了一个封装而已,更容易使用。从下面代码可以看出,所谓的句子嵌入就是Bert或其它预训练模型的输出后面加一池化层而已。

代码

  1. from transformers import AutoTokenizer, AutoModel
  2. import torch
  3. import torch.nn.functional as F
  4. #Mean Pooling - Take attention mask into account for correct averaging
  5. def mean_pooling(model_output, attention_mask):
  6. token_embeddings = model_output[0] #First element of model_output contains all token embeddings
  7. input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
  8. return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
  9. # Sentences we want sentence embeddings for
  10. sentences = ['This is an example sentence', 'Each sentence is converted']
  11. # Load model from HuggingFace Hub
  12. tokenizer = AutoTokenizer.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')
  13. model = AutoModel.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')
  14. # Tokenize sentences
  15. encoded_input = tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
  16. # Compute token embeddings
  17. with torch.no_grad():
  18. model_output = model(**encoded_input)
  19. # Perform pooling
  20. sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])
  21. # Normalize embeddings
  22. sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1)
  23. print("Sentence embeddings:")
  24. print(sentence_embeddings)

输出

  1. Sentence embeddings:
  2. tensor([[ 0.0077, -0.0245, 0.0131, ..., -0.0114, 0.0473, -0.0270],
  3. [-0.0200, -0.0029, 0.0531, ..., 0.0568, -0.0134, -0.0038],
  4. [-0.0139, -0.0500, 0.0053, ..., -0.0067, 0.0627, -0.0159]])

2.2.2. 根据句子的embeding向量,计算句子的距离,有三种方法。

1. 利用 SentenceTransformer 中的util 

其实它里面的实现是利用Torch来实现

  1. print(util.cos_sim(passage_embedding[0], passage_embedding[1]))
  2. print(util.cos_sim(passage_embedding[0], passage_embedding[2]))
  3. print(util.cos_sim(passage_embedding[1], passage_embedding[2]))

输出

  1. tensor([[0.4644]])
  2. tensor([[0.9070]])
  3. tensor([[0.3276]])

2. 利用 scipy

代码

  1. from scipy.spatial import distance
  2. print(1 - distance.cosine(sentence_embeddings[0], sentence_embeddings[1]))
  3. print(1 - distance.cosine(sentence_embeddings[0], sentence_embeddings[2]))
  4. print(1 - distance.cosine(sentence_embeddings[1], sentence_embeddings[2]))

输出

  1. 0.4643629193305969
  2. 0.9069876074790955
  3. 0.3275738060474396

3. 利用 torch

代码

  1. import torch.nn
  2. cos = torch.nn.CosineSimilarity(dim=0, eps=1e-6)
  3. b = torch.from_numpy(sentence_embeddings)
  4. print(cos(b[0], b[1]))
  5. print(cos(b[0], b[2]))
  6. print(cos(b[1], b[2]))

输出

  1. tensor(0.4644)
  2. tensor(0.9070)
  3. tensor(0.3276)

2.3. 方法之TFHub Universal Sentence Encoder

https://tfhub.dev/google/universal-sentence-encoder/4

https://colab.research.google.com/github/tensorflow/hub/blob/master/examples/colab/semantic_similarity_with_tf_hub_universal_encoder.ipynb

这个大约 1GB 的模型非常大,而且看起来比其他模型慢。这也会生成句子的嵌入

代码

  1. import tensorflow_hub as hub
  2. embed = hub.load("https://tfhub.dev/google/universal-sentence-encoder/4")
  3. embeddings = embed([
  4. "the person wear red T-shirt",
  5. "this person is walking",
  6. "the boy wear red T-shirt"
  7. ])
  8. print(embeddings)
  9. from scipy.spatial import distance
  10. print(1 - distance.cosine(embeddings[0], embeddings[1]))
  11. print(1 - distance.cosine(embeddings[0], embeddings[2]))
  12. print(1 - distance.cosine(embeddings[1], embeddings[2]))

输出

  1. tf.Tensor(
  2. [[ 0.063188 0.07063895 -0.05998802 ... -0.01409875 0.01863449
  3. 0.01505797]
  4. [-0.06786212 0.01993554 0.03236153 ... 0.05772103 0.01787272
  5. 0.01740014]
  6. [ 0.05379306 0.07613157 -0.05256693 ... -0.01256405 0.0213196
  7. -0.00262441]], shape=(3, 512), dtype=float32)
  8. 0.15320375561714172
  9. 0.8592830896377563
  10. 0.09080004692077637

其它嵌入

https://github.com/facebookresearch/InferSent

GitHub - Tiiiger/bert_score: BERT score for text generation

2.4. 方法之TF-IDF

TF-IDF原理

TF(Term Frequency) 表示词频,即一个词在在一篇文章中出现的次数,但在实际应用时会有一个漏洞,就是篇幅长的文章给定词出现的次数会更多一点。因此我们需要对次数进行归一化,通常用给定词的次数除以文章的总词数。

这其中还有一个漏洞,就是 ”的“ ”是“ ”啊“ 等类似的词在文章中出现的此时是非常多的,但是这些大多都是没有意义词,对于判断文章的关键词几乎没有什么用处,我们称这些词为”停用词“,也就是说,在度量相关性的时候不应该考虑这些词的频率。

IDF(Inverse Document Frequency)逆文本频率指数,如果包含关键词w的文档越少,则说明关键词w具有很好的类别区分能力。某一关键词的IDF,可以用总的文章数量除以包含该关键词的文章的数量,然后对结果取对数得到

 注:分母加1是为了避免没有包含关键词的文章时分母是0的情况

一个词预测主题的能力越强,权重就越大,反之,权重越小,因此一个词的TF-IDF就是:

TF-IDF=TF\cdot IDF

代码实践

安装sklearn包

pip install scikit-learn

代码

  1. from sklearn.feature_extraction.text import TfidfVectorizer
  2. corpus = ["I 'd like an apple",
  3. "An apple a day keeps the doctor away",
  4. "Never compare an apple to an orange",
  5. "I prefer scikit-learn to Orange",
  6. "The scikit-learn docs are Orange and Blue"]
  7. vect = TfidfVectorizer(min_df=1, stop_words="english")
  8. tfidf = vect.fit_transform(corpus)
  9. print(vect.vocabulary_)
  10. print('tfidf:',tfidf)
  11. pairwise_similarity = tfidf * tfidf.T
  12. print("pairwise_similarity:",pairwise_similarity)
  13. print(pairwise_similarity.toarray() )
  14. import numpy as np
  15. arr = pairwise_similarity.toarray()
  16. np.fill_diagonal(arr, np.nan)
  17. input_doc = "The scikit-learn docs are Orange and Blue"
  18. input_idx = corpus.index(input_doc)
  19. print(input_idx)
  20. result_idx = np.nanargmax(arr[input_idx])
  21. print(corpus[result_idx])

输出结果

  1. {'like': 9, 'apple': 0, 'day': 4, 'keeps': 7, 'doctor': 6, 'away': 1, 'compare': 3, 'orange': 10, 'prefer': 11, 'scikit': 12, 'learn': 8, 'docs': 5, 'blue': 2}
  2. tfidf: (0, 0) 0.5564505207186616
  3. (0, 9) 0.830880748357988
  4. (1, 1) 0.4741246485558491
  5. (1, 6) 0.4741246485558491
  6. (1, 7) 0.4741246485558491
  7. (1, 4) 0.4741246485558491
  8. (1, 0) 0.31752680284846835
  9. (2, 10) 0.48624041659157047
  10. (2, 3) 0.7260444301457811
  11. (2, 0) 0.48624041659157047
  12. (3, 8) 0.4864843177105593
  13. (3, 12) 0.4864843177105593
  14. (3, 11) 0.6029847724484662
  15. (3, 10) 0.40382592962643526
  16. (4, 2) 0.516373967614865
  17. (4, 5) 0.516373967614865
  18. (4, 8) 0.4166072657167829
  19. (4, 12) 0.4166072657167829
  20. (4, 10) 0.3458216642191991
  21. pairwise_similarity: (0, 2) 0.27056873300683837
  22. (0, 1) 0.17668795478716204
  23. (0, 0) 0.9999999999999998
  24. (1, 2) 0.1543943648960287
  25. (1, 0) 0.17668795478716204
  26. (1, 1) 0.9999999999999999
  27. (2, 1) 0.1543943648960287
  28. (2, 0) 0.27056873300683837
  29. (2, 4) 0.16815247007633355
  30. (2, 3) 0.1963564882520361
  31. (2, 2) 1.0
  32. (3, 2) 0.1963564882520361
  33. (3, 4) 0.5449975578692606
  34. (3, 3) 0.9999999999999999
  35. (4, 2) 0.16815247007633355
  36. (4, 3) 0.5449975578692606
  37. (4, 4) 1.0
  38. [[1. 0.17668795 0.27056873 0. 0. ]
  39. [0.17668795 1. 0.15439436 0. 0. ]
  40. [0.27056873 0.15439436 1. 0.19635649 0.16815247]
  41. [0. 0. 0.19635649 1. 0.54499756]
  42. [0. 0. 0.16815247 0.54499756 1. ]]
  43. 4

参考资料

python - How to compute the similarity between two text documents? - Stack Overflow

优势是:速度快

2.5.  CountVectorizer

下面将结合代码实现基于词袋模型的文本匹配任务。

1 数据预处理

首先需要对数据进行预处理,将文本转化成向量表示。可以使用sklearn库中的CountVectorizer

  1. from sklearn.feature_extraction.text import CountVectorizer
  2. # 定义文本数据
  3. text1 = "Hello, how are you?"
  4. text2 = "I am doing well, thank you."
  5. text3 = "What is your name?"
  6. text4 = "My name is ChatGPT."
  7. # 定义CountVectorizer
  8. vectorizer = CountVectorizer()
  9. # 将文本数据转化成向量表示
  10. X = vectorizer.fit_transform([text1, text2, text3, text4])

2 计算文本相似度

接下来可以使用余弦相似度来计算文本之间的相似度,可以使用scipy库中的spatial库中的distance函数。

  1. from scipy.spatial import distance
  2. # 计算第1个和第2个文本的相似度
  3. similarity_12 = 1 - distance.cosine(X[0].toarray(), X[1].toarray())
  4. print("Text 1 and Text 2 similarity: ", similarity_12)
  5. # 计算第1个和第3个文本的相似度
  6. similarity_13 = 1 - distance.cosine(X[0].toarray(), X[2].toarray())
  7. print("Text 1 and Text 3 similarity: ", similarity_13)
  8. # 计算第2个和第4个文本的相似度
  9. similarity_24 = 1 - distance.cosine(X[1].toarray(), X[3].toarray())
  10. print("Text 2 and Text 4 similarity: ", similarity_24)

以上代码中,使用的是余弦相似度计算文本之间的相似度。可以看到,文本1和文本2的相似度较高,文本1和文本3的相似度较低,文本2和文本4的相似度为0。

2.6. Jaccard Index--杰卡德系数计算

杰卡德系数,英文叫做 Jaccard index, 又称为 Jaccard 相似系数,用于比较有限样本集之间的相似性与差异性。Jaccard 系数值越大,样本相似度越高。

实际上它的计算方式非常简单,就是两个样本的交集除以并集得到的数值,当两个样本完全一致时,结果为 1,当两个样本完全不同时,结果为 0。

具体的可以参考Jaccard Index / Similarity Coefficient - Statistics How To

1. 代码-只使用于英语

  1. import numpy as np
  2. def Jaccard_Similarity(doc1, doc2):
  3. # List the unique words in a document
  4. words_doc1 = set(doc1.lower().split())
  5. words_doc2 = set(doc2.lower().split())
  6. # Find the intersection of words list of doc1 & doc2
  7. intersection = words_doc1.intersection(words_doc2)
  8. # Find the union of words list of doc1 & doc2
  9. union = words_doc1.union(words_doc2)
  10. # Calculate Jaccard similarity score
  11. # using length of intersection set divided by length of union set
  12. return float(len(intersection)) / len(union)
  13. if __name__ == '__main__':
  14. doc_1 = "Data is the new oil of the digital economy"
  15. doc_2 = "Data is a new oil"
  16. possiblity = Jaccard_Similarity(doc_1, doc_2)
  17. print(possiblity)

输出

0.4444444444444444

代码二--适用于中英文

  1. import numpy as np
  2. def jaccard_similarity_cn(s1, s2):
  3. from sklearn.feature_extraction.text import CountVectorizer
  4. def add_space(s):
  5. return ' '.join(list(s))
  6. # 将字中间加入空格
  7. s1, s2 = add_space(s1), add_space(s2)
  8. # 转化为TF矩阵
  9. cv = CountVectorizer(tokenizer=lambda s: s.split())
  10. corpus = [s1, s2]
  11. vectors = cv.fit_transform(corpus).toarray()
  12. # 获取词表内容
  13. ret = cv.get_feature_names()
  14. print(ret)
  15. # 求交集
  16. numerator = np.sum(np.min(vectors, axis=0))
  17. # 求并集
  18. denominator = np.sum(np.max(vectors, axis=0))
  19. # 计算杰卡德系数
  20. return 1.0 * numerator / denominator
  21. if __name__ == '__main__':
  22. s1 = '你在干嘛呢'
  23. s2 = '你在干什么呢'
  24. print(jaccard_similarity_cn(s1, s2))

输出

  1. ['么', '什', '你', '呢', '嘛', '在', '干']
  2. 0.5714285714285714

总结

本文介绍了NLP中的文本匹配任务,包括任务的定义、原理以及代码实现。文本匹配任务的核心是将文本表示成向量,并计算文本向量之间的相似度。在实现中,可以使用不同的方法进行文本表示和相似度计算。通过文本匹配任务,可以实现很多NLP应用,比如问答系统、对话系统等。

参考资料

How to compute the similarity between two text documents?

https://en.wikipedia.org/wiki/Cosine_similarity#Angular_distance_and_similarity

https://towardsdatascience.com/word-distance-between-word-embeddings-cc3e9cf1d632

scipy.spatial.distance.cosine — SciPy v0.14.0 Reference Guide

https://www.tensorflow.org/api_docs/python/tf/keras/losses/CosineSimilarity

deep learning - is there a way to check similarity between two full sentences in python? - Stack Overflow

NLP Town

https://welearnnlp.blog.csdn.net/article/details/129904773

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/367618
推荐阅读
相关标签
  

闽ICP备14008679号