Python | 一次代码优化的经历

1 背景

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

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

df_fie_vec
字段序号字段编号字段向量表示
01024A1-1[0.19721896946430206, -0.0352601520717144, 0.2...
1780A1-2[0.29541513323783875, -0.4418157935142517, -0....
21707A1-3[-0.3145236074924469, 0.49423694610595703, 0.0...
3638A1-4[0.2523365914821625, -0.34962689876556396, 0.3...
4488A1-5[0.11142489314079285, -0.1334211230278015, 0.0...
............
1514342xm_cq[0.4867716431617737, -0.11022625863552094, 0.2...
15151545xm_rz[-0.26083433628082275, -0.3224470615386963, 0....
15161939xm_xs[0.12439519912004471, -0.32213300466537476, -0...
1517205xm_xb[0.4061014950275421, -0.26851871609687805, -0....
1518143xm_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
01234
001111
101000
201100
cosine_similarity(df)
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()
0123456789...22232425262728293031
00.197219-0.0352600.234267-0.2385970.729414-0.0439840.545444-0.242304-0.382071-0.066994...-0.1353780.3877810.1299930.5458400.1920780.230260-0.384609-0.559378-0.017961-0.332608
10.295415-0.441816-0.1248530.4421240.1292520.2619060.3973940.013566-0.500405-0.102849...-0.1959180.082014-0.040048-0.406025-0.235162-0.222131-0.2569860.2840300.150090-0.140470
2-0.3145240.4942370.049765-0.044412-0.3465290.817963-0.0130900.5160520.2690540.364272...-0.2985400.178190-0.2920350.2452950.805189-0.8774990.2798460.4351990.3543990.790817
30.252337-0.3496270.3841340.0250220.063544-0.4100630.0791550.121017-0.304140-0.298541...0.2158770.2089610.271162-0.425740-0.5006830.101276-0.3661630.0924700.1828670.180764
40.111425-0.1334210.0522570.169252-0.1267790.1858960.1041480.351276-0.158014-0.287047...-0.274204-0.0993650.147621-0.323882-0.359847-0.064740-0.199751-0.4490620.1071820.177525

5 rows × 32 columns

df_split_cos = pd.DataFrame(cosine_similarity(df_split))
df_split_cos.head()
0123456789...1509151015111512151315141515151615171518
01.000000-0.0432880.006152-0.059217-0.1724030.162926-0.201611-0.0554190.060050-0.097351...0.1018680.0692040.875012-0.208005-0.144403-0.000191-0.106282-0.003221-0.034875-0.057523
1-0.0432881.000000-0.1620920.2692360.4111680.0583260.1840880.9966630.2384280.453066...0.1915360.153067-0.0526350.3019930.3743500.2408880.4320530.1539140.258447-0.080208
20.006152-0.1620921.000000-0.379690-0.189085-0.095867-0.180086-0.152678-0.147555-0.215645...-0.4656620.058269-0.166440-0.112754-0.287545-0.180860-0.052068-0.062936-0.1187360.382595
3-0.0592170.269236-0.3796901.0000000.3327260.2551880.5691980.2817280.0739570.210326...0.440469-0.0357540.0159800.1921790.4159640.2148280.4518260.1622830.278713-0.347243
4-0.1724030.411168-0.1890850.3327261.0000000.2001530.4794260.4196130.3985130.303516...0.175635-0.246281-0.1222750.3716520.3003090.3532970.4176760.3218460.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()
相似字段编号相似性得分字段编号
0A30-120.944624A1-1
1A35-240.904645A1-1
2A37-160.901192A1-1
3A43-240.875394A1-1
4xm_zz0.875012A1-1

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

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值