使用Sentence Transformers构建播放列表生成器
不久前,我发布了一个使用Sentence Transformers和Gradio构建的播放列表生成器,随后我又写了一篇关于我如何尝试将我的项目作为有效的学习经验的思考。但我究竟是如何*构建*这个播放列表生成器的呢?在这篇文章中,我们将剖析这个项目,并探讨**两个**技术细节:嵌入是如何生成的,以及*多步骤*Gradio演示是如何构建的。
正如我们在Hugging Face博客上之前的文章中探讨过的,Sentence Transformers(ST)是一个为我们提供生成句子嵌入工具的库,这些嵌入有多种用途。由于我能够访问一个歌曲歌词数据集,我决定利用ST的语义搜索功能,从给定的文本提示生成播放列表。具体来说,目标是从提示创建一个嵌入,使用该嵌入对一组*预生成的歌词嵌入*进行语义搜索,以生成一组相关的歌曲。所有这些都将包装在一个使用新Blocks API的Gradio应用程序中,并托管在Hugging Face Spaces上。
我们将探讨Gradio的一些高级用法,因此如果您是该库的初学者,我建议您在阅读本文中Gradio特定部分之前,先阅读Blocks简介。此外,请注意,虽然我不会发布歌词数据集,但**歌词嵌入可在Hugging Face Hub上获取**,供您随意使用。让我们开始吧!🪂
Sentence Transformers:嵌入和语义搜索
嵌入在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 = [...] # Load up your strings in a list
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 数据集。阅读本教程了解更多信息,或者访问 Datasets 文档亲自尝试!简而言之,一旦您在 Hub 上创建了一个新的数据集,您只需单击“添加文件”按钮,如下图所示,手动上传您的 Pickle 文件。
现在我们需要做的最后一件事就是真正使用嵌入进行语义搜索!以下代码加载嵌入,为给定字符串生成新的嵌入,并对歌词嵌入运行语义搜索以找到最接近的匹配。为了更方便地处理结果,我还喜欢将它们放入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'])
# Note that "corpus_id" is the index of the verse for that embedding
# You can use the "corpus_id" to look up the original song
由于我们正在搜索与文本提示匹配的任何诗句,因此语义搜索很有可能会找到同一歌曲中的多个诗句。当我们删除重复项时,最终可能只剩下几首不同的歌曲。如果我们使用`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"))
# I'm loading the lyrics from my private dataset, with my own API token
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="Enter a song prompt, or choose an example"
)
fetch_songs = gr.Button(value="Generate Your Playlist!")
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'])
# ... code to map from the verse IDs to the song names
song_names = ... # e.g. ["Thank U, Next", "Freebird", "La Cucaracha"]
return (
gr.Radio.update(label="Songs", interactive=True, choices=song_names)
)
在该函数中,我们使用文本提示进行语义搜索。如上所示,要向应用程序中的Gradio组件推送更新,函数只需要返回使用`.update()`方法创建的组件。由于我们将`song_option` `Radio`组件通过其`output`参数连接到`fetch_songs.click`,`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
- Hugging Face + Sentence Transformers 文档
- Gradio Blocks 派对 - 查看一些展示 Gradio Blocks 的精彩社区项目!