vector_trans udf函数的开发及优化
一、背景
集团数据挖掘项目下,用户画像工程需要开发一些基础标签,如年龄、性别、婚姻状况、有车、有房、资产情况等。这些标签不能简单的使用集团下的各个数据作为最终评价结果,因为不同数据的置信度情况不一样。
因此我们需要使用决策树+XGBoost等模型来综合预测用户标签。
在我自己已经实现的决策树+算法模型预测用户标签的项目下,需要保证XGBoost模型输入的数据为一个不同特征数据转换而来的向量数组。下边贴一段scala代码:
//主要做的是将app_list特征数据转换成XGBoost需要的向量数组
data.map(row => {
val appList = row.getAs[String]("app_list")
if(null == appList || appList.length == 0) {
(row.getString(0), row.getString(1), row.getString(2),
row.getString(3), row.getString(4), null)
} else {
val arr = appList.split("|")
val map = new mutable.LinkedHashMap[String, Double]() ++= appMap
arr.foreach(i => if (map.contains(i)) map(i) = 1d)
(row.getString(0), row.getString(1), row.getString(2),
row.getString(3), row.getString(4), map.values.toArray)
}
}).toDF("id", "finance_age", "xinan_age", "resume_age", "umc_age", "model_data")
为了减少业务逻辑对抽象工程的影响,我建议把这段代码放到前一段ETL去做,而我这边的决策树项目本身只做数据集的分类计算的工作,这样可以让工程本身的扩展性大大提高。于是接下来便是怎么处理几百上千各特征在分布式环境下高效遍历的问题。我想到了自定义UDF。
二、实现
代码实现其实不难,下边举个粒子:
str1:"王者荣耀|i漫游服务|qq音乐"
str2:"王者荣耀|爱奇艺|i漫游服务|qq音乐|seetong|天猫精灵|qq邮箱"
str1是我们某个用户的app软件列表,str2是我们模型需要的特征列表。
我们想要得到的结果为:
out:Array[1.0,0.0,1.0,1.0,,0.0,0.0,0.0]
意思是在str2中某个位置如果存在,那这个位置的编码为1,否则为0。这样就把特征转换成了向量数组。
udf代码如下:
package com.bj58.fbuudf.trans;
import org.apache.hadoop.hive.ql.exec.UDF;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 遍历传入数组,将其转为目标向量数组(XGBoost模型)
* 入参:【String, String, String】:待转换数组字符串,目标数组字符串,分隔符
* 例:select vector_trans("1,6", "1,2,3,4,5,6", ",")
* 返回值:【Array<Double>】:目标向量数组
* 例:[1.0,0.0,0.0,0.0,0.0,1.0]
*
* @author yangfan
* @since 2021/3/18
* @version 1.0.0 init
*/
public class ModelVectorTransUdf extends UDF {
private Map<String, Double> targetMap;
public List<Double> evaluate(String str1,
@Nonnull String str2,
@Nonnull String regex) {
if (null == str1 || str1.length() == 0) {
return null;
}
if (null == targetMap) {
targetMap = new LinkedHashMap<>();
for (String s : str2.split(regex)) {
targetMap.put(s, 0d);
}
}
Map<String, Double> map = new LinkedHashMap<String, Double>() {{
putAll(targetMap);
}};
for (String s : str1.split(regex)) {
if (map.containsKey(s)) {
map.put(s, 1d);
}
}
return new ArrayList<>(map.values());
}
public static void main(String[] args) {
List<Double> result = new ModelVectorTransUdf()
.evaluate("1,6", "1,2,3,4,5,6", ",");
System.out.println(result);
}
}
三、优化
其实优化已经提现在udf代码中了,这里提一下。根据实际情况,参数str2其实是模型数组,同一个模型下,这个字符串是不变的。因此可以在第一次传入后将其转换成LinkedHashMap<String, Double>缓存下来,这样下次再计算时,就不用每次都去解析字符串了。至于为什么是LinkedHashMap,而不是HashMap、List结构,是因为模型向量数组中值的顺序是不能改变的,否则预测结果将不准确,而所以用LinkedHashMap而不是HashMap;前边说过我们的特征有几百上千个,那就需要在遍历str1数组时,效率越高越好,而Map的查找时间复杂度为O(1),所以不用List结构。那就说这么多吧~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。