分类技术——二分网络上的链路预测
参考资料1 参考资料2
实验内容
采用二分网络模型,对ml-1m文件夹中的“用户—电影”打分数据进行建模,考虑将用户信息、电影详细信息、以及打分分值作为该网络上的边、点的权重;
根据网络结构特征给出节点相似性度量指标;
基于相似性在二分网络上进行链路预测;
画出ROC曲线来度量预测方法的准确性 ;
详细实现
-
导入数据包.py import pandas as pd
'''
该文件是将数据包中的ratings.dat导入到python中;
导入后在python中的保存形式:<class 'pandas.core.frame.DataFrame'>;
该文件仅对ratings.dat文件做了数据导入处理,因为后面仅用了这一个文件的信息(电影打分相似度)来推荐电影;
如果想要得到更精确的结果,可以导入其他两个文件,计算出不同的相似度矩阵(例如:性别相似度,电影类别相似度等);
'''
ratings_path = './数据包/ratings.dat'
ratings_names = ["UserID", "MovieID", "Rating", "Timestamp"]
all_ratings = pd.read_table(ratings_path, sep='::', engine='python', names=ratings_names)
-
数据处理.py import numpy as np
from 实验一电影推荐 import 导入数据包 as sj
'''
该文件实现功能;
对导入的数据进行处理,最终生成权重矩阵W;
矩阵W:行代表用户i,列代表电影j,值代表用户i给电影j的打分分值转换的权重;
'''
mask = np.random.rand(len(sj.all_ratings)) < .9
train_ratings = sj.all_ratings[mask]
test_ratings = sj.all_ratings[~mask]
'''
一共有多少部电影和多少个用户;
all_ratings中存有有效的(评过分的)UserID和MovieID
但是all_ratings中包含是重复的ID,若使用需要先用unique()去重后再len();
'''
r_users = sj.all_ratings["UserID"].unique()
r_movies = sj.all_ratings.MovieID.unique()
users_size = len(r_users)
movies_size = len(r_movies)
'''
对用户ID和电影建立索引;
原因:原始数据中用户和电影并不是按顺序存储的(甚至ID都不是按顺序存在的,可能某一处99之后就是101);
k代表索引,uid、mid代表ID,enumerate是建立索引的函数,{}里的写法类似与三元表达式;
'''
uid2idx = {uid: k for k, uid in enumerate(r_users)}
mid2idx = {mid: k for k, mid in enumerate(r_movies)}
'''
用户对电影的评分(score)范围为[1,5],没看过的电影可以认为评分为0,可以将评分转化为权重w;
一种权重转换方法:
w = score / 5;w 范围为[0,1];(AUC = 0.85左右,效果不太理想)
(可能是因为太过于均分权重,五个评分1星的电影相当于一个评分五星的电影,这显然是不合理的)
另一种转换方法:
将w设计为一个score的函数(下凸的)或者用一个列表来存储score和w的转化;
建立W矩阵,行为用户i,列为电影j,值为权重w;
'''
movies_w = [0, 0.000001, 0.0001, 0.01, 0.1, 1]
W = np.zeros((users_size, movies_size))
for _, rating in train_ratings.iterrows():
W[uid2idx[rating.UserID], mid2idx[rating.MovieID]] = movies_w[rating.Rating]
'''
改变score对应的w值,发现评分为4星的权重越低,AUC反而越好(AUC最大和最小差距在0.05左右);
从结果来看,似乎代表:一个电影被评4星,代表用户就喜欢类似的电影程度不高;一个电影被评5星,才能较大程度说明用户的喜好。
但是按照常理来说,4星的评分已经相对较高了,应该可以很大程度代表用户的喜好了。
这里面的原因不太了解。
'''
-
相似度设定.py import numpy as np
from 实验一电影推荐 import 数据处理 as sjcl
'''
相似度矩阵是预测的关键,可以从多个维度建立相似度矩阵;
Sim_1是从评分的角度建立的相似度关系,也就是电影的评分相似度;
用矩阵乘法根据推导出的Sim_1的公式进行计算;
我们还可以尝试在性别、电影类别等角度建立相似度矩阵(多个二部图),最后将所有的相似度矩阵分别乘以系数相加,得到总体的预测率更高的相似度矩阵;
'''
W = sjcl.W
movieJ_w = W.sum(axis=0)
userI_w = W.sum(axis=1)
Sim_1 = np.zeros((sjcl.movies_size, sjcl.movies_size))
W1 = W / userI_w.reshape((-1, 1))
W1[np.isnan(W1)] = 0
Sim_1 = np.dot(W1.T, W)
Sim_1 = Sim_1 / movieJ_w
Sim_1[np.isnan(Sim_1)] = 0
-
训练和测试.py import numpy as np
from 实验一电影推荐 import 数据处理 as sjcl, 相似度设定 as xsd
'''
该文件功能:
通过训练集生成"电影排序矩阵";
生成测试集;
电影排序矩阵:行代表用户i,列代表电影j,值代表在用户i眼里电影j在所有电影里的排名;
'''
u_size = sjcl.users_size
m_size = sjcl.movies_size
Sim = xsd.Sim_1
W = xsd.W
'''
argsort()例子:
F = [[0.1, 1, 1], [1, 0.2, 1]]
F_sort_index = [[0 1 2], [1 0 2]]
F_sort = [[3. 2. 1.], [2. 3. 1.]]
'''
F = np.dot(Sim, W.T).T
F_sort_index = np.argsort(F, axis=1)
F_sort = np.zeros((u_size, m_size))
for i in range(u_size):
for j in range(m_size):
F_sort[i, F_sort_index[i][j]] = m_size - j
'''
测试集中的评分我们转换为一个权重数组movies_w,数组里的值代表我们有多大程度希望推荐该星级的电影;
例如:movies_w[1],movies_w[2],movies_w[3]都设置为0,代表我们不希望推荐和1到3星的类似的电影;
movies_w[4]=0.2,movie_w[5]=1,代表我们很希望推荐5星的电影;
同时对推荐4星电影的期望程度只有5星的五分之一;
如果可推荐的电影足够多的话,给用户推荐类似于5星显然更优;
'''
movies_w = [0, 0, 0, 0, 0, 1]
Test = np.zeros((u_size, m_size))
for _, rating in sjcl.test_ratings.iterrows():
Test[sjcl.uid2idx[rating.UserID], sjcl.mid2idx[rating.MovieID]] = movies_w[rating.Rating]
-
效果验证.py from 实验一电影推荐 import 训练和测试 as tfianl
import numpy as np
import matplotlib.pyplot as plt
'''
输出ROC曲线;
输出AUC值;
'''
Test = tfianl.Test
F = tfianl.F_sort
m_size = tfianl.m_size
TPR, FPR = [], []
T_all = np.sum(Test)
F_all = np.sum(Test == False)
for threshold in np.arange(0, 1, 0.005):
F_out = F < (m_size * threshold)
TP = np.sum(Test * F_out)
FP = np.sum((1 - Test) * F_out)
TPR.append(TP / T_all)
FPR.append(FP / F_all)
plt.plot(FPR, TPR)
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.show()
AUC = np.sum([0.005 * tpr for tpr in TPR])
print(AUC)
实验结果
(数据量为100万条左右的情况下,大概跑一次代码十分钟左右)
-
AUC输出值:
- 红色部分只是一个警告,因为在数组运算时出现了0/0=NaN的情况,可以忽略;
-
ROC曲线:
|