數據挖掘入門系列教程(五)之Apriori算法Python實現

數據挖掘入門系列教程(五)之Apriori算法Python實現

在上一篇博客中,咱們介紹了Apriori算法的算法流程,在這一片博客中,主要介紹使用Python實現Apriori算法。數據集來自grouplens中的電影數據,一樣個人GitHub上面也有這個數據集。html

推薦下載這個數據集,1MB大小夠了,由於你會發現數據集大了你根本跑不動,Apriori的算法的複雜度實在是😔。python


那麼,這個咱們使用數據集的做用是什麼呢?簡單點來講,就是某一個用戶如喜歡看$(A,B,C)$電影,那麼他極可能也喜歡看$D$電影。咱們就是須要分析這個關係。git

萬物始於加載數據集github

加載數據集

由於下載的數據集是一個zip壓縮包,首先,咱們須要將數據解壓出來:算法

import zipfile
zFile = zipfile.ZipFile("ml-latest-small.zip", "r")
#ZipFile.namelist(): 獲取ZIP文檔內全部文件的名稱列表
for fileM in zFile.namelist(): 
    zFile.extract(fileM)

解壓出來的數據以下圖:markdown


主要介紹兩個文件數據結構

  • ratings.csv 每一個用戶對於電影的評分,包括movieId,userId,rating,time
  • tags.csv 是電影的標籤

咱們目前只是使用rating.csv。而後咱們將csv文件加載到內存中。app

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')

讓咱們看一看數據長什麼樣?ide


電影中的數據就是👆這副B樣svg

  • 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_movielike表示的是電影被人喜歡的數量。


到目前爲止咱們全部的數據集就都已經準備完成了,接下來就是生成頻繁項。

頻繁項的生成

算法的流程圖的一個例子以下:


首先,咱們生成初始頻繁項集,也就是圖中的$L_1$。

ps:在本文中$K$表明項集中每一項包含元素的個數(好比{A,B}中$K=2$,{A,B,C}中$K=3$)

下面代碼與上圖不一樣是咱們使用的去除規則不一樣,規則是若是項集的數量少於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]中間,keymovie_id的集合,value爲集合中電影被喜歡的數量。

frequent_itemsets[1]的數據以下(key = 1表明$K=1$,value爲數量):


接下來咱們就能夠進行循環操做了,生成$K=2,3,4……$等狀況。讓咱們定義一個方法。

# 步驟②③,
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]

此時,咱們就已經獲得$K = 2,3……$的數據,以下(圖中只截取了$K =5,6$的數據)。


生成規則

在上面咱們得到了項集,接下來咱們來進行構建規則。

如下圖中{50,593,2571}爲例子


咱們能夠生成如下的規則:

其中前面一部分(綠色部分)表示用戶喜歡看的電影,後面一部分表示若是用戶喜歡看綠色部分的電影也會喜歡看紅色部分的電影。能夠生成$K-1$項規則


生成規則的代碼以下:

# 生成規則
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,在這篇博客中介紹了怎麼去求置信度。$confidence = \frac{應驗}{應驗+沒有應驗}$

而後咱們就能夠計算出每一條規則的置信度,而後進行從大到小的排序:

# 計算每一條規則的置信度
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的具體解釋在前一篇博客。公式以下:

$$
\begin{equation}
\begin{aligned}
Lift(X \Leftarrow Y) &= \frac{support(X,Y)}{support(X) \times support(Y)}

&= \frac{P(X,Y)}{P(X) \times P(Y)}\
& = \frac{P(X|Y)}{P(X)}\
& = \frac{confidenc(X\Leftarrow Y)}{P(X)}
\end{aligned}
\end{equation}
$$
所以咱們直接去用$Lift(X \Leftarrow Y) = \frac{confidenc(X\Leftarrow Y)}{P(X)}$去得到Lift便可。

首先咱們須要得到訓練集中的$P(X)$。

# 計算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去驗證,這裏就很少作介紹了。一樣,咱們能夠限定$K$取值,也能夠多用幾個規則去驗證選取最好的一個規則。

總結

經過上面的一些步驟,咱們就實現了Apriori算法,並進行了驗證(實際上驗證效果並很差)。實際上,上面的算法存在必定的問題,由於訓練集佔數據集的比例過小了。可是沒辦法,實在是數據集太大,電腦計算能力太差,I5十代U也大概只能跑$userId < 300$的數據。

項目地址:GitHub

參考

  1. 《Python數據挖掘入門與實踐》
  2. 數據挖掘入門系列教程(四點五)之Apriori算法
相關文章
相關標籤/搜索