好久不寫技術文章了,以前寫了好多,發現都找不到了,不知道是不是某次ITeye的更新導致的,還是我自己喝多了刪除了。ALS(Alternating Least Squares)交替的最小二乘法,網絡上太多的文章去介紹了,同時,在電商領域也有非常多的實踐,根本不算是什么新鮮的玩意兒。本身就是利用和用戶的有效互動,然后通過內容(太喜歡內容這個詞了,包含了一切的含義,內容可以是具體的商品,可以是一篇討論性的文章,可以是一則廣告,可以是一段視頻;我們在商業環境下,把一切可以用來探知用戶興趣及與用戶產生互動的一切載體都稱之為內容,內容是我們了解用戶的開始,也是構建用戶體系完成用戶運營的根本。)ALS只是我們CF算法中的一種方式而已,沒有什么特別的。
上面這個公式,就是要找到用戶對隱藏特征的偏好與產品包含隱藏特征的程度的最優匹配結果。目標函數中U和V相互作用,先假設U的初始值U(0),這樣就將問題轉化成了一個最小二乘問題,可以根據U(0)可以計算出V(0),再根據V(0)計算出U(1),反復進行迭代,直到該函數收斂,
關于函數f(x)在點x0處的收斂定義。對于任意實數b>0,存在c>0,對任意x1,x2滿足0<|x1-x0|<c,0<|x2-x0|<c,有|f(x1)-f(x2)|<b。
其實就是是指會聚于一點,向某一值靠近。本身還是最小二乘法,一個利用線性方式解決問題的模式。這張圖是我能找到的說的最明白的,也是為啥要求解函數E = |y1-y’1|+|y2-y’2|+...+|yn-y’n|的最小值的原因。
- 簡單,算法簡單,實現簡單,團隊接受度高,學習曲線平緩(給團隊選技術的第一要務)
- 因為與用戶之間可能是正反饋,可能負反饋,可能沒有反饋,所以會選
- 前期開發時,對樣本量的要求沒有這么苛刻,當然需要架構師有對領域的深度認知,冷啟動過程幾乎沒有
當然這需要感謝Spark MLlib的作用。我們要實現的場景非常簡單,就是給用戶發內容,看用戶點選路徑,收集用戶使用行為信息,并根據過往的同類型用戶的使用情況進行內容推薦。
為了讓團隊能夠更有目標感,選用了以下基本的技術:
HBase(存儲用戶的行為信息,商品標簽,用戶標簽), Kafka(消息隊列),Zookeeper做了HBase/Kafka的集群,Spark(計算引擎),Redis(匹配結果),Mysql(基本數據存儲,比如商品/用戶)
大概的模式就是這么簡單:
貼段代碼看看評分是如何工作的:
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.recommendation.ALS
case class Rating(userId: Int, movieId: Int, rating: Float, timestamp: Long)
def parseRating(str: String): Rating = {
val fields = str.split("::")
assert(fields.size == 4)
Rating(fields(0).toInt, fields(1).toInt, fields(2).toFloat, fields(3).toLong)
}
val ratings = spark.read.textFile("data/mllib/als/prd_sample.txt")
.map(parseRating)
.toDF()
val Array(training, test) = ratings.randomSplit(Array(0.9, 0.2))
// Build the recommendation model using ALS on the training data
val als = new ALS()
.setMaxIter(5)
.setRegParam(0.01)
.setUserCol("userId")
.setItemCol("prdId")
.setRatingCol("rating")
val model = als.fit(training)
// Evaluate the model by computing the RMSE on the test data
// Note we set cold start strategy to 'drop' to ensure we don't get NaN evaluation metrics
model.setColdStartStrategy("drop")
val predictions = model.transform(test)
val evaluator = new RegressionEvaluator()
.setMetricName("rmse")
.setLabelCol("rating")
.setPredictionCol("prediction")
val rmse = evaluator.evaluate(predictions)
println(s"Root-mean-square error = $rmse")
// Generate top 10 prd(whatever it is) for each user
val userRecs = model.recommendForAllUsers(10)
// Generate top 10 user recommendations for each prd
val prdRecs = model.recommendForAllItems(10)
// Generate top 10 prd recommendations for a specified set of users
val users = ratings.select(als.getUserCol).distinct().limit(3)
val userSubsetRecs = model.recommendForUserSubset(users, 10)
// Generate top 10 user recommendations for a specified set of prd
val prdlist = ratings.select(als.getItemCol).distinct().limit(3)
val prdSubSetRecs = model.recommendForItemSubset(prdlist, 10)
上文提到的關于隱式反饋的設置,只需要“setImplicitPrefs(true)”即可。如果針對具體的預測,可以按照如下去做:
val user_product = trainingRDD.map {
case Rating(user, product, rate) =>
(user, product)
}
val predictions =
alsModel.predict(user_product).map {
case Rating(user, product, rate) =>
((user, product), rate)
}
其中,ComputeService就是記錄了用戶的正負反饋(沒有反饋的相應的行為權重比負反饋還要低,因為被認為是根本不關注)。實際使用過程中,Redis中的所有匹配結果,都會記錄到我們的Hive中,因為很多的關鍵性指標都需要在Hive中進行存儲和計算,還是那句話,推薦算法早就已經有了,所有的數字化媒體或電商都在用,一點不新鮮,但是每周的針對推薦結果的復盤才讓這些工具更有價值。HBase二級索引支持不好我們有Phoenix,Spark對實時事件處理不了我們還有Flink,其實重點的工作落在了用戶的屬性,內容的屬性標簽的拆分上,內容可能標準化程度更高一些,但是人的個性化就太多了。由于涉及商業項目的具體設計和實現,在這里不寫這么多了;實際過程中以下兩個過程極為重要:
- 召回階段:用些成本低、易實現、速度快的模型進行初步篩選;
- 排序階段:用更全面的數據、更精細的特征、更復雜的模型進行精挑細選
特別注意人工規則的植入,我們的ComputeService里面會放入人工規則的排序幫助作出更好的推薦判斷。
小結一下,ALS的使用過程,對訓練集合,特征值,迭代的參數設定及防過擬合參數都有要求(我們需要假設,假設是一切數據期望的前提,但是別使假設變得過度嚴格)。大年初一,還是祝大家新春快樂,萬事可期!