经过前三篇博客的介绍,关于文档相似度的分析已经基本结束了,下面做下总结。
此处给出一个完整的相似项发现方法:
首先找出可能的候选对相似文档集合,然后基于该集合发现真正的相似文档。必须强调的是,这种方法可能会产生伪反例,即某些相似文档对由于没有进入候选对所以最终没有被识别出来。同样,该方法也可能产生伪正例,即在评估了某些候选对后,发现其相似度不足。
(1)选择某个k,并对每篇文章构建其k-shingle集合。将这些k-shingle映射成更短的桶编号(后一步可选)。
(2)将文档-shingle对按照shingle排序。
(3)选择最小哈希签名的长度n。将(2)中排好序的表传递给第二篇博客中介绍的最小哈希签名算法来计算所有文档的最小哈希签名。
(4)选择阈值t来定义应该达到的相似程度使之被看做是预期的“相似对”。选择行条数b和每个行条中的行数r,使得br=n,而阈值t近似等于(1/b)^(1/r)。如果避免伪反例的产生很重要,那么选择合适的b和r以产生小于t的阈值。而如果速度相当重要且希望限制伪正例的数目,那么选择合适的b和r来获得更高的阈值。
(5)应用上一篇博客中提到的LSH技术来构建候选对。
(6)检查每个候选对的签名,确定它们一致性的比例是否大于t。
(7)(该步可选)如果签名足够相似,则直接检查文档本身看它们是否真正相似。不相似的文档有时碰巧会具有相似的签名。
以上便是完整的文档相似度处理的过程,下面给出完整的python程序示例:
#coding:utf-8 import os import random import math import sys def to_unicode_or_bust(obj,encoding='utf-8'): if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) return obj """ 此函数用于获取dir文件夹中的文件的内容 """ def getFilesName(dir): fileList=[] t=os.walk(dir) file=dir+'\' for item in t: for name in item[2]: fileList.append(file+name) return fileList """ 此函数用于获得fileName文件中的内容,文件内容存放在字符串中返回 """ def getFileContent(fileName): file=open(fileName,"r") fileContent=file.read() fileContent=fileContent.replace(" "," ") fileContent=fileContent.replace(" "," ") fileContent=fileContent.replace(" "," ") file.close() return fileContent #return fileConten.decode("gb2312").encode("utf-8")用于显示 """ 此函数用于对各个文件中的内容进行k-shingle,然后对词条进行哈希(此处就用字典存储了) 其中dir是文件夹的名称字符串类型,k是int型 """ def getShingleList(dir,k): fileList=getFilesName(dir) shingleList=list() for fileName in fileList: fileContent=getFileContent(fileName) shingle = set() for index in range(0,len(fileContent)-k+1): shingle.add(fileContent[index:index+k]) shingleList.append(shingle) return shingleList """ 此处是新版的函数,将哈希签名的矩阵换的行列换了一下,便于接下来使用 """ def getMinHashSignature(shingleList,signatureNum): #tatalSet用于存放所有集合的并集 totalSet=shingleList[0] for i in range(1,len(shingleList)): totalSet=totalSet|shingleList[i] temp=int(math.sqrt(signatureNum)) #randomArray用于模拟随机哈希函数 randomArray=[] #signatureList用于存放总的哈希签名 signatureList=[] maxNum=sys.maxint for i in range(signatureNum): randomArray.append(random.randint(1,temp*2)) randomArray.append(random.randint(1,temp*2)) #buketNum用于记录所有元素的个数,作为随机哈希函数的桶号 buketNum=len(totalSet) """ 此处将不同文档的自己的哈希签名存成一个list,然后再进行汇总到一个总的list """ for shingleSet in shingleList: """ signature用于存放哈希函数产生的签名 """ signature=[] for i in range(signatureNum): minHash=maxNum for index,item in enumerate(totalSet): if item in shingleSet: num=(randomArray[i*2]*index+randomArray[i*2+1])%buketNum minHash=min(minHash,num) signature.append(minHash) signatureList.append(signature) return signatureList """ 在使用局部敏感哈希中,我们假设行条(也叫分组)和每个行条中行的个数的乘积刚好等于总的签名数,这样可以减少不必要的繁琐, 这就要求我们在选取相关参数时要注意 """ def localSensitiveHash(signatureList,filesName,signatureNum,bands): lshResult=[] """ 此循环用于初始化这个list,构造一个下三角的模拟数组,用于存放两个文件之间行的相似个数 """ for i in range(len(signatureList)): temp=[] for j in range(i): temp.append(0) lshResult.append(temp) row=signatureNum/bands #此循环是对签名的行条进行处理 for i in range(0,bands): dicResult={} index=i*row #此循环是对行条中的每一列进行计算,num用于记录签名的下标 for num,signature in enumerate(signatureList): #hashCode用于记录每个行条的hash桶号 hashCode = 0 #遍历行条中的列,此处的hash函数就暂时用累加,稍后修改 for j in range(index,index+row): hashCode+=signature[j] if hashCode in dicResult: dicResult[hashCode].append(num) else: dicResult[hashCode]=[num] for valueList in dicResult.values(): #print valueList if(len(valueList)>=2): for index1 in range(len(valueList)): for index2 in range(index1+1,len(valueList)): lshResult[valueList[index2]][valueList[index1]]+=1 similarityResult=[] for i in range(1,len(lshResult)): for j in range(len(lshResult[i])): similarity=lshResult[i][j]/(bands*1.0) similarityResult.append((similarity,filesName[i],filesName[j])) return similarityResult dir="D://E13" # 存放文档的目录路径 filesName=getFilesName(dir) shingleList=getShingleList(dir,9) signatureList=getMinHashSignature(shingleList,200) result=localSensitiveHash(signatureList,filesName,200,40) result.sort() for each in result: print each