1 背景
小编最近在做知识图谱表示学习相关的一个项目,而在结果整理过程中,遇到了一个问题,并自主解决,现通过博客记录一下思考的过程。
现在通过知识图谱的表示学习得到了如下结果:即每个字段和对应的向量表示。
df_fie_vec
字段序号 | 字段编号 | 字段向量表示 | |
---|---|---|---|
0 | 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
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | 1 | 1 | 1 | 1 |
1 | 0 | 1 | 0 | 0 | 0 |
2 | 0 | 1 | 1 | 0 | 0 |
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()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 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()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 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()
相似字段编号 | 相似性得分 | 字段编号 | |
---|---|---|---|
0 | 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秒,效率比之前提高的不是一点半点!完美!