赞
踩
通过计算并比较文档的摘要,可实现文本的相似度比较。
下面先阐述向量类(Vector) 和文档摘要类(Sketch)的设计与实现,然后使用文档摘要类来比较文档的相似度。
向量是一种数学抽象,n维向量可以使用一个n个实数的有序列表: (x0, x1,…,xn-1)。向量支持基本的四则算数运算,故可通过运算符重载来实现。
向量的基本运算
包括:两个向量的加法、一个向量乘以一个标量(一个实数)、计算两个向量的点积、计算向量大小和方向。
基本的向量(Vector) 类设计思路如下:
_coords
。def __init__(self, a):
"""构造函数:切片拷贝列表参数a到对象实例变量_coords"""
self._coords = a[:]
self._n = len(a)
__getitem__
,返回第 i 个元素,即第 i 维坐标。def __getitem__(self, i):
"""返回第i个元素,即第i维坐标"""
return self._coords[i]
__len__
, 返回向量的维度。def __len__(self):
"""返回向量的维度"""
return self._n
__str__
,返回向量的字符串表示。.def __str__(self):
"""返回向量的字符串表示"""
return str(self._coords)
__add__
、__sub__
,实现向量的运算:加法、减法。加法:x+y = (x0+y0, x1+y1, …, xn-1+yn-1)
减法:x-y = (x0-y0,x1-y1,…,xn-1-yn-1)
def __add__(self, other):
"""返回2个向量之和"""
result = []
for i in range(self._n):
result.append(self._coords[i] + other._coords[i])
return Vector(result)
def __sub__(self, other):
"""返回2个向量之差"""
result = []
for i in range(self._n):
result.append(self._coords[i] - other._coords[i])
return Vector(result)
标量积:ax = (ax0,ax1, …,axn-1)
def scale(self, n):
"""返回向量与数值的乘积(标量积)"""
result = []
for i in range(self._n):
result.append(self._coords[i] * n)
return Vector(result)
点积:xy = x0y0 + x1y1 +… + xn-1yn-1
def dot(self, other):
"""返回2向量的内积(点积)"""
result = 0
for i in range(self._n):
result += self._coords[i] * other._coords[i]
return result
__abs__
,实现向量的运算:大小(模)。大小:|x| = (x02 + x12 + … + xn-12)1/2
def __abs__(self):
"""返回向量的模"""
return math.sqrt(self.dot(self))
方向:x / |x| = ( x0 / |x|,x1 / |x|,…,xn-1 / |x| )
def direction(self):
"""返回向量的单位向量"""
return self.scale(1.0 / abs(self))
vector.py
import math class Vector: """笛卡尔坐标系向量""" def __init__(self, a): """构造函数:切片拷贝列表参数a到对象实例变量_coords""" self._coords = a[:] self._n = len(a) def __getitem__(self, i): """返回第i个元素,即第i维坐标""" return self._coords[i] def __add__(self, other): """返回2个向量之和""" result = [] for i in range(self._n): result.append(self._coords[i] + other._coords[i]) return Vector(result) def __sub__(self, other): """返回2个向量之差""" result = [] for i in range(self._n): result.append(self._coords[i] - other._coords[i]) return Vector(result) def scale(self, n): """返回向量与数值的乘积(标量积)""" result = [] for i in range(self._n): result.append(self._coords[i] * n) return Vector(result) def dot(self, other): """返回2向量的内积(点积)""" result = 0 for i in range(self._n): result += self._coords[i] * other._coords[i] return result def __abs__(self): """返回向量的模""" return math.sqrt(self.dot(self)) def direction(self): """返回向量的单位向量""" return self.scale(1.0 / abs(self)) def __str__(self): """返回向量的字符串表示""" return str(self._coords) def __len__(self): """返回向量的维度""" return self._n # 测试代码 def main(): xCoords = [2.0, 2.0, 2.0] yCoords = [5.0, 5.0, 0.0] x = Vector(xCoords) y = Vector(yCoords) print('x = {}, y = {}'.format(x, y)) print() print('x + y = {}'.format(x + y)) print() print('10x = {}'.format(x.scale(10.0))) print() print('|x| = {}'.format(abs(x))) print() print('<x, y> = {}'.format(x.dot(y))) print() print('|x - y| = {}'.format(abs(x-y))) if __name__ == '__main__': main()
运行示例:
x = [2.0, 2.0, 2.0], y = [5.0, 5.0, 0.0]
x + y = [7.0, 7.0, 2.0]
10x = [20.0, 20.0, 20.0]
|x| = 3.4641016151377544
<x, y> = 20.0
|x - y| = 4.69041575982343
文档摘要类(Sketch) 用于封装文档的摘要信息。
设计思路如下:
定义带3个列表参数(text (文本)、k (k-grams) 、d (文档摘要向量的维度) )的构造函数。
def __init__(self, text, k, d):
使用列表解析创建一个包含d个元素的列表freq (初始值为0),用于存储k-grams的频率。
freq = [0 for i in range(d)]
循环抽取文本的所有k-grams,并使用hash函数映射到0-d之间的整数,从而更新对应的列表freq的元素(递增)。
for i in range(len(text) - k):
kgram = text[i:i+k]
freq[hash(kgram) % d] += 1
然后使用freq创建Vector对象vector,并调用向量对象的Direction方法进行归一化。
vector = Vector(freq)
最后把文档摘要向量vector并保存到实例对象属性:_sketch
。
self._sketch = vector.direction()
similarTo()
,计算两个文档摘要向量的余弦相似度。abs(x - y)
, 余弦相似性度的计算方法为: x.dot(y)
。def similarTo(self, other):
"""比较两个文档摘要对象Sketch的余弦相似度"""
return self._sketch.dot(other._sketch)
__str__
,返回向量的字符串表示。def __str__(self):
return str(self._sketch)
sketch.py
import sys from vector import Vector class Sketch: """计算文本text的k-grams的文档摘要向量(d维) """ def __init__(self, text, k, d): """初始化函数:计算文本text的文档摘要向量""" freq = [0 for i in range(d)] #创建长度为d的列表,初始值0 for i in range(len(text) - k): #循环抽取k- grams,计算频率 kgram = text[i:i+k] freq[hash(kgram) % d] += 1 vector = Vector(freq) #创建文档摘要向量 self._sketch = vector.direction() #归一化并赋值给对象实例变量 def similarTo(self, other): """比较两个文档摘要对象Sketch的余弦相似度""" return self._sketch.dot(other._sketch) def __str__(self): return str(self._sketch) # 测试代码 def main(): with open("华夏.txt", "r", encoding='UTF-8') as f: text = f.read() sketch = Sketch(text, 5, 100) print(sketch) if __name__ == '__main__':main()
因为我打开的文本“华夏.txt”的编码方式是UTF-8,所以需要指定第三个参数:open("华夏.txt", "r", encoding='UTF-8')
而且,上述代码的输出是列表,列表中的每个值是文本循环输出的的k-grams(k个连续字符)的向量方向(即余弦值),就不将运行结果放出来了。
补充说明:哈希函数基于一个数值“种子”计算,在Python 3中,哈希种子会改变(缺省情况下),即给定对象的哈希值可能每次运行结果都不一样。因而,程序输出结果可能不同。
document_compare.py
import sys from vector import Vector from sketch import Sketch # 测试文档列表 filenames = ['华夏.txt', '中国.txt', '炎黄子孙.txt'] k = 5 #k-grams d = 10000 #文档摘要维度 sketches = [0 for i in filenames] for i in range(len(filenames)): with open(filenames[i], 'r', encoding='UTF-8') as f: text = f.read() sketches[i] = Sketch(text, k, d) # 输出结果标题 print(' ' * 20, end = ' ') for filename in filenames: print('{:>25}'.format(filename), end = ' ') print() # 输出结果比较明细 for i in range(len(filenames)): print('{:10}'.format(filenames[i]), end = ' ') for j in range(len(filenames)): print('{: <22}'.format(sketches[i].similarTo(sketches[j])), end = ' ') print()
运行结果:
华夏.txt 中国.txt 炎黄子孙.txt
华夏.txt 0.9999999999999785 0.5011577874531165 0.42527486798147096
中国.txt 0.5011577874531165 1.0000000000000089 0.536571712819202
炎黄子孙.txt 0.42527486798147096 0.536571712819202 1.0000000000000757
结果表明,相同文档的相似度为1,相同类型的文档(华夏.txt和中国.txt)相似度尚可,而不同类型的文档(华夏.txt和炎黄子孙.txt)的相似度则稍微低些。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。