SparkML中关联规则的应用


1. 概念

什么是关联规则?(Association Rules)

关联规则是数据挖掘中的概念, 通过分析数据, 找到数据之间的关联, 电商中经常用来分析购买商品之间的相关性, 例如,”购买尿布的用户 有大概率购买啤酒”, 这就是一个关联规则.

如果把买尿布记作A, 买啤酒记作B

“买尿布的用户有较大概率买啤酒” 这个关联规则记作 A -> B

什么是关联规则推荐? (Association Rule Based Recommendaion)

顾名思义, 利用关联规则, 来实施推荐的目标, 是希望达到

将尿布放进购物车之后, 再推荐啤酒” 比 “直接推荐啤酒” 获得更好的售卖效果

ps: 这个目标非常重要, 有些场景, 或许直接推荐更有效

关联规则的典型应用

  • 线上: 可以在 用户将尿布放进购物车后, 立刻推荐啤酒

  • 线下: 可以将尿布和啤酒放在一起

2. 如何实施

假设某电商会售卖ABCD四种商品, 历史上共有5笔订单, 分别卖出 {A,B,C}, {B,C,D}, {A,B,C,D}, {A,C}, {C} 如何来实施“关联规则”推荐呢?

2.1 数据准备

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1

如上表格, 纵坐标是所有历史订单, 横坐标是每笔单出售的商品

2.2 计算关联规则(商品组合) 的支持度

什么是支持度?
一共五笔订单, 3笔订单包括商品A, 那么A的支持度就是3/5

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5

很容易计算出, 各个商品的支持度, 从支持度可以看出 Best Seller 的商品是C, 100% 的订单中都包含商品C , C的支持度是1.

除了单个商品, 组合商品也有支持度

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5
support(A->B) (A->B) 2/5

共5笔订单, 两笔同时包含AB, 即A->B的支持度是 2/5

ps: 全局共四种商品, 假设关联规则只关联两种商品, 则一共需要计算 C(4,2) 共六种组合商品的支持度,{AB,AC,AD,BC,BD,CD} 支持度评估商品包含在订单中的”概率”, 一个订单, 有多大概率包含这个商品。

ps: 一般会先对支持度高的商品实施推荐, 如果先优化了支持度低的商品, 即使推荐效果翻倍, 总体订单提升效果也会很有限。

2.3 计算关联规则的置信度

什么是置信度?(confidence)

已知购买了A, 有多大概率购买了B(即同时购买AB), 称 A-> B 的置信度

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5
support(A->B) (A->B) 2/5
confidence(A->B) s(A->B) / s(A) = 2/3

可以看到, 商品A有三次购买, 这三次购买中有两次购买了B , A->B 的置信度是2/3
ps: confidence(A->B) = (support(A->B)) / (support(A)) = (2/5) / (3/5) = 2/3

相对比较好理解

分子: support(A->B) 是同时购买AB的比例,

分母: support(A) 是只购买了A的比例

二者相除, 得到了”购买了A, 有多个概率购买B”, 置信度本质是条件概率

这里需要注意的是: X->Y 与 Y->X的置信度是不定相等的

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5
support(A->B) (A->B) 2/5
confidence(A->B) s(A->B) / s(A) = 2/3
confidence(B->C) s(B->C) / S(B) = 1
confidence(C->B) s(C->B) / S(C) = 3/5

ps:

support(B->C) = 3/5

support(C->B) = 3/5

support(B) = 3/5

support(C) = 1

confidence(B->C) = support(B->C) / support(B) = 1

confidence(C->B) = support(C->B) / support(C) = 3/5

2.4 计算关联规则的提升度

上一个例子里, confidence(B->C) = 1 , 即, 如果用户购买了商品B, 100%会买C , 那么是不是意味着, 如果用户将商品B放进购物车, 就可以向用户推荐商品C呢?

答案是否定的.

我们来回顾一下, 关联规则的推荐目标, 是希望达到

“将尿布放进购物车之后, 再推荐啤酒” 比 “直接推荐啤酒” 获得更好的售卖效果

虽然购买商品B, 100% 会购买商品C

confidence(B->C) = 1

但是直接推荐C, 用户也会100% 购买C

support(c)= 1

会发现, 购买B 与购买C是独立事件, 用户买不买C和用户买不买B没有直接关系, 这里的关联规则推荐, 并没有比直接推荐获取更好的效果, 那么用什么指标来品谷关联规则的效果呢?

提升度

什么是提升度?(lift)

A->B 关联规则推荐, 与直接推荐B的比值, 可以用来评估推荐效果:

大于1, 说明有效, 在购买A时, 推荐B 比直接推荐B 效果更好

等于1, 说明无关, 在购买A 与购买B时, 是独立事件

小于1, 说明负相关, 购买A时推荐B, 效果还不如直接推荐B

提升度简单公式如下:

lift(A->B) = confidence(A->B) / support(B)

分子: confidence(A->B) , 购买A时, 有多大概率同时购买B

分母:support(B) , 有多大概率直接购买B

二者相除, 得到效果是否更好

还是通过两个直观的例子来看

1.计算 lift(A->B)

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5
support(A->B) (A->B) 2/5
confidence(A->B) s(A->B) / s(A) = 2/3
lift(A->B) c(A->B)/s(B) = 10/9

来看看关联规则, A->B 与直接推荐B , 效果有没有提升

有3个订单购买A , 这三个订单中有两个订单购买了B, 所以A-> B 的置信度是2/3, 即 买了A有2/3的概率会买B

直接推荐B的话, 5个订单中有三个购买了B, 所以B的支持度是3/5, 即有3/5的概率会直接购买B

会发现, 关联规则推荐的效果更好

confidence(A->B) = support(A->B)/support(A) = 2/3

support(B) = 3/5

lift(A->B) = confidence(A->B)/support(B) = (2/3)/(3/5) = 10/9

lift(A->B) > 1 所以,关联规则推荐是正相关的

2. 计算 lift(A->D)

订单号 商品A 商品B 商品C 商品D
x001 1 1 1
x002 1 1 1
x003 1 1 1 1
x004 1 1
x005 1
support (A)3/5 (B)3/5 (C)1 (D)2/5
support(A->B) (A->B) 2/5
support(D) (D) 2/5
confidence(A->B) s(A->B) / s(A) = 2/3
lift(A->B) c(A->B)/s(B) = 10/9

来看看关联规则A->D 与直接推荐D, 效果有没有提升

有三个订单购买A, 这三个订单中有1个购买了D, 所以A->D 的置信度是1/3, 即买了A有1/3的概率会买D

直接推荐D的话, 5个订单有2个购买了D, 所以D的支持度是2/5, 即有2/5的概率会直接买D

会发现, 关联规则推荐效果差, 还不如直接推荐

confidence(A->) = support(A->D)/support(A) = 1/3

support(D) = 2/5

lift(A->D) = confidence(A->D) / support(D) = 5/6

lift(A->D) <1 所以,关联规则推荐是负相关的

3. 总结

关联规则A->B 推荐, 目标是, 在“用户将A放入购物车时, 推荐B” 比“单独推荐B” 获取更好的效果。

A->B 的支持度, 是用户同时购买A和B的概率

A->B 的置信度, 是用户购买A的同时, 有多大概率购买B

A->B 的提升度, 是“用户购买A的同时, 有多大概率购买B” 与“直接购买B”的比值

提升度大于1时: 说明 A->B是正向效果

提升度等于1时: 说明A和B 是独立事件

提升度小于1时: 说明 A->B是负向效果

4. Spark FPGrowth 代码示例

源码地址:https://github.com/hnbian/MySpark/blob/master/SparkML/src/main/scala/hnbian/sparkml/association/FPGrowth_mllib.scala

package hnbian.sparkml.association

import hnbian.spark.utils.SparkUtils
import org.apache.spark.mllib.fpm.FPGrowth
import utils.FileUtils

/**
  * @author hnbian 2019/4/17 14:31
  *         关联规则代码示例
  */
object FPGrowth_mllib extends App {
  val spark = SparkUtils.getSparkSession("FPGrowth", 4)

  import spark.implicits._

  //最小支持度
  val minSupport = 0.055
  //最小置信度
  val minConfidence = 0.2
  //数据分区
  val numPartitions = 10

  //取出数据
  val filePath = FileUtils.getFilePath("association_rules.txt")
  println(filePath)
  val data = spark.sparkContext.textFile(filePath)
  //把数据通过空格分割
  val transactions = data.map(x => x.split(","))
  val data_count = data.collect().length
  println("length==>" + data_count)
  transactions.persist()
  val transactionsDF = transactions.toDF("items")
  //transactionsDF.show()


  //使用  sparkmllib包下面的算法
  //创建一个FPGrowth 算法实例
  val fpg = new FPGrowth()

  //设置训练时候的最小支持度和分区数据
  fpg.setMinSupport(minSupport) //默认0.3
  fpg.setNumPartitions(numPartitions)

  //把数据带入算法中
  val model = fpg.run(transactions)
  println("打印频繁项集出现次数")
  //查看所有频繁项集, 并列出他们出现的次数
  val support_rdd = model.freqItemsets.filter(itemset => {
    itemset.items.length >= 3 //选择过滤出几项数据
  }).map(itemset => {
    (itemset.items.mkString(","), itemset.freq, itemset.freq.toDouble / data_count) //itemset.freq 出现次数
  })

  val support_df = support_rdd.toDF("items", "count", "support")
  support_df.orderBy(support_df("items").asc).show(5) //按照频繁项集出现的次数排序
  /**
    * +-----------+-----+-------------------+
    * |      items|count|            support|
    * +-----------+-----+-------------------+
    * |A 1,B 3,C 4|   52|0.05591397849462366|
    * |A 1,C 2,D 4|   52|0.05591397849462366|
    * |A 2,B 4,C 4|   80|0.08602150537634409|
    * |A 2,B 4,H 4|   73|0.07849462365591398|
    * |A 2,C 4,H 4|   76|0.08172043010752689|
    * +-----------+-----+-------------------+
    */
  val bb = model.freqItemsets.persist()

  println("打印置信度")
  // 通过置信度筛选出推荐规则,
  //antecedent 表示前项
  //consequent 表示后项
  val confidence_rdd = model.generateAssociationRules(minConfidence)
    .filter(
      x => {(x.antecedent.length>=2 && x.consequent.length == 1)} //过滤出前项长度为3 并且后项长度为1 的条件
    )
    .map(x => {
    println(s"前项:${x.antecedent.toSet.toBuffer},后项:${x.consequent.toSet.toBuffer},置信度:${x.confidence},提升度:${x.lift}")
    /**
      * 前项:ArrayBuffer(A 4, B 4),后项:ArrayBuffer(H 4),置信度:0.488,提升度:Some(1.0935903614457831)
      * 前项:ArrayBuffer(C 3, F 3),后项:ArrayBuffer(H 4),置信度:0.875,提升度:Some(1.9608433734939759)
      * 前项:ArrayBuffer(F 3, A 2),后项:ArrayBuffer(H 4),置信度:0.7261904761904762,提升度:Some(1.6273666092943202)
      * ...
      */
    (x.antecedent.mkString(","), x.consequent.mkString(","),x.confidence,x.lift,x.toString())
  })

  val confidence_df = confidence_rdd.toDF("antecedent", "consequent", "confidence","lift","remark")
  confidence_df.orderBy(confidence_df("confidence").desc).show(5,false)
  /**
    * +-----------+----------+------------------+------------------+----------------------------------------------------------------------------------------+
    * |antecedent |consequent|confidence        |lift              |remark                                                                                  |
    * +-----------+----------+------------------+------------------+----------------------------------------------------------------------------------------+
    * |F 3,A 4    |H 4       |0.8795180722891566|1.9709682101901582|{F 3,A 4} => {H 4}: (confidence: 0.8795180722891566; lift: Some(1.9709682101901582))    |
    * |C 3,F 3    |H 4       |0.875             |1.9608433734939759|{C 3,F 3} => {H 4}: (confidence: 0.875; lift: Some(1.9608433734939759))                 |
    * |F 3,B 4    |H 4       |0.8088235294117647|1.8125442948263644|{F 3,B 4} => {H 4}: (confidence: 0.8088235294117647; lift: Some(1.8125442948263644))    |
    * |F 3,C 4    |H 4       |0.7941176470588235|1.7795889440113395|{F 3,C 4} => {H 4}: (confidence: 0.7941176470588235; lift: Some(1.7795889440113395))    |
    * |E 3,B 4,H 4|D 2       |0.7761194029850746|1.9667330920330228|{E 3,B 4,H 4} => {D 2}: (confidence: 0.7761194029850746; lift: Some(1.9667330920330228))|
    * +-----------+----------+------------------+------------------+----------------------------------------------------------------------------------------+
    */
}

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
JVM系列(二)、Jvm内存结构(上)、堆 JVM系列(二)、Jvm内存结构(上)、堆
1. Java内存结构 JVM内存结构主要有三大块:堆内存、方法区和栈。 堆 :存放 new 出来的对象和数组,堆是JVM中最大的一块,由年轻代和老年代组成,而年轻代内存又被分为三部分:Eden空间、Form Survivor空间、To
2019-12-25
下一篇 
Intellij IDEA修改默认 Target bytecode version Intellij IDEA修改默认 Target bytecode version
1. 问题现象使用Intellij IDEA 新建Java项目,使用jdk1.8版本,设置好Target bytecode version 为1.8,Language level为8。但是,每次新加一个module,所有的设置都变成默认的1
2019-12-23
  目录