天天看点

Python | 一次代码优化的经历1 背景2 思路3 具体做法4 合并为一个函数

Python | 一次代码优化的经历

  • 1 背景
  • 2 思路
    • 2.1 思路1
    • 2.2 思路2
  • 3 具体做法
  • 4 合并为一个函数

1 背景

小编最近在做知识图谱表示学习相关的一个项目,而在结果整理过程中,遇到了一个问题,并自主解决,现通过博客记录一下思考的过程。

现在通过知识图谱的表示学习得到了如下结果:即每个字段和对应的向量表示。

df_fie_vec
           
字段序号 字段编号 字段向量表示
1024 A1-1 [0.19721896946430206, -0.0352601520717144, 0.2...
1 780 A1-2 [0.29541513323783875, -0.4418157935142517, -0....
2 1707 A1-3 [-0.3145236074924469, 0.49423694610595703, 0.0...
3 638 A1-4 [0.2523365914821625, -0.34962689876556396, 0.3...
4 488 A1-5 [0.11142489314079285, -0.1334211230278015, 0.0...
... ... ... ...
1514 342 xm_cq [0.4867716431617737, -0.11022625863552094, 0.2...
1515 1545 xm_rz [-0.26083433628082275, -0.3224470615386963, 0....
1516 1939 xm_xs [0.12439519912004471, -0.32213300466537476, -0...
1517 205 xm_xb [0.4061014950275421, -0.26851871609687805, -0....
1518 143 xm_zk [0.26478642225265503, 0.14694373309612274, 0.3...

1519 rows × 3 columns

而现在的任务是返回每个字段最相似的Top5的字段以及相似性得分,而【相似性得分】可以通过【余弦相似度】进行度量。

2 思路

2.1 思路1

思路1:进行双重for循环,外层循环是针对每一个字段,里层循环是计算该字段和所有剩余字段的相似性得分,然后每一个字段会对应一个1519行的小数据框,再按照得分降序排列取前五即可。

问题:效率较低,1519个字段的Top5相似需要4分钟左右才能跑完,主要原因是复杂度为O(N^2)

2.2 思路2

由于思路1效率较低,笔者就尝试去优化。一方面是要降低复杂度,争取降为O(N)的复杂度;另一方面则是能否避免重复计算?因为思路1每次都要考虑一个字段和其余所有字段的相似度得分,产生了大量重复的工作,其实字段两两之间的关联一开始就可以全部计算出来!

顺着这个思路下去,我们需要完成的任务大概有如下两点:

  • 如何得到Dataframe两两行之间的相似度得分矩阵?
  • 如何根据得分矩阵取出Top5以及对应的分值、变量名?

3 具体做法

先看第一个任务:如何得到Dataframe两两行之间的相似度得分矩阵?

通过查阅资料发现,sklearn中有一个方法可以直接用,即cosine_similarity,但是这个是需要数据框每行每列的值都是向量的一个元素,示例如下:

import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

df = pd.DataFrame(np.random.randint(0, 2, (3, 5)))
df
           
1 2 3 4
1 1 1 1
1 1
2 1 1
array([[1.        , 0.5       , 0.70710678],
       [0.5       , 1.        , 0.70710678],
       [0.70710678, 0.70710678, 1.        ]])
           

那接下来的问题就变为了如何将上述 df_fie_vec 的【字段向量表示】列拆分为32列呢?每列一个元素

一行代码就可以解决:

df_split = pd.DataFrame(df_fie_vec['字段向量表示'].values.tolist())
df_split.head()
           
1 2 3 4 5 6 7 8 9 ... 22 23 24 25 26 27 28 29 30 31
0.197219 -0.035260 0.234267 -0.238597 0.729414 -0.043984 0.545444 -0.242304 -0.382071 -0.066994 ... -0.135378 0.387781 0.129993 0.545840 0.192078 0.230260 -0.384609 -0.559378 -0.017961 -0.332608
1 0.295415 -0.441816 -0.124853 0.442124 0.129252 0.261906 0.397394 0.013566 -0.500405 -0.102849 ... -0.195918 0.082014 -0.040048 -0.406025 -0.235162 -0.222131 -0.256986 0.284030 0.150090 -0.140470
2 -0.314524 0.494237 0.049765 -0.044412 -0.346529 0.817963 -0.013090 0.516052 0.269054 0.364272 ... -0.298540 0.178190 -0.292035 0.245295 0.805189 -0.877499 0.279846 0.435199 0.354399 0.790817
3 0.252337 -0.349627 0.384134 0.025022 0.063544 -0.410063 0.079155 0.121017 -0.304140 -0.298541 ... 0.215877 0.208961 0.271162 -0.425740 -0.500683 0.101276 -0.366163 0.092470 0.182867 0.180764
4 0.111425 -0.133421 0.052257 0.169252 -0.126779 0.185896 0.104148 0.351276 -0.158014 -0.287047 ... -0.274204 -0.099365 0.147621 -0.323882 -0.359847 -0.064740 -0.199751 -0.449062 0.107182 0.177525

5 rows × 32 columns

df_split_cos = pd.DataFrame(cosine_similarity(df_split))
df_split_cos.head()
           
1 2 3 4 5 6 7 8 9 ... 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518
1.000000 -0.043288 0.006152 -0.059217 -0.172403 0.162926 -0.201611 -0.055419 0.060050 -0.097351 ... 0.101868 0.069204 0.875012 -0.208005 -0.144403 -0.000191 -0.106282 -0.003221 -0.034875 -0.057523
1 -0.043288 1.000000 -0.162092 0.269236 0.411168 0.058326 0.184088 0.996663 0.238428 0.453066 ... 0.191536 0.153067 -0.052635 0.301993 0.374350 0.240888 0.432053 0.153914 0.258447 -0.080208
2 0.006152 -0.162092 1.000000 -0.379690 -0.189085 -0.095867 -0.180086 -0.152678 -0.147555 -0.215645 ... -0.465662 0.058269 -0.166440 -0.112754 -0.287545 -0.180860 -0.052068 -0.062936 -0.118736 0.382595
3 -0.059217 0.269236 -0.379690 1.000000 0.332726 0.255188 0.569198 0.281728 0.073957 0.210326 ... 0.440469 -0.035754 0.015980 0.192179 0.415964 0.214828 0.451826 0.162283 0.278713 -0.347243
4 -0.172403 0.411168 -0.189085 0.332726 1.000000 0.200153 0.479426 0.419613 0.398513 0.303516 ... 0.175635 -0.246281 -0.122275 0.371652 0.300309 0.353297 0.417676 0.321846 0.338909 -0.167168

5 rows × 1519 columns

再看任务2:如何根据得分矩阵取出Top5以及对应的分值、变量名?

进一步细拆,主要有两步:

  • Step1:返回 df_split_cos 每一行最大的六个值对应的列名(排除自身对自身的1)
  • Step2:根据列名返回对应的变量名列表和相似性得分列表

其中Step1可以采用argsort函数,而Step2可以采用列表表达式

# 每一行按照降序排列
arr = np.argsort(-df_split_cos.values, axis=1)
arr = arr[:,0:6] # 要去掉自己!
arr
           
array([[   0,  329,  426,  458,  612, 1511],
       [   1,  898,   53, 1005,  420, 1096],
       [   2,  769,  545, 1069,  316,  921],
       ...,
       [1516,  299,  294,  830,   81,   20],
       [1517,  796, 1451,  321,  403,  801],
       [1518, 1489,  140,  335, 1506, 1298]])
           
# 对应的相似字段名
sim_sh = [df_fie_vec['字段编号'][x] for x in arr[0][1:]]
sim_sh
           
['A30-12', 'A35-24', 'A37-16', 'A43-24', 'xm_zz']
           
# 对应的关联得分
score = [df_split_cos[0][x] for x in arr[0][1:]]
score
           
[0.9446235884373899,
 0.9046447042254955,
 0.9011919167484993,
 0.8753941173269864,
 0.8750120235830159]
           

4 合并为一个函数

上面具体做法实现了整个的过程,接下来的任务就是合并成函数,对数据框df_fie_vec进行批量操作了~

def get_sim_top5_simple(df_sh_vec, var1, var2):
    '''
    作用:返回每个审核点/字段最相似的top5 并组装为df 返回
    参数:
    - df_sh_vec: 数据框
    - var1: 审核点/字段 编号
    - var2: 审核点/字段 向量表示
    
    '''
    n = len(df_sh_vec)
    res_all = []
    
    # 对向量进行拆分为32列
    df_split = pd.DataFrame(df_sh_vec['字段向量表示'].values.tolist())
    # 得到矩阵相似度结果
    df_split_cos = pd.DataFrame(cosine_similarity(df_split))
    # 得到df_split_cos每一行按照降序排列前6 
    arr = np.argsort(-df_split_cos.values, axis=1)
    arr = arr[:,0:6] # 要去掉自己!    
    
    for i in range(n):
        # 遍历每一个字段
        
        # 对应的相似字段名
        sim_sh = [df_sh_vec['字段编号'][x] for x in arr[i][1:]]
        
        # 对应的关联得分
        score = [df_split_cos[i][x] for x in arr[i][1:]]
        
        # 构建数据框
        df = pd.DataFrame({'相似'+var1: sim_sh, '相似性得分': score})
        df[var1] = df_sh_vec[var1][i] # 添加线下行业
        # 保存到全局结果
        res_all.append(df)

    # 结果concat
    df_final = pd.concat(res_all, axis = 0)
    df_final = df_final.reset_index(drop=True)
    return df_final
           
df_final_fie = get_sim_top5_simple(df_fie_vec, var1='字段编号', var2='字段向量表示')
df_final_fie.head()
           
相似字段编号 相似性得分 字段编号
A30-12 0.944624 A1-1
1 A35-24 0.904645 A1-1
2 A37-16 0.901192 A1-1
3 A43-24 0.875394 A1-1
4 xm_zz 0.875012 A1-1

大功告成~!运行时间只需要不到3秒,效率比之前提高的不是一点半点!完美!