文章目录
## Part4 文本相似度匹配
### 背景介绍
文本语义匹配是自然语言处理中一个重要的基础问题,NLP 领域的很多任务都可以抽象为文本匹配任务。例如,信息检索可以归结为查询项和文档的匹配,问答系统可以归结为问题和候选答案的匹配,对话系统可以归结为对话和回复的匹配。语义匹配在搜索优化、推荐系统、快速检索排序、智能客服上都有广泛的应用。如何提升文本匹配的准确度,是自然语言处理领域的一个重要挑战。
- 信息检索:在信息检索领域的很多应用中,都需要根据原文本来检索与其相似的其他文本,使用场景非常普遍。
- 新闻推荐:通过用户刚刚浏览过的新闻标题,自动检索出其他的相似新闻,个性化地为用户做推荐,从而增强用户粘性,提升产品体验。
- 智能客服:用户输入一个问题后,自动为用户检索出相似的问题和答案,节约人工客服的成本,提高效率。
让我们来看一个简单的例子,比较各候选句子哪句和原句语义更相近:
- 原句:“车头如何放置车牌”
- 比较句1:“前牌照怎么装”
- 比较句2:“如何办理北京车牌”
- 比较句3:“后牌照怎么装”
比较结果:
- 比较句1与原句,虽然句式和语序等存在较大差异,但是所表述的含义几乎相同
- 比较句2与原句,虽然存在“如何” 、“车牌”等共现词,但是所表述的含义完全不同
- 比较句3与原句,二者讨论的都是如何放置车牌的问题,只不过一个是前牌照,另一个是后牌照。二者间存在一定的语义相关性
- 所以语义相关性,句1大于句3,句3大于句2,这就是语义匹配。
### 数据说明
LCQMC数据集比释义语料库更通用,因为它侧重于意图匹配而不是释义。LCQMC数据集包含 260,068 个带有人工标注的问题对。
- 包含 238,766 个问题对的训练集
- 包含 8,802 个问题对的开发集
- 包含 12,500 个问题对的测试集
### 评估方式
使用准确率Accuracy来评估,即:
准确率(Accuracy)=预测正确的条目数/预测总条目数准确率=预测正确的条目数/预测总条目数
也可以使用文本相似度与标签的皮尔逊系数进行评估,不匹配的文本相似度应该更低。
### 学习打卡
任务名称 | 难度 |
---|---|
任务1:数据集读取 | 低、1 |
任务2:文本数据分析 | 低、1 |
任务3:文本相似度(统计特征) | 中、2 |
任务4:文本相似度(词向量与句子编码) | 高、3 |
任务5:文本匹配模型(LSTM孪生网络) | 搞、2 |
任务6:文本匹配模型(Sentence-BERT模型) | 高、3 |
任务7:文本匹配模型(SimCSE模型) | 高、3 |
- 任务1:数据集读取
自然语言处理(Natural Language Processing, NLP)是计算机科学、人工智能和语言学的交叉领域,其目标是使计算机能够理解、生成和处理人类语言。常见的 NLP 技术包括语音识别、文本分析、机器翻译等。这些技术都是基于人工智能和机器学习的算法来实现的。
文本匹配是自然语言处理中的一种常见任务。它可以用来判断两个文本之间的相似度或相关性。常见的文本匹配任务包括:文本相似性匹配、问答匹配、查询-文档匹配等。这些任务的具体实现可以使用机器学习技术,例如使用神经网络模型进行文本嵌入,然后使用余弦相似度或其他相似度度量来计算文本之间的相似度。
LCQMC(Large-scale Chinese Question Matching Corpus)是一个大规模的中文文本匹配数据集。它包含超过 400,000 个标记为重复或非重复的问题对。该数据集由中国科学院自动化研究所(CASIA)深度学习技术与应用国家工程实验室(NEL-DLT)创建。
LCQMC 数据集中的问题涵盖广泛的主题,并以口语化的中文编写,使其成为文本匹配模型具有挑战性的数据集。该数据集通常用于训练和评估各种中文文本匹配模型的性能,例如基于神经网络的模型。它还用于中文自然语言处理的研究,例如文本匹配、文本分类和其他 NLP 任务。该数据集为研究人员提供了一个基准,用于评估其模型的性能并将其与最先进的方法进行比较。
import pandas as pd
def load_lcqmc():
'''LCQMC文本匹配数据集
'''
train = pd.read_csv('https://mirror.coggle.club/dataset/LCQMC.train.data.zip',
sep='\t', names=['query1', 'query2', 'label'])
valid = pd.read_csv('https://mirror.coggle.club/dataset/LCQMC.valid.data.zip',
sep='\t', names=['query1', 'query2', 'label'])
test = pd.read_csv('https://mirror.coggle.club/dataset/LCQMC.test.data.zip',
sep='\t', names=['query1', 'query2', 'label'])
return train, valid, test
这一步非常简单,如果要仔细研究数据,我们也可以wget下载到本地看看。
这个数据集就是两个文本是否相似,然后给个标签。
- 任务2:文本数据分析
- 步骤1:分析赛题文本长度,相似文本对与不相似文本对的文本长度是否存在差异?
train['len_q1'] = train.query1.apply(lambda x:len(x))
train['len_q2'] = train.query2.apply(lambda x:len(x))
train['len_diff'] = (train['len_q1']-train['len_q2']).apply(lambda x:abs(x))
train_match = train[train['label']==1]
train_mismatch = train[train['label']==0]
train_match['len_q1'].mean(),train_match['len_q2'].mean(),train_match['len_diff'].mean(),train_mismatch['len_q1'].mean(),train_mismatch['len_q2'].mean(),train_mismatch['len_diff'].mean()
我们可以看到,不相似文本的差别与相似文本相比,长度差别更大。
- 步骤2:分析赛题单词和字符个数,在所有文本中包含多少个单词(用jieba进行分析)和字符?
import jieba
counts = {}
def count_text(txt,counts):
# 读取文本
# txt = open("data.txt", "r", encoding='utf-8').read()
# 使用精确模式对文本进行分词
words = jieba.lcut(txt)
# 通过键值对的形式存储词语及其出现的次数
for word in words:
# 去掉词语中的空格
word = word.replace(' ', '')
# 如果词语长度为1,则忽略统计
if len(word) == 0:
continue
# 进行累计
else:
counts[word] = counts.get(word, 0) + 1
if __name__ == '__main__':
for txt in train.query1:
count_text(txt, counts)
for txt in train.query2:
count_text(txt, counts)
for txt in test.query1:
count_text(txt, counts)
for txt in test.query1:
count_text(txt, counts)
for txt in valid.query1:
count_text(txt, counts)
for txt in valid.query1:
count_text(txt, counts)
我们得到,数据集一共有40171条数据,单词总数为3397960个。
出现较多的词汇除了常见语气词、介词,有:手机、牌子等。
备注:
在LCQMC数据集中数据采用三列进行存储,其中label为是否含义相同的标签。在任务2中我们希望各位同学,能对中文文本进出初步的分析,找到相似文本对和不相似文本对的差异。
query1 | query2 | label | |
---|---|---|---|
0 | 喜欢打篮球的男生喜欢什么样的女生 | 爱打篮球的男生喜欢什么样的女生 | 1 |
1 | 我手机丢了,我想换个手机 | 我想买个新手机,求推荐 | 1 |
2 | 大家觉得她好看吗 | 大家觉得跑男好看吗? | 0 |
jieba是一个中文分词库,用于将中文句子分解为词组。它使用了基于前缀词典的最大匹配算法,并支持用户自定义词典。要使用Jieba库,首先需要安装它。使用 pip 可以轻松安装:
pip install jieba
安装完成后,可以使用下面的代码对句子进行分词:
import jieba
sentence = "我在学习使用jieba分词"
seg_list = jieba.cut(sentence)
print(" ".join(seg_list))
输出结果是:我 在 学习 使用 jieba 分词。还可以使用jieba.lcut() or jieba.lcut_for_search() 获取词组列表。
利用jieba.cut对数据进行处理。
def _tokenize(self, text):
# 文本分词
tokens = list(jieba.cut(text))
token_ids = []
for token in tokens:
if token in self.vocab_mapping:
# 如果当前词存在于词表,将词转换为词的ID.
token_id = self.vocab_mapping[token]
token_ids.append(token_id)
else:
# OOV情况处理
# 如果该词为多字的词,将其拆分多个字,分别将这些字转换为相应的ID;
# 如果该词为单字,则从词表中随机采样一个词,其ID作为该词的ID
if len(token) > 1:
for t in list(token):
if t in self.vocab_mapping:
token_ids.append(self.vocab_mapping[t])
else:
token_ids.append(np.random.choice(len(self.vocab_mapping), 1)[0])
else:
token_ids.append(np.random.choice(len(self.vocab_mapping), 1)[0])
# 对文本进行填充或者截断
token_ids, attention_mask = self._pad_truncate(token_ids)
return token_ids, attention_mask
- 任务3:文本相似度(统计特征)
- 步骤1:对query1和query2计算文本统计特征
- query1和query2文本长度
- query1和query2文本单词个数
- query1和query2文本单词差异
- query1和query2文本最长公用字符串长度
- query1和query2文本的TFIDF编码相似度
利用TF-IDF计算相似文章步骤:
1)使用jieba分词,找出两篇文章的关键词
2)每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于这个集合中的词的词频(为了避免文章长度的差异,可以使用相对词频)
3)生成两篇文章各自的词频向量
4)计算两个向量的余弦相似度,值越大就表示越相似
可以参考:https://gitee.com/qmckw/tfidf.git
得到tiidf向量以后,我们可以计算余弦相似度。
- 步骤2:根据相似度标签,上述哪一个特征最有区分性?
由上面的计算可知,显然余弦相似度最有区分性。
文本统计特征是指对文本进行统计并得到的一些数值,可以用来描述文本的特征。基础的文本特征包括:
文本长度: 文本中的字符数或单词数
字符频率: 每个字符在文本中出现的次数或频率
单词频率: 每个单词在文本中出现的次数或频率
句子长度: 文本中句子的平均长度
句子数量: 文本中句子的数量
上述文本特征都是无监督的,不局限语言和模型,而且计算快速,在任务3中我们需要大家使用python统计相似的文本和不相似文本的基础统计特征。
- 任务4:文本相似度(词向量与句子编码)
-
步骤1:使用jieba分词,然后使用word2vec训练词向量
-
步骤2:计算单词的TFIDF或BM25权重
在前面计算余弦值时,我已经用tiidf计算了每个句子中每个词汇的tfidf值。
计算bm25权重,参考:https://blog.csdn.net/Tink1995/article/details/104745144
权重即为idf值,
- 步骤3:尝试如下无监督句子编码过程
最开始,我尝试的是一个单词,一个单词权重,然后为了让两个向量一致,不存在的词用0补全,这种方案不一定是题目要求的方案,但是因为向量维度相同,因此具有了一定的可比性。-
Mean-Pooling
平均池化,那么我们可以通过分段取均值的方式进行调整向量维度,通过池化可以适当调小维度,减小运算量。以最开始完成的tf idf得到的向量为例池化。
假设窗格大小包含了最大的句向量,即对向量求均值。
显然,平均池化可以较好的体现整个句子的特性。 -
Max-Pooling
而maxpool比较能反应处贡献较大的单词的特性。 -
IDF-Pooling / BM25-Pooling
将TF-IDF和池化层结合进行无监督编码的方法可以分为以下步骤:对文本进行预处理,并使用IDF特征,将每个单词的IDF值乘以到单词词向量上。
-
SIF-Pooling
-
- 步骤4:根据相似度标签,上述哪一个特征最有区分性?
sif更具有区分性。
备注:
词向量(Word Embedding)是在自然语言处理中的一种广泛使用的技术,用于将文本中的单词映射到一个多维空间中的向量表示。这些向量可以保留单词之间的语义相似性,并且可以在模型训练中被用来作为特征。
一种常见的词向量模型是Word2Vec, 其通过训练神经网络来预测上下文单词,学习单词的向量表示。 另外还有基于Transformer模型的词向量模型, 比如 BERT, RoBERTa, GPT, 通过构建大型语料预训练模型并在其上进行 fine-tune来学习向量表示。
在本次任务中我们将尝试使用词向量来计算文本相似度。无监督句子编码(Unsupervised Sentence Encoding)是自然语言处理中的一种技术,用于将句子转换为向量表示。它和词向量类似,但是它通常不需要大量的标记数据来训练。无监督句子编码可以用来做许多自然语言处理任务,如文本分类,问答系统,对话系统等。句子向量表示可以用来计算句子之间的相似度或者在聚类或者知识图谱等系统中使用。
Word2Vec 是一种将单词表示为向量的方法,它使用了神经网络来学习单词之间的语义关系。有两种常见的计算 word2vec 的方法:
- 负采样 (Negative Sampling):在训练过程中,对于每个目标单词,随机采样一些不相关的单词作为负样本,来帮助训练网络。这种方法可以通过减少训练样本的数量来减少计算量。
- 连续词袋模型 (Continuous Bag-of-Words, CBOW):这种方法将目标词周围的单词作为上下文,来预测目标词。对于每个单词都有一个输入向量和一个输出向量,通过训练网络来学习输入向量与输出向量之间的映射关系。
Gensim是一个开源的NLP库,它提供了一系列的工具来训练和使用词向量,包括 word2vec。下面是一个简单的示例,说明如何使用 Gensim 来训练中文词向量:
import jieba
from gensim.models import word2vec
# 预处理文本数据,分词并去掉停用词
sentences = [jieba.lcut(line) for line in open('text.txt')]
# 训练词向量
model = word2vec.Word2Vec(sentences, size=100, window=5, min_count=5, workers=4)
# 保存模型
model.save('word2vec.model')
# 查询词向量
print(model['中国'])
无监督句子编码是指使用无监督学习方法(不需要人为标注的样本)将句子转换为向量的过程。这个过程大致分为以下几步:
- 数据预处理:对文本进行分词和词干提取等预处理操作,得到一系列分词后的句子。
- 词嵌入:使用预训练的词向量或自己训练的词向量将每个词转换为向量表示。
- 句子编码:对于每个句子,将每个词的词向量组合在一起得到句子向量。
- 池化层:对于每个句子向量,使用聚合函数(如平均值,最大值)得到固定维度的向量。
通过过池化层,我们可以长度不同的文本转换为相同的维度。基础的池化层包括Mean-Pooling、Max-Pooling,可以直接使用Numpy进行计算。基础的池化层是无监督方法,并没有考虑到单词的重要性。
TF-IDF (term frequency-inverse document frequency)是一种常用的文本特征提取方法,用于衡量单词在文档中的重要性。将TF-IDF和池化层结合进行无监督编码的方法可以分为以下步骤:对文本进行预处理,并使用IDF特征,将每个单词的IDF值乘以到单词词向量上。
SIF (Smooth Inverse Frequency) 不需要训练词向量权重,而是通过简单的参数调整来生成词向量。 SIF计算了句子的嵌入中最重要的元素。然后它减去这些句子嵌入中的主要成分。这就可以删除与频率和句法有关的变量,他们和语义的联系不大。
# https://github.com/PrincetonML/SIF/blob/master/src/SIF_embedding.py
def compute_pc(X,npc=1):
svd = TruncatedSVD(n_components=npc, n_iter=7, random_state=0)
svd.fit(X)
return svd.components_
def remove_pc(X, npc=1):
pc = compute_pc(X, npc)
if npc==1:
XX = X - X.dot(pc.transpose()) * pc
else:
XX = X - X.dot(pc.transpose()).dot(pc)
return XX
通过上述池化方法,我们可以将任意长度的句子转换为相同的长度的向量。然后我们就对向量进行归一化,然后向量之间的相似度,以此来衡量句子之间的相似度。结合数据集中的根据相似度标签,上述哪一个特征最有区分性?
如果对此任务不太了解,可以阅读下面博客:https://zhuanlan.zhihu.com/p/37104535
- 任务5:文本匹配模型(LSTM孪生网络)
-
步骤1:定义孪生网络(嵌入层、LSTM层、全连接层)
参考:https://tianchi.aliyun.com/notebook/409641
https://tianchi.aliyun.com/notebook/409589
-
步骤2:使用文本匹配数据训练孪生网络
参考:https://tianchi.aliyun.com/notebook/409589
bilstm = keras.layers.Bidirectional(keras.layers.LSTM(
self._params['lstm_units'],
return_sequences=True,
dropout=self._params['dropout_rate']
))
rep_left = bilstm(embed_left)
rep_right = bilstm(embed_right)
#运行cdssm模型
#进入text_match文件夹路劲
python word2vec_static.py #训练词向量,仅在第一次运行本项目的模型时需要
python train.py cdssm
- 步骤3:对测试数据进行预测
loss, acc = model.evaluate(
x=x_test,
y=y_test,
batch_size=128,
verbose=1
)
print("Test loss:",loss, "Test accuracy:",acc)
参考:
RNN(Recurrent Neural network)是一类具有递归结构的神经网络,它可以处理序列数据,例如文本、音频、时间序列数据等。RNN 的核心思想是在网络的隐藏层中循环地计算当前时间步的输出。
https://pytorch.org/docs/stable/generated/torch.nn.RNN.html
import torch
from torch import nn
rnn = nn.RNN(10, 20, 2)
input = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
output, hn = rnn(input, h0)
LSTM(Long Short-Term Memory)是一种特殊的 RNN 模型,它在 RNN 的基础上增加了“记忆门”的机制来控制记忆的编写、读取和遗忘。LSTM 的结构主要由三部分组成:输入门、输出门和遗忘门。它们控制 LSTM 对于当前输入和历史记忆的重视程度。
https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html
import torch
from torch import nn
rnn = nn.LSTM(10, 20, 2)
input = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
c0 = torch.randn(2, 3, 20)
output, (hn, cn) = rnn(input, (h0, c0))
RNN/LSTM孪生网络是一种文本匹配模型,网络的输入是两个文本序列,分别对文本进行编码,编码后的向量通过一些运算得到相似度分数,来判断文本之间的相似度。使用RNN/LSTM孪生网络进行文本匹配的网络结构如下:文本嵌入、文本特征提取和文本匹配。
参考资料:
- https://www.jianshu.com/p/c578a77e7111
- https://github.com/ChiYeungLaw/TextMatching-Chinese
- 任务6:文本匹配模型(BERT模型)
参考:https://github.com/datawhalechina/competition-baseline/blob/master/tutorial/bert/bert-nsp-example.ipynb
-
步骤0:使用BERT对文本进行编码,计算句子对相似度
-
步骤1:定义BERT网络
-
步骤2:使用数据完成BERT-NSP训练
无奈,训练的时候空间不够了。 -
步骤3:对测试数据进行预测
-
步骤4(进阶):使用BERT模型完成Sentence-BERT,训练并进行预测。
BERT (Bidirectional Encoder Representations from Transformers) 是一种预训练深度神经网络语言模型。它是由 Google AI Language Team 在 2018 年提出的。BERT 是基于 Transformer 架构,具有双向预训练的能力,能够在大量的文本数据上学习到有效的特征表示。
在预训练中,BERT 模型使用了两种预训练任务来学习语言表示(BERT模型本身可以用来做文本匹配,也就是直接使用NSP的训练思路):
- Masked Language Model (MLM) 任务:将部分词掩盖,让模型预测掩盖的词。
- Next Sentence Prediction (NSP) 任务:给定两个句子,让模型预测第二个句子是否是第一个句子的后续。
Google AI Language Team在2019年发布了中文版BERT,称作"中文版BERT-Base, Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters" 。此外,还有许多开发者和研究者针对中文语料进行了BERT的预训练,如 BERT-wwm、BERT-wwm-ext、RoBERTa-wwm-ext等,提供了更好的中文语言表示能力。
transformers
是一个由 Hugging Face 团队开发和维护的 Python 库,提供了许多用于自然语言处理的预训练模型。这个库提供了对大量热门模型的支持,如 BERT, GPT-2, RoBERTa, XLM, DistilBert, XLNet, CTRL 等,并且提供了许多有用的工具来简化模型的加载和使用。transformers
库是一个很好的工具,可以帮助开发者轻松的使用预训练的深度学习模型进行自然语言处理任务,并提高开发效率。
学习资料:
- 任务7:文本匹配模型(SimCSE模型)
-
步骤1:定义SimCSE网络和损失函数
-
步骤2:使用比赛数据先进行无监督训练,然后进行有监督训练
-
步骤3:对测试数据进行预测
-
SimCSE模型的核心是对比学习,对比学习是通过拉近相似数据的距离,拉远不相似数据的距离为目标,更好地学习数据的表征。使得其在文本匹配任务中产生更好的效果。SimCSE模型是一种简单的对比句向量表征的框架,
SimCSE包含无监督和有监督两种方法。
- 无监督学习:会采用Dropout技术,对原始文本进行数据增强,构造出正样本,用于对比学习训练;
- 监督学习:由于本身有正样本(相近样本),故无需使用Dropout技术,直接训练即可。
学习资料:
- https://spaces.ac.cn/archives/8348
- https://zhuanlan.zhihu.com/p/452761704
- https://github.com/vdogmcgee/SimCSE-Chinese-Pytorch
- https://github.com/KwangKa/SIMCSE_unsup
- https://github.com/shuxinyin/SimCSE-Pytorch