Logo

郎哥编程

改进相似性分析算法,提高运行效率

2022-03-03 317

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

03.png

通过上图可知,分析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)复杂度。

改进后的程序运行结果如下图所示:

04.png

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


代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论