简单音乐推荐系统的设计与实现

本文提供两种简单的传统音乐推荐系统(next-songs 方向)的思路与实现。(数学原理和机器学习方法从略)

下文仅给出思路以及关键代码,完整代码实现见: https://github.com/cdfmlr/murecom-intro

1. 基于音频特征

分析音频特征,做基于内容的推荐(Content-Based Filtering,CBF)。

1.1 设计思路

一个喜欢巴赫的人可能也喜欢肖邦,所以一种自然的想法是,我们可以把音频送给机器进行学习,试图让它分别不同种类、风格的音乐。给定一首歌,送入训练好的模型,推荐出风格上最相近的其他歌曲。

song-classification.ipynb 实现了这种模型的训练。

加拿大维多利亚大学的 genres 数据集(http://opihi.cs.uvic.ca/sound/genres.tar.gz),提供了良好标注的不同种类音乐片段。

$ ls genres
blues     country   hiphop    metal     reggae
classical disco     jazz      pop       rock

我们把这些片段利用 librosa 库转化为梅尔频谱图(mel-spectrogram)。

(上图为数据集中 Hip-Hop 风格片段的平均频谱图)

把频谱送入一个一维卷积池化堆叠 + 全连接分类头的神经网络,训练,得到的模型即一个音乐风格检测器。

def cnn_model(input_shape):inputs = Input(input_shape)x = inputs# 一维卷积池化levels = 64for level in range(3):x = Conv1D(levels, 3, activation='relu')(x)x = BatchNormalization()(x)x = MaxPooling1D(pool_size=2, strides=2)(x)levels *= 2# x -> shape(128)x = GlobalMaxPooling1D()(x)# 计算类型标签的全连接网络for fc in range(2):x = Dense(256, activation='relu')(x)x = Dropout(0.5)(x)labels = Dense(10, activation='softmax')(x)model = Model(inputs=[inputs], outputs=[labels])# optimizer and compile modelsgd = SGD(learning_rate=0.0003, momentum=0.9, decay=1e-5, nesterov=True)model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])return modelmodel = cnn_model((128, 128))

训练得出的模型 song_classify.h5 可以很好的分类特征明显的音乐类型(例如古典乐),但对界限相对模糊的乐种(如摇滚乐)分类效果欠佳。

(分类结果的混淆矩阵)

利用这个模型,在 index-local-mp3s.ipynb 中实现了相似音乐的推荐。

具体的做法是,手动做了一个简单的数据集,选取一些个人常听的音乐,转化为同样品质的 mp3 文件。

(选取的音乐种类)

遍历处理这些文件,提取梅尔频谱图。

def process_mp3(path):signal, sr = librosa.load(path,res_type="kaiser_fast",offset=30,duration=30)melspec = librosa.feature.melspectrogram(signal, sr=sr).T[:1280, ]if len(melspec) != 1280:return Nonereturn {'path': path,'melspecs': np.asarray(np.split(melspec, 10))}# 对每个 MP3 的所有频谱进行索引
songs = [process_mp3(path) for path in tqdm(mp3s)]
songs = [song for song in songs if song]# 可以把他们连在一起,方便一批完成
inputs = []
for song in songs:inputs.extend(song['melspecs'])

接下来将预处理好的数据集送入训练好的模型。

由于我们只需要提取音频特征,而并不需要做分类,所以把模型最后基层的全连接分类头去掉,只留下前面的卷积特征提取层。输入音频频谱,输出一个 256 维的向量作为音乐的“特征向量”。

cnn_model = load_model('song_classify.h5')
vectorize_model = Model(inputs=cnn_model.input,outputs=cnn_model.layers[-4].output)
vectors = vectorize_model.predict(inputs)

建立一个无监督的最邻近模型,计算这些特征向量的相似度,也就其代表的 mp3 歌曲的相似度。

nbrs = NearestNeighbors(n_neighbors=10, algorithm='ball_tree'
).fit(vectors)def most_similar_songs(song_idx):distances, indices = nbrs.kneighbors(vectors[song_idx * 10: song_idx * 10 + 10])c = Counter()for row in indices:for idx in row[1:]:c[idx // 10] += 1return c.most_common()def print_similar_songs(song_idx, start=1, end=6):print("指定歌曲:", song_name(song_idx))for idx, score in most_similar_songs(song_idx)[start:end]:print(f"[相似度{score}] {song_name(idx)}")

最后,给定一首歌,就可以从最邻近模型中找到最接近的几首歌。

(推荐结果示例)

模型最终表现还行吧。只是和分类的结果类似,不善于处理摇滚乐。

1.2 模型优缺点

这是我比较喜欢的一种方式,从音乐本身的特征出发,不基于以往用户数据,没有曲目列表限制。借助训练好的分类器网络,可以对任意没见过的音频进行推荐。

但是,需要处理完整的音频。频谱分析的过程比较消耗算力。并且只能推荐本地拥有的曲目在另一方面也可以看作一种限制。

这种模型可以用于离线的设备端音乐推荐。

1.3 改进空间

  1. 用于训练分类器的 genres 数据集虽然质量极高,但数据量不太大。考虑用更多数据,或许能得到更好的模型;
  2. 分类器网络的结构也比较粗糙,可以考虑进一步研究调整。例如考虑使用预训练的 NLP 模型进行迁移学习,或许能更加敏锐;
  3. 考虑构建多输入的模型(或者使用多个模型),加上一些其他方面的数据,比如歌曲的元数据(歌名、艺人、专辑、时常等)、以及歌词等不容易从频谱中得出的方面。

2. 基于现有播放列表数据

基于以往的、其他用户的数据,做协同过滤(Collaborative Filtering ,CF) 。

这种思路其实更常见。获取一系列的人建好的播放列表。通过某种方法建立其中曲目的距离关系。给定歌曲,推荐距离最近的。

2.1 获取数据

spotify-playlist.ipynb 中,利用 Spotify 的 API,随机获取一些播放列表,及其中曲目(只是获取元数据,不下载音频)。

但由于这种方法需要获取大量数据(需要数十万歌曲),而网络、数据库环境都有限制,Python 实现不甚稳定,难以完成工作,所以在 spotify/ 子目录中,使用 Golang 重写了这个实现,提供更加鲁棒的数据获取服务,将获取的数据存放在一个 SQLite 数据库中。

(获取的播放列表及曲目数据)

这里目前获取了数 GiB 数据,包含 17 万个播放列表中,来自 80 万个艺人的近 500 万首歌曲。

sqlite> select count(*) from playlists;
177889
sqlite> select count(*) from artists;
801357
sqlite> select count(*) from tracks;
4995249

下面实现了两种思路来利用这些数据:

2.2 Word2vec

train-a-music-recommender.ipynb 中,将歌曲作为单词、将歌曲最成的播放列表作为句子:

sentences = [["track_1_id", "track_2_id", ...], # playlist_1[...], # playlist_2...
]

以此为语料,建立 Word2vec 模型。

model = gensim.models.Word2Vec(sentences=PlaylistTracksIter(DB), min_count=4)

训练完成后,给定曲目,可获取到最接近的推荐。

def suggest_songs(song_id):similar = dict(model.wv.most_similar([song_id]))song_ids = ', '.join(("'%s'" % x) for x in similar.keys())c = conn.cursor()c.execute("SELECT * FROM tracks WHERE id in (%s)" % song_ids)res = sorted((rec + (similar[rec[4]], find_artists(rec[4])) for rec in c.fetchall()),key=itemgetter(-1),reverse=True)return suggest_songs_result([*res])def suggest_from(song_name: str):s = find_song(song_name, limit=1)return s + suggest_songs(s[0]["id"])

这个模型也可用,但效果不算特别理想。

(Word2Vec 模型推荐实例)

2.3 Surprise KNNBaseline

surprise.ipynb 中,将歌曲作为 item,将播放列表作为 user,播放列表包含某歌曲即看作 user 给 item 打了一分(rating=1)。

将这样处理好的数据集交给 Surprise 进行基本的协同过滤

from surprise import KNNBaseline
from surprise import Reader, Dataset# custom dataset
reader = Reader(rating_scale=(0, 1))
train_data = Dataset.load_from_df(pt_train[['userID', 'itemID', 'rating']],reader)
trainset = train_data.build_full_trainset()# compute  similarities between items
sim_options = {'user_based': False
}# 算法、训练
algo = KNNBaseline(sim_options=sim_options)
algo.fit(trainset)

同样得到 KNN 的模型,给定歌曲,从模型中获取最邻近的推荐。

def find_sim(track_id, k=5):sim = algo.get_neighbors(iid=algo.trainset.to_inner_iid(track_id), k=k)track_ids = [track_id] + list(map(algo.trainset.to_raw_iid, sim))tracks = []c = conn.cursor()for tid in track_ids:c.execute(f"SELECT * FROM tracks WHERE id = '{tid}'")tk = c.fetchall()[0]tracks.append(tk + (find_artists(tid),))c.close()return sim_result(tracks)

这个做出来效果不错。

(Surprise 模型推荐结果,Shout Baby 是输入的歌曲,下面 5 首是推荐出的,二刺螈狂喜。)

2.4 模型优缺点

这种思路是传统的过往用户数据分析,是推荐系统比较常规的实现方式,方案较为成熟。基于海量数据,可以达到比较好的推荐效果。

但是,大数据的处理速度可能较慢,并且存储器开销不是终端可以承受的。同时,对于用户,基于数据的邻近推荐会容易造成信息茧房问题,并不健康。

这种方案可以用于云端的音乐推荐。

2.5 改进空间

  1. 算法:目前实现的是最基本的基准算法,可以考虑尝试其他的算法。
  2. 数据:对于这种模型更多的数据几乎一定会带来更好的结果。
  3. 考虑抓取网易云音乐的数据,可能更优质:本土化音乐,评论、热度、播放列表标签分类。可以用更综合的模型进行推荐

参考文献

[1] Douwe Osinga. Deep Learning Cookbook[M]. O’Reilly, 2018: 210-227.

[2] Nicolas Hug. Surprise: A Python library for recommender systems[J]. Journal of Open Source Software, 2020, 5(52): 2174.

动手写简单的音乐推荐系统相关推荐

  1. 【向量空间】:如何实现一个简单的音乐推荐系统

    很多人喜爱听歌,现在直接通过音乐App在线就可以听歌.而且,各种音乐App的功能越来越强大,不仅可以自己选歌听,还可以根据你听歌的口味偏好,给你推荐可能喜爱的音乐.有时候推荐的音乐还非常适合你的口味, ...

  2. 自己动手写简单的web应用服务器(4)—利用socket实现文件的下载

    直接上源码: 服务器: 1 package download; 2 3 import java.io.BufferedInputStream; 4 import java.io.BufferedOut ...

  3. 自己动手写简单的web应用服务器(1)—tcp通信

    1.socket简介: socket通常称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.在Internet上的主机一般运行了多个服务软件,同时提供几种服务.每种服务都打 ...

  4. 如何使用Python实现音乐推荐系统

    如何使用Python实现音乐推荐系统 在我的大学三年中,我最大的技术难题之一是如何使用Python实现音乐推荐系统.音乐推荐系统是基于用户听歌历史.用户喜好和音乐特征等因素,为用户推荐最合适的音乐.在 ...

  5. 自己动手写一个推荐系统,推荐系统小结,推荐系统:总体介绍、推荐算法、性能比较, 漫谈“推荐系统”, 浅谈矩阵分解在推荐系统中的应用...

    自己动手写一个推荐系统 废话: 最近朋友在学习推荐系统相关,说是实现完整的推荐系统,于是我们三不之一会有一些讨论和推导,想想索性整理出来. 在文中主要以工程中做推荐系统的流程着手,穿插一些经验之谈,并 ...

  6. 自己动手写第一阶段的处理器(1)——计算机的简单模型、架构、指令系统

    我们会继续上传新书<自己动手写处理器>(未公布),今天是第二,我每星期试试4 第1章 处理器与MIPS 时间開始了. --胡风 · 1949 让我们以一句诗意的话,開始本书的阅读. 时间从 ...

  7. 自己动手写CPU(5)简单算术操作指令实现_1

    自己动手写CPU(5)简单算数操作指令实现_1 指令介绍 MIPS32指令集架构定义的所有算术操作指令,共有21条 共有三类,分别是: 简单算术指令 乘累加.乘累减指令 除法指令 算术指令操作介绍 一 ...

  8. 自己动手写处理器之第一阶段(3)——MIPS32指令集架构简单介绍

    将陆续上传本人写的新书<自己动手写处理器>(尚未出版).今天是第四篇.我尽量每周四篇 1.4 MIPS32指令集架构简单介绍 本书设计的处理器遵循MIPS32 Release 1架构,所以 ...

  9. 用Qt写一个简单的音乐播放器(三):增加界面(播放跳转与音量控制)

    一.前言 在用Qt写一个简单的音乐播放器(一):使用QMediaPlayer播放音乐中,我们已经知道如何去使用QMediaPlayer播放音乐. 在用Qt写一个简单的音乐播放器(二):增加界面(开始和 ...

  10. 自己动手写一个简单的bootloader

    自己动手写一个简单的bootloader 15年10月31日19:44:27 (一) start.S 写这一段代码前,先要清楚bootloader开始的时候都做什么了.无非就是硬件的初始化,我们想要写 ...

最新文章

  1. java和python哪个好学-学java好还是Python好?
  2. 【CSS3】好玩的动画线框
  3. 【sklearn学习】决策树、分类树、剪枝策略
  4. vue-resource全攻略
  5. easyui蛋疼之二 tabs与accordion
  6. 从零开始学前端:OPPO商城轮播图 --- 今天你学习了吗?(CSS:Day23)
  7. 如何扩容LVM逻辑卷
  8. linux笔记本设置休眠
  9. jquery 筛选不到 checkbox, radio 表单元素
  10. 对文字颜色从左到右(横向)渐变的一点理解(坑)
  11. uva 1585 Score(Uva-1585)
  12. 如何安装matlab?官网下载详细教程
  13. mac如何配置环境变量
  14. MGS摄像头:USF56S335_3238_V2 IMX335 5MP UVC应用手册
  15. DB2 执行SQL报错: DB2 SQL Error: SQLCODE=-1585, SQLSTATE=54048
  16. Oracle系列之--Profile
  17. java 多线程(四)—— 线程同步/互斥=队列+锁
  18. 数字图像处理 第八章 图像压缩
  19. 第三周作业#183;
  20. minecraft刷怪笼java_Minecraft怪物经验top9!刷怪箱位列第4,杀玩家第2出乎意料

热门文章

  1. 前端页面调试、抓包工具——spy-debugger
  2. 用安卓手机看epub小说,哪些阅读器APP更好用?
  3. 利用gitee搭建pdf在线阅读功能
  4. dd wrt php,HG255D(DDWRT)挂载U盘安装emlog和Discuz!教程
  5. 多平台翻译=有道翻译+百度翻译+必应翻译+get Curl+xml转array
  6. CocoStudio 创建简单UI资源并添加到工程
  7. LINUX移植——LED驱动移植
  8. 酒精传感器实验-传感器原理及应用实验
  9. Java高手速成│编写你第一个数据库程序
  10. Oracle的安装步骤(详细图示)