天天看點

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秒,效率比之前提高的不是一點半點!完美!