1 前言
在NLP机器学习任务中,一个首要的步骤就是将词向量化,也称为词编码。对于词编码,目前主要存在两类方法,一是词袋方法,二是分布式表示;前者又称为 one-hot 编码,是传统的经典方法。分布式表示(distribution representation)是现在流行的方法,也是深度学习必要步骤,对应着embedding layer。
今天主要分享如何利用词袋的方法向量化,然后做句子相似度计算任务。虽然词袋方法已不是主流,但它背后的思想也是分布式表示方法的基础,理解它的原理,有助于我们更好理解现在各种各样新的embedding方法。
2 词袋方法
基本思想:
step1:先统计语料库生成一个有序词表,假设词表大小为N(即词的数量);
step2:接着初始化一个长度N的零向量(),长度N代表着词和句子向量后的维度;
step3:最后,对一个词,检索它在词表的索引,然后将索引对应的初始向量维度值变为1,生成一个one-hot向量即为该词的向量,若词的索引为3,则词向量为,第4维度为1,其他维度都为0;对于一个句子,遍历句子中的每个词,按词编码的方式将初始向量对应的维度变为1,若句子有3个词,对应词表的索引为0,1,3,这句子向量为,第0,1,3维度为1,其他维度都为0。
举个例子:假设有个有序词表为['临床','表现','及','实验室','检查','即可','做出','诊断'],长度为8。对于‘临床’词来说,在词表的索引为0,则词向量为; 对于‘实验室’词来说,在词表的索引为3,则词向量为;对于“检查做出诊断为:”句子来说,分词后为“检查/做出/诊断/为/:”,每个词在词表对应的索引为4,6,7,后面两个词不在词表中,不考虑,最后句子向量为。在实际情况,一般词表很大,大到可能百万级别,当然也会去掉没太意义的词,如停用词或标点符号。
基于TF-IDF词袋方法
今天实现句子向量化,是基于TF-IDF的词袋方法。该方法很简单,就是在词袋基本思想上,将向量中的1值用该词的tf-idf的值代替,这样的好处就是一句话中不同词的权重是不一样的,重要的词所在维度取得值应该更大些,而tf-idf值就是衡量一个词的重要性。
同样,也有将1用词的词频(tf值)来代替,与基于TF-IDF词袋方法是一致的,但TF-IDF的值比TF值更具有代表性。
词袋方法缺陷
不管词袋方法如何优化,但有一个明显的缺陷:就是编码后的句子向量失去了原有词的顺序,换句话来说就是,丢弃了词的上下文信息,而这在很多NLP任务中是很重要的信息,尤其序列标注任务。也是因为这个问题,才有了现在主流的分布式表征方法,后续详细介绍分布式表征的词向量方法。
3 相似度计算
在进行句子相似度计算时,有很多可选方法。本文选择了最基本的余弦(cosine)相似度方法,其背后的思想是计算两个句子向量的夹角,夹角越小,相似度越高。假定和是两个句子的向量,维度都为n,即,,它们的余弦值等于
4 实验
任务说明:利用TF-IDF词袋方法,进行句子相似度计算。
实验数据:使用上一篇“TF-IDF的理论与实践“(https://www.jianshu.com/p/c55c6cae24ad)中同样的语料库file_corpus,然后从语料库中切分句子,取出现句子频率最高的前10000句子样本集。选取5个样本句子,然后利用相似度来计算出与样本句子最相似的句子。
4.1 生成句子样本集
引入所用的包:
import codecs
import re
import jieba
from scipy import spatial
import json
句子切分
def get_sentence(corpus_dir,sentence_dir):
re_han= re.compile(u"([\u4E00-\u9FD5a-zA-Z0-9]+)") #the method of cutting text to sentence
file_corpus=codecs.open(corpus_dir,'r',encoding='utf-8')
file_sentence=codecs.open(sentence_dir,'w',encoding='utf-8')
st=dict()
for _,line in enumerate(file_corpus):
line=line.strip()
blocks=re_han.split(line)
for blk in blocks:
if re_han.match(blk) and len(blk)>10:
st[blk]=st.get(blk,0)+1
st=sorted(st.items(),key=lambda x:x[1],reverse=True)
for s in st[:10000]:
file_sentence.write(s[0]+'\n')
file_corpus.close()
file_sentence.close()
4.2 相似度计算
class ComputeSimilarity():
def __init__(self,tf_idf_dir):
self.tf_idf_dir=tf_idf_dir
self.tf_idf=self.load_dict()
self.vocab=list(self.tf_idf.keys())
self.N=len(self.vocab)
def load_dict(self):
tf_idf=dict()
with codecs.open(tf_idf_dir,'r',encoding='utf-8') as f:
for line in f:
line=line.split('\t')
try:
assert len(line)==2
tf_idf[line[0]]=float(line[1])
except:
pass
return tf_idf
def similarity(self,s1,s2,use_idf=True):
v1=np.zeros((1,self.N))
v2=np.zeros((1,self.N))
if use_idf:
for w in jieba.cut(s1):
if w in self.tf_idf:
v=self.tf_idf[w]
i=self.vocab.index(w)
v1[:,i]+=v
for w in jieba.cut(s2):
if w in self.tf_idf:
v=self.tf_idf[w]
i=self.vocab.index(w)
v2[:,i]+=v
else:
for w in jieba.cut(s1):
if w in self.tf_idf:
i=self.vocab.index(w)
v1[:,i]=1
for w in jieba.cut(s2):
if w in self.tf_idf:
i=self.vocab.index(w)
v2[:,i]=1
sim=1-spatial.distance.cosine(v1,v2)
return sim
4.3 样本测试
def test(tf_idf_dir,sentence_dir,test_dir):
cs=ComputeSimilarity(tf_idf_dir)
ssm=cs.similarity
test_data=[u'临床表现及实验室检查即可做出诊断',
u'面条汤等容易消化吸收的食物为佳',
u'每天应该摄入足够的维生素A',
u'视患者情况逐渐恢复日常活动',
u'术前1天开始预防性运用广谱抗生素']
model_list=['use_idf','unuse_idf']
file_sentence=codecs.open(sentence_dir,'r',encoding='utf-8')
train_data=file_sentence.readlines()
for model in model_list:
if model=='use_idf':
use_idf=True
else:
use_idf=False
dataset=dict()
result=dict()
for s1 in test_data:
dataset[s1]=dict()
for s2 in train_data:
s2=s2.strip()
if s1!=s2:
sim=ssm(s1,s2,use_idf=use_idf)
dataset[s1][s2]=dataset[s1].get(s2,0)+sim
for r in dataset:
top=sorted(dataset[r].items(),key=lambda x:x[1],reverse=True)
result[r]=top[0]
with codecs.open(test_dir,'a',encoding='utf-8') as f:
f.write('--------------The result of %s method------------------\n '%model)
f.write(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=False))
f.write('\n\n')
file_sentence.close()
执行代码
if __name__=="__main__":
sentence_dir=r'E:\jianshu\data\file_sentence.txt'
tf_idf_dir=r'E:\jianshu\data\tf_idf.txt'
test_dir=r'E:\jianshu\data\test_result.txt'
test(tf_idf_dir,sentence_dir,test_dir)
5 结果分析
将跑出的test_result.txt的结果用表格形式展示出来,如下图:
从5条样例数据来看,在二者匹配结果一致的情况下(第2,4,5句子),使用tf_idf的方法(use_idf)计算的相似度更高些;通过第1个句子对比看,二者匹配差异在“实验室”这个词,这个词的tf_idf影响了最终的结果,显示use_idf优于后者;通过第2个句子对比看,应该unuse_idf结果更好些,出现这种情况的原因是词“维生素“的tf_idf很高的词且匹配句子重复出现,就导致这个句子匹配度提升,这种情形下是不合理的。
整体来说,tf_idf相似度计算会更合理些,但也不是完美的,当句子的匹配中涉及语义理解的含义时,往往这两种方法都不能很好处理,这时候可以利用word2vec或者bert能得到一定程度的处理,这类方法下次再介绍。