基于Spark MLlib.FPGrowth挖掘电商物品间的关联规则

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_15443203/article/details/82711089

本文是个人对分析商品间关联关系的一篇总结。

不同于找相似商品,关联关系想要找到商品间有潜在购买关系,比如啤酒尿布,香烟和打火机,炒菜锅和炒勺等等。

首先从Apriori开始讲起:

关联规则简述

此处大部分是对一篇英文博客的理解,原地址找不到了…

Association rules analysis is a technique to uncover how items are associated to each other. There are three common ways to measure association.

关联规则分析是一种揭示item如何相互关联的技术。 度量关联性有三种常用方法。

Measure 1: Support支持度. This says how popular an itemset is, as measured by the proportion of transactions in which an itemset appears. In Table 1 below, the support of {apple} is 4 out of 8, or 50%. Itemsets can also contain multiple items. For instance, the support of {apple, beer, rice} is 2 out of 8, or 25%.

这表示项目集的受欢迎程度,在所有交易记录中出现越多,值越大。 在下面的表1中,{apple}的支持是8个中的4个,或50%。 项目集还可以包含多个项目。 例如,{apple,beer,rice}的支持是8个中的2个,或25%。

通俗解释:简单地说,X=>Y的支持度就是在所有交易记录中,物品集X和物品集Y同时出现的概率。每一条Transaction 就是一条交易或浏览记录

Measure 2: 置信度. This says how likely item Y is purchased when item X is purchased, expressed as {X -> Y}. This is measured by the proportion of transactions with item X, in which item Y also appears. In Table 1, the confidence of {apple -> beer} is 3 out of 4, or 75%.

 这表示购买商品X时购买商品Y的可能性,表示为{X - > Y}。 在表1中,{apple - > beer}的置信度是4中的3或75%。

通俗解释:简单地说,置信度就是指在购买了X的交易记录中,物品集Y也同时出现的概率有多大。

缺陷:没有考虑Y的受欢迎程度。如果Y物品也很受欢迎,那么含有X的记录中,也大概率会出现Y,从而提高了confidence的值。

Measure 3: Lift. This says how likely item Y is purchased when item X is purchased, while controlling for how popular item Y is. In Table 1, the lift of {apple -> beer} is 1,which implies no association between items. A lift value greater than 1 means that item Y is likely to be bought if item X is bought, while a value less than 1 means that item Y is unlikely to be bought if item X is bought.

这表示购买商品X时购买商品Y的可能性,同时控制商品Y的受欢迎程度。 在表1中,{apple - > beer}的lift值为1,这意味着项目之间没有关联。 lift大于1意味着如果购买物品X则可能购买物品Y,而小于1的值意味着如果购买物品X则不太可能购买物品Y.

在实际的应用中,通常使用Lift作为关联关系的度量准则:

Lift(apple->beer)= confidence(apple->beer)/support(beer) <=> P(AB)/[P(A)*P(B)]

关联规则挖掘任务分解为如下两个主要的子任务

  • 频繁项集产生:目标是发现满足最小支持度阈值的所有项集,这些项集称作频繁项集(frequent itemset)。
  • 关联规则的产生:目标是从上一步发现的频繁项集中提取所有高置信度的规则,这些规则称作强规则(strong rule)。

通常,频繁项集产生所需的计算开销远大于产生规则所需的计算开销。

经典Apriori算法的两个非常重要的结论

Apriori定律2:如果一个集合不是频繁项集,则它的所有超集都不是频繁项集。

Apriori定律1:如果一个集合是频繁项集(frequent set,满足最小支持度阈值的所有项集),则它的所有子集都是频繁项集。

FPGrowth

极大地提升了经典算法的效率。上学时候不懂效率的重要性,工作后,动辄上亿的数据量,效率为王。

原理参考博客:https://blog.csdn.net/yutao03081/article/details/77127500

大概工作流程如下:

首先构建FP树 ,然后利用它来挖掘频繁项集。为构建FP树 ,需要对原始数据集扫描两遍。第一遍对所有元素项(单个项目)的出现次数进行计数。根据Apriori原理,即如果某元素是不频繁的,那么包含该元素的超集也是不频繁的,所以就不需要考虑这些超集。数据库的第一遍扫描用来统计出现的频率,而第二遍扫描中只考虑那些频繁元素。

业务分析:

为了分析用户购买行为模式,找到购买商品之间潜在的联系。在本次测试中,只取一个月的浏览,加购记录。每一个用户一天的行为作为一条Trans。如果后期有要求,可以将行为更细分到一定时间之内,比如10min,一小时等。

在FPGrowth中,比较重要的参数是miniSupport。这个参数在网上找了好久,很少有人说怎么设置,只有一个建议是说根据业务需要,考虑售卖多少商品会对利润带来影响,由此设置。我在此处分析商品在一个月内被浏览,加购,收藏次数的百分位数(如果有更好的方法还请大神在下面讨论),过滤一些冷门商品,确定一个minisupport。

业务上只关心2项频繁项集,即A->B的这种形式,让算法递归完成整棵树是非常耗时和浪费内存的,因此考虑更改源码

先逐步分析源码(基于scala):

  def run[Item: ClassTag](data: RDD[Array[Item]]): FPGrowthModel[Item] = {
    if (data.getStorageLevel == StorageLevel.NONE) {
      logWarning("Input data is not cached.")
    }
    val count = data.count()
    val minCount = math.ceil(minSupport * count).toLong
    val numParts = if (numPartitions > 0) numPartitions else data.partitions.length
    val partitioner = new HashPartitioner(numParts)
    val freqItems = genFreqItems(data, minCount, partitioner)
    val freqItemsets = genFreqItemsets(data, minCount, freqItems, partitioner)
    new FPGrowthModel(freqItemsets)
  }

spark MLlib FPGrowth要求输入数据data的形式是RDD[Array[Item]],每一个Array表示一条Trans,一个Item表示一件商品。genFreqItems用于过滤输入数据集,得到满足最小支持度的Item,并按照降序排列,得到频繁项。genFreqItemsets用于计算频繁项集,这一步最为耗时,也是需要修改的地方。

/**
   * Generate frequent itemsets by building FP-Trees, the extraction is done on each partition.
   * @param data transactions
   * @param minCount minimum count for frequent itemsets
   * @param freqItems frequent items
   * @param partitioner partitioner used to distribute transactions
   * @return an RDD of (frequent itemset, count)
   */
  private def genFreqItemsets[Item: ClassTag](
      data: RDD[Array[Item]],
      minCount: Long,
      freqItems: Array[Item],
      partitioner: Partitioner): RDD[FreqItemset[Item]] = {
    val itemToRank = freqItems.zipWithIndex.toMap
    data.flatMap { transaction =>
      genCondTransactions(transaction, itemToRank, partitioner)
    }.aggregateByKey(new FPTree[Int], partitioner.numPartitions)(
      (tree, transaction) => tree.add(transaction, 1L),
      (tree1, tree2) => tree1.merge(tree2))
    .flatMap { case (part, tree) =>
      tree.extract(minCount, x => partitioner.getPartition(x) == part)
    }.map { case (ranks, count) =>
      new FreqItemset(ranks.map(i => freqItems(i)).toArray, count)
    }
  }

其中extract是核心的迭代方法,用于提取以某个频繁项集为后缀(suffix)的频繁项集。在业务中只取2项频繁项集,要知道每一次迭代都是向频繁后缀(valid suffix)前加入一个item,这里需要加入一个限制条件,限制迭代只进行两次,或者限制只取后缀长度为1的情况再迭代。我这里选择第一种情况,加入一个level,表示迭代终止条件,这样就只会产生A->B的情况,不考虑{A,C}->B等情况。

  def extract(minCount: Long,
              level: Int = 0,
              validateSuffix: T => Boolean = _ => true): Iterator[(List[T], Long)] = {
    summaries.iterator.flatMap { case (item, summary) =>
      if (validateSuffix(item) && summary.count >= minCount && level <= 1) {
        Iterator.single((item :: Nil, summary.count)) ++
          project(item).extract(minCount, level + 1).map { case (t, c) =>
            (item :: t, c)
          }
      } else {
        Iterator.empty
      }
    }
  }

这样,在有7千万条trans的情况下, 大概能跑40mins,效果可以接受。原生代码没有设置lift的地方,稍微写下即可得到每个关联规则的lift,这样对于每个商品,可以根据lift排序,选择一个召回商品池。根据实际效果,可以取top10得到最近似的商品(大多数是同品牌不同型号),以及后5得到有关联的商品(大多是相关产品)。

val lift = confidenceA2B / supportB

总结完毕。

猜你喜欢

转载自blog.csdn.net/sinat_15443203/article/details/82711089
今日推荐