使用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 的精彩社群專案!