spark音乐推荐系统源码 协同过滤音乐推荐 技术栈:springboot vue 协同过滤算法 算法:als基于模型协同过滤 用户行为:近一年听歌日志与收藏记录,听一次歌算1分,收藏算5分

搞音乐推荐系统最带劲的就是看着算法从用户行为里挖出宝藏。这次用SpringBoot+Vue搭了个推荐系统,核心算法选了ALS协同过滤,处理用户近一年的听歌日志和收藏记录。听一次歌算1分,收藏直接给5分,这权重设置得够狠吧?

数据炼金术

原始日志长得像这样:

user_id | song_id | action_type | timestamp
1024    | 8876    | play         | 2023-03-12 14:23
1024    | 7765    | collect      | 2023-03-12 14:25

用Spark做数据清洗时,发现个有趣现象——某些用户收藏了从没听过的歌。处理逻辑得加个判断:只有听过才能收藏。下面这段Scala代码负责生成训练数据:

val rawData = spark.read.parquet("hdfs://music_logs/*.parquet")
val validActions = rawData.filter(
  (col("action_type") === "collect" && col("song_id").isin(playedSongs)) || 
  col("action_type") === "play"
)

val ratingData = validActions.groupBy("user_id", "song_id")
  .agg(
    sum(when(col("action_type") === "collect", 5).otherwise(1)).alias("rating")
  )

这段代码干了三件事:过滤无效收藏、按用户-歌曲分组、用when表达式计算评分。有个坑点是collect动作可能比play多,所以用了isin(playedSongs)来约束。

ALS实战现场

spark音乐推荐系统源码 协同过滤音乐推荐 技术栈:springboot vue 协同过滤算法 算法:als基于模型协同过滤 用户行为:近一年听歌日志与收藏记录,听一次歌算1分,收藏算5分

矩阵分解就像把用户和歌曲的关系拆成两个矩阵相乘。Spark的ALS实现用起来是真香:

val als = new ALS()
  .setRank(10)
  .setMaxIter(15)
  .setRegParam(0.01)
  .setUserCol("user_id")
  .setItemCol("song_id")
  .setRatingCol("rating")

val model = als.fit(ratingData)

参数调教是个玄学活。rank=10是试出来的——设小了模型太笨,大了又过拟合。迭代15次后loss基本稳定,正则化参数0.01防止放飞自我。训练完记得处理冷启动:

model.setColdStartStrategy("drop")  // 直接过滤未知用户/歌曲

推荐服务落地

SpringBoot里搞了个推荐接口,核心逻辑是这样的:

@GetMapping("/recommend/{userId}")
public List<Song> getRecommendations(@PathVariable String userId) {
    Dataset<Row> userDF = spark.createDataFrame(
        Collections.singletonList(new User(userId)), 
        User.class
    );
    
    Dataset<Row> recs = model.recommendForUserSubset(userDF, 20);
    return convertToSongList(recs); // 转成前端需要的VO
}

有个性能优化点:别在每次请求都加载模型。实战中用了单例模式持有SparkSession和ALS模型,避免重复加载开销。

前端骚操作

Vue这边有个细节挺有意思——当推荐列表出现用户收藏过的歌时,得做去重处理:

watch: {
  recommendations(newVal) {
    this.filteredList = newVal.filter(
      song => !this.collectedSongs.has(song.id)
    )
  }
}

这比在后端过滤更灵活,毕竟用户收藏状态可能实时变化。

踩坑实录

  1. 内存爆过两次,后来发现是笛卡尔积计算时没控制分区数,加了个repartition(1000)才稳住
  2. 初期没处理隐式反馈,导致推荐结果全是热门歌曲。加了权重衰减因子才解决
  3. Vue的无限滚动加载和推荐分数排序打架,最后用了个混合排序策略才顺眼

这套系统上线后,有个意外发现——那些收藏分高的冷门歌曲推荐效果出奇好。可能用户觉得收藏是"真爱认证",这波评分规则算是赌对了。下次想试试把分享行为也加进去,说不定能挖出更多宝藏歌曲。

更多推荐