根据用户的活跃度及消费情况,判断用户的流失意向。可及时对有流失趋向的用户做营销召回
像这样的概率型结论,通过一个普通的算术运算来得出的话,不会太靠谱!
要通过历史既定事实的经验(满足某些特征的人群中,哪些流失了,哪些没流失),来得出,才会更靠谱!
用户是否流失,是一个典型的概率分类问题
所以,第一时间想到用朴素贝叶斯算法来实现
特征选择的原则: 所选取的特征,应该跟流失与否有关联!
活跃属性:
3/7/15/30日登录次数
3/7/15/30日访问时长
3/7/15/30日访问深度
消费属性:
3/7/15/30日消费金额
3/7/15/30 订单均价
3/7/15/30日最大单笔消费金额
3/7/15/30日最小单笔消费金额
3/7/15/30日退换货次数、占比
3/7/15/30日拒收次数、占比
最后一次登录距今天数
最后一次购买距今天数
事件属性:
3/7/15/30/累计 好评数
3/7/15/30/累计 差评数
3/7/15/30/累计 分享数
《详细参考数仓中的几个画像报表: 活跃行为画像报表,消费订单画像报表,消费商品画像报表》
从公司大量的流失用户(标签:已流失)中,随机挑选1万人
从公司大量的未流失用户中,随机挑选1万人
将这2万人的数据(特征)组成先验样本集!
标签,特征1,特征2,特征3,特征4,特征5,特征6………
标签,特征1,特征2,特征3,特征4,特征5,特征6………
标签,特征1,特征2,特征3,特征4,特征5,特征6………
这些样本数据从哪里去获取?
从事实明细标签库(存在hdfs中)中取
连续数字的区间化处理,减少值的种类数
由于本案例中,各个特征值,都是一些“连续”数字,无法在概率计算上体现同类别的共同特征,所以,此案例中的特征向量化,需要做一个非常关键的处理:
将特征值区间化(离散化)!
3日登录次数: 0~2 低频 3~6 ->中频 7~ + -> 高频
7登录次数: 0~6 低频 7~14 ->中频 15~ + -> 高频
15录次数: 0~14 低频 15~30->中频 31~ + -> 高频
30登录次数: 0~29 低频 30~60 ->中频 61~ + -> 高频
3日访问时长: 0~2 低频 3~6 ->中频 7~ + -> 高频
7日访问时长: 0~6 低频 7~14 ->中频 15~ + -> 高频
15日访问时长: 0~14 低频 15~30->中频 31~ + -> 高频
30日访问时长: 0~29 低频 30~60 ->中频 61~ + -> 高频
数字特征的值域差别太大的问题
有些特征的值域范围在50万-100万
有些特征的值域范围在0-30
在算法中,值域范围大的,对最终结果的影响会明显超出值域范围小的特征,带来预测准确度的降低(相当于把别的特征给忽略掉了)
所以,还需要对特征的值域进行规范化处理!
/**
* @date: 2019/8/10
* @site: www.doitedu.cn
* @author: hunter.d 涛哥
* @qq: 657270652
* @description: 模型标签计算程序
* @模型标签: 价值指数
* @模型标签: 流失概率
* @模型标签: 退拒风险
*/
object LossProbabilityModelTrainer {
def main(args: Array[String]): Unit = {
val spark = SparkUtil.getSparkSession(this.getClass.getSimpleName)
import spark.implicits._
/**
* 加载样本特征数据:
* 数仓的用户活跃度统计报表(每个人的平均访问深度、时长,次数、间隔....)
* 数仓的用户消费订单画像统计报表(每个人的消费总额、平均金额、客单价、消费时间间隔、笔数、首单距今天数、末单距今天数,退换拒收次数、占比)
* 数仓的用户商品画像统计报表(每个人消费的最多品类,最多商品,消费的价格偏好区间..........)
* 事实标签
*/
val sample = spark.read.option("header", "true").csv("user_profile/data/modeltag/modeltag_sample/liushi_sample.csv")
// 将特征数据向量化
val featureMatrix = sample.rdd.map({
case Row(label: String, gid: String, t1: String, t2: String, t3: String, t4: String, t5: String, t6: String, t7: String, t8: String, t9: String, t10: String, t11: String, t12: String)
=>
(gid,label.toDouble,
Vectors.dense(
Array(
t1.toDouble,
t2.toDouble,
t3.toDouble,
t4.toDouble,
t5.toDouble,
t6.toDouble,
t7.toDouble,
t8.toDouble,
t9.toDouble,
t10.toDouble,
t11.toDouble,
t12.toDouble
)
)
)
}).toDF("gid","label","vec")
featureMatrix.show(30,false)
/**
* 特征向量处理:统一特征值的顺序,对需要进行区间化的做区间化
* 对需要进行值域规范化的进行规范化操作
*
* sparkmllib中提供了4种向量规范化算法工具
*
* 这里是4种向量规范化算法
* val norm = new Normalizer().setInputCol("vec").setOutputCol("norm").setP(1)
*norm.transform(data).show(10,false)
* *
* val sd = new StandardScaler().setInputCol("vec").setOutputCol("stand").setWithMean(true).setWithStd(true)
*sd.fit(data).transform(data).show(10,false)
* *
* val minscalar = new MinMaxScaler().setInputCol("vec").setOutputCol("minmax")
*minscalar.fit(data).transform(data).show(10,false)
* *
* val maxscalar = new MaxAbsScaler().setInputCol("vec").setOutputCol("maxabs")
*maxscalar.fit(data).transform(data).show(10,false)
*/
val scaler = new MinMaxScaler().setInputCol("vec").setOutputCol("normed_vec")
val normed = scaler.fit(featureMatrix).transform(featureMatrix).drop("vec")
normed.show(30,false)
/**
* 构造朴素贝叶斯算法,并用样本特征来训练模型
*/
val bayes = new NaiveBayes()
.setSmoothing(0.01)
.setLabelCol("label")
.setFeaturesCol("normed_vec")
val model = bayes.fit(normed)
/**
* 保存训练好的模型
*/
model.save("user_profile/data/modeltag/model")
spark.close()
}
}
/**
* @date: 2019/8/15
* @site: www.doitedu.cn
* @author: hunter.d 涛哥
* @qq: 657270652
* @description: 流失概率预测 : 即流失概率标签计算
*/
object LossProbabilityPredict {
def main(args: Array[String]): Unit = {
val spark = SparkUtil.getSparkSession(this.getClass.getSimpleName)
import spark.implicits._
import org.apache.spark.sql.functions._
val sample = spark.read.option("header", "true").csv("user_profile/data/modeltag/modeltag_test")
// 将特征数据向量化
val featureMatrix = sample.rdd.map({
case Row(gid: String, t1: String, t2: String, t3: String, t4: String, t5: String, t6: String, t7: String, t8: String, t9: String, t10: String, t11: String, t12: String)
=>
(gid,
Vectors.dense(
Array(
t1.toDouble,
t2.toDouble,
t3.toDouble,
t4.toDouble,
t5.toDouble,
t6.toDouble,
t7.toDouble,
t8.toDouble,
t9.toDouble,
t10.toDouble,
t11.toDouble,
t12.toDouble
)
)
)
}).toDF("gid","vec")
// 向量规范化
val scaler = new MinMaxScaler().setInputCol("vec").setOutputCol("normed_vec")
val normedMatrix = scaler.fit(featureMatrix).transform(featureMatrix).drop("vec")
val model = NaiveBayesModel.load("user_profile/data/modeltag/model")
// 用模型去预测流失率
val prediction = model.transform(normedMatrix)
// .show(10,false)
/**
* +---+-------------------------------------------------+-----------------------------------------+----------+
* |gid|normed_vec |probability |prediction|
* +---+-------------------------------------------------+-----------------------------------------+----------+
* |11 |[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.0]|[4.499154745714537E-4,0.9995500845254286]|1.0 |
* |12 |[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0]|[0.999999999999992,8.079939852611191E-15]|0.0 |
* +---+-------------------------------------------------+-----------------------------------------+----------+
*/
val getLossProb: UserDefinedFunction = udf((v:linalg.Vector)=> {v.toArray(1)})
spark.udf.register("getprob",getLossProb)
prediction.createTempView("prediction")
// 整理结果成为标签模型: gid,模块名,标签名,标签值,权重值
spark.sql(
"""
|select
|gid as gid,
|'M010' as module,
|'T107' as tagname,
|getprob(probability) as tagvalue,
|-9999 as weight
|from prediction
|
""".stripMargin)
.show(10,false)
spark.close()
}
}
《方案和流程都雷同“流失概率”标签的计算,只是特征选取不同》
《详见项目代码》