數據挖掘入門系列教程(五)之Apriori算法Python實現
在上一篇博客中,我們介紹了Apriori算法的算法流程,在這一片博客中,主要介紹使用Python實現Apriori算法。數據集來自grouplens中的電影數據,同樣我的GitHub上面也有這個數據集。
推薦下載這個數據集,1MB大小夠了,因為你會發現數據集大了你根本跑不動,Apriori的算法的復雜度實在是😔。
那么,這個我們使用數據集的作用是什么呢?簡單點來說,就是某一個用戶如喜歡看電影,那么他很可能也喜歡看
電影。我們就是需要分析這個關系。
萬物始於加載數據集
加載數據集
因為下載的數據集是一個zip壓縮包,首先,我們需要將數據解壓出來:
import zipfile
zFile = zipfile.ZipFile("ml-latest-small.zip", "r")
#ZipFile.namelist(): 獲取ZIP文檔內所有文件的名稱列表
for fileM in zFile.namelist():
zFile.extract(fileM)
解壓出來的數據如下圖:
主要介紹兩個文件
- ratings.csv 每個用戶對於電影的評分,包括movieId,userId,rating,time
- tags.csv 是電影的標簽
我們目前只是使用rating.csv
。然后我們將csv文件加載到內存中。
import pandas as pd
all_ratings = pd.read_csv("ml-latest-small/ratings.csv")
# 格式化時間,但是沒什么必要
all_ratings["timestamp"] = pd.to_datetime(all_ratings['timestamp'],unit='s')
讓我們看一看數據長什么樣?
電影中的數據就是👆這副B樣
- userId :評分人的ID
- movieId:電影的ID
- rating:評分分數
- tiemstamp:評分時間
讓我們來左手畫個圖,看一下rating數據的分布:
from eplot import eplot
df = all_ratings["rating"].value_counts()
df.eplot.bar(title='柱形圖')
柱狀圖如下圖:
加載完數據集后。我們需要進行判斷出用戶是否喜歡某個電影,因此我們可以使用評分
來判斷。當用戶對某一個電影的評分大於等於4分的時候,我們就可以認為該用戶喜歡這部電影。
# 評分大於等於4分表示喜歡這個電影
all_ratings["like"] = all_ratings["rating"]>=4
處理后的數據集如下,新的數據集添加了一個like
列:
like
為True代表喜歡,False為不喜歡。
獲得訓練集
在這里我們選擇userId
小於200的數據。
train_num = 200
# 訓練數據
train_ratings = all_ratings[all_ratings['userId'].isin(range(train_num))]
數據格式如下:
為什么只選擇userId
小於200
的數據呢?而不大一點呢?emm,你電腦夠好就行,自己看情況選擇。在阿里雲學生機上,推薦200
吧,大了的話,服務直接GG了。
然后我們再從這個數據集中獲得like=True
的數據集。
like_ratings = train_ratings[train_ratings["like"] == True]
然后我們再從訓練集中獲得每一個用戶喜歡哪一些電影,key
對應的是用戶的Id,value
對應的是用戶喜歡的電影。
# 每一個人喜歡哪一些電影
like_by_user = dict((k,frozenset(v.values)) for k,v in like_ratings.groupby("userId")["movieId"])
繼續從訓練集中獲得每一部電影被人喜歡的數量。
# 電影被人喜歡的數量
num_like_of_movie = like_ratings[["movieId", "like"]].groupby("movieId").sum()
此時num_like_of_movie
中like
表示的是電影被人喜歡的數量。
到目前為止我們所有的數據集就都已經准備完成了,接下來就是生成頻繁項。
頻繁項的生成
算法的流程圖的一個例子如下:
首先,我們生成初始頻繁項集,也就是圖中的。
ps:在本文中代表項集中每一項包含元素的個數(比如{A,B}中
,{A,B,C}中
)
下面代碼與上圖不同是我們使用的去除規則不同,規則是如果項集的數量少於min_support
就去除。
# frequent_itemsets是一個字典,key為K項值,value為也為一個字典
frequent_itemsets = {}
min_support = 50
# first step 步驟一:生成初始的頻繁數據集
frequent_itemsets[1] = dict((frozenset((movie_id,)),row["like"])
for movie_id,row in num_like_of_movie.iterrows()
if row["like"] > min_support)
在frequent_itemsets[1]
中間,key
為movie_id
的集合,value
為集合中電影被喜歡的數量。
frequent_itemsets[1]
的數據如下(key = 1代表,value為數量):
接下來我們就可以進行循環操作了,生成等情況。讓我們定義一個方法。
# 步驟②③,
from collections import defaultdict
def find_new_frequent_items(movies_like_by_user,frequent_of_k,min_support):
"""
movies_like_by_user:每一個人喜歡電影的集合,也就是前面的like_by_user
frequent_of_k:超集,也就是前面例子圖中的L1,L2等等
min_support:最小的支持度
"""
counts = defaultdict(int)
# 獲得用戶喜歡的movies的集合
for user,movie_ids in movies_like_by_user.items():
# 遍歷超集中間的數據項
for itemset in frequent_of_k:
# 如數據項在用戶的movie集合中,則代表用戶同時喜歡這幾部電影
if itemset.issubset(movie_ids):
# 遍歷出現在movie集合但是沒有出現在數據項中間的數據
for other_movie in movie_ids - itemset:
# current_superset為數據項和other_movie的並集
current_superset = itemset | frozenset((other_movie,))
counts[current_superset] += 1
# 去除support小於min_support的,返回key為數據項,value為support的集合
return dict([(itemset,support) for itemset,support in counts.items()
if support >= min_support])
這里值得注意的frozenset
這個數據結構,即使里面的結構不同,但是他們是相等的:
然后我們調用函數生成其他的項集。
for k in range(2,5):
current_set = find_new_frequent_items(like_by_user,frequent_itemsets[k-1],min_support)
if len(current_set) ==0:
print("{}項生成的備選項集長度為0,不再進行生成".format(k))
break
else:
print("准備進行{}項生成備選項集".format(k))
frequent_itemsets[k] = current_set
# 刪除第一項(也就是k=1的項)
del frequent_itemsets[1]
此時,我們就已經得到的數據,如下(圖中只截取了
的數據)。
生成規則
在上面我們獲得了項集,接下來我們來進行構建規則。
以下圖中{50,593,2571}為例子
我們可以生成以下的規則:
其中前面一部分(綠色部分)表示用戶喜歡看的電影,后面一部分表示如果用戶喜歡看綠色部分的電影也會喜歡看紅色部分的電影。可以生成項規則
生成規則的代碼如下:
# 生成規則
rules = []
for k,item_counts in frequent_itemsets.items():
# k代表項數,item_counts代表里面的項
for item_set in item_counts.keys():
for item in item_set:
premise = item_set - set((item,))
rules.append((premise,item))
獲得support
支持度挺好求的(實際上再上面已經得到support了),簡單點來說就是在訓練集中驗證規則是否應驗。比如說有{A,B},C
規則,如果在訓練集中某一條數據出現了A,B
也出現了C
則代表規則應驗,如果沒有出現C
則代表規則沒有應驗。然后我們將規則是否應驗保存下來(應驗表示的是support,但是我們吧沒有應驗的也保存下來,目的是為了后面計算置信度)。
# 得到每一條規則在訓練集中的應驗的次數
# 應驗
right_rule = defaultdict(int)
# 沒有應驗
out_rule = defaultdict(int)
for user,movies in like_by_user.items():
for rule in rules:
# premise,item代表購買了premise就會購買item
premise,item = rule
if premise.issubset(movies):
if item in movies:
right_rule[rule] +=1
else:
out_rule[rule] += 1
right_rule
保存的數據如下:
獲得confidence
我們通過上面的right_rule和out_rule
去求Confidence,在這篇博客中介紹了怎么去求置信度。
然后我們就可以計算出每一條規則的置信度,然后進行從大到小的排序:
# 計算每一條規則的置信度
rule_confidence = {rule:right_rule[rule]/float(right_rule[rule] + out_rule[rule]) for rule in rules}
from operator import itemgetter
# 進行從大到小排序
sort_confidence = sorted(rule_confidence.items(),key=itemgetter(1),reverse = True)
結果如下:
可以很明顯的看到,有很多置信度為1.0
的規則。前面的博客我們介紹了confidence存在一定的不合理性,所以我們需要去求一下Lift
獲得Lift
Lift的具體解釋在前一篇博客。公式如下:
因此我們直接去用去獲得Lift即可。
首先我們需要獲得訓練集中的。
# 計算X在訓練集中出現的次數
item_num = defaultdict(int)
for user,movies in like_by_user.items():
for rule in rules:
# item 代表的就是X
premise,item = rule
if item in movies:
item_num[rule] += 1
# 計算P(X) item_num[rule]代表的就是P(X)
item_num = {k: v/len(like_by_user) for k, v in item_num.items()}
接着繼續計算每一條規則的lift
# 計算每一條規則的Lift
rule_lift = {rule:(right_rule[rule]/(float(right_rule[rule] + out_rule[rule])))/item_num[rule] for rule in rules}
from operator import itemgetter
# 進行排序
sort_lift = sorted(rule_lift.items(),key=itemgetter(1),reverse = True)
結果如下所示:
進行驗證
驗證的數據集我們使用剩下的數據集(也就是),在這里面測試數據集比訓練集大得多:
# 去除訓練使用的數據集得到測試集
ratings_test = all_ratings.drop(train_ratings.index)
# 去除測試集中unlike數據
like_ratings_test = ratings_test[ratings_test["like"]]
user_like_test = dict((k,frozenset(v.values)) for k,v in like_ratings_test.groupby("userId")["movieId"])
然后將規則代入到測試集中,檢驗規則是否符合。
# 應驗的次數
right_rule = 0
# 沒有應驗的次數
out_rule = 0
for movies in user_like_test.values():
if(sort_lift[0][0][0].issubset(movies)):
if(sort_lift[0][0][1] in movies):
right_rule +=1
else:
out_rule +=1
print("{}正確度為:{}".format(i,right_rule/(right_rule+out_rule)))
我們使用lift
最大的一項進行驗證,也就是下圖中被圈出來的部分。sort_lift[0][0][0]
表示的是下圖中紅色框框圈出來的,sort_lift[0][0][1]
表示是由綠色框框圈出來的。
然后我們可以得到結果:
同樣我們可以使用confidence
去驗證,這里就不多做介紹了。同樣,我們可以限定取值,也可以多用幾個規則去驗證選取最好的一個規則。
總結
通過上面的一些步驟,我們就實現了Apriori算法,並進行了驗證(實際上驗證效果並不好)。實際上,上面的算法存在一定的問題,因為訓練集占數據集的比例太小了。但是沒辦法,實在是數據集太大,電腦計算能力太差,I5十代U也大概只能跑的數據。
項目地址:GitHub
參考
- 《Python數據挖掘入門與實踐》
- 數據挖掘入門系列教程(四點五)之Apriori算法