分類技術---二分網絡上的鏈路預測
一、實驗內容
- 采用二分網絡模型,對ml-1m文件夾中的“用戶---電影”打分數據進行建模,考慮將用戶信息、電影詳細信息、以及打分分值作為該網絡上的邊、點的權重;
- 根據網絡結構特征給出節點相似性度量指標;
- 基於相似性在二分網絡上進行鏈路預測;
- 采用交叉驗證的方法驗證預測結果;
- 畫出ROC曲線來度量預測方法的准確性。
二、問題分析及模型設計
-
數據集的電影序號是一個大范圍的稀疏序列,考慮將電影序號和用戶序號分別存入特定數組,用數組下標唯一對應電影序號和用戶序號,並以數組長度為依據,建立二分網絡模型
-
將數據分為訓練集90%與測試集10%
-
利用訓練集計算每個用戶看過多少電影和每個電影被多少用戶看過,結果分別放入兩個數組
-
利用訓練集矩陣和分別存放每個用戶看過電影數量和存放每個電影看過的用戶數量
的數組,按照公式:
\[w_{ij}=\frac{1}{k_j}\sum_{l=1}^m\frac{a_{il}a_{jl}}{k_l} \]計算出對應的資源配置矩陣W
在上面公式中,電影 j 與電影 i 的興趣關聯度𝑤𝑖𝑗顯然應該與k𝑗和 𝑘𝑙呈反比。對於k𝑗值,當電影 j 被很多人評價過時,那么這可能就是 個泛化的熱門產品,某用戶選擇了電影 j 並不意味着他有強烈的選擇 電影 i 的傾向。同理,對於𝑘𝑙值,用戶 l 看了非常多的電影,那么他選 擇了產品 j,也並不意味着他對產品 j 非常的專注,相應的推薦度也就不會很高。
-
目標用戶的資源分配矢量f。初始時,將他選擇過的電影對應項資源設置為1,其他為0,得到初始n維0/1向量。按一下公式計算資源分配矢量:
\[f^{'}=Wf \]並進行數組排序
f′矩陣中第 m 列為對用戶 m 的電影推薦向量,排序后把得分最高 的一部分電影對應推薦給用戶 m,即完成了電影推薦工作。
-
遍歷測試集,對給定的用戶 i,假設其有𝐿𝑖個產品是未選擇的,如果在測 試集中用戶 i 選擇的電影 j,而電影 j 依據向量f′被排在第𝑅𝑖𝑗位,則計 算其相對位置:
\[r_{ij}=\frac{R_{ij}}{L_i} \] -
均勻選出 1—0.3 之間的 50 個數作為閾值,計算每個閾值對應的 TP(在推薦集中,用戶評分大於 3), FP(在推薦集中,但用戶評分小 於等於 3), FN(不在推薦集中,但用戶評分大於 3), TN(不在推薦 集中,用戶評分小於等於 3)。據此計算出真陽性率和假陽性率,並繪 制出 ROC 曲線
三、具體實現
1.數據導入
將data中的ratings.dat數據利用pandas的read_csv函數導入
import pandas as pd
import numpy as np
filename = "datamining\\movie\\data\\ratings.dat"
# rating.dat文件路徑,根據本地修改↑
all_ratings = pd.read_csv(filename, header=None, sep="::", names=["UserID", "MovieID", "Rating", "Datetime"], engine="python")
2.數據處理
利用隨機數給已讀入的數據添加“class”屬性,數值為1-9,方便對於數據的測試集和訓練集的分類操作,同時添加Favorite屬性,值為“True”或“False”,用於標記用戶是否喜歡電影(分數>3記為喜歡)
# 2、對數據進行預處理
all_ratings["Favorable"] = all_ratings["Rating"] > 3.0
# 將分數>3視為用戶喜歡此電影
# 3、隨機將數據分為訓練集與測試集
randum = np.random.rand(len(all_ratings), 1)
for i in range(len(randum)):
randum[i] = int(randum[i]*10)
all_ratings["class"] = randum
# 4、導入函數、數據,預處理數據。
all_ratings.to_csv('classified_ratings.csv')
MovieID_set = set(all_ratings["MovieID"])
MovieID_tuple = tuple(MovieID_set)
UserID_set = set(all_ratings["UserID"])
UserID_tuple = tuple(UserID_set)
M = len(UserID_set) # 用戶數量
N = len(MovieID_set) # 電影數量
values = range(N)
MovieID_dict = dict(zip(MovieID_tuple, values)) # 把MovieID從小到大逐一映射
all_ratings = pd.read_csv('classified_ratings.csv',
usecols=[1, 2, 5, 6])
預處理完畢后的數據保存在“classified_ratings.csv”中,預處理之后的格式為
3.計算資源配置矩陣W 資源分配矢量**F ** 以及 TP(真陽性率) FP(假陽性率)
計算矩陣F
# 計算資源分配矢量F
def computeF():
F = []
for x in range(M):
index = UserID_tuple[x] # index = Uid
reviews_set = set(reviews_by_users[index]) # 用戶看過的所有電影mid集合
reviews_set_modified = set()
for t in reviews_set:
reviews_set_modified.add(MovieID_dict[t]) # 用戶看過的電影對應的索引集合
for y in range(N):
if y in reviews_set_modified:
F.append(1)
else:
F.append(0)
F = np.array(F).reshape((M, N)).transpose()
return F
計算矩陣W
矩陣 W 為一個 n*n 維的矩陣,在數據集中電影數 約有 10,000 個,可以看出矩陣 W 規模非常大,若是存儲為一個普通 的矩陣很困難。但是可以看出它其實是一個稀疏矩陣,即其中有很多 元素是 0,所以我利用了稀疏矩陣的數據結構,把 W 矩陣化成稀疏矩陣的形式來存儲。
而由分析易知,當用戶 l 既給電影 i 評過分也給電影 j 評過分時,
求和符號內部分子為 1,故參與求和的數化為:
減少計算量,簡化計算過程
row = []
col = []
data = []
keys_list = favor_reviews_by_users.keys()
# 計算W矩陣
for l in range(M): # print "enter outer iteration %d" % l
index = UserID_tuple[l]
# print l
if index in keys_list:
# movie_favored,臨時tuple變量,用來存儲用戶所喜歡的電影, leng 存儲用戶喜歡電影的個數
movie_favored = tuple(favor_reviews_by_users[index])
leng = len(movie_favored) # 訓練集中用戶index=UserID_tuple評分大於3的電影
# ; print "movie_favored number:%d" %leng
kl = len(reviews_by_users[index]) #該用戶總評價的電影數量
for x in range(leng):
# x位置的電影ID,j 為該電影在對照表中的位置
mid = movie_favored[x]
j = MovieID_dict[mid]
kj = len(movie_reviewed[mid]) # 訓練集中電影j被評價的總次數
for y in np.arange(x+1, leng):
i = MovieID_dict[movie_favored[y]] # y位置對應電影的ID,去檢索,得到該ID對應的索引
ki = len(movie_reviewed[movie_favored[y]])
row.append(i)
col.append(j)
data.append(1.0/kl/kj)
# 行列反轉,順勢計算對稱位置上的W[i][j]
row.append(j)
col.append(i)
data.append(1.0/kl/ki)
row = np.array(row)
col = np.array(col)
data = np.array(data)
W = sparse.coo_matrix((data, (row, col)), shape=(N, N), dtype=float) # 使用稀疏矩陣對W進行存儲
print("W矩陣:\n", W)
計算F‘及其修正(代碼中記為F2)
利用矩陣W與矩陣F的點乘法計算F2,同時考慮推薦算法不推薦用戶已看過的電影,所以進一步處理將用戶已看過的並評分的項置0
# 計算F矩陣, shape=(N,M), 存放用戶看過那些電影
F = computeF()
print("F:\n", F)
F2 = W.dot(F)
print("F2矩陣為:\n", F2)
# 修正F2矩陣,歸零那些用戶已經看過的電影
for i in range(M):
slic = F[:, i]
for j in range(N):
if slic[j] == 1:
F2[j][i] = 0
print("F2 have modified")
print(F2)
計算R矩陣並計算r的均值
def calculate_r():
global sort_result
global test_by_users
global M
global N
print("enter")
sum = 0.0
for i in range(M):
index = UserID_tuple[i] # uid
if ((index in favor_in_test_by_users) == False): # 這里的 == 不能改成 is ,下面的也一樣,pandas的問題會報錯
# print "uid %d " % index
continue
mv_set = favor_in_test_by_users[index]
slic_dict = dict(zip(sort_result[:, i], range(N)))
for m in mv_set:
mindex = MovieID_dict[m]
sum += slic_dict[mindex]+1
sum = float(sum) / (N - len(reviews_by_users[index]))
# print "sum=%f, count=%d" % (sum, i)
return sum
# 計算r
R = []
r = calculate_r()
R.append(r)
print("r: %f" % r)
繪制ROC曲線(計算FPR TPR)
選取 1—0.5 之間的 50 個數作為閾值,統計測試集里推薦給 用戶的電影集。對某條測試集中的“用戶—電影”數據,若用戶 i不在 測試集評分大於 3 的集合中,電影 mem 在推薦集中,則 FP+1;電影 mem 不在推薦集中,則 TN+1。若用戶 i 在測試集評分大於 3 的集合 中,電影 mem在推薦集中且評分大於 3,則 TP+1;電影 mem 在推薦 集中但評分小於等於 3,則 FP+1;電影 mem 不在推薦集中但評分大 於 3,則 FN+1;電影 mem 不在推薦集中且評分小於等於 3,則 TN+1。 計算出相應的 TPR 和 FPR,利用 matplotlib 中的函數來繪制 ROC 曲線,度量該方法預測的准確性
# 繪制ROC曲線所需相關參數
# 計算TP、FP
TPR = []
FPR = []
area = []
TP = 0 # 在推薦集中,實際也給了好評
FN = 0 # 不在推薦集中,實際卻給了好評
FP = 0 # 在推薦集中,實際給了差評
TN = 0 # 不在推薦集中,實際給了差評
# 為每個用戶選取推薦一定比例的電影的電影
recommend_rate = np.linspace(0.001, 0.5, 50) #設置閾值在1-0.5之間的50個值
for rate in recommend_rate:
recommend_dict = recommend(rate)
for uid, reviews in test_by_users.items():
recommend_movies = recommend_dict[uid]
if ((uid in favor_in_test_by_users) == False):
# print "uid = %d" % uid
for m in reviews:
if m in recommend_movies:
FP += 1
else:
TN += 1
continue
for m in reviews:
if m in recommend_movies and m in favor_in_test_by_users[uid]:
TP = TP + 1
elif m in recommend_movies and m not in favor_in_test_by_users[uid]:
FP = FP + 1
elif m not in recommend_movies and m in favor_in_test_by_users[uid]:
FN = FN + 1
else:
TN = TN + 1
TPR.append(float(TP)/(TP+FN))
FPR.append(float(FP)/(FP+TN))
area.append(float(TP)/(TP+FN) * (1-float(FP)/(FP+TN)))
best_index = int(np.argmax(np.array(area)))
# 畫圖
plt.figure(1)
plt.plot(FPR, TPR, 'bx', linewidth=20)
plt.plot(np.linspace(0, 1, 200), np.linspace(0, 1, 200), 'r--', )
plt.xlabel('FPR rate')
plt.ylabel("TPR rate")
plt.scatter(FPR, TPR, linewidths=3, s=3, c='b')
plt.legend(("points with changing recommend rate", "reference curve"))
title = "ROC Curve"
plt.title(title)
f1 = plt.gcf()
plt.show()
四、實驗結果
W矩陣
F矩陣&&F2矩陣
r值
本次r值為: 0.845882
*由於每次的訓練與測試集不一樣所以 r 值不唯一
ROC_Curve
五、實驗心得
通過本次實驗我更好的學習了數據挖掘的基礎知識,同時實踐了python中numpy庫與pandas庫中的各種該函數,提高了自己的動手能力與實踐能力,更好的了解了數據挖掘知識的應用領域與方法,在實驗過程中一開始沒有考慮到本實驗較大的數據量,直接采用矩陣存儲會爆炸,經過學習改用稀疏矩陣存儲,同時在數據處理和讀入的過程中經常導致由於算力不足造成各種問題,給debug工作也造成了一定的困難,同時我的二分類模型並不是十分優秀,ROC曲線顯然性能非常一般,希望在今后的學習中可以更好的提高自己