咱們在網上購物的時候,常常會遇到這樣的推送, 好比買了A書的人, 同時購買了B書的情景, 在這個描述中: 包含以下的信息:python
好比用購物車的例子來講, 購物車裏面的每一件item都叫作一個項, 辣條是一個項, 薯片也是一個項, 全部的商品加起來叫作項集記做itemset, 若是有多件商品記做 k-itemset算法
transaction就是一組非空的 item的集合, 能夠把它想成是購物時的小票。數據庫
多個 transaction 記錄的總和稱爲 datasetapp
關聯規則的定義如上公式:機器學習
這樣理解: 就是驗證一下P到Q之間有什麼關係, 其中P屬於itemset ,Q也屬於itemiset, 而且P並非Qide
好比:P是牙刷,Q是牙膏, 牙刷和牙膏都是商品,而且牙膏並非牙刷,咱們要作的就是推導出牙膏和牙刷之間之間被用戶購買的rule函數
Lk表示有k個元素的頻繁項集學習
CK表示有K個元素的候選項集, 既然是候選項集, 意味着它裏面的元素極可能存在不知足場景規定的支持度大小, 須要被丟棄掉 (什麼是支持度? 往下看...)spa
Cross Selling: 交叉銷售, 好比很經典的數據挖掘問題, 購買啤酒的人一般會購買尿布 , 商家知道這個規則後就能夠將這兩個商品放在一塊兒, 方便了購物者, 也能起到提醒的做用, 提升營業額。3d
介紹數據挖掘中的兩個重要概念:
itemset的支持度(Support)
**公式: **
**含義: **Support就是頻率的意思,好比說牛奶的支持度,好比一共10我的,其中3我的買了牛奶, 那麼牛奶的支持度就是3/10 , 它是個比例。若是物品的支持度低,極可能會被忽略不計
關聯規則的支持度
**公式: **
含義: 關聯規則的支持度,說的其實也是頻率,即同時購買X和Y的訂單數量,佔所有訂單數量的百分比
置信度(Confidence)
公式:
含義: 什麼是置信度? 好比X=牛奶, Y=麪包。置信度就是同時購買牛奶和麪包的人, 佔購買麪包的數量
實際上它描述的就是咱們原來學過的條件機率P(Y|X) =P(Y*X) / P(X)
,這個公式至少高中都學過
根據具體的場景,定義兩個變量:
所謂頻繁項集大白話說就是這個項集出現的次數很頻繁,界定方法:這個項集的 支持度 要大於最小支持度
所謂強規則大白話說就是這個規則很強大頗有意義,界定方法:這個它置信度(條件機率)要大於 最小置信度
給定 I ,D ,σ 和 Φ, 而後咱們去找出 X->Y之間的全部的強規則
用大白話說就是: 當我有了全部的itemset ,dataset,最小支持度,最小置信度,而後去把X與Y之間,符合前面四個要要素的全部強規則都找出來,稱爲關聯規則的挖掘
能夠看到, 哪怕僅僅存在4件商品, 他們之間的排列組合也是至關的多
假設咱們如今有以下的數據: 一共是9條記錄, 咱們規定這個場景下的支持度是3, 置信度是70%
transactions | itemset |
---|---|
1 | {a, b, c, d} |
2 | {a, c, d, f} |
3 | {a, c, d, e, g} |
4 | {a, b, d, f} |
5 | {b, c, h} |
6 | {d, f, g} |
7 | { a, b, g } |
8 | {c, d, f, g} |
9 | {b, c, d} |
若是咱們想找出頻繁1項集, 也就是 L1的話, 很容易就能想到, 只要遍歷全部的itemset中的每個item ,而後記錄下他們出現的次數, 但凡是出現次數大於3的, 都符合要求而被保留, 小於3的都不知足要求而被丟棄, 因此咱們能獲得下面的結果:
item | support |
---|---|
a | 5 |
b | 5 |
c | 6 |
d | 7 |
f | 4 |
g | 4 |
因此, 這個問題比較複雜的地方就是如何經過 lK -> CK+1 , 換句話說, 就是如何經過如今已知的 l1求出 C2
思路:
一樣的咱們能夠對L1進行排列組合, 得出全部的狀況稱它爲set1, 而後再遍歷set1每一項, 去看看最開始給定的itemsets中是否包含它, 若是找到了包含它的項, 咱們就給他的支持度計數+1, 一樣的遍歷完set1中全部的項後, 把支持度不知足的項刪除, 保留支持度大於3的項, 至此, 也就意味着咱們找到了 C2, 因而咱們能夠獲得下面的結果
item | support |
---|---|
{a,b} | 3 |
{a,c} | 3 |
{a,d} | 4 |
{b,c} | 3 |
{b,d} | 3 |
{c,d} | 5 |
{d,f} | 4 |
{d,g} | 3 |
再日後就是從 L2 - > C3的過程:
思路1: 若是咱們仍然使用自由組合的規則生成C3的話, 能產生向下先這麼多的組合,實際上裏面有不少沒必要要的項 根據Apriori算法的規定, 若是一個項集是不頻繁的, 那麼它的全部超集都是不頻繁的
好比下面的{a,d,f} 其中的子項{a,d} 並非頻繁二項集, 那麼咱們根本不必把它也組合進來, 由於一旦被組合進來以後, 就意味着咱們要求去爲他掃描數據庫
item | support |
---|---|
{a,b,c} | 1 |
{a,b,d} | 2 |
{a,c,d} | 3 |
{b,c,d} | 2 |
{a,d,f} | X |
{a,d,g} | X |
{b,d,f} | X |
{b,d,g} | X |
{c,d,f} | X |
{c,d,g} | X |
{d,f,g} | X |
那若是不自由組合, 具體怎麼作呢?
第一步: 對全部的itemset進行一次排序, 由於第二步就要進行相似前綴的操做
第二步: 若是前k-1項相同, 第k項不一樣, 那麼把第k項搬過去, 就獲得新的 候選項, 重複這個動做
經過步驟二的選擇方式, 能夠排除掉大量的確定不頻繁的候選項集, 可是也不能保證, 保存下來的候選項集就必定會是頻繁的
因此, 若是咱們遵循Apriori算法規定的話, 咱們獲得的集合以下應該長下面這樣,
item | support |
---|---|
{a,b,c} | 1 |
{a,b,d} | 2 |
{a,c,d} | 3 |
{b,c,d} | 2 |
一樣他們的支持度是具體是多少, 仍然得去掃描數據庫, 實際上就是遍歷這個C3, 而後看看一開始給定的itemsets中有多少個包含了它, 一旦發現就給它的計數+1 , 一樣僅僅保存符合規則的, 獲得以下的結果:
item | support |
---|---|
{a,c,d} | 3 |
找出全部的強關聯規則, 大白話講就是挨個計算全部的 L2 , L3 ... (頻繁項集) 中, 他們的置信度是否在當前場景要求的70%以上, 若是知足了這個條件, 說明它就是強關聯規則, (啤酒和尿布的關係)
計算公式就是它:
實際上就是計算:在Y發生的基礎上, X發生的條件機率, 直接看公式就是 X和Y同時出現的次數 / X 出現的次數
舉個例子: 好比求a->b , 就是 a b同時發生的支持度/ a的支持度 , 前者中C2中能夠查處來, 後者從L1中能夠查出來,也就是 3/5 = 60%
若是規定置信度是70% , 那麼強關聯規則以下:
頻繁3項集是 {a,c,d}, 那麼有以下幾種狀況
計算公式如上, {a,c,d}的置信度 / 箭頭左邊出現的項集的置信度
和標準的置信度比較,獲得的結強關聯規則以下:
# 加載數據集 def loadDataSet(): return [ ['a', 'b', 'c', 'd'], ['a', 'c', 'd', 'f'], ['a', 'c', 'd', 'e', 'g'], ['a', 'b', 'd', 'f'], ['b', 'c', 'h'], ['d', 'f', 'g'], ['a', 'b', 'g'], ['c', 'd', 'f', 'g'], ['b', 'c', 'd']] # 選取數據集的非重複元素組成候選集的集合C1 def createC1(dataSet): C1 = [] for transaction in dataSet: # 對數據集中的每條購買記錄 for item in transaction: # 對購買記錄中的每一個元素 if [item] not in C1: # 注意,item外要加上[],便於與C1中的[item]對比 C1.append([item]) C1.sort() return list(map(frozenset, C1)) # 將C1各元素轉換爲frozenset格式,注意frozenset做用對象爲可迭代對象 # 如今的CK是全部的一項集, 經過下面的前兩個循環計算出每個候選1項集出現的次數,進而才能判斷出是否知足給定的支持度 # 由Ck產生Lk:掃描數據集D,計算候選集Ck各元素在D中的支持度,選取支持度大於設定值的元素進入Lk def scanD(DataSet, Ck, minSupport): ssCnt = {} # map字典結構, key = 項集 value = 支持度 for tid in DataSet: # 遍歷每一條購買記錄 for can in Ck: # 遍歷Ck,也就是全部候選集 if can.issubset(tid): # 若是候選集包含在購買記錄中,計數+1 ssCnt[can] = ssCnt.get(can, 0) + 1 numItems = float(len(DataSet)) # 購買記錄數 retList = [] # 用於存放支持度大於設定值的項集 supportData = {} # 用於記錄各項集對應的支持度 for key in ssCnt.keys(): support = ssCnt[key] / numItems if support >= minSupport: retList.insert(0, key) # 知足最小支持度就存儲起來 supportData[key] = support return retList, supportData # 返回頻繁1項集和他們各自的支持度 # 由Lk產生Ck+1 , 那第一次進來就是頻繁1項集, 企圖生成頻繁二項集 def aprioriGen(Lk, k): # Lk的k和參數k不是同一個概念,Lk表示頻繁k項集, 參數K = CK+1 中的K+1 retList = [] lenLk = len(Lk) for i in range(lenLk): # 分別遍歷Lk中的每一項, 讓它們和其餘的項組合 # 下面的for循環, 使用的是正宗的 相似前綴匹配的方式生成候選項集的方式 for j in range(i + 1, lenLk): # 從i+1開始, 是不讓當前元素和本身也進行組合 # 經過這兩層循環, 就能實現相似這種組合 01, 02 ,03 ... 12,13,... L1 = list(Lk[i])[:k - 2] # 將當前的頻繁項集轉換成list ,而後進行切片, 若是lk是頻繁1項集, 這裏根本切不出東西來 L2 = list(Lk[j])[:k - 2] # 前綴匹配前確定須要先排序,讓它們相對是有序的 L1.sort() L2.sort() if L1 == L2: # 若前k-2項相同,則合併這兩項 retList.append(Lk[i] | Lk[j]) # 01, 02 ,03 ... 12,13,... return retList # Apriori算法主函數 def apriori(dataSet, minSupport=0.5): # 建立c1 C1 = createC1(dataSet) # todo 爲啥須要去除重複? # 將dataset中全部的元素進行一次set去重, 而後轉換成list返回獲得D DataSet = list(map(set, dataSet)) # 獲得頻繁1項集和各個項集的最小支持度 L1, supportData = scanD(DataSet, C1, minSupport) L = [L1] k = 2 # 從頻繁1項集開始, 進行從Lk -> CK+1的生成 while len(L[k - 2]) > 0: # 當L[k]爲空時,中止迭代 Ck = aprioriGen(L[k - 2], k) # L[k-2]對應的值是Lk-1 # 獲取到候選項集後, 從新掃描數據庫, 計算出頻繁項集,以及他們各自的支持度 Lk, supK = scanD(DataSet, Ck, minSupport) # 更新他們各自的支持度 supportData.update(supK) # 將新推導出來的頻繁項集也添加進去 L.append(Lk) k += 1 return L, supportData # 處理兩個item項的 可信度(confidence) def calcConf(freqSet, supportData, strong_rule, minConf): # 那麼如今須要求出 frozenset([1]) -> frozenset([3]) 的可信度和 frozenset([3]) -> frozenset([1]) 的可信度 for conseq in freqSet: # print 'confData=', freqSet, H, conseq, freqSet-conseq # 支持度定義: a -> b = support(a | b) / support(a). the_list = list(conseq) left = the_list[0] right = the_list[1] # 計算a-b conf = supportData[conseq] / supportData[frozenset({left})] if conf >= minConf: strong_rule.append("{} -> {}".format(frozenset({left}),frozenset({right}))) # 計算b-a conf = supportData[conseq] / supportData[frozenset({right})] if conf >= minConf: strong_rule.append("{} -> {}".format(frozenset({right}), frozenset({left}))) def calcConfidence(Ck, fullSet,all_set,supportData, strong_rule, minConf): ''' Ck : k階的頻繁項集 fullSet : k階頻繁項集拆開後的元素 set : 由set 反向拆分出 Ck supportData, strong_rule, minConf ''' for itemset in Ck: the_list = list(itemset) temp = fullSet.copy() for item in the_list: temp.remove({item}) s = set() for i in temp: for j in i: s.add(j) # confidence(a->b) = support(a,b) / support(a) confidence1 = supportData[all_set] / supportData[frozenset(itemset)] if(confidence1>=minConf): strong_rule.append("{} -> {}".format(frozenset(itemset), frozenset(s))) # confidence(b->a) = support(a,b) / support(b) confidence2 = supportData[all_set] / supportData[frozenset(s)] if (confidence2 >= minConf): strong_rule.append("{} -> {}".format(frozenset(s), frozenset(itemset))) def rulesFromConseq(freqSet, supportData, strong_rule, minConf): for itemset in freqSet: for set in itemset: H = list(set) the_list = [] for i in H: the_list.append({i}) LL = [] LL.append(the_list) k = 2 # 從頻繁1項集開始, 進行從Lk -> CK+1的生成 while len(LL[k - 2]) > 0: # 當L[k]爲空時,中止迭代 # 直接對CK進行 統計置信度, 是由於根據Apriori算法的約定, 若是CK是頻繁的,那麼它的非空子集也是頻繁的 Ck = aprioriGen(LL[k - 2], k) # L[k-2]對應的值是Lk-1 # temp = the_list - Ck if len(Ck[0]) == len(the_list): break else: # 統計強關聯規則 calcConfidence(Ck, LL[k - 2],set,supportData, strong_rule, minConf) # 添加繼續循環的條件 LL.append(Ck) k += 1 def get_strong_rule(L,supportData,confidence): freSet = [L[k] for k in range(2, len(L))] strong_rule = [] # 處理兩項 calcConf(L[1], supportData, strong_rule, confidence) # 處理三項及以上 rulesFromConseq(freSet, supportData, strong_rule, confidence) return strong_rule if __name__ == '__main__': minSupport = 0.3 confidence = 0.7 # 加載數據 dataset = loadDataSet() # 找出各頻繁項集, 及其支持度 L, supportData = apriori(dataset, minSupport) # 找出強規則 strong_rules = get_strong_rule(L, supportData,confidence) print('全部項集及其支持度: ',supportData) print('全部頻繁項集: ',L) print("強規則: ",strong_rules)
算法參考: 《機器學習實戰》 Peter Harrington Chapter11
另外, 添加了求強規則的邏輯
每次雖然從Lk->CK+1 都是儘量的生成出數量最少候選項集, 還保證了不會丟失頻繁的候選項集, 可是依然須要校驗生成出來的全部的候選項集的支持度是否符合場景要求的支持度大小, 不足的地方就體如今這裏, 由於每次去校驗的時候都得掃描一遍數據庫, 若是數據庫很大, 大量的IO會讓算法變慢