前言

文本匹配一直是自然语言处理(NLP)领域一个基础且重要的方向,一般研究两段文本之间的关系。

文本相似度计算、自然语言推理、问答系统、信息检索等,都可以看作针对不同数据和场景的文本匹配应用。

比如信息检索可以归结为搜索词和文档资源的匹配,问答系统可以归结为问题和候选答案的匹配,复述问题可以归结为两个同义句的匹配,这些自然语言处理任务在很大程度上都可以抽象成文本匹配问题。

而文本匹配整体流程基本上都可以分为两个大的步骤:1)提取文本特征,如果是文段,则首先利用主题模型提取文段的主题文本,再将主题文本的文本特征提取;2)计算文本特征的相似度,相似度较高的文本对,则认为是匹配对较高的文本对,目的就达到了。

词粒度文本匹配

词粒度文本匹配,即是用于计算两个词语之间的文本特征相似度,是文本匹配中最为简单的任务。

计算两个词语之间的文本相似度,可以用word2vec模型来进行比较。

导入gensim的库和对应的语料,语料中包含多个用空格(’ \n ')分开的词语

import gensim

txt_path = 'data/C000008_test.txt'
sentences = [i.split() for i in open(txt_path, 'r', encoding='utf-8').read().split('\n')]
sentences[:3]

nlp 关键词 nlp关键词匹配算法_nlp 关键词

使用上述语料训练word2vec模型,并计算两个词语之间的相似度

model = gensim.models.Word2Vec(
    sentences, vector_size=50, window=5, min_count=1, workers=4)
# compare two word
print(model.wv.similarity('中国', '澳大利亚'))

0.07167525

句粒度文本匹配

基于句子的短文本匹配,是文本匹配领域研究比较多的一个内容,而且最近几年也有比较的文章和模型发布,详情可以参看深度文本匹配survey这篇文章。

本次的基于句粒度文本匹配,选用的SimCSE模型。SimCSE可以看成是SimBERT的简化版,这个模型去掉了SimBERT的生成部分,仅保留检索模型。

SimCSE训练过程中是没有标签,所以把每个句子自身视为相似句传入,本质上来说就是(自己,自己)作为正例、(自己,别人)作为负例来训练对比学习模型。

代码非常简单,主要使用的了hugging face的预训练模型,输出文本句的embeddings特征,然后计算特征相似度即可

from transformers import AutoModel, AutoTokenizer

import os

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

tokenizer = AutoTokenizer.from_pretrained("princeton-nlp/sup-simcse-bert-base-uncased")
model = AutoModel.from_pretrained("princeton-nlp/sup-simcse-bert-base-uncased")

# Tokenize input texts
texts = [
    "There's a kid on a skateboard.",
    "A kid is skateboarding.",
    "A kid is inside the house."
]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
import torch
from scipy.spatial.distance import cosine

# Get the embeddings
with torch.no_grad():
    embeddings = model(**inputs, output_hidden_states=True, return_dict=True).pooler_output

print(embeddings.shape)
embeddings = embeddings.detach().numpy()
from sklearn.metrics.pairwise import cosine_similarity

cos1 = cosine_similarity([embeddings[0]], [embeddings[1]])
cos2 = cosine_similarity([embeddings[0]], [embeddings[2]])

print("Cosine similarity between \"%s\" and \"%s\" is: %.3f" % (texts[0], texts[1], cos1))
print("Cosine similarity between \"%s\" and \"%s\" is: %.3f" % (texts[0], texts[2], cos2))

篇章粒度文本匹配

篇章粒度的文本匹配,首先要使用主题模型对文章主题进行提取,笔者这里使用的gensim库中的LDA模型,算是比较精度的主题模型,但是精度可能就没有那么好。

from nltk.tokenize import RegexpTokenizer
from gensim import corpora, models
import gensim

tokenizer = RegexpTokenizer(r'\w+')
    
# create sample documents
doc_a = "Brocolli is good to eat. My brother likes to eat good brocolli, but not my mother."
doc_b = "My mother spends a lot of time driving my brother around to baseball practice."
doc_c = "Some health experts suggest that driving may cause increased tension and blood pressure."
doc_d = "I often feel pressure to perform well at school, but my mother never seems to drive my brother to do better."
doc_e = "Health professionals say that brocolli is good for your health." 

# compile sample documents into a list
doc_set = [doc_a, doc_b, doc_c, doc_d, doc_e]

# list for tokenized documents in loop
texts = []

# loop through document list
for i in doc_set:
    # clean and tokenize document string
    raw = i.lower()
    tokens = tokenizer.tokenize(raw)
    # add tokens to list
    texts.append(tokens)

# turn our tokenized documents into a id <-> term dictionary
dictionary = corpora.Dictionary(texts)
    
# convert tokenized documents into a document-term matrix
corpus = [dictionary.doc2bow(text) for text in texts]

# generate LDA model
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics=2, id2word = dictionary, passes=20)

然后通过LDA模型求出所有文章的主题词,并计算各个文章之间的相似度矩阵。

如果存在文章较多的情况下,这样的操作是比较耗内存的,不建议这样做,当然,这里是做一个demo,就无所谓了。

index = gensim.similarities.MatrixSimilarity(ldamodel[corpus])

最后便可以求出任一文章与其他文章的相似度

def get_text_sim(text):
    doc_bow = [dictionary.doc2bow(text) for text in [tokenizer.tokenize(text.lower())]]
    print(doc_bow)
    vec_lda = ldamodel[doc_bow]
    sims = index[vec_lda]

    return sims
    
get_text_sim(doc_a)

[[(0, 2), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 1), (7, 1), (8, 2), (9, 1), (10, 2)]]
array([[0.99999994, 0.99999577, 0.07365547, 0.9999525 , 0.08877519]],
      dtype=float32)

参考

NLP之文本匹配及语义匹配应用介绍深度文本匹配surveyNLP 语义匹配:业务场景、数据集及比赛

·