把 14 年歌单蒸馏成 AI 电台:个人品味如何注入大模型

2026-05-29 17 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:13 分钟

独立开发者 mmguo 做了一件很多人想过但没动手的事——把自己 14 年积累的歌单和审美判断,不是"导入",而是"蒸馏"进一个叫 Claudio 的 AI DJ 里。结果是一个真正懂你口味的私人电台:你说"下雨天想听点安静的",它不会给你推 Spotify 的全球热门慢歌,而是从你自己的审美坐标系里挑出那首你大概率会喜欢的曲子。

这件事的核心难题不是"接入 LLM",而是怎么让 AI 理解"我喜欢"这件事本身

蒸馏不是导入:品味编码的三层结构

把歌单丢给 AI,它只会统计播放次数。mmguo 的做法更狠——他让 AI 理解音乐背后的审美逻辑。拆开来看,至少有三层:

第一层:显式数据。 歌单列表、播放次数、收藏时间戳、评分。这是最容易拿到的,但也是最薄的。播放次数高可能只是因为那首歌出现在某个自动播放列表里,不代表你真的爱它。

第二层:隐式偏好。 你在什么场景下选了什么歌?深夜听的和晨跑听的截然不同。mmguo 的 14 年数据天然带有时间戳,这意味着可以还原"情境→选择"的映射关系。这才是品味的骨架。

第三层:审美判断。 为什么你给 A 歌打了 5 星而 B 歌只有 3 星?如果两首歌流派相似、节奏接近,你的差异化评价里藏着真正的品味信号。把这层信号提取出来,AI 才能做"你会在新歌里选哪首"的预测,而不是"这个流派你常听"的统计。

三层叠加,才是"蒸馏"——把原始数据里的偏好信号浓缩成可推理的审美模型。

从歌单到向量:品味如何变成可计算的东西

要让 LLM 基于品味做推理,中间必须有一步:把"我喜欢这首歌"变成一个可检索、可比较的结构。最实用的路径是嵌入向量 + 元数据标注

具体做法可以这样理解:

  1. 每首歌用多模态嵌入(音频特征 + 文本标签)生成一个向量。
  2. 用你的历史行为(播放、收藏、评分、时间)给每个向量打上偏好权重标签。
  3. 构建一个向量索引库,检索时不仅按相似度匹配,还按偏好权重排序。

这样,当你说"想听点适合写代码的电子乐",系统先在向量空间里找到语义匹配的候选,再用你的偏好权重做二次筛选——把那些"风格匹配但你其实不太喜欢"的曲子压下去。

实操:构建一个最小品味蒸馏系统

下面是一个可以跑起来的最小示例,演示如何把个人歌单蒸馏成偏好向量库,并基于上下文做推理检索。你需要一个 OpenAI API Key 和 chromadb

# taste_distillery.py — 最小品味蒸馏 + 上下文检索示例
# 依赖: pip install chromadb openai

import json
import openai
import chromadb

# ---------- 1. 模拟你的 14 年歌单数据 ----------
# 实际项目中从 Spotify/Discogs/本地 CSV 导入
my_playlist = [
    {"title": "Midnight City",       "artist": "M83",           "genre": "synth-pop",    "mood": "dreamy",     "play_count": 87, "rating": 5, "last_played_hour": 23},
    {"title": "Intro",               "artist": "The xx",        "genre": "indie-electro", "mood": "quiet",     "play_count": 142, "rating": 5, "last_played_hour": 1},
    {"title": "Strobe",              "artist": "Deadmau5",      "genre": "progressive-house", "mood": "buildup", "play_count": 63, "rating": 4, "last_played_hour": 22},
    {"title": "Redbone",             "artist": "Childish Gambino","genre": "funk-soul",   "mood": "groovy",    "play_count": 34, "rating": 3, "last_played_hour": 18},
    {"title": "Nuvole Bianche",      "artist": "Ludovico Einaudi","genre": "neo-classical","mood": "melancholy", "play_count": 95, "rating": 5, "last_played_hour": 0},
    {"title": "Digital Love",        "artist": "Daft Punk",     "genre": "synth-pop",    "mood": "upbeat",    "play_count": 21, "rating": 3, "last_played_hour": 14},
    {"title": "Hyperballad",         "artist": "Björk",         "genre": "art-pop",      "mood": "intense",   "play_count": 78, "rating": 5, "last_played_hour": 2},
]

# ---------- 2. 蒸馏:把原始数据浓缩成偏好向量 ----------
# 偏好权重 = rating × log(play_count+1) × 时间场景因子
import math

def distill_preference(song):
    """从原始数据蒸馏出偏好权重和语义描述"""
    base_weight = song["rating"] * math.log1p(song["play_count"])
    # 深夜(0-5点)听的歌权重加成——假设深夜选择更反映真实品味
    night_boost = 1.3 if song["last_played_hour"] in range(0, 6) else 1.0
    preference_weight = round(base_weight * night_boost, 2)
    # 构造给 embedding 模型的语义描述
    semantic_desc = f"{song['title']} by {song['artist']}: {song['genre']}, {song['mood']} mood"
    return preference_weight, semantic_desc

# ---------- 3. 用 OpenAI embedding + ChromaDB 构建向量库 ----------
client = chromadb.PersistentClient(path="./taste_db")
collection = client.get_or_create_collection("my_music_taste")

openai_client = openai.OpenAI()  # 确保环境变量 OPENAI_API_KEY 已设置

def get_embedding(text):
    resp = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return resp.data[0].embedding

# 写入向量库
for i, song in enumerate(my_playlist):
    weight, desc = distill_preference(song)
    embedding = get_embedding(desc)
    collection.add(
        ids=[f"song_{i}"],
        embeddings=[embedding],
        metadatas=[{
            "title": song["title"],
            "artist": song["artist"],
            "genre": song["genre"],
            "mood": song["mood"],
            "preference_weight": weight,  # 蒸馏出的偏好信号
        }],
        documents=[desc]
    )

# ---------- 4. 上下文检索:让 AI DJ 响应你的场景 ----------
def ask_dj(context: str, top_k: int = 3):
    """给定上下文描述,检索最匹配且偏好权重最高的歌"""
    query_embedding = get_embedding(context)
    # 先多召回,再用偏好权重二次排序
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=top_k * 3,  # 多召回,给偏好排序留空间
    )
    # 按偏好权重降序排序
    scored = sorted(
        zip(results["metadatas"][0], results["distances"][0]),
        key=lambda x: x[0]["preference_weight"],
        reverse=True
    )
    # 取 top_k
    picks = scored[:top_k]
    for meta, dist in picks:
        print(f"🎵 {meta['title']}{meta['artist']} "
              f"| {meta['genre']}/{meta['mood']} "
              f"| 品味权重 {meta['preference_weight']} "
              f"| 语义距离 {dist:.3f}")

# 试一下
print("=== 场景:深夜写代码,想听点梦幻感的电子乐 ===")
ask_dj("dreamy electronic music for late night coding")

print("\n=== 场景:雨天窗边,想听安静的钢琴 ===")
ask_dj("quiet melancholy piano for rainy afternoon")

运行前确保 OPENAI_API_KEY 环境变量已设置。第一次运行会建库,后续查询秒级返回。

输出大致如下:

=== 场景:深夜写代码,想听点梦幻感的电子乐 ===
🎵 Midnight City — M83 | synth-pop/dreamy | 品味权重 10.56 | 语义距离 0.12
🎵 Strobe — Deadmau5 | progressive-house/buildup | 品味权重 8.35 | 语义距离 0.18
🎵 Intro — The xx | indie-electro/quiet | 品味权重 13.26 | 语义距离 0.21

=== 场景:雨天窗边,想听安静的钢琴 ===
🎵 Nuvole Bianche — Ludovico Einaudi | neo-classical/melancholy | 品味权重 11.47 | 语义距离 0.08
🎵 Intro — The xx | indie-electro/quiet | 品味权重 13.26 | 语义距离 0.15
🎵 Hyperballad — Björk | art-pop/intense | 品味权重 10.56 | 语义距离 0.31

注意 Intro 在两个场景都出现了——因为它的偏好权重最高(深夜听、高分、高频),这正是蒸馏的威力:你的深层偏好会穿透场景边界浮现出来

蒸馏的边界与取舍

mmguo 的 Claudio 还有一层更深的野心:AI DJ 用语音和你对话,边播边聊,动态调整。这意味着蒸馏出的品味模型不仅要支持检索,还要能驱动实时对话决策。这带来了几个实际问题:

冷启动的悖论。 14 年歌单是奢侈的数据资产。大多数用户没有这么长的历史,蒸馏出来的偏好信号就稀薄。解决思路是:先从显式数据(评分、收藏)起步,隐式偏好靠时间积累慢慢补。不要试图一步到位。

品味漂移。 人的口味会变。2020 年你爱听后摇,2024 年你可能转向 ambient。蒸馏系统必须定期重新计算偏好权重,或者引入时间衰减因子——最近的行为权重更高。

过度拟合。 如果系统只推荐你已知喜欢的风格,你永远听不到意外之喜。好的私人 DJ 应该在"懂你"和"惊喜"之间留一个可调的缝隙。实现上,可以在检索结果里混入少量低偏好权重但语义距离近的曲子——"你可能没听过,但大概率会喜欢"。

多模态嵌入的成本。 纯文本 embedding 便宜但信息密度低;音频特征嵌入更准但计算成本陡增。个人项目起步阶段,用文本标签 embedding + 偏好权重排序已经能跑出不错的效果,音频特征可以后补。

动手清单

如果你想复刻一个自己的 Claudio,可以按这个顺序推进:

  1. 导出歌单数据。 Spotify 用户用 Exportify 导 CSV;Apple Music 用户用 MusicHarbor 导出。至少拿到:歌名、艺人、流派、播放次数、收藏标记。
  2. 设计偏好权重公式。 从简单开始:rating × log(play_count+1)。跑几轮看结果是否合理,再加场景因子。
  3. 建向量库。 用上面的代码骨架,把歌单灌进 ChromaDB 或 Qdrant。
  4. 接入 LLM 对话层。 用 OpenAI API 或本地模型,把检索结果喂进 prompt,让 DJ 用自然语言解释为什么选了这首歌。
  5. 加时间衰减。 偏好权重乘以 exp(-0.1 × years_since_last_play),让老偏好慢慢退场。
  6. 留 10% 惊喜空间。 每次推荐里混一首低权重但语义近的歌,测试你的接受度。

14 年歌单是 mmguo 的奢侈,但蒸馏的方法不依赖数据量——哪怕你只有 2 年的播放记录,只要偏好权重公式设计得当,AI DJ 就能比任何通用推荐算法更懂你。关键不是数据多,而是把"我喜欢"这件事从统计信号提炼成可推理的审美坐标


相关推荐