本篇文章的開頭筆者提出一個疑問,何為數據科學,數據科學是做什么的?大家帶着這個疑問去讀接下來的這篇音樂推薦的公眾號。
從經驗上講,推薦引擎屬於大規模機器學習,在日常購物中大家或許深有體會,比如:你在淘寶上瀏覽了一些商品,或者購買了一些商品,那么淘寶就會根據你的偏好給你推薦一些其他類似的商品。然而,相比較其他機器學習算法,推薦引擎的輸出更加的直觀,有時候的推薦效果讓人吃驚。作為機器學習開篇文章,本篇文章會系統的介紹基於Audioscrobbler數據集的音樂推薦。
數據集介紹
Audioscrobbler數據集是一個公開發布的數據集,讀者可以在(http://www-etud.iro.umontreal.ca/~bergstj/audioscrobbler_data.html)網站獲取。數據集主要有三部分組成,user_artist_data.txt文件是主要的數據集文件記錄了約2420條用戶id、藝術家id以及用戶收聽藝術家歌曲的次數數據,包含141000個用戶和160萬個藝術家;artist_data.txt文件記錄了藝術家id和對應的名字;artist_alias.txt記錄了藝術家id和對應的別稱id。
推薦算法介紹
由於所選取的數據集只記錄了用戶和歌曲之間的交互情況,除了藝術家名字之外沒有其他信息。因此要找的學習算法不需要用戶和藝術家的屬性信息,這類算法通常被稱為協同過濾。如果根據兩個用戶的年齡相同來判斷他們可能具有相似的偏好,這不叫協同過濾。相反,根據兩個用戶播放過許多相同歌曲來判斷他們可能都喜歡某首歌,這是協調過濾。
本篇所用的算法在數學上稱為迭代最小二乘,把用戶播放數據當成矩陣A,矩陣低i行第j列上的元素的值,代表用戶i播放藝術家j的音樂。矩陣A是稀疏的,絕大多數元素是0,算法將A分解成兩個小矩陣X和Y,既A=XYT,X代表用戶特征矩陣,Y代表特征藝術家矩陣。兩個矩陣的乘積當做用戶-藝術家關系矩陣的估計。可以通過下邊一組圖直觀的反映:
現在假如有5個聽眾,音樂有5首,那么A是一個5*5的矩陣,假如評分如下:

圖2.1 用戶訂閱矩陣
假如d是三個屬性,那么X的矩陣如下:

圖2.2 用戶-特征矩陣
Y的矩陣如下:

圖2.3 特征-電影矩陣
實際的求解過程中通常先隨機的固定矩陣Y,則
,為提高計算效率,通常采用並行計算X的每一行,既
。得到X之后,再反求出Y,不斷的交替迭代,最終使得XYT與A的平方誤差小於指定閾值,停止迭代,得到最終的X(代表用戶特征矩陣)和Y矩陣(代表特征藝術家矩陣)。在根據最終X和Y矩陣結果,向用戶進行推薦。
ALS的Spark實現
Spark MLlib的ALS算法實現有點缺陷,要求用戶和產品的ID必須是數值型,並且是32位非負整數。在計算之前應該首先檢驗一下數據量。
1)數據預處理
過濾無效的用戶藝術家ID和名字行,將格式不正確的數據行剔除掉。
def buildArtistByID(rawArtistData: Dataset[String]): DataFrame = {
rawArtistData.flatMap { line =>
val (id, name) = line.span(_ != '\t')
if (name.isEmpty) {
None
} else {
try {
Some((id.toInt, name.trim))
} catch {
case _: NumberFormatException => None
}
}
}.toDF("id", "name")
}
過濾藝術家id和對應的別名id,將格式拼寫錯誤的行剔除掉。
def buildArtistAlias(rawArtistAlias: Dataset[String]): Map[Int,Int] = {
rawArtistAlias.flatMap { line =>
val Array(artist, alias) = line.split('\t')
if (artist.isEmpty) {
None
} else {
Some((artist.toInt, alias.toInt))
}
}.collect().toMap
}
將數據轉換成Rating對象,Rating對象是ALS算法對“用戶-產品-值”的抽象。
def buildCounts(
rawUserArtistData: Dataset[String],
bArtistAlias: Broadcast[Map[Int,Int]]): DataFrame = {
rawUserArtistData.map { line =>
val Array(userID, artistID, count) = line.split(' ').map(_.toInt)
val finalArtistID = bArtistAlias.value.getOrElse(artistID, artistID)
(userID, finalArtistID, count)
}.toDF("user", "artist", "count")
}
2)模型構建
def model(
rawUserArtistData: Dataset[String],
rawArtistData: Dataset[String],
rawArtistAlias: Dataset[String]): Unit = {
val bArtistAlias = spark.sparkContext.broadcast(buildArtistAlias(rawArtistAlias)) //藝術家別名數據
val trainData = buildCounts(rawUserArtistData, bArtistAlias).cache() //將數據轉換成需要的格式
val model = new ALS().
setSeed(Random.nextLong()).
setImplicitPrefs(true).
setRank(10).
setRegParam(0.01).
setAlpha(1.0).
setMaxIter(5).
setUserCol("user").
setItemCol("artist").
setRatingCol("count").
setPredictionCol("prediction").
fit(trainData)
trainData.unpersist()
model.userFactors.select("features").show(truncate = false)
val userID = 2093760
val existingArtistIDs = trainData.
filter($"user" === userID).
select("artist").as[Int].collect()
val artistByID = buildArtistByID(rawArtistData)
artistByID.filter($"id" isin (existingArtistIDs:_*)).show()
val topRecommendations = makeRecommendations(model, userID, 5)
topRecommendations.show()
val recommendedArtistIDs = topRecommendations.select("artist").as[Int].collect()
artistByID.filter($"id" isin (recommendedArtistIDs:_*)).show()
model.userFactors.unpersist()
model.itemFactors.unpersist()
}
本篇文章主要對ALS音樂推薦進行簡單的介紹,下一篇會對模型的參數,以及模型的推薦效果進行評估,並且會對推薦結果進行優化。
備注:如果文中排版出現錯亂,請點擊https://mp.weixin.qq.com/s/aqF38rDQdT35YrLAyLm-nA
更多精彩內容,歡迎掃碼關注以下微信公眾號:大數據技術宅。大數據、AI從關注開始

