文章目录
day14_挖掘类标签
一、基于K-Means的RFM客户价值标签开发(熟悉)
1、计算方案
在介绍RFM模型的构建方式时,一种是直接定义阈值,然后对用户进行划分,在统计类标签开发中采用的就是这种方式。另一种是对用户进行评分,然后根据对用户的评分将用户进行分类。
在采用第一种方式时,如果阈值定义的不好,结果会产生非常大的差异,所以也可以使用基于K-Means的方法进行划分,避免硬编码带来的问题。
具体的方式:
(1)对所有用户的最近一次消费时间/总共消费次数/总共消费金额进行统计。
(2)然后根据运营或产品提供的打分规则,对用户进行评分,比如按照如下方式
R:小于3天=5分,3-6天=4分,6-10天=3分,10-15天=2分,大于等于15天=1分
F: 大于等于32=5分,24-32=4分,16-24=3分,8-16=2分,0-8=1分
M:大于等于900=5分,675-900=4分,450-675=3分,225-450=2分,小于225=1分
(3)得到用户评分后,相当于对数据进行了归一化处理,然后使用聚类方法,让算法自动学习用户之间的相似度,然后相似度高的用户,自动聚成一类,最后完成聚类的划分。
(4)最后根据聚类结果给用户打标签。
2、代码实现
在虚拟机上执行如下命令,安装pyhdfs
pip install pyhdfs
from pyspark.ml.clustering import KMeans
from pyspark.ml.feature import VectorAssembler
from pyspark.sql import DataFrame
import pyspark.sql.functions as F
from pyspark.sql.types import StringType
from tags.base.abstract_tag_base import AbstractTagBase
import numpy as np
class RFMTagML(AbstractTagBase):
def mark_tag(self,business_df:DataFrame, five_tag_df:DataFrame):
# 1- 统计R、F、M的值
tmp_business_df = business_df.groupBy("zt_id").agg(
F.min(F.datediff(F.current_date(),F.to_date(business_df.trade_date))).alias("r_data"),
F.count("order_no").alias("f_data"),
F.sum("real_paid_amount").alias("m_data")
)
# 2- 对用户进行打分
"""
case when使用注意事项
1- 如果在when中需要写多个判断条件,那么每个判断条件都需要用单独的小括号包起来
2- 如果在when中需要写多个判断条件,那么只能使用符号& | ~,不能使用and or not
"""
new_business_df = tmp_business_df.select(
"zt_id",
F.when(tmp_business_df.r_data<3,5)
.when((tmp_business_df.r_data>=3) & (tmp_business_df.r_data<6),4)
.when((tmp_business_df.r_data>=6) & (tmp_business_df.r_data<10),3)
.when((tmp_business_df.r_data>=10) & (tmp_business_df.r_data<15),2)
.when(tmp_business_df.r_data>=15, 1).alias("r"),
F.when(tmp_business_df.f_data >=32, 5)
.when((tmp_business_df.f_data >= 24) & (tmp_business_df.f_data < 32), 4)
.when((tmp_business_df.f_data >= 16) & (tmp_business_df.f_data < 24), 3)
.when((tmp_business_df.f_data >= 8) & (tmp_business_df.f_data < 16), 2)
.when(tmp_business_df.f_data < 8, 1).alias("f"),
F.when(tmp_business_df.m_data >=900, 5)
.when((tmp_business_df.m_data >= 675) & (tmp_business_df.m_data < 900), 4)
.when((tmp_business_df.m_data >= 450) & (tmp_business_df.m_data < 675), 3)
.when((tmp_business_df.m_data >= 225) & (tmp_business_df.m_data < 450), 2)
.when(tmp_business_df.m_data <225, 1).alias("m"),
)
# new_business_df.show()
# 3- 使用KMeans实现聚类
# 3.1- 特征预处理
assembler = VectorAssembler(
inputCols=["r","f","m"],
outputCol="features"
)
# 对数据内容进行真正的处理
vector_business_df = assembler.transform(new_business_df)
# 3.2- 实例化KMeans算法模型
"""
1- featuresCol的参数值需要与前面VectorAssembler设置的outputCol的值保持一致
2- 本次k的值,需要与对应的五级标签个数相同
3- seed的值没有固定大小,随意
4- predictionCol算法模型预测结果值存放的字段
"""
kmeans = KMeans(featuresCol="features",predictionCol="prediction",k=7,seed=1)
# 3.3- 模型训练
kmeans_model = kmeans.fit(vector_business_df)
# 3.4- 训练好的模型对业务数据进行处理
kmeans_result:DataFrame = kmeans_model.transform(vector_business_df)
# kmeans_result.printSchema()
# kmeans_result.show()
# 4- 给用户打上具体的标签
# 4.1- 获取质心位置。有7个质心,每个质心是三维的
cluster_centers_list = kmeans_model.clusterCenters()
print(cluster_centers_list)
# 4.2- 对质心进行求和
center_list_sum = [np.sum(center) for center in cluster_centers_list]
print(center_list_sum)
# 4.3- 构造一个质心得分字典
center_dict_sum = {}
for i in range(len(center_list_sum)):
center_dict_sum[i] = center_list_sum[i]
print(center_dict_sum)
# 4.4- 将字典的value进行降序排序
"""
sorted:对容器进行排序,默认是升序
key:自定义排序规则
reverse:是否对排完序以后的内容进行顺序反转。默认不反转
"""
ordered_dict = dict(sorted(center_dict_sum.items(),key=lambda item:item[1],reverse=True))
print(ordered_dict)
# 4.5- 将排完序的聚合结果与五级标签配置数据关联起来
five_tag_id_list = five_tag_df.rdd.map(lambda row:str(row.id)).collect()
kmeans_and_tagid_dict = dict(zip(ordered_dict.keys(),five_tag_id_list))
print(kmeans_and_tagid_dict)
# 4.6- 真正的打标签
@F.udf(returnType=StringType())
def get_tag_id(prediction_id):
return kmeans_and_tagid_dict.get(prediction_id)
result_df = kmeans_result.select(
kmeans_result.zt_id.alias("user_id"),
get_tag_id(kmeans_result.prediction).alias("tags_id_times")
)
# result_df.show()
return result_df
if __name__ == '__main__':
condition = " and zt_id is not null and datediff(current_date(),to_date(trade_date))<=90 "
tag_obj = RFMTagML()
tag_obj.execute(
app_name="",
partitions=2,
four_tag_id=39,
condition=condition
)
注意: 只要是用到K-Means算法,那么从上面代码中的3.1步开始到后面结尾的代码,全部都是通用的。
可能遇到的错误一:
原因: 如果在when中需要写多个判断条件,那么每个判断条件都需要用单独的小括号包起来
可能遇到的错误二:
原因: 如果在when中需要写多个判断条件,那么只能使用符号& | ~,不能使用and or not
二、基于K-Means的PSM标签开发(熟悉)
1、计算方案
在统计类标签开发时,使用的psm值直接对用户进行的划分。其中,psm = 优惠订单占比 + 平均优惠金额占比 + 优惠总金额占比。这三个比值其实从不同的角度衡量了用户的行为,单纯的将其加到一起再进行分类,会损失很多信息,比如,psm同样是等于0.8的两个人,一个是0.4,0.2和0.2,另一个是0.2,0.3和0.3,相对来说,前者只要打折,购买的概率就会提升,但后者会更看重优惠的金额或折扣是否足够大。这样在做具体营销时,前一类人可以多设置一些小折扣,而后者可以偶尔设置一次大的折扣。
那如何将人群划分出来,这里也可以使用聚类的方式。将优惠订单占比、平均优惠金额占比、优惠总金额占比作为特征字段,放入到K-Means模型中进行聚类。聚类的结果处理方式同RFM标签开发。
2、代码实现
from pyspark.ml.clustering import KMeans, KMeansModel
from pyspark.ml.feature import VectorAssembler
from pyspark.sql import DataFrame
import pyspark.sql.functions as F
from pyspark.sql.types import StringType
from tags.base.abstract_tag_base import AbstractTagBase
import numpy as np
from tags.utils.hdfs_utils import HDFSUtil
class PsmTagML(AbstractTagBase):
def mark_tag(self,business_df:DataFrame, five_tag_df:DataFrame):
# zt_id,order_no,order_total_amount,discount_amount,real_paid_amount
# 1- 分别计算出优惠订单占比、平均优惠金额占比、优惠总金额占比
# 1.1- 标记每笔订单是优惠订单(1表示)还是非优惠订单(0表示)
is_discount_df = business_df.withColumn(
"is_discount",
F.when(business_df.discount_amount>0,1).otherwise(0)
)
# 1.2- 字段重命名
rename_df = is_discount_df.select(
"zt_id",
"order_no",
"is_discount",
is_discount_df.order_total_amount.alias("ra"),
is_discount_df.discount_amount.alias("da"),
is_discount_df.real_paid_amount.alias("pa")
)
# 1.3- 指标计算
indi_df = rename_df.groupBy("zt_id").agg(
F.sum("is_discount").alias("tdon"), #优惠订单数
F.count("order_no").alias("ton"), #总订单总数
F.avg("da").alias("ada"), #平均优惠金额
F.avg("ra").alias("ara"), #平均每单应收金额
F.sum("da").alias("tda"), #优惠总金额
F.sum("ra").alias("tra") #应收总金额
)
# 1.4- 计算占比
tmp_business_df = indi_df.select(
"zt_id",
(indi_df.tdon/indi_df.ton).alias("tdonr"),#优惠订单占比
(indi_df.ada/indi_df.ara).alias("adar"),#平均优惠金额占比
(indi_df.tda/indi_df.tra).alias("tdar")#优惠总金额占比
)
# 注意:这里要使用fillna(0)去填充空值,否则KMeans无法正常进行模型训练
new_business_df = tmp_business_df.fillna(0)
# new_business_df.printSchema()
# new_business_df.show()
# 2- 特征预处理
assembler = VectorAssembler(
inputCols=["tdonr","adar","tdar"],
outputCol="features"
)
# 对数据进行真正的预处理
assembler_df = assembler.transform(new_business_df)
# 这块代码主要用来加快后续模型训练速度
path = "/xtzg/ml/psm"
if HDFSUtil().exists(path):
kmeans_model = KMeansModel.load("hdfs://192.168.88.166:8020"+path)
else:
# 重新训练
# 3- 创建模型
model = KMeans(featuresCol="features",predictionCol="prediction",k=5,seed=1)
# 4- 对模型进行训练
kmeans_model = model.fit(assembler_df)
# 将训练好的模型再重新写到磁盘上
kmeans_model.save("hdfs://192.168.88.166:8020"+path)
# 5- 对数据进行预测
kmeans_df = kmeans_model.transform(assembler_df)
# 6- 对质心数据进行处理
# 6.1- 获取质心信息
cluster_center_list = kmeans_model.clusterCenters()
print(cluster_center_list)
# 6.2- 对质心数据进行累加求和
# center_sum_list = [np.sum(center) for center in cluster_center_list]
center_sum_list = []
for center in cluster_center_list:
center_sum_list.append(np.sum(center))
print(center_sum_list)
# 6.3- 变成字典
center_sum_dict = {}
for i in range(len(center_sum_list)):
center_sum_dict[i] = center_sum_list[i]
print(center_sum_dict)
# 6.4- 对字典按照value值进行降序排序
ordered_dict = dict(sorted(center_sum_dict.items(),key=lambda tup:tup[1],reverse=True))
# 6.5- 将字典与五级标签拉链
five_tag_list = five_tag_df.rdd.map(lambda row:row.id).collect()
kmeans_and_tagid_dict = dict(zip(ordered_dict.keys(),five_tag_list))
print(kmeans_and_tagid_dict)
# 7- 给业务数据打上具体标签
@F.udf(returnType=StringType())
def get_tag_id(prediction):
return kmeans_and_tagid_dict.get(prediction)
result_df = kmeans_df.select(
kmeans_df.zt_id.alias("user_id"),
get_tag_id(kmeans_df.prediction).alias("tags_id_times")
)
return result_df
if __name__ == '__main__':
condition = " and datediff(`current_date`(),to_date(trade_date))<=90 and zt_id!=0 and zt_id is not null "
tag_obj = PsmTagML()
tag_obj.execute(app_name="psm_tag_ml",partitions=2,condition=condition,four_tag_id=52)
可能遇到的错误:
原因: 处理后的业务数据很多是空值
解决办法: 使用fillna(0)进行空值填充
三、决策树模型(理解)
1、决策树的引入
决策树算法是一种有监督学习算法,英文是Decision tree。决策树是机器学习中分类方法中的一个重要算法。
决策树思想的来源非常朴素,试想每个人的大脑都有类似于if-else这样的逻辑判断,这其中的if表示的是条件,if之后的then就是一种选择或决策。程序设计中的条件分支结构就是if-then结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法。
2、决策树概述
决策树是一个类似于流程图的树结构:其中,每个内部结点表示一个特征或属性,而每个树叶结点代表一个分类。树的最顶层是根结点。使用决策树分类时就是将实例分配到叶节点的类中,该叶节点所属的类就是该节点的分类。
决策树的构建:
- 特征选择:选取有较强分类能力的特征。
- 决策树生成:典型的算法有 ID3 和 C4.5, 它们生成决策树过程相似, ID3 是采用信息增益作为特征选择度量, 而 C4.5 采用信息增益比率。
- 决策树剪枝:剪枝原因是决策树生成算法生成的树对训练数据的预测很准确, 但是对于未知数据分类很差, 这就产生了过拟合的现象。涉及算法有CART算法。
决策树基本算法:
- ID3算法:使用信息增益进行特征选择。主要用于分类问题,处理离散型数据。
- C4.5算法:使用信息增益率进行特征选择。信息增益比是信息增益和属性固有值的比值,能够更好地处理多值属性的问题。可以处理离散型和连续型数据。
- CART算法:CART(分类与回归树)在分类任务中使用基尼指数选择分裂属性,在回归任务中使用平方误差。可以处理分类和回归问题,能够处理离散型和连续型数据。
决策树算法的特点:
- 决策树的优点:
- 直观,便于理解
- 小规模数据集有效
- 执行效率高,执行只需要一次构建,可反复使用
- 对数据的要求较低,不需要对数据进行标准化或归一化处理。它能够处理缺失值和不完整的数据集。
- 决策树的缺点:
- 决策树容易生成复杂的树结构,从而导致过拟合,尤其是在训练数据噪声较大时。虽然可以通过剪枝技术缓解这个问题,但是剪枝技术有可能会造成欠拟合,需要平衡。
- 处理连续变量不好,较难预测连续字段
- 数据的细微变化可能会导致树结构的巨大变化,使得结果不稳定
- 决策树算法只考虑单个特征的重要性,可能忽略特征之间的相互影响
3、决策树归纳算法
ID3算法起源于CLS概念学习系统,以信息熵的下降速度为选取测试属性的标准,即在每个节点选取尚未被用来划分的具有最高信息增益的属性作为划分标准,继续这个过程,直到生成决策树能完美分类训练样例。
输入:样本的集合S,属性集合A
输出:ID3决策树
4、树剪枝叶
问题1:什么是剪枝?
剪枝(Pruning)是一种在决策树算法中用于减少模型复杂度和防止过拟合的技术。通过剪枝,可以去除冗余或不重要的分支,简化决策树的结构,从而提高模型的泛化能力。
如下图将一颗子树的子节点全部删掉,利用叶子节点替换子树(实质上是后剪枝技术):
问题2:为什么要进行树的剪枝?
决策树是充分考虑了所有的数据点而生成的复杂树,有可能出现过拟合的情况,决策树越复杂,过拟合的程度会越高。通过剪枝后,能够降低决策树的复杂度,降低过拟合出现的概率。
问题3:怎样剪枝?
两种方案:先剪枝和后剪枝
- 先剪枝说白了就是提前结束决策树的增长,跟上述决策树停止生长的方法一样。
- 后剪枝是指在决策树生长完成之后再进行剪枝的过程。
5、实战案例(熟悉)
SparkML决策树实战Iris数据集案例
5.1 数据描述
iris以鸢尾花的特征作为数据来源,常用在分类操作中。该数据集由3种不同类型的鸢尾花的50个样本数据构成。其中的一个种类与另外两个种类是线性可分离的,后两个种类是非线性可分离的。
该数据集包含了5个属性:
- Sepal.Length(花萼长度),单位是cm;
- Sepal.Width(花萼宽度),单位是cm;
- Petal.Length(花瓣长度),单位是cm;
- Petal.Width(花瓣宽度),单位是cm;
- Class|Species种类:Iris Setosa(山鸢尾)、Iris Versicolour(杂色鸢尾),以及Iris Virginica(维吉尼亚鸢尾)。
样例数据:
sepal_length sepal_width petal_length petal_width class
5.1 3.5 1.4 0.2 Iris-setosa
4.9 3 1.4 0.2 Iris-setosa
4.7 3.2 1.3 0.2 Iris-setosa
4.6 3.1 1.5 0.2 Iris-setosa
5 3.6 1.4 0.2 Iris-setosa
5.4 3.9 1.7 0.4 Iris-setosa
5.2 代码
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.feature import StringIndexer, VectorAssembler
from pyspark.sql import SparkSession
import os
import pyspark.sql.functions as F
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'
if __name__ == '__main__':
# 1- 创建SparkSession对象
spark = SparkSession.builder.appName("决策树").master("local[*]").getOrCreate()
# 2- 数据输入
init_df = spark.read.csv(
path="file:///export/data/workspace/user_profile/test/SparkML_决策树案例/iris.csv",
sep=",",
encoding="UTF-8",
header=True,
inferSchema=True
)
init_df.show()
init_df.printSchema()
# 3- 数据处理
# 3.1- 特征预处理
# 区分数据中哪些字段是特征,哪些字段是目标值/标签;将目标值以数值进行替代,方便算法模型的训练
string_indexer = StringIndexer(inputCol="class",outputCol="label")
indexer_model = string_indexer.fit(init_df)
iris_df = indexer_model.transform(init_df)
iris_df.show(n=200)
# 将特征组合为向量
assembler = VectorAssembler(
inputCols=["sepal_length","sepal_width","petal_length","petal_width"],
outputCol="features"
)
assembler_df = assembler.transform(iris_df)
# assembler_df.show()
# 3.2- 数据集划分
"""
数据集划分目的?
为了对训练后的模型效果进行评估。训练集与测试集的比例一般是8:2/7:3
也就是对创建好的算法模型先使用训练集进行模型训练,接着再使用测试集评估模型的好坏
"""
trian_df,test_df = assembler_df.randomSplit(weights=[0.8,0.2],seed=1)
# 3.3- 决策树模型
# 模型实例化
model = DecisionTreeClassifier(
featuresCol="features",
labelCol="label",
predictionCol="prediction",
maxDepth=5
)
# 模型训练
classifier_model = model.fit(trian_df)
# 预测
result = classifier_model.transform(test_df)
result.show()
# 4- 数据输出
# 5- 释放资源
spark.stop()