@R星校长
推荐系统数据处理首先是将 Hive 中的用户 app 历史下载表与 app 浏览信息表按照设备 id 进行关联,然后将关联数据使用 python 文件进行处理,将数据预处理为 label 和 feature 两列的临时数据,后期经过处理转换成逻辑回归 模型的训练集,进而得到模型文件。
1. 创建临时表
创建处理数据时所需要的临时表
1. CREATE TABLE IF NOT EXISTS tmp_dw_rcm_hitop_prepare2train_dm
2. (
3. device_id STRING,
4. label STRING,
5. hitop_id STRING,
6. screen STRING,
7. ch_name STRING,
8. author STRING,
9. sversion STRING,
10. mnc STRING,
11. interface STRING,
12. designer STRING,
13. is_safe INT,
14. icon_count INT,
15. update_date STRING,
16. stars DOUBLE,
17. comment_num INT,
18. font STRING,
19. price INT,
20. file_size INT,
21. ischarge SMALLINT,
22. dlnum INT,
23. idlist STRING,
24. device_name STRING,
25. pay_ability STRING
26. )row format delimited fields terminated by '\t';
27.
最终保存训练集的表
1. CREATE TABLE IF NOT EXISTS dw_rcm_hitop_prepare2train_dm
2. (
3. label STRING,
4. features STRING
5. )row format delimited fields terminated by '\t';
6.
2. 训练数据预处理过程
首先将数据从正负例样本和用户历史下载表数据加载到临时表中:
1. INSERT OVERWRITE TABLE tmp_dw_rcm_hitop_prepare2train_dm
2. SELECT
3. t2.device_id,
4. t2.label,
5. t2.hitop_id,
6. t2.screen,
7. t2.ch_name,
8. t2.author,
9. t2.sversion,
10. t2.mnc,
11. t2.interface,
12. t2.designer,
13. t2.is_safe,
14. t2.icon_count,
15. to_date(t2.update_time),
16. t2.stars,
17. t2.comment_num,
18. t2.font,
19. t2.price,
20. t2.file_size,
21. t2.ischarge,
22. t2.dlnum,
23. t1.devid_applist,
24. t1.device_name,
25. t1.pay_ability
26. FROM
27. (
28. SELECT
29. device_id,
30. devid_applist,
31. device_name,
32. pay_ability
33. FROM
34. dw_rcm_hitop_userapps_dm
35. ) t1
36. RIGHT OUTER JOIN
37. (
38. SELECT
39. device_id,
40. label,
41. hitop_id,
42. screen,
43. ch_name,
44. author,
45. sversion,
46. IF (mnc IN ('00','01','02','03','04','05','06','07'), mnc,'x') AS mnc,
47. interface,
48. designer,
49. is_safe,
50. IF (icon_count <= 5,icon_count,6) AS icon_count,
51. update_time,
52. stars,
53. IF ( comment_num IS NULL,0,
54. IF ( comment_num <= 10,comment_num,11)) AS comment_num,
55. font,
56. price,
57. IF (file_size <= 2*1024*1024,2,
58. IF (file_size <= 4*1024*1024,4,
59. IF (file_size <= 6*1024*1024,6,
60. IF (file_size <= 8*1024*1024,8,
61. IF (file_size <= 10*1024*1024,10,
62. IF (file_size <= 12*1024*1024,12,
63. IF (file_size <= 14*1024*1024,14,
64. IF (file_size <= 16*1024*1024,16,
65. IF (file_size <= 18*1024*1024,18,
66. IF (file_size <= 20*1024*1024,20,21)))))))))) AS file_size,
67. ischarge,
68. IF (dlnum IS NULL,0,
69. IF (dlnum <= 50,50,
70. IF (dlnum <= 100,100,
71. IF (dlnum <= 500,500,
72. IF (dlnum <= 1000,1000,
73. IF (dlnum <= 5000,5000,
74. IF (dlnum <= 10000,10000,
75. IF (dlnum <= 20000,20000,20001)))))))) AS dlnum
76. FROM
77. dw_rcm_hitop_sample2learn_dm
78. ) t2
79. ON (t1.device_id = t2.device_id);
1. python文件预处理数据
针对 Hive 中 “tmp_dw_rcm_hitop_prepare2train_dm” 数据,可以使用 Hive 自定义函数进行预处理,得到逻辑回归模型的训练集,这种方式需要编写代码,并且打包上传集群处理数据。这里,我们也可以在 Hive 中直接使用 python 对 Hive 中的数据进行预处理。
将 python 文件加载到 Hive 中:
1. ADD FILE /opt/sxt/recommender/script/dw_rcm_hitop_prepare2train_dm.py;
2.
可以通过 list files; 查看是不是 python 文件加载到了 hive:
在 hive 中使用 python 脚本处理数据的原理:Hive 会以输出流的形式将数据交给 python 脚本,python 脚本以输入流的形式来接受数据,接受来数据以后,在 python 中就可以一行行做一系列的数据处理,处理完毕后,又以输出流的形式交给 Hive,交给了 hive 就说明了就处理后的数据成功保存到 hive 表中。
2. python脚本内容
1. import sys
2.
3. if __name__ == "__main__":
4. # random.seed(time.time())
5. for l in sys.stdin:
6. d = l.strip().split('\t')
7. if len(d) != 21:
8. continue
9. # Extract data from the line
10. label = d.pop(0)
11. hitop_id = d.pop(0)
12. screen = d.pop(0)
13. ch_name = d.pop(0)
14. author = d.pop(0)
15. sversion = d.pop(0)
16. mnc = d.pop(0)
17. interface = d.pop(0)
18. designer = d.pop(0)
19. icon_count = d.pop(0)
20. update_date = d.pop(0)
21. stars = d.pop(0)
22. comment_num = d.pop(0)
23. font = d.pop(0)
24. price = d.pop(0)
25. file_size = d.pop(0)
26. ischarge = d.pop(0)
27. dlnum = d.pop(0)
28.
29. hitopids = d.pop(0)
30. device_name = d.pop(0)
31. pay_ability = d.pop(0)
32.
33. # Construct feature vector
34. features = []
35. features.append(("Item.id,%s" % hitop_id, 1))
36. features.append(("Item.screen,%s" % screen, 1))
37. features.append(("Item.name,%s" % ch_name, 1))
38. features.append(("Item.author,%s" % author, 1))
39. features.append(("Item.sversion,%s" % sversion, 1))
40. features.append(("Item.network,%s" % mnc, 1))
41. features.append(("Item.dgner,%s" % designer, 1))
42. features.append(("Item.icount,%s" % icon_count, 1))
43. features.append(("Item.stars,%s" % stars, 1))
44. features.append(("Item.comNum,%s" % comment_num,1))
45. features.append(("Item.font,%s" % font,1))
46. features.append(("Item.price,%s" % price,1))
47. features.append(("Item.fsize,%s" % file_size,1))
48. features.append(("Item.ischarge,%s" % ischarge,1))
49. features.append(("Item.downNum,%s" % dlnum,1))
50.
51. #User.Item and User.Item*Item
52. idlist = hitopids.split(',')
53. flag = 0;
54. for id in idlist:
55. features.append(("User.Item*Item,%s" % id +'*'+hitop_id, 1))
56. flag += 1
57. if flag >= 3:
58. break;
59.
60. # Output
61.
62. output = "%s\t%s" % (label, ";".join([ "%s:%d" % (f, v) for f, v in features ]))
63. print(output)
64.
3. python预处理数据使用
1. INSERT OVERWRITE TABLE dw_rcm_hitop_prepare2train_dm
2. SELECT
3. TRANSFORM (t.*)
4. USING 'python dw_rcm_hitop_prepare2train_dm.py'
5. AS (label,features)
6. FROM
7. (
8. SELECT
9. label,
10. hitop_id,
11. screen,
12. ch_name,
13. author,
14. sversion,
15. mnc,
16. interface,
17. designer,
18. icon_count,
19. update_date,
20. stars,
21. comment_num,
22. font,
23. price,
24. file_size,
25. ischarge,
26. dlnum,
27. idlist,
28. device_name,
29. pay_ability
30. FROM
31. tmp_dw_rcm_hitop_prepare2train_dm
32. ) t;
4. 导出数据
将 “dw_rcm_hitop_prepare2train_dm” 表中的数据导入到本地处理,这里可以直接在集群中使用 SparkMLlib 直接处理,为了方便演示,将数据导入到本地处理。
1. insert overwrite local directory '/opt/data/traindata' row format delimited fields terminated by '\t' select * from dw_rcm_hitop_prepare2train_dm;
2.
注:这里是将数据导出到本地,方便后面再本地模式跑数据,导出模型数据。这里是方便演示真正的生产环境是直接用脚本提交 spark 任务,从 Hive 中获取数据经过 Spark 处理得到模型文件,将模型数据写往 Redis 中。
1. 模型训练代码
模型训练代码参照 scala 文件:Recommonder.scala
1. Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
2. val conf = new SparkConf().setAppName("recommonder").setMaster("local[*]")
3. val sc = new SparkContext(conf)
4. //加载数据,用\t分隔开
5. val data: RDD[Array[String]] = sc.textFile("./traindata").map(_.split("\t")).sample(false,0.1,100L)
6.
7. //得到第一列的值,也就是label
8. val label: RDD[String] = data.map(_(0))
9.
10. //sample这个RDD中保存的是每一条记录的特征名
11. //-1 Item.id,hitop_id53:1;Item.screen,screen6:1;Item.name,ch_name80:1;Item.author,author1:1
12. val sample: RDD[Array[String]] = data.map(_(1)).map(x => {
13. val arr: Array[String] = x.split(";").map(_.split(":")(0))
14. arr
15. })
16. //将所有元素压平,得到的是所有分特征,然后去重,最后索引化,也就是加上下标,最后转成map是为了后面查询用
17. //dict 是所有数据的所有不重复的特征
18. val allFeaturesMap: Map[String, Long] = sample.flatMap(x =>x).distinct().zipWithIndex().collectAsMap()
19. //得到稀疏向量,为每条数据的features,与dict对比,缺少的特征补成0
20. val sam: RDD[SparseVector] = sample.map((sampleFeatures :Array[String])=> {
21. //index中保存的是,未来在构建训练集时,下面填1的索引号集合
22. val currentOneInfoAllFeatureIndexs: Array[Int] = sampleFeatures.map(feature => {
23. //get出来的元素程序认定可能为空,做一个类型匹配
24. val currentFeatureIndex: Long = allFeaturesMap.get(feature).get
25. //非零元素下标,转int符合SparseVector的构造函数
26. currentFeatureIndex.toInt
27. })
28. //SparseVector创建一个向量
29. new SparseVector(allFeaturesMap.size, currentOneInfoAllFeatureIndexs, Array.fill(currentOneInfoAllFeatureIndexs.length)(1.0))
30. })
31.
32. //mllib中的逻辑回归只认1.0和0.0,这里进行一个匹配转换
33. val trainData: RDD[LabeledPoint] = label.map(x => {
34. x match {
35. case "-1" => 0.0
36. case "1" => 1.0
37. }
38. //标签组合向量得到labelPoint
39. }).zip(sam).map(tuple => new LabeledPoint(tuple._1, tuple._2.toDense))
40.
41. //逻辑回归训练,两个参数,迭代次数和步长,生产常用调整参数
42. val model = new LogisticRegressionWithLBFGS()
43. .setNumClasses(2)
44. .setIntercept(true)
45. .run(trainData)
46.
47. //模型结果权重
48. val weights: Array[Double] = model.weights.toArray
49. //将map反转,weights相应下标的权重对应map里面相应下标的特征名
50. val map: Map[Long, String] = allFeaturesMap.map(_.swap)
51. val pw = new PrintWriter("./model");
52. for(i<- 0 until weights.length){
53. //通过map得到每个下标相应的特征名
54. val featureName = map.get(i)match {
55. case Some(feature) => feature
56. case None => ""
57. }
58. //特征名对应相应的权重
59. val str = featureName+"\t" + weights(i)
60. pw.write(str)
61. pw.println()
62. }
63. pw.flush()
64. pw.close()
65.
2. 将数据导入到Redis
将 app 基本信息表、app 历史下载表、app 浏览下载表导入到 Redis 中,供后期 dubbo 推荐服务使用。
1. import redis
2.
3. # 将特征值模型文件数据存入redis数据库,将用户历史下载数据存入redis,将app基本描述商品词表存入redis数据库
4. pool = redis.ConnectionPool(host='mynode4', port='6379', db=2)
5. r = redis.Redis(connection_pool=pool)
6.
7. f = open('./ModelFile.txt', "rb")
8. while True:
9. lines = f.readlines(100)
10. if not lines:
11. break
12. for line in lines:
13. kv = line.decode("utf-8").split('\t')
14. r.hset("rcmd_features_score", kv[0], kv[1])
15.
16. f = open('./UserItemsHistory.txt', "rb")
17. while True:
18. lines = f.readlines(100)
19. if not lines:
20. break
21. for line in lines:
22. kv = line.decode("utf-8").split('\t')
23. r.hset('rcmd_user_history', kv[0], kv[1])
24.
25. f = open('./ItemList.txt', "rb")
26. while True:
27. lines = f.readlines(100)
28. if not lines:
29. break
30. for line in lines:
31. kv = line.decode("utf-8").split('\t')
32. # line[:-2] 取line 字符串的开头到倒数第二个的位置 数据,含头不含尾,也就是-2 就是将s 字符串中倒数后两个字符删除,常用在从文本读入数据的时候消除换行符的影响
33. r.hset('rcmd_item_list', kv[0], line[:-2])
34. print('all finished...')
35. f.close()
36.
利用 dubbo 实现推荐服务,核心代码如下:
1. public List<String> getRcmdList(String uid) {
2.
3. // 获得数据库连接
4. Jedis jedis = new Jedis("mynode4", 6379);
5. jedis.select(2);
6. // 从用户历史下载表来获取最近下载
7. String downloads = jedis.hget("rcmd_user_history", uid);
8. String[] downloadList = downloads.split(",");
9.
10. // 获取所有appID列表
11. Set<String> appList = jedis.hkeys("rcmd_item_list");
12.
13. // 存储总的特征分值
14. Map<String, Double> scoresMap = new HashMap<String, Double>();
15.
16. // 分别计算所有应用的总权重
17. for (String appId : appList) {
18. if(Arrays.asList(downloadList).contains(appId)) {
19. continue;
20. }
21. // 计算关联权重
22. double relativeFeatureScore = getRelativeFeatureScore(appId, downloadList, jedis);
23. updateScoresMap(scoresMap, appId, relativeFeatureScore);
24. // 计算基本权重
25. double basicFeatureScore = getBasicFeatureScore(appId, jedis);
26. updateScoresMap(scoresMap, appId, basicFeatureScore);
27. }
28.
29. //这里将map.entrySet()转换成list
30. List<Map.Entry<String, Double>> list = new ArrayList<Map.Entry<String, Double>>(scoresMap.entrySet());
31. //然后通过比较器来实现排序
32. Collections.sort(list, new Comparator<Map.Entry<String, Double>>() {
33. //降序排序
34. public int compare(Map.Entry<String, Double> entry1,
35. Map.Entry<String, Double> entry2) {
36. return -entry1.getValue().compareTo(entry2.getValue());
37. }
38. });
39. // 打印分值
40. for (Map.Entry<String, Double> mapping : list) {
41. System.out.println(mapping.getKey() + ":" + mapping.getValue());
42. }
43.
44. // 取前10个appID返回
45. List<String> result = new ArrayList<>();
46. int count = 0;
47. for (Map.Entry<String, Double> mapping : list) {
48. count++;
49. result.add(mapping.getKey());
50. if(count==10){
51. break;
52. }
53. }
54.
55. jedis.close();
56. return result;
57. }
58.