Press "Enter" to skip to content

用句子转换器构建一个播放列表生成器

不久前,我发布了一个使用Sentence Transformers和Gradio构建的播放列表生成器,并在随后的一篇文章中反思了如何将我的项目作为有效的学习经验。但是,我究竟是如何构建这个播放列表生成器的呢?在这篇文章中,我们将分解该项目并查看两个技术细节:嵌入是如何生成的,以及多步骤的Gradio演示是如何构建的。

正如我们在Hugging Face博客的先前文章中所探讨的那样,Sentence Transformers(ST)是一个提供工具生成句子嵌入的库,具有各种用途。由于我可以访问一组歌词数据集,我决定利用ST的语义搜索功能从给定的文本提示生成播放列表。具体来说,目标是从提示中创建一个嵌入,使用该嵌入对一组预生成的歌词嵌入进行语义搜索,从而生成一组相关的歌曲。所有这些都将包含在一个使用新的Blocks API在Hugging Face Spaces上托管的Gradio应用中。

我们将看到Gradio的稍微高级用法,所以如果你是该库的新手,我建议你在处理本文的Gradio特定部分之前先阅读《入门指南》。另外,请注意,虽然我不会发布歌词数据集,但你可以在Hugging Face Hub上找到歌词嵌入可供你玩耍。让我们开始吧!🪂

嵌入在Sentence Transformers中非常关键!我们在之前的一篇文章中了解了嵌入是什么以及如何生成它们,我建议在继续阅读本文之前先查看那篇文章。

Sentence Transformers提供了一个大型的预训练嵌入模型集合!它甚至包括使用我们自己的训练数据对这些模型进行微调的教程,但对于许多用例(如在一组歌词语料库上进行语义搜索),预训练模型在开箱即用时表现出色。然而,由于有这么多的嵌入模型可供选择,我们如何知道该使用哪个模型呢?

ST文档突出了许多选择,以及它们的评估指标和一些用例描述。MS MARCO模型是在Bing搜索引擎查询上进行训练的,但由于它们在其他领域上也表现良好,我决定任何一个模型都可能是这个项目的一个好选择。对于播放列表生成器,我们只需要找到具有一定语义相似性的歌曲,而且由于我并不关心达到特定性能指标,我随机选择了sentence-transformers/msmarco-MiniLM-L-6-v3。

ST中的每个模型都有一个可配置的输入序列长度(最大长度),在这之后,你的输入将被截断。我选择的模型具有512个单词片段的最大序列长度,但正如我发现的那样,这通常不足以嵌入整首歌曲。幸运的是,我们可以很容易地将歌词分成较小的部分,以便模型能够处理——诗节!一旦我们将歌曲分成诗节并嵌入每个诗节,我们将发现搜索效果更好。

歌曲被分割成诗节,然后每个诗节被嵌入。

要实际生成嵌入,你可以调用Sentence Transformers模型的.encode()方法,并将其传入一个字符串列表。然后,你可以以任何你喜欢的方式保存嵌入——在这种情况下,我选择了将它们存储为Pickle文件。

from sentence_transformers import SentenceTransformer
import pickle

embedder = SentenceTransformer('msmarco-MiniLM-L-6-v3')
verses = [...] # 在一个列表中加载你的字符串
corpus_embeddings = embedder.encode(verses, show_progress_bar=True)

with open('verse-embeddings.pkl', "wb") as fOut:
    pickle.dump(corpus_embeddings, fOut)

为了能够与他人共享你的嵌入,你甚至可以将Pickle文件上传到Hugging Face数据集。阅读本教程以了解更多信息,或访问数据集文档以自己尝试!简而言之,一旦你在Hub上创建了一个新的数据集,你只需点击下面显示的“添加文件”按钮,就可以手动上传你的Pickle文件。

你可以手动在Hub上上传数据集文件。

现在我们需要做的最后一件事就是实际上使用嵌入进行语义搜索!以下代码加载嵌入,为给定的字符串生成一个新的嵌入,并在歌词嵌入上运行语义搜索以找到最接近的匹配。为了更容易处理结果,我还喜欢将它们放入Pandas DataFrame中。

from sentence_transformers import util
import pandas as pd

prompt_embedding = embedder.encode(prompt, convert_to_tensor=True)
hits = util.semantic_search(prompt_embedding, corpus_embeddings, top_k=20)
hits = pd.DataFrame(hits[0], columns=['corpus_id', 'score'])
# 注意,"corpus_id" 是该嵌入的诗歌的索引
# 您可以使用 "corpus_id" 来查找原始歌曲

由于我们正在搜索与文本提示匹配的任何诗句,很有可能语义搜索会找到同一首歌曲的多个诗句。当我们删除重复项时,可能只会得到几首不同的歌曲。如果我们通过 top_k 参数增加了 util.semantic_search 获取的诗句嵌入的数量,我们可以增加找到的歌曲数量。通过实验,我发现当我将 top_k=20 设置为时,几乎总能得到至少 9 首不同的歌曲。

制作一个多步骤的 Gradio 应用

对于演示,我希望用户输入一个文本提示(或选择一些示例),并进行语义搜索以找到最相关的前9首歌曲。然后,用户应该能够从结果中选择歌曲来查看歌词,这可能会给他们一些关于为什么选择这些特定歌曲的见解。这是我们如何做到的!

在 Gradio 演示的顶部,我们在应用启动时从 Hugging Face 数据集中加载嵌入、映射和歌词。

from sentence_transformers import SentenceTransformer, util
from huggingface_hub import hf_hub_download
import os
import pickle
import pandas as pd

corpus_embeddings = pickle.load(open(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="verse-embeddings.pkl"), "rb"))
songs = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="songs_new.csv"))
verses = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator", repo_type="dataset", filename="verses.csv"))

# 我正在使用自己的 API 令牌从我的私有数据集中加载歌词
auth_token = os.environ.get("TOKEN_FROM_SECRET") 
lyrics = pd.read_csv(hf_hub_download("NimaBoscarino/playlist-generator-private", repo_type="dataset", filename="lyrics_new.csv", use_auth_token=auth_token))

Gradio Blocks API 允许您构建多步骤界面,这意味着您可以为演示创建相当复杂的序列。我们将在这里看一些示例代码片段,但是请查看项目代码以查看它们的实际效果。对于这个项目,我们希望用户选择一个文本提示,然后在语义搜索完成后,用户应该能够选择一个歌曲来检查歌词。使用 Gradio,可以通过首先定义初始输入组件,然后在按钮上注册一个 click 事件来逐步构建它。还有一个 Radio 组件,它将更新以显示播放列表中歌曲的名称。

import gradio as gr

song_prompt = gr.TextArea(
    value="Running wild and free",
    placeholder="输入一个歌曲提示,或选择一个示例"
)

fetch_songs = gr.Button(value="生成您的播放列表!")

song_option = gr.Radio()

fetch_songs.click(
    fn=generate_playlist,
    inputs=[song_prompt],
    outputs=[song_option],
)

这样,当按钮被点击时,Gradio 获取 TextArea 的当前值并将其传递给下面的函数:

def generate_playlist(prompt):
    prompt_embedding = embedder.encode(prompt, convert_to_tensor=True)
    hits = util.semantic_search(prompt_embedding, corpus_embeddings, top_k=20)
    hits = pd.DataFrame(hits[0], columns=['corpus_id', 'score'])
    # ... 从诗歌 ID 映射到歌曲名称的代码
    song_names = ... # 例如 ["Thank U, Next", "Freebird", "La Cucaracha"]
    return (
        gr.Radio.update(label="歌曲", interactive=True, choices=song_names)
    )

在该函数中,我们使用文本提示进行语义搜索。如上所示,要向应用中的 Gradio 组件推送更新,函数只需要返回使用 .update() 方法创建的组件。由于我们将 song_optionRadio 组件与 fetch_songs.clickoutput 参数连接在一起,generate_playlist 可以控制 Radio 组件的选择!

您甚至可以类似于Radio组件,让用户选择要查看的歌词。访问Hugging Face Spaces上的代码,详细了解它!

一些想法

Sentence Transformers和Gradio是这类项目的绝佳选择!ST具有我们快速生成嵌入向量所需的实用函数,以及使用最少的代码进行语义搜索的功能。拥有大量预训练模型也非常有帮助,因为我们不需要为这类任务创建和训练自己的模型。在Gradio中构建我们的演示意味着我们只需要专注于使用Python编程,并且将Gradio项目部署到Hugging Face Spaces也非常简单!

我希望我有更多时间来将其他功能构建到这个项目中,例如这些我将来可能会探索的想法:

  • 与Spotify集成以自动生成播放列表,甚至可以使用Spotify的内嵌播放器让用户立即收听歌曲。
  • 使用**HighlightedText** Gradio组件来识别语义搜索找到的具体诗句。
  • 创建一些嵌入向量空间的可视化,例如Radamés Ajna的这个Space中的可视化。

虽然歌词没有发布,但我已经发布了诗句的嵌入向量以及每首歌曲的映射,所以您可以自由地进行尝试和创作!

记得来Discord上提问和分享您的作品!我很期待看到您如何使用Sentence Transformers嵌入向量 🤗

额外资源

  • 通过Omar Espejel入门嵌入向量
    • 或者Omar Sanseviero的Twitter线程
  • Hugging Face + Sentence Transformers文档
  • Gradio Blocks party – 查看一些展示Gradio Blocks的了不起的社区项目!
Leave a Reply

Your email address will not be published. Required fields are marked *