寫在前面: 博主是一名軟件工程系大數據應用開發專業大二的學生,暱稱來源於《愛麗絲夢遊仙境》中的Alice和自己的暱稱。作爲一名互聯網小白,
寫博客一方面是爲了記錄自己的學習歷程,一方面是希望能夠幫助到很多和自己一樣處於起步階段的萌新
。由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!個人小站:http://alices.ibilibili.xyz/ , 博客主頁:https://alice.blog.csdn.net/
儘管當前水平可能不及各位大佬,但我還是希望自己能夠做得更好,因爲一天的生活就是一生的縮影
。我希望在最美的年華,做最好的自己
!
在前面的幾篇博客中,博主不僅爲大家介紹了匹配型標籤和統計型標籤的開發流程,還爲大家科普了關於機器學習的一些"乾貨",包括但不限於KMeans算法等…本篇博客,我們將正式開發一個基於RFM模型的挖掘型標籤,對RFM不瞭解的朋友可以👉大數據【企業級360°全方位用戶畫像】之RFM模型和KMeans聚類算法~
我們本次需要開發的標籤是用戶價值。相信光聽這個標籤名,大家就應該清楚這種比較抽象的標籤,只能通過挖掘型算法去進行開發。
話不多說,我們來看看開發一個這樣的標籤需要經歷哪些步驟?
添加標籤
首先我們需要在用戶畫像項目中的web頁面添加這個需求所需要的四級標籤(標籤名)和五級標籤(標籤值)。
添加成功之後,我們可以在後臺數據庫中看到數據。
開發
頁面所需標籤和標籤值已經準備好了,剩下的就該我們擼代碼了。
準備pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>userprofile29</artifactId>
<groupId>cn.itcast.up</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Job</artifactId>
<properties>
<scala.version>2.11.8</scala.version>
<spark.version>2.2.0</spark.version>
<hbase.version>1.2.0-cdh5.14.0</hbase.version>
<solr.version>4.10.3-cdh5.14.0</solr.version>
<mysql.version>8.0.17</mysql.version>
<slf4j.version>1.7.21</slf4j.version>
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<build-helper-plugin.version>3.0.0</build-helper-plugin.version>
<scala-compiler-plugin.version>3.2.0</scala-compiler-plugin.version>
<maven-shade-plugin.version>3.2.1</maven-shade-plugin.version>
</properties>
<dependencies>
<!-- Spark -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.scalanlp</groupId>
<artifactId>breeze_2.11</artifactId>
<version>0.13</version>
</dependency>
<!-- HBase -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>${hbase.version}</version>
</dependency>
<!-- Solr -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
<version>${solr.version}</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>${solr.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>cn.itcast.up29</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-plugin.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java</source>
<source>src/main/scala</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<verbose>true</verbose>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala-compiler-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!--這裏要替換成jar包main方法所在類 -->
<mainClass>cn.itcast.up29.TestTag</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- 指定在打包節點執行jar包合併操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
</repositories>
</project>
代碼開發
這裏需要提及一點,因爲在之前寫的一篇介紹👉RFM模型和KMeans聚類算法的博客。最後在代碼演示階段,爲大家展示了利用KMeans算法計算鳶尾花所屬分類的一個小Demo,那一篇雖說每一步的註釋和實現的最終效果都在代碼中體現出來了,但沒有詳細地爲大家介紹代碼流程。所以,藉着本篇同樣爲挖掘型算法的一個經典案例,下面將好好爲大家介紹一下挖掘型標籤的開發流程。
1、繼承BaseModel,設置任務名稱,設置自己標籤的ID,調用exec,重寫getNewTag方法,getNewTag實現新標籤的製作
對於不清楚什麼是BaseModel
類的朋友,可以先去看看博主的這一篇博客👉標籤開發代碼抽取。因爲在開發不同類型的標籤過程中,存在着大量的代碼重複性冗餘,所以博主就在那一篇博客中,介紹瞭如何抽取標籤的過程,並將其命名爲BaseModel
。我們往後還想基於這個項目進行標籤的開發,只需要創建一個類,實現這個特質,然後就只需要編寫較少的核心部分代碼即可,可謂是十分的便捷了~
object TestModel extends BaseModel {
// 設置任務名稱
override def setAppName: String = "RFMModel"
// 設置用戶價值id
override def setFourTagId: String = "168"
override def getNewTag(spark: SparkSession, fiveTagDF: DataFrame, hbaseDF: DataFrame): DataFrame = {
}
}
2、根據傳入的hbase數據的DF,獲取出RFM三個數據
因爲我們計算的是用戶價值,符合我們之前提到的RFM模型
,所以我們需要分別針對這三個角度,將各自的數據求取出來。
//RFM三個單詞
val recencyStr: String = "recency"
val frequencyStr: String = "frequency"
val monetaryStr: String = "monetary"
// 特徵單詞
val featureStr: String = "feature"
val predictStr: String = "predict"
// 計算業務數據
// R(最後的交易時間到當前時間的距離)
// F(交易數量【半年/一年/所有】)
// M(交易總金額【半年/一年/所有】)
// 引入隱式轉換
import spark.implicits._
//引入java 和scala相互轉換
import scala.collection.JavaConverters._
//引入sparkSQL的內置函數
import org.apache.spark.sql.functions._
// 用於計算 R 數值
// 與當前時間的時間差 - 當前時間用於求訂單中最大的時間
val getRecency: Column = functions.datediff(current_timestamp(),from_unixtime(max("finishTime")))-300 as recencyStr
// 計算F的值
val getFrequency: Column = functions.count("orderSn") as frequencyStr
// 計算M數值 sum
val getMonetary: Column = functions.sum("orderAmount") as monetaryStr
// 由於每個用戶有多個訂單,所以計算一個用戶的RFM,需要使用用戶id進行分組
val getRFMDF: DataFrame = hbaseDF.groupBy("memberId")
.agg(getRecency, getFrequency, getMonetary)
getRFMDF.show(false)
/*
+---------+-------+---------+------------------+
|memberId |recency|frequency|monetary |
+---------+-------+---------+------------------+
|13822725 |10 |116 |179298.34 |
|13823083 |10 |132 |233524.17 |
|138230919|10 |125 |240061.56999999998|
*/
這裏,體貼的博主還將答案以註釋的形式標記在了上邊。大家可以參考一下喲~
3、歸一化【打分】
這裏需要解釋下,爲什麼需要進行數據的歸一化。由於三個數據的量綱(單位)不統一,所以無法直接計算,需要進行數據的歸一化。
這裏歸一化的方法,我們採用的是自定義方法,與之前鳶尾花的案例所直接調用的MinMaxScaler
還有是有差異的。
//現有的RFM 量綱不統一,需要執行歸一化 爲RFM打分
//R: 1-3天=5分,4-6天=4分,7-9天=3分,10-15天=2分,大於16天=1分
//F: ≥200=5分,150-199=4分,100-149=3分,50-99=2分,1-49=1分
//M: ≥20w=5分,10-19w=4分,5-9w=3分,1-4w=2分,<1w=1分
//計算R的分數
var getRecencyScore: Column =functions.when((col(recencyStr)>=1)&&(col(recencyStr)<=3),5)
.when((col(recencyStr)>=4)&&(col(recencyStr)<=6),4)
.when((col(recencyStr)>=7)&&(col(recencyStr)<=9),3)
.when((col(recencyStr)>=10)&&(col(recencyStr)<=15),2)
.when(col(recencyStr)>=16,1)
.as(recencyStr)
//計算F的分數
var getFrequencyScore: Column =functions.when(col(frequencyStr) >= 200, 5)
.when((col(frequencyStr) >= 150) && (col(frequencyStr) <= 199), 4)
.when((col(frequencyStr) >= 100) && (col(frequencyStr) <= 149), 3)
.when((col(frequencyStr) >= 50) && (col(frequencyStr) <= 99), 2)
.when((col(frequencyStr) >= 1) && (col(frequencyStr) <= 49), 1)
.as(frequencyStr)
//計算M的分數
var getMonetaryScore: Column =functions.when(col(monetaryStr) >= 200000, 5)
.when(col(monetaryStr).between(100000, 199999), 4)
.when(col(monetaryStr).between(50000, 99999), 3)
.when(col(monetaryStr).between(10000, 49999), 2)
.when(col(monetaryStr) <= 9999, 1)
.as(monetaryStr)
//計算RFM的分數
val getRFMScoreDF: DataFrame = getRFMDF.select('memberId ,getRecencyScore,getFrequencyScore,getMonetaryScore)
println("--------------------------------------------------")
//getRENScoreDF.show()
/* +---------+-------+---------+--------+
| memberId|recency|frequency|monetary|
+---------+-------+---------+--------+
| 13822725| 2| 3| 4|
| 13823083| 2| 3| 5|
|138230919| 2| 3| 5|
| 13823681| 2| 3| 4|
*/
4、將RFM的分數進行向量化
因爲我們接下來就要對RFM的數據就行KMeans聚類計算,爲了將RFM的數據轉換成與KMeans計算所要求數據格式相同,我們這裏還需要多一個操作,便是將上邊歸一化後的分數結果進行向量化。
val RFMFeature: DataFrame = new VectorAssembler()
.setInputCols(Array(recencyStr, frequencyStr, monetaryStr))
.setOutputCol(featureStr)
.transform(getRFMScoreDF)
RFMFeature.show()
/* +---------+-------+---------+--------+-------------+
| memberId|recency|frequency|monetary| feature|
+---------+-------+---------+--------+-------------+
| 13822725| 2| 3| 4|[2.0,3.0,4.0]|
| 13823083| 2| 3| 5|[2.0,3.0,5.0]|
|138230919| 2| 3| 5|[2.0,3.0,5.0]|
| 13823681| 2| 3| 4|[2.0,3.0,4.0]|
| 4033473| 2| 3| 5|[2.0,3.0,5.0]| */
5、數據分類
這裏我們終於調用上了KMeans聚類算法
,對數據進行分類。
val model: KMeansModel = new KMeans()
.setK(7) // 設置7類
.setMaxIter(5) // 迭代計算5次
.setFeaturesCol(featureStr) // 設置特徵數據
.setPredictionCol("featureOut") // 計算完畢後的標籤結果
.fit(RFMFeature)
// 將其轉換成 DF
val modelDF: DataFrame = model.transform(RFMFeature)
modelDF.show()
/*+---------+-------+---------+--------+-------------+----------+
| memberId|recency|frequency|monetary| feature|featureOut|
+---------+-------+---------+--------+-------------+----------+
| 13822725| 2| 3| 4|[2.0,3.0,4.0]| 1|
| 13823083| 2| 3| 5|[2.0,3.0,5.0]| 0|
|138230919| 2| 3| 5|[2.0,3.0,5.0]| 0|
| 13823681| 2| 3| 4|[2.0,3.0,4.0]| 1|*/
6、計算每個類別的價值,針對價值進行倒敘排序
這裏所謂的每種類別的價值,指的是每一箇中心點,也就是質心包含所有點的總和。
至於爲什麼需要倒序排序,是因爲我們不同的價值標籤值在數據庫中的rule是從0開始的,而將價值分類按照價值高低倒序排序後,之後我們獲取到分類索引時,從高到底的索引也是從0開始的,這樣我們後續進行關聯的時候就輕鬆很多。
//6、分類排序 遍歷所有的分類(0-6)
//獲取每個類別內的價值()中心點包含的所有點的總和就是這個類的價值
//model.clusterCenters.indices 據類中心角標
//model.clusterCenters(i) 具體的某一個類別(簇)
val clusterCentersSum: immutable.IndexedSeq[(Int, Double)] = for(i <- model.clusterCenters.indices) yield (i,model.clusterCenters(i).toArray.sum)
val clusterCentersSumSort: immutable.IndexedSeq[(Int, Double)] = clusterCentersSum.sortBy(_._2).reverse
clusterCentersSumSort.foreach(println)
/*
(4,11.038461538461538)
(0,10.0)
(1,9.0)
(3,8.0)
(6,6.0)
(5,4.4)
(2,3.0)
*/
7、對排序後的分類數據獲取角標
正如我們第六步所說的,我們這裏獲取到分類數據的角標,方便後續的關聯查詢。
// 獲取到每種分類及其對應的索引
val clusterCenterIndex: immutable.IndexedSeq[(Int, Int)] = for(a <- clusterCentersSumSort.indices) yield (clusterCentersSumSort(a)._1,a)
clusterCenterIndex.foreach(println)
/*
類別的價值從高到底
角標是從0-6
(4,0)
(0,1)
(1,2)
(3,3)
(6,4)
(5,5)
(2,6)
*/
8、排序後的數據與標籤系統內的五級標籤數據進行join
這裏我們在獲取到了排序後的數據後,將其與標籤系統內的五級標籤數據進行join。爲了後續我們方便查找調用,我們將join後的數據,封裝到了List集合。
val clusterCenterIndexDF: DataFrame = clusterCenterIndex.toDF("type","index")
// 開始join
val JoinDF: DataFrame = fiveTagDF.join(clusterCenterIndexDF,fiveTagDF.col("rule") === clusterCenterIndexDF.col("index"))
println("- - - - - - - -")
JoinDF.show()
/*+---+----+----+-----+
| id|rule|type|index|
+---+----+----+-----+
|169| 0| 4| 0|
|170| 1| 0| 1|
|171| 2| 1| 2|
|172| 3| 3| 3|
|173| 4| 6| 4|
|174| 5| 5| 5|
|175| 6| 2| 6|
+---+----+----+-----+*/
val fiveTageList: List[TagRule] = JoinDF.map(row => {
val id: String = row.getAs("id").toString
val types: String = row.getAs("type").toString
TagRule(id.toInt, types)
}).collectAsList() // 將DataSet轉換成util.List[TagRule] 這個類型遍歷時無法獲取id,rule數據
.asScala.toList
println("- - - - - - - -")
9、編寫UDF,實現標籤的開發計算
到了這一步,我們就可以編寫UDF函數,在函數中調用第八步所封裝的List集合對傳入參數進行一個匹配。然後我們在對KMeans聚合計算後的數據進行一個查詢的過程中,就可以調用UDF,實現用戶id和用戶價值分類id進行一個匹配。
// 需要自定義UDF函數
val getRFMTags: UserDefinedFunction = udf((featureOut: String) => {
// 設置標籤的默認值
var tagId: Int = 0
// 遍歷每一個五級標籤的rule
for (tagRule <- fiveTageList) {
if (tagRule.rule == featureOut) {
tagId = tagRule.id
}
}
tagId
})
val CustomerValueTag: DataFrame = modelDF.select('memberId .as("userId"),getRFMTags('featureOut).as("tagsId"))
CustomerValueTag.show(false)
10、返回最新計算的標籤
到了最後一步,就比較簡單了,我們只需要將第九步得到的結果返回即可。
CustomerValueTag
爲了方便大家閱讀,這裏我再貼上完整的源碼。
對代碼中有任何的疑問,歡迎在評論區留言或者後臺私信我都可以喲~
完整源碼
import com.czxy.base.BaseModel
import com.czxy.bean.TagRule
import org.apache.spark.ml.clustering.{KMeans, KMeansModel}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.{Column, DataFrame, SparkSession, functions}
import scala.collection.immutable
/*
* @Author: Alice菌
* @Date: 2020/6/22 09:18
* @Description:
此代碼用於計算 用戶畫像價值模型
*/
object RFMModel extends BaseModel{
// 設置任務名稱
override def setAppName: String = "RFMModel"
// 設置用戶價值id
override def setFourTagId: String = "168"
override def getNewTag(spark: SparkSession, fiveTagDF: DataFrame, hbaseDF: DataFrame): DataFrame = {
//fiveTagDF.show()
/*
+---+----+
| id|rule|
+---+----+
|169| 0|
|170| 1|
|171| 2|
|172| 3|
|173| 4|
|174| 5|
|175| 6|
+---+----+
*/
//hbaseDF.show()
/*
+---------+----------+--------------------+-----------+
| memberId|finishTime| orderSn|orderAmount|
+---------+----------+--------------------+-----------+
| 13823431|1564415022|gome_792756751164275| 2479.45|
| 4035167|1565687310|jd_14090106121770839| 2449.00|
| 4035291|1564681801|jd_14090112394810659| 1099.42|
| 4035041|1565799378|amazon_7877495617...| 1999.00|
*/
//RFM三個單詞
val recencyStr: String = "recency"
val frequencyStr: String = "frequency"
val monetaryStr: String = "monetary"
// 特徵單詞
val featureStr: String = "feature"
val predictStr: String = "predict"
// 計算業務數據
// R(最後的交易時間到當前時間的距離)
// F(交易數量【半年/一年/所有】)
// M(交易總金額【半年/一年/所有】)
// 引入隱式轉換
import spark.implicits._
//引入java 和scala相互轉換
import scala.collection.JavaConverters._
//引入sparkSQL的內置函數
import org.apache.spark.sql.functions._
// 用於計算 R 數值
// 與當前時間的時間差 - 當前時間用於求訂單中最大的時間
val getRecency: Column = functions.datediff(current_timestamp(),from_unixtime(max("finishTime")))-300 as recencyStr
// 計算F的值
val getFrequency: Column = functions.count("orderSn") as frequencyStr
// 計算M數值 sum
val getMonetary: Column = functions.sum("orderAmount") as monetaryStr
// 由於每個用戶有多個訂單,所以計算一個用戶的RFM,需要使用用戶id進行分組
val getRFMDF: DataFrame = hbaseDF.groupBy("memberId")
.agg(getRecency, getFrequency, getMonetary)
getRFMDF.show(false)
/*
+---------+-------+---------+------------------+
|memberId |recency|frequency|monetary |
+---------+-------+---------+------------------+
|13822725 |10 |116 |179298.34 |
|13823083 |10 |132 |233524.17 |
|138230919|10 |125 |240061.56999999998|
*/
//現有的RFM 量綱不統一,需要執行歸一化 爲RFM打分
//R: 1-3天=5分,4-6天=4分,7-9天=3分,10-15天=2分,大於16天=1分
//F: ≥200=5分,150-199=4分,100-149=3分,50-99=2分,1-49=1分
//M: ≥20w=5分,10-19w=4分,5-9w=3分,1-4w=2分,<1w=1分
//計算R的分數
var getRecencyScore: Column =functions.when((col(recencyStr)>=1)&&(col(recencyStr)<=3),5)
.when((col(recencyStr)>=4)&&(col(recencyStr)<=6),4)
.when((col(recencyStr)>=7)&&(col(recencyStr)<=9),3)
.when((col(recencyStr)>=10)&&(col(recencyStr)<=15),2)
.when(col(recencyStr)>=16,1)
.as(recencyStr)
//計算F的分數
var getFrequencyScore: Column =functions.when(col(frequencyStr) >= 200, 5)
.when((col(frequencyStr) >= 150) && (col(frequencyStr) <= 199), 4)
.when((col(frequencyStr) >= 100) && (col(frequencyStr) <= 149), 3)
.when((col(frequencyStr) >= 50) && (col(frequencyStr) <= 99), 2)
.when((col(frequencyStr) >= 1) && (col(frequencyStr) <= 49), 1)
.as(frequencyStr)
//計算M的分數
var getMonetaryScore: Column =functions.when(col(monetaryStr) >= 200000, 5)
.when(col(monetaryStr).between(100000, 199999), 4)
.when(col(monetaryStr).between(50000, 99999), 3)
.when(col(monetaryStr).between(10000, 49999), 2)
.when(col(monetaryStr) <= 9999, 1)
.as(monetaryStr)
// 2、計算RFM的分數
val getRFMScoreDF: DataFrame = getRFMDF.select('memberId ,getRecencyScore,getFrequencyScore,getMonetaryScore)
println("--------------------------------------------------")
//getRENScoreDF.show()
/* +---------+-------+---------+--------+
| memberId|recency|frequency|monetary|
+---------+-------+---------+--------+
| 13822725| 2| 3| 4|
| 13823083| 2| 3| 5|
|138230919| 2| 3| 5|
| 13823681| 2| 3| 4|
*/
// 3、將數據轉換成向量
val RFMFeature: DataFrame = new VectorAssembler()
.setInputCols(Array(recencyStr, frequencyStr, monetaryStr))
.setOutputCol(featureStr)
.transform(getRFMScoreDF)
RFMFeature.show()
/* +---------+-------+---------+--------+-------------+
| memberId|recency|frequency|monetary| feature|
+---------+-------+---------+--------+-------------+
| 13822725| 2| 3| 4|[2.0,3.0,4.0]|
| 13823083| 2| 3| 5|[2.0,3.0,5.0]|
|138230919| 2| 3| 5|[2.0,3.0,5.0]|
| 13823681| 2| 3| 4|[2.0,3.0,4.0]|
| 4033473| 2| 3| 5|[2.0,3.0,5.0]| */
// 4、數據分類
val model: KMeansModel = new KMeans()
.setK(7) // 設置7類
.setMaxIter(5) // 迭代計算5次
.setFeaturesCol(featureStr) // 設置特徵數據
.setPredictionCol("featureOut") // 計算完畢後的標籤結果
.fit(RFMFeature)
// 將其轉換成 DF
val modelDF: DataFrame = model.transform(RFMFeature)
modelDF.show()
/*+---------+-------+---------+--------+-------------+----------+
| memberId|recency|frequency|monetary| feature|featureOut|
+---------+-------+---------+--------+-------------+----------+
| 13822725| 2| 3| 4|[2.0,3.0,4.0]| 1|
| 13823083| 2| 3| 5|[2.0,3.0,5.0]| 0|
|138230919| 2| 3| 5|[2.0,3.0,5.0]| 0|
| 13823681| 2| 3| 4|[2.0,3.0,4.0]| 1|
截止到目前,用戶的分類已經完畢,用戶和對應的類別已經有了
缺少類別與標籤ID的對應關係
這個分類完之後,featureOut的 0-6 只表示7個不同的類別,並不是標籤中的 0-6 的級別
*/
modelDF.groupBy("featureOut")
.agg(max(col("recency")+col("frequency")+col("monetary")) as "max",
min(col("recency")+col("frequency")+col("monetary")) as "min").show()
/*
+----------+---+---+
|featureOut|max|min|
+----------+---+---+
| 1| 9| 9|
| 6| 6| 6|
| 3| 9| 7|
| 5| 5| 4|
| 4| 12| 11|
| 2| 3| 3|
| 0| 10| 10|
+----------+---+---+
*/
println("===========================================")
//5、分類排序 遍歷所有的分類(0-6)
//獲取每個類別內的價值()中心點包含的所有點的總和就是這個類的價值
//model.clusterCenters.indices 據類中心角標
//model.clusterCenters(i) 具體的某一個類別(簇)
val clusterCentersSum: immutable.IndexedSeq[(Int, Double)] = for(i <- model.clusterCenters.indices) yield (i,model.clusterCenters(i).toArray.sum)
val clusterCentersSumSort: immutable.IndexedSeq[(Int, Double)] = clusterCentersSum.sortBy(_._2).reverse
clusterCentersSumSort.foreach(println)
/*
(4,11.038461538461538)
(0,10.0)
(1,9.0)
(3,8.0)
(6,6.0)
(5,4.4)
(2,3.0)
*/
// 獲取到每種分類及其對應的索引
val clusterCenterIndex: immutable.IndexedSeq[(Int, Int)] = for(a <- clusterCentersSumSort.indices) yield (clusterCentersSumSort(a)._1,a)
clusterCenterIndex.foreach(println)
/*
類別的價值從高到底
角標是從0-6
(4,0)
(0,1)
(1,2)
(3,3)
(6,4)
(5,5)
(2,6)
*/
//6、分類數據和標籤數據join
// 將其轉換成DF
val clusterCenterIndexDF: DataFrame = clusterCenterIndex.toDF("type","index")
// 開始join
val JoinDF: DataFrame = fiveTagDF.join(clusterCenterIndexDF,fiveTagDF.col("rule") === clusterCenterIndexDF.col("index"))
println("- - - - - - - -")
JoinDF.show()
/*+---+----+----+-----+
| id|rule|type|index|
+---+----+----+-----+
|169| 0| 4| 0|
|170| 1| 0| 1|
|171| 2| 1| 2|
|172| 3| 3| 3|
|173| 4| 6| 4|
|174| 5| 5| 5|
|175| 6| 2| 6|
+---+----+----+-----+*/
val fiveTageList: List[TagRule] = JoinDF.map(row => {
val id: String = row.getAs("id").toString
val types: String = row.getAs("type").toString
TagRule(id.toInt, types)
}).collectAsList() // 將DataSet轉換成util.List[TagRule] 這個類型遍歷時無法獲取id,rule數據
.asScala.toList
println("- - - - - - - -")
//7、獲得數據標籤(udf)
// 需要自定義UDF函數
val getRFMTags: UserDefinedFunction = udf((featureOut: String) => {
// 設置標籤的默認值
var tagId: Int = 0
// 遍歷每一個五級標籤的rule
for (tagRule <- fiveTageList) {
if (tagRule.rule == featureOut) {
tagId = tagRule.id
}
}
tagId
})
val CustomerValueTag: DataFrame = modelDF.select('memberId .as("userId"),getRFMTags('featureOut).as("tagsId"))
println("*****************************************")
CustomerValueTag.show(false)
println("*****************************************")
//8、表現寫入hbase
CustomerValueTag
}
def main(args: Array[String]): Unit = {
exec()
}
}
如果程序運行完畢無誤,我們可以去Hbase中查看我們標籤是否寫入到test表中。
scan "test",{LIMIT => 10}
發現有用戶已經有了用戶價值的標籤值後,說明我們的標籤開發工作就完成了~~
結語
本篇博客,主要爲大家簡單介紹了用戶畫像項目中挖掘型標籤的開發流程,相信大家在看完這篇博客之後,對機器學習算法會更感興趣。博主後續呢,會爲大家帶來關於機器學習的面試題,各位小夥伴們,敬請期待😎
如果以上過程中出現了任何的紕漏錯誤,煩請大佬們指正😅
受益的朋友或對大數據技術感興趣的夥伴記得點贊關注支持一波🙏
希望我們都能在學習的道路上越走越遠😉