一、前述
經過之前的訓練資料的構建可以得到所有特徵值為1的模型檔案,本文將繼續構建訓練資料特徵並構建模型。
二、詳細流程
將處理完成後的訓練資料匯出用做線下訓練的源資料(可以用Spark_Sql對資料進行處理)
insert overwrite local directory '/opt/data/traindata' row format delimited fields terminated by '\t' select * from dw_rcm_hitop_prepare2train_dm;
注:這裡是將資料匯出到本地,方便後面再本地模式跑資料,匯出模型資料。這裡是方便演示真正的生產環境是直接用指令碼提交spark任務,從hdfs取資料結果仍然在hdfs,再用ETL工具將訓練的模型結果檔案輸出到web專案的檔案目錄下,用來做新的模型,web專案設定了定時更新模型檔案,每天按時讀取新模型檔案
三、程式碼詳解
package com.bjsxt.data
import java.io.PrintWriter
import org.apache.log4j.{ Level, Logger }
import org.apache.spark.mllib.classification.{ LogisticRegressionWithLBFGS, LogisticRegressionModel, LogisticRegressionWithSGD }
import org.apache.spark.mllib.linalg.SparseVector
import org.apache.spark.mllib.optimization.SquaredL2Updater
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.{ SparkContext, SparkConf }
import scala.collection.Map
/**
* Created by root on 2016/5/12 0012.
*/
class Recommonder {
}
object Recommonder {
def main(args: Array[String]) {
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
val conf = new SparkConf().setAppName("recom").setMaster("local[*]")
val sc = new SparkContext(conf)
//載入資料,用\t分隔開
val data: RDD[Array[String]] = sc.textFile("d:/result").map(_.split("\t"))
println("data.getNumPartitions:" + data.getNumPartitions) //如果檔案在本地的話,預設是32M的分片
// -1 Item.id,hitop_id85:1,Item.screen,screen2:1 一行資料格式
//得到第一列的值,也就是label
val label: RDD[String] = data.map(_(0))
println(label)
//sample這個RDD中儲存的是每一條記錄的特徵名
val sample: RDD[Array[String]] = data.map(_(1)).map(x => {
val arr: Array[String] = x.split(";").map(_.split(":")(0))
arr
})
println(sample)
// //將所有元素壓平,得到的是所有分特徵,然後去重,最後索引化,也就是加上下標,最後轉成map是為了後面查詢用
val dict: Map[String, Long] = sample.flatMap(x =>x).distinct().zipWithIndex().collectAsMap()
//得到稀疏向量
val sam: RDD[SparseVector] = sample.map(sampleFeatures => {
//index中儲存的是,未來在構建訓練集時,下面填1的索引號集合
val index: Array[Int] = sampleFeatures.map(feature => {
//get出來的元素程式認定可能為空,做一個型別匹配
val rs: Long = dict.get(feature) match {
case Some(x) => x
}
//非零元素下標,轉int符合SparseVector的建構函式
rs.toInt
})
//SparseVector建立一個向量
new SparseVector(dict.size, index, Array.fill(index.length)(1.0)) //通過這行程式碼,將哪些地方填1,哪些地方填0
})
//mllib中的邏輯迴歸只認1.0和0.0,這裡進行一個匹配轉換
val la: RDD[LabeledPoint] = label.map(x => {
x match {
case "-1" => 0.0
case "1" => 1.0
}
//標籤組合向量得到labelPoint
}).zip(sam).map(x => new LabeledPoint(x._1, x._2))
// val splited = la.randomSplit(Array(0.1, 0.9), 10)
//
// la.sample(true, 0.002).saveAsTextFile("trainSet")
// la.sample(true, 0.001).saveAsTextFile("testSet")
// println("done")
//邏輯迴歸訓練,兩個引數,迭代次數和步長,生產常用調整引數
val lr = new LogisticRegressionWithSGD()
// 設定W0截距
lr.setIntercept(true)
// // 設定正則化
// lr.optimizer.setUpdater(new SquaredL2Updater)
// // 看中W模型推廣能力的權重
// lr.optimizer.setRegParam(0.4)
// 最大迭代次數
lr.optimizer.setNumIterations(10)
// 設定梯度下降的步長,學習率
lr.optimizer.setStepSize(0.1)
val model: LogisticRegressionModel = lr.run(la)
//模型結果權重
val weights: Array[Double] = model.weights.toArray
//將map反轉,weights相應下標的權重對應map裡面相應下標的特徵名
val map: Map[Long, String] = dict.map(_.swap)
//模型儲存
// LogisticRegressionModel.load()
// model.save()
//輸出
val pw = new PrintWriter("model");
//遍歷
for(i<- 0 until weights.length){
//通過map得到每個下標相應的特徵名
val featureName = map.get(i)match {
case Some(x) => x
case None => ""
}
//特徵名對應相應的權重
val str = featureName+"\t" + weights(i)
pw.write(str)
pw.println()
}
pw.flush()
pw.close()
}
}
model檔案截圖如下:
各個特徵下面對應的權重:
將模型檔案和使用者歷史資料,和商品表資料載入到redis中去。
程式碼如下:
# -*- coding=utf-8 -*- import redis pool = redis.ConnectionPool(host='node05', port='6379',db=2) r = redis.Redis(connection_pool=pool) f1 = open('../data/ModelFile.txt') f2 = open('../data/UserItemsHistory.txt') f3 = open('../data/ItemList.txt') for i in list: lines = i.readlines(100) if not lines: break for line in lines: kv = line.split('\t') if i==f1: r.hset("rcmd_features_score", kv[0], kv[1]) if i == f2: r.hset('rcmd_user_history', kv[0], kv[1]) if i==f3: r.hset('rcmd_item_list', kv[0], line[:-2]) f1.close()
最終redis檔案中截圖如下: