我们先来分析前面编写的相似性分析程序的运算效率,从数据库读取100条新闻条目,取第1条新闻条目作为待分析的新文档,程序运行结果如下图所示:

通过上图可知,分析100条新闻条目数据,中文分词耗费时间2.017秒,转换词袋数据耗费时间13.673秒,相似性分析耗费时间0.225秒,这些计算过程耗费时间都比较长。中文分词使用的外部库,我们无法修改其算法来提高运算效率,下面主要改进转换词袋和相似性分析的算法,来提高程序的运算效率。
先来分析转换词袋数据的代码,转换词袋数据的代码在项目的Dictionary类,类的doc2bow()方法将传入的文档分词数据转换为词袋数据。代码如下:
# 输出词袋模型 def doc2bow(self,texts): # 存储词袋数据 self.bow = [] # 遍历文档序列 for text in texts: v = [] # 遍历字典全部单词 for key in self.token2id.keys(): if key in text: ''' 若text包含字典单词,创建二元组(数字ID,词频) 添加二元组到词袋 ''' v.append((self.token2id[key], text.count(key))) else: ''' 若text不包含字典单词,创建二元组(数字ID,0) 添加二元组到词袋 ''' v.append((self.token2id[key], 0)) # 词袋数据按二元组的数字ID排序 v = sorted(v, key= lambda x: x[0]) self.bow.append(v) return self.bow
代码是O(n^2)复杂度,若texts长度是n,token2id的长度是m,需要进行n*m次运算。当m远大于n时,算法复杂度接近O(n^m/n)。若要降低算法复杂度,需要降低m,尽量维持算法在O(n^2)复杂度。改进的算法代码如下:
# 输出词袋模型 def new_doc2bow(self,texts): # 存储词袋数据 self.bow = [] # 遍历文档序列 for text in texts: v = [(id,0) for id in range(len(self.token2id))] for words in text: if words in self.token2id.keys(): v[self.token2id[words]] = (self.token2id[words],text.count(words)) self.bow.append(v) return self.bow
改进的算法代码将m降低为每个文档的长度,文档的长度比字典的长度要小的多,算法基本接近O(n^2)复杂度。实际上,当文档数量激增时,例如几万篇文档,算法的复杂度要低于O(n^2)。
再来分析相似性分析的算法,相似性分析的代码在项目的Similarity类,类的one_analysis方法对传入的单文档词袋数据与多文档词袋数据进行相似性分析。代码如下:
# 与多文档词袋数据逐一分析 # text 待分析的单文档词袋数据 def one_analysis(self,text): # 存储相似度 sim = [] v_text = self.bow2Vector(text) for item in self.corpus: v_item = self.bow2Vector(item) value = self.consine(np.array(v_text),np.array(v_item)) sim.append(value) return sim
代码是O(n)复杂度,n为corpus的长度,重复n次的运算过程主要是抽取向量,计算文档间夹角余弦值。实际上我们可以应用矩阵运算将计算文档间夹角余弦值的O(n)复杂度降低为O(1)复杂度,改进代码如下:
# 使用矩阵计算文档间夹角余弦值 def matrix_analysis(self,text): # 存储相似度 sim = [] # 抽取向量 v_text = np.array(self.bow2Vector(text)) v_texts = np.array([self.bow2Vector(item) for item in self.corpus]) # 计算点积 dot = np.dot(v_texts,v_text) # 计算v_text和v_texts内向量的模长 v_text_norm = np.linalg.norm(v_text) v_texts_norm = np.array([ np.linalg.norm(v) for v in v_texts ]) # 计算文档间夹角余弦值 sim = np.divide(dot,v_texts_norm*v_text_norm) return sim
虽然算法在抽取向量时还是O(n)复杂度,但主要的余弦值运算通过矩阵运算来实现,降低了O(n)复杂度。
改进后的程序运行结果如下图所示:

从输出结果可以看出,在相同测试数据的情况下,总运算效率提高了近6倍。转换词袋数据运算效率提高了近16倍,相似性分析提高了1.6倍,文档数量越多,相似性分析运算效率提高越多。