在推荐系统中,经常会计算用户—用户相似度、用户—产品相似度,而两类业务中都会涉及到很大量级的数据,导致最终的相似度矩阵计算规模更大。
本文旨在解决高效计算大规模数据的余弦相似度计算问题。
#导入 tensorflow 模块,因部分用到1.x版本的Tensorflow,因此用如下方式导入
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
import numpy as np
import pandas as pd
import math
from datetime import datetime
# 定义余弦相似度函数,便于后续检验结果
def cos_sim(a, b):
a_norm = np.linalg.norm(a)
b_norm = np.linalg.norm(b)
s = a_norm * b_norm
if s==0:
cos = 0
else:
cos = np.dot(a,b)/s
return cos
#设定余弦相似度矩阵按多少行进行拆分,40GB内存,6GB显存的机器大概能计算31000*31000的余弦相似矩阵,
#因此大多数分析场景均需要对数据源矩阵进行拆分,实际数值根据机器所承受的最大数值进行设定
BRANCH_C = 10000
#导入处理好的新老用户特征数据
csv_New = pd.read_csv('./df_1_unknown_group.txt', header=None) #新用户的特征数据集
csv_Old = pd.read_csv('./df_2_ydy_group.txt', header=None) #老用户的特征数据集
# 随机抽取新用户与老用户各20000条数据
csv_New = csv_New.sample(n=20000)
csv_New.index = range(20000)
csv_Old = csv_Old.sample(n=20000)
csv_Old.index = range(20000)
csv_New
csv_Old
#计算矩阵拆分后需要运算的次数
RunTimes = math.ceil(len(csv_Old)/BRANCH_C )
#初始化缓存结果的矩阵
csv_Result = np.empty(shape=[len(csv_New), 0])
# 开始循环处理
for i in range(RunTimes):
# 将新用户矩阵与截取的已订购用户的特征矩阵进行拼接
Branch_M = csv_Old.iloc[(BRANCH_C * i):(BRANCH_C * (i + 1)), :]
csv_Train = np.concatenate((csv_New, Branch_M), axis=0)
csv_Train_M, csv_Train_N = csv_Train.shape
with tf.Session() as sess:
# 拼接处理后的矩阵长与宽
M = csv_Train_M
N = csv_Train_N
# tensorflow初始化输入矩阵
input = tf.placeholder(tf.float32, shape=(M, N))
normalized = tf.nn.l2_normalize(input, dim = 1)
# 矩阵*自己的转置
prod = tf.matmul(normalized, normalized, adjoint_b=True)
dist = 1 - prod
print(f"({datetime.now()}) Use tensorflow to resolving {i}:")
# 得到该部分矩阵的相似度矩阵
TF_Result = 1 - sess.run(dist, feed_dict={input: csv_Train})
# 删除冗余数据,截取结果矩阵获得新用户与老用户的相似度矩阵
csv_ResultT = TF_Result[0:len(csv_New), len(csv_New):len(TF_Result)]
csv_Result = np.concatenate((csv_Result, csv_ResultT), axis=1)
可以看到,量级为10^4 x 10^4 = 10^8的余弦相似度计算时长约为20秒,相比普通的余弦相似度计算方式,性能提高不少。
# 总览最终相似度矩阵
pd.DataFrame(csv_Result)
# 任意查看第768位置的新用户与第512位置的老用户的余弦相似度
pd.DataFrame(csv_Result).iloc[768, 512]
# 利用定义的余弦相似度函数计算第768位置的新用户与第512位置的老用户的余弦相似度
cos_sim(np.array(csv_New.iloc[768, :]), np.array(csv_Old.iloc[512, :]))