音訊課程文件
微調模型進行音樂分類
並獲得增強的文件體驗
開始使用
微調模型進行音樂分類
在本節中,我們將逐步指導您如何微調一個僅編碼器Transformer模型進行音樂分類。我們將使用一個輕量級模型進行此演示,並使用一個相當小的資料集,這意味著程式碼可以在任何消費級 GPU 上端到端執行,包括 Google Colab 免費層提供的 T4 16GB GPU。本節包含各種提示,如果您有較小的 GPU 並且在此過程中遇到記憶體問題,您可以嘗試這些提示。
資料集
為了訓練我們的模型,我們將使用 GTZAN 資料集,這是一個包含 1,000 首歌曲的流行音樂流派分類資料集。每首歌都是 30 秒的片段,來自 10 種音樂流派之一,涵蓋迪斯科到金屬。我們可以使用 🤗 Datasets 的 load_dataset()
函式從 Hugging Face Hub 獲取音訊檔案及其對應的標籤
from datasets import load_dataset
gtzan = load_dataset("marsyas/gtzan", "all")
gtzan
輸出
Dataset({
features: ['file', 'audio', 'genre'],
num_rows: 999
})
GTZAN 中的一個錄音損壞了,因此已從資料集中刪除。這就是為什麼我們有 999 個示例而不是 1,000 個。
GTZAN 未提供預定義的驗證集,因此我們必須自己建立一個。資料集在流派之間是平衡的,因此我們可以使用 train_test_split()
方法快速建立 90/10 的分割,如下所示
gtzan = gtzan["train"].train_test_split(seed=42, shuffle=True, test_size=0.1)
gtzan
輸出
DatasetDict({
train: Dataset({
features: ['file', 'audio', 'genre'],
num_rows: 899
})
test: Dataset({
features: ['file', 'audio', 'genre'],
num_rows: 100
})
})
太好了,現在我們有了訓練集和驗證集,讓我們看看其中一個音訊檔案
gtzan["train"][0]
輸出
{
"file": "~/.cache/huggingface/datasets/downloads/extracted/fa06ce46130d3467683100aca945d6deafb642315765a784456e1d81c94715a8/genres/pop/pop.00098.wav",
"audio": {
"path": "~/.cache/huggingface/datasets/downloads/extracted/fa06ce46130d3467683100aca945d6deafb642315765a784456e1d81c94715a8/genres/pop/pop.00098.wav",
"array": array(
[
0.10720825,
0.16122437,
0.28585815,
...,
-0.22924805,
-0.20629883,
-0.11334229,
],
dtype=float32,
),
"sampling_rate": 22050,
},
"genre": 7,
}
正如我們在單元 1 中看到的,音訊檔案表示為 1 維 NumPy 陣列,其中陣列的值表示該時間步的振幅。對於這些歌曲,取樣率為 22,050 Hz,這意味著每秒取樣 22,050 個振幅值。在使用具有不同取樣率的預訓練模型時,我們必須記住這一點,自行轉換取樣率以確保它們匹配。我們還可以看到流派表示為整數,或類標籤,這是模型進行預測的格式。讓我們使用 genre
特徵的 int2str()
方法將這些整數對映到人類可讀的名稱
id2label_fn = gtzan["train"].features["genre"].int2str
id2label_fn(gtzan["train"][0]["genre"])
輸出
'pop'
此標籤看起來是正確的,因為它與音訊檔案的檔名匹配。現在讓我們使用 Gradio 透過 Blocks
API 建立一個簡單的介面,來聽幾個更多的示例
import gradio as gr
def generate_audio():
example = gtzan["train"].shuffle()[0]
audio = example["audio"]
return (
audio["sampling_rate"],
audio["array"],
), id2label_fn(example["genre"])
with gr.Blocks() as demo:
with gr.Column():
for _ in range(4):
audio, label = generate_audio()
output = gr.Audio(audio, label=label)
demo.launch(debug=True)
從這些樣本中,我們當然可以聽到流派之間的差異,但 Transformer 也能做到嗎?讓我們訓練一個模型來找出答案!首先,我們需要為這項任務找到一個合適的預訓練模型。讓我們看看如何做到這一點。
選擇用於音訊分類的預訓練模型
首先,讓我們為音訊分類選擇一個合適的預訓練模型。在這個領域,預訓練通常在大量的未標記音訊資料上進行,使用諸如 LibriSpeech 和 Voxpopuli 之類的資料集。在 Hugging Face Hub 上找到這些模型的最佳方法是使用“音訊分類”篩選器,如上一節所述。儘管 Wav2Vec2 和 HuBERT 等模型非常流行,但我們將使用一個名為 DistilHuBERT 的模型。這是 HuBERT 模型的一個小得多(或蒸餾)版本,其訓練速度快約 73%,但保留了大部分效能。
從音訊到機器學習特徵
預處理資料
與 NLP 中的分詞類似,音訊和語音模型需要輸入編碼成模型可以處理的格式。在 🤗 Transformers 中,從音訊到輸入格式的轉換由模型的特徵提取器處理。與分詞器類似,🤗 Transformers 提供了一個方便的 AutoFeatureExtractor
類,可以自動為給定模型選擇正確的特徵提取器。為了瞭解我們如何處理音訊檔案,讓我們首先從預訓練檢查點例項化 DistilHuBERT 的特徵提取器
from transformers import AutoFeatureExtractor
model_id = "ntu-spml/distilhubert"
feature_extractor = AutoFeatureExtractor.from_pretrained(
model_id, do_normalize=True, return_attention_mask=True
)
由於模型和資料集的取樣率不同,我們需要在將音訊檔案傳遞給特徵提取器之前將其重取樣到 16,000 Hz。我們可以透過首先從特徵提取器獲取模型的取樣率來做到這一點
sampling_rate = feature_extractor.sampling_rate sampling_rate
輸出
16000
接下來,我們使用 cast_column()
方法和 🤗 Datasets 的 Audio
特徵對資料集進行重取樣
from datasets import Audio
gtzan = gtzan.cast_column("audio", Audio(sampling_rate=sampling_rate))
我們現在可以檢查資料集訓練拆分中的第一個樣本,以驗證它確實是 16,000 Hz。🤗 Datasets 將在載入每個音訊樣本時即時重取樣音訊檔案
gtzan["train"][0]
輸出
{
"file": "~/.cache/huggingface/datasets/downloads/extracted/fa06ce46130d3467683100aca945d6deafb642315765a784456e1d81c94715a8/genres/pop/pop.00098.wav",
"audio": {
"path": "~/.cache/huggingface/datasets/downloads/extracted/fa06ce46130d3467683100aca945d6deafb642315765a784456e1d81c94715a8/genres/pop/pop.00098.wav",
"array": array(
[
0.0873509,
0.20183384,
0.4790867,
...,
-0.18743178,
-0.23294401,
-0.13517427,
],
dtype=float32,
),
"sampling_rate": 16000,
},
"genre": 7,
}
太棒了!我們可以看到取樣率已降取樣到 16kHz。陣列值也不同,因為我們現在每個振幅值大約是我們以前的 1.5 倍。
Wav2Vec2 和 HuBERT 等模型的一個顯著特徵是它們接受對應於語音訊號原始波形的浮點陣列作為輸入。這與 Whisper 等其他模型形成對比,在 Whisper 中,我們預處理原始音訊波形以生成頻譜圖格式。
我們提到音訊資料表示為一維陣列,因此它已經處於模型可以讀取的正確格式(離散時間步長的連續輸入集)。那麼,特徵提取器究竟做了什麼呢?
音訊資料格式是正確的,但我們沒有對其取值施加任何限制。為了使我們的模型最佳地工作,我們希望將所有輸入保持在相同的動態範圍內。這將確保我們的樣本獲得相似範圍的啟用和梯度,有助於訓練過程中的穩定性和收斂。
為此,我們透過將每個樣本重新縮放為零均值和單位方差來標準化我們的音訊資料,這個過程稱為特徵縮放。這正是我們的特徵提取器所執行的特徵標準化!
我們可以透過將特徵提取器應用於我們的第一個音訊樣本來檢視其操作。首先,讓我們計算原始音訊資料的均值和方差
import numpy as np
sample = gtzan["train"][0]["audio"]
print(f"Mean: {np.mean(sample['array']):.3}, Variance: {np.var(sample['array']):.3}")
輸出
Mean: 0.000185, Variance: 0.0493
我們可以看到均值已經接近零,但方差更接近 0.05。如果樣本的方差更大,可能會給我們的模型帶來問題,因為音訊資料的動態範圍會非常小,因此難以分離。讓我們應用特徵提取器,看看輸出是什麼樣的
inputs = feature_extractor(sample["array"], sampling_rate=sample["sampling_rate"])
print(f"inputs keys: {list(inputs.keys())}")
print(
f"Mean: {np.mean(inputs['input_values']):.3}, Variance: {np.var(inputs['input_values']):.3}"
)
輸出
inputs keys: ['input_values', 'attention_mask']
Mean: -4.53e-09, Variance: 1.0
好的!我們的特徵提取器返回一個包含兩個陣列的字典:input_values
和 attention_mask
。input_values
是我們將傳遞給 HuBERT 模型的預處理音訊輸入。當我們一次處理一批音訊輸入時,會使用attention_mask
,它用於告訴模型我們填充了不同長度輸入的位置。
我們可以看到均值現在非常接近零,方差正好是 1!這正是我們希望在將音訊樣本輸入 HuBERT 模型之前所採用的形式。
請注意我們如何將音訊資料的取樣率傳遞給特徵提取器。這是一個好習慣,因為特徵提取器在後臺執行檢查,以確保音訊資料的取樣率與模型預期的取樣率匹配。如果我們的音訊資料的取樣率與模型的取樣率不匹配,我們需要對音訊資料進行上取樣或下采樣以達到正確的取樣率。
太好了,現在我們知道如何處理重取樣後的音訊檔案了,最後一步是定義一個可以應用於資料集中所有示例的函式。由於我們期望音訊片段的長度為 30 秒,我們還將使用特徵提取器的 max_length
和 truncation
引數來截斷任何更長的片段,如下所示
max_duration = 30.0
def preprocess_function(examples):
audio_arrays = [x["array"] for x in examples["audio"]]
inputs = feature_extractor(
audio_arrays,
sampling_rate=feature_extractor.sampling_rate,
max_length=int(feature_extractor.sampling_rate * max_duration),
truncation=True,
return_attention_mask=True,
)
return inputs
定義此函式後,我們現在可以使用 map()
方法將其應用於資料集。.map()
方法支援處理批次示例,我們將透過設定 batched=True
來啟用它。預設批次大小為 1000,但我們將將其減少到 100,以確保峰值 RAM 保持在 Google Colab 免費層的合理範圍內
gtzan_encoded = gtzan.map(
preprocess_function,
remove_columns=["audio", "file"],
batched=True,
batch_size=100,
num_proc=1,
)
gtzan_encoded
輸出
DatasetDict({
train: Dataset({
features: ['genre', 'input_values','attention_mask'],
num_rows: 899
})
test: Dataset({
features: ['genre', 'input_values','attention_mask'],
num_rows: 100
})
})
為了簡化訓練,我們從資料集中刪除了 audio
和 file
列。input_values
列包含編碼的音訊檔案,attention_mask
是一個由 0/1 值組成的二進位制掩碼,指示我們在何處填充了不同長度的音訊輸入,而 genre
列包含相應的標籤(或目標)。為了使 Trainer
能夠處理類標籤,我們需要將 genre
列重新命名為 label
gtzan_encoded = gtzan_encoded.rename_column("genre", "label")
最後,我們需要從資料集中獲取標籤對映。這個對映將把我們從整數 ID(例如 7
)對映到人類可讀的類標籤(例如 "pop"
),反之亦然。透過這樣做,我們可以將模型的整數 ID 預測轉換為人類可讀的格式,使我們能夠將模型用於任何下游應用程式。我們可以使用 int2str()
方法來完成此操作,如下所示
id2label = {
str(i): id2label_fn(i)
for i in range(len(gtzan_encoded["train"].features["label"].names))
}
label2id = {v: k for k, v in id2label.items()}
id2label["7"]
'pop'
好了,我們現在有了一個可以進行訓練的資料集!讓我們看看如何在這個資料集上訓練一個模型。
微調模型
為了微調模型,我們將使用 🤗 Transformers 的 Trainer
類。正如我們在其他章節中看到的,Trainer
是一個高階 API,旨在處理最常見的訓練場景。在這種情況下,我們將使用 Trainer
在 GTZAN 上微調模型。為此,我們首先需要為該任務載入一個模型。我們可以透過使用 AutoModelForAudioClassification
類來完成此操作,該類將自動向我們預訓練的 DistilHuBERT 模型新增適當的分類頭。讓我們繼續例項化模型
from transformers import AutoModelForAudioClassification
num_labels = len(id2label)
model = AutoModelForAudioClassification.from_pretrained(
model_id,
num_labels=num_labels,
label2id=label2id,
id2label=id2label,
)
我們強烈建議您在訓練期間將模型檢查點直接上傳到 Hugging Face Hub。Hub 提供
- 整合的版本控制:你可以確保在訓練過程中不會丟失任何模型檢查點。
- Tensorboard 日誌:在訓練過程中跟蹤重要指標。
- 模型卡片:記錄模型的功能及其預期用途。
- 社群:一種與社群輕鬆共享和協作的方式! 🤗
將筆記本連結到 Hub 很簡單——只需在提示時輸入您的 Hub 身份驗證令牌即可。在此處查詢您的 Hub 身份驗證令牌
from huggingface_hub import notebook_login
notebook_login()
輸出
Login successful Your token has been saved to /root/.huggingface/token
下一步是定義訓練引數,包括批次大小、梯度累積步數、訓練 epoch 數量和學習率
from transformers import TrainingArguments
model_name = model_id.split("/")[-1]
batch_size = 8
gradient_accumulation_steps = 1
num_train_epochs = 10
training_args = TrainingArguments(
f"{model_name}-finetuned-gtzan",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=5e-5,
per_device_train_batch_size=batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
per_device_eval_batch_size=batch_size,
num_train_epochs=num_train_epochs,
warmup_ratio=0.1,
logging_steps=5,
load_best_model_at_end=True,
metric_for_best_model="accuracy",
fp16=True,
push_to_hub=True,
)
在這裡,我們已將 push_to_hub=True
設定為在訓練期間啟用微調檢查點的自動上傳。如果您不希望將檢查點上傳到 Hub,可以將其設定為 False
。
我們最後需要做的是定義指標。由於資料集是平衡的,我們將使用準確率作為我們的指標,並使用 🤗 Evaluate 庫載入它
import evaluate
import numpy as np
metric = evaluate.load("accuracy")
def compute_metrics(eval_pred):
"""Computes accuracy on a batch of predictions"""
predictions = np.argmax(eval_pred.predictions, axis=1)
return metric.compute(predictions=predictions, references=eval_pred.label_ids)
我們現在擁有一切!讓我們例項化 Trainer
並訓練模型
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=gtzan_encoded["train"],
eval_dataset=gtzan_encoded["test"],
tokenizer=feature_extractor,
compute_metrics=compute_metrics,
)
trainer.train()
根據您的 GPU,您在開始訓練時可能會遇到 CUDA 的“記憶體不足”錯誤。在這種情況下,您可以將 batch_size
逐漸減小 2 倍,並採用gradient_accumulation_steps
進行補償。
輸出
| Training Loss | Epoch | Step | Validation Loss | Accuracy |
|:-------------:|:-----:|:----:|:---------------:|:--------:|
| 1.7297 | 1.0 | 113 | 1.8011 | 0.44 |
| 1.24 | 2.0 | 226 | 1.3045 | 0.64 |
| 0.9805 | 3.0 | 339 | 0.9888 | 0.7 |
| 0.6853 | 4.0 | 452 | 0.7508 | 0.79 |
| 0.4502 | 5.0 | 565 | 0.6224 | 0.81 |
| 0.3015 | 6.0 | 678 | 0.5411 | 0.83 |
| 0.2244 | 7.0 | 791 | 0.6293 | 0.78 |
| 0.3108 | 8.0 | 904 | 0.5857 | 0.81 |
| 0.1644 | 9.0 | 1017 | 0.5355 | 0.83 |
| 0.1198 | 10.0 | 1130 | 0.5716 | 0.82 |
訓練大約需要 1 小時,具體取決於您的 GPU 或分配給 Google Colab 的 GPU。我們最好的評估準確率是 83%——對於僅有 899 個訓練資料示例的 10 個 epoch 來說,這還不錯!我們當然可以透過訓練更多 epoch、使用正則化技術(例如dropout)或將每個音訊示例從 30 秒細分為 15 秒的片段來改進此結果,以使用更有效的資料預處理策略。
一個大問題是這與其他音樂分類系統相比如何 🤔 為此,我們可以檢視自動評估排行榜,這是一個按語言和資料集對模型進行分類,然後根據其準確率進行排名的排行榜。
當我們將訓練結果推送到 Hub 時,我們可以自動將檢查點提交到排行榜——我們只需設定適當的關鍵字引數(kwargs)。您可以更改這些值以匹配您的資料集、語言和模型名稱
kwargs = {
"dataset_tags": "marsyas/gtzan",
"dataset": "GTZAN",
"model_name": f"{model_name}-finetuned-gtzan",
"finetuned_from": model_id,
"tasks": "audio-classification",
}
訓練結果現在可以上傳到 Hub。為此,執行 .push_to_hub
命令
trainer.push_to_hub(**kwargs)
這將把訓練日誌和模型權重儲存在 "your-username/distilhubert-finetuned-gtzan"
下。對於此示例,請檢視上傳至 "sanchit-gandhi/distilhubert-finetuned-gtzan"
。
分享模型
您現在可以使用 Hub 上的連結與任何人共享此模型。他們可以使用識別符號 "your-username/distilhubert-finetuned-gtzan"
將其直接載入到 pipeline()
類中。例如,要載入微調後的檢查點 "sanchit-gandhi/distilhubert-finetuned-gtzan"
from transformers import pipeline
pipe = pipeline(
"audio-classification", model="sanchit-gandhi/distilhubert-finetuned-gtzan"
)
結論
在本節中,我們提供了微調 DistilHuBERT 模型進行音樂分類的逐步指南。雖然我們專注於音樂分類任務和 GTZAN 資料集,但此處介紹的步驟更普遍適用於任何音訊分類任務——相同的指令碼可用於口語音訊分類任務,例如關鍵詞識別或語言識別。您只需將資料集替換為與您感興趣的任務相對應的資料集即可!如果您有興趣微調其他 Hugging Face Hub 模型進行音訊分類,我們鼓勵您檢視 🤗 Transformers 儲存庫中的其他示例。
在下一節中,我們將使用您剛剛微調的模型構建一個音樂分類演示,您可以在 Hugging Face Hub 上共享它。
< > 在 GitHub 上更新