日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
基于Huggingface的定制音頻數(shù)據(jù)情感識別

譯者 | 朱先忠

成都創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),肅北企業(yè)網(wǎng)站建設(shè),肅北品牌網(wǎng)站建設(shè),網(wǎng)站定制,肅北網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,肅北網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

審校 | 孫淑娟

簡介

為什么選擇音頻數(shù)據(jù)?

與用于文本和計算機視覺任務(wù)的NLP相比,用于音頻數(shù)據(jù)的NLP目前尚沒有得到業(yè)界足夠的重視。因此,現(xiàn)在是時候進行一些改變了!

任務(wù)

情緒識別——識別語音中是否表現(xiàn)出憤怒、幸福、悲傷、厭惡、驚訝或中性情緒。 注意:如果你能夠完整地閱讀完本教程,那么,你應該能夠重用本文示例工程中提供的代碼來實現(xiàn)任何音頻分類任務(wù)。

數(shù)據(jù)集

在本教程中,我們將使用Kaggle上公開可用的??Crema-D數(shù)據(jù)集????(在此非常感謝David? Cooper Cheyney整理了這個令人敬畏的數(shù)據(jù)集)。然后點擊??鏈接????處的下載(Download)按鈕。您應該會看到包含Crema-D音頻文件的文件archive.zip開始下載了,其中包含了7k+.wav格式的音頻文件。

注意:您可以自由使用您自己收集的任何音頻數(shù)據(jù)來取代CremaD數(shù)據(jù)集。

如果您想繼續(xù)學習本教程,GitHub示例工程所在地址是https://github.com/V-Sher/Audio-Classification-HF/tree/main/src。

Hugging Face庫與訓練API

正如本文標題中提到的,我們將使用Hugging Face庫來訓練模型。特別是,我們將使用這個庫中提供的Trainer類API。

那么,為什么使用這里的Trainer類呢?為什么不在PyTorch中編寫一個標準的訓練循環(huán)呢?

以下給出Pytorch框架中的標準樣板代碼,供您參考:

摘自我自己撰寫的PyTorch入門教程

相比之下,使用Trainer類能夠簡化編寫訓練循環(huán)所涉及的復雜性,因此可以通過一行代碼來實現(xiàn)訓練任務(wù):

trainer.train()

除了支持基本訓練循環(huán)外,Trainer類還支持在多個GPU/TPU上進行分布式訓練,如提前停止這樣的回調(diào)操作,在測試集上評估結(jié)果,等等。所有這些都可以通過在初始化Trainer類時設(shè)置幾個參數(shù)來實現(xiàn)。

如果不是因為什么,我覺得用Trainer類代替普通的PyTorch編碼肯定會有助于您開發(fā)出一個更有組織性和更干凈的代碼庫。

開發(fā)示例工程

安裝

雖然是可選的一步,但我強烈建議您通過創(chuàng)建并激活一個新的虛擬環(huán)境來開始本教程:在這個虛擬環(huán)境中,我們可以完成所有的pip安裝。

python -m venv audio_env
source activate audio_env/bin/activate

加載數(shù)據(jù)集

與任何數(shù)據(jù)建模任務(wù)一樣,我們首先需要使用數(shù)據(jù)集庫加載數(shù)據(jù)集(我們將把此數(shù)據(jù)集傳遞給Trainer類)。

pip install datasets

假定我們正在使用自定義數(shù)據(jù)集(與此庫附帶的預安裝數(shù)據(jù)集相反)的話,我們需要首先編寫一個加載腳本(我們不妨稱之為crema.py),并以Trainer類可以接受的格式來加載數(shù)據(jù)集。

在??前一篇文章?? 中,我已經(jīng)非常詳細地介紹了如何創(chuàng)建這個腳本。(我強烈建議您仔細閱讀源碼工程,以便充分了解下面代碼片段中提到的config、cache_dir、data_dir等的用法)。數(shù)據(jù)集中的每個示例都有兩個特征:文件(file)和標簽(label)。

dataset_config = {
"LOADING_SCRIPT_FILES": os.path.join(PROJECT_ROOT, "crema.py"),
"CONFIG_NAME": "clean",
"DATA_DIR": os.path.join(PROJECT_ROOT, "data/archive.zip"),
"CACHE_DIR": os.path.join(PROJECT_ROOT, "cache_crema"),
}
ds = load_dataset(
dataset_config["LOADING_SCRIPT_FILES"],
dataset_config["CONFIG_NAME"],
data_dir=dataset_config["DATA_DIR"],
cache_dir=dataset_config["CACHE_DIR"]
)
print(ds)
********* OUTPUT ********DatasetDict({
train: Dataset({
features: ['file', 'label'],
num_rows: 7442
})
})

注意:當我們?yōu)镃remaD數(shù)據(jù)集創(chuàng)建一個datasets.Dataset對象時(要傳遞給Trainer類),不一定非要這樣做。我們還可以定義和使用torch.utils.data.Dataset(類似于我們在教程?https://towardsdatascience.com/recreating-keras-code-in-pytorch-an-introductory-tutorial-8db11084c60c中創(chuàng)建的CSVDataset)。

編寫模型訓練腳本

Github倉庫中的工程目錄結(jié)構(gòu)如下圖所示:

下面,讓我們開始撰寫腳本文件audio_train.py。

導入必需的庫

import os
import logging
import librosa

import wandb
import numpy as np

from datasets import DatasetDict, load_dataset, load_metric
from transformers import (
HubertForSequenceClassification,
PretrainedConfig,
Trainer,
TrainingArguments,
Wav2Vec2FeatureExtractor,
)
from utils import collator

logging.basicConfig(
format="%(asctime)s | %(levelname)s: %(message)s", level=logging.INFO
)

實驗跟蹤(可選)

USER = "vsher"
WANDB_PROJECT = "audio-classifier"
wandb.init(entity=USER, project=WANDB_PROJECT)

這段代碼節(jié)選自文件wandp.py。

我們在本處代碼中使用了Weight&Biases進行實驗跟蹤。因此,請確保您已經(jīng)創(chuàng)建了一個相應的賬戶;然后,根據(jù)您個人的詳細信息替換上述代碼中的USER和WANDB_PROJECT。

加載特征提取器

[問題]從廣義上講,什么是特征提取器?

[回答]特征提取器是一個負責為模型準備輸入特征的類。例如:對于圖像,可以包括裁剪圖像、填充;對于音頻,可以包括將原始音頻轉(zhuǎn)換為頻譜圖特征、應用標準化、填充等。

針對圖像數(shù)據(jù)的特征提取程序示例代碼如下:

>>> from transformers import ViTFeatureExtractor
>>> vit_extractor = ViTFeatureExtractor()
>>> print(vit_extractor)

ViTFeatureExtractor {
“do_normalize”: true,
“do_resize”: true,
“feature_extractor_type”: “ViTFeatureExtractor”,
“image_mean”: [0.5, 0.5, 0.5],
“image_std”: [0.5, 0.5, 0.5],
“resample”: 2,
“size”: 224
}

更具體地說,我們將使用Wav2Vec2FeatureExtractor類。其實這個類是SequenceFeatureExtractor類的一個派生類,它是Huggingface提供的用于語音識別的通用特征提取類。

歸納來看,共有三種方法可以使用Wav2Vec2FeatureExtractor類:

方法1:使用默認值。

from transformers import Wav2Vec2FeatureExtractor
feature_extractor = Wav2Vec2FeatureExtractor()
print(feature_extractor)
**** OUTPUT ****
Wav2Vec2FeatureExtractor {
"do_normalize": true,
"feature_extractor_type": "Wav2Vec2FeatureExtractor",
"feature_size": 1,
"padding_side": "right",
"padding_value": 0.0,
"return_attention_mask": false,
"sampling_rate": 16000
}

方法2:修改任何Wav2Vec2FeatureExtractor參數(shù)以創(chuàng)建自定義特征提取程序。

from transformers import Wav2Vec2FeatureExtractor
feature_extractor = Wav2Vec2FeatureExtractor(
sampling_rate=24000,
truncation=True
)
print(feature_extractor)
**** OUTPUT ****
Wav2Vec2FeatureExtractor {
"do_normalize": true,
"feature_extractor_type": "Wav2Vec2FeatureExtractor",
"feature_size": 1,
"padding_side": "right",
"padding_value": 0.0,
"return_attention_mask": false,
"sampling_rate": 24000,
"truncation": true
}

方法3:因為我們不需要任何定制,所以我們只需使用from_pretrained()方法加載預處理模型的默認特征提取器參數(shù)(通常存儲在名為preprocessor_config.json的文件中)。由于我們將使用facebook/hubert-base-ls960作為基本模型,因此我們可以獲得其特征提取器參數(shù)(可在鏈接https://huggingface.co/facebook/wav2vec2-base-960h/tree/main處的preprocessor_config.json下進行可視化檢查)。

from transformers import Wav2Vec2FeatureExtractor
model = "facebook/hubert-base-ls960"
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model)
print(feature_extractor)
*** OUTPUT ***
Wav2Vec2FeatureExtractor {
"do_normalize": true,
"feature_extractor_type": "Wav2Vec2FeatureExtractor",
"feature_size": 1,
"padding_side": "right",
"padding_value": 0,
"return_attention_mask": false,
"sampling_rate": 16000
}

為了看一下特征提取器的具體使用情形,讓我們將一個虛擬音頻文件作為raw_speech提供給Wav2Vec2FeatureExtractor:

model_id = "facebook/hubert-base-ls960"
feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_id)
audio_file = "dummy1.wav"
audio_array = librosa.load(audio_file, sr=16000, mono=False)[0]
input = feature_extractor(
raw_speech=audio_array,
sampling_rate=16000,
padding=True,
return_tensors="pt"
)
print(input)
print(input.shape)
print(audio_array.shape)
***** OUTPUT ******
{'input_values': tensor([[-0.0003, -0.0003, -0.0003, ..., 0.0006, -0.0003, -0.0003]])}
torch.Size([1, 36409])
(36409,)

需要注意的幾點:

  • 特征提取器的輸出是一個包含input_values的字典。它的值只不過是應用于audio_array的規(guī)范化,即librosa庫的輸出。事實上,input.input_values和audio_array兩者都具有相同的形狀。
  • 調(diào)用特征提取程序時,請確保使用的采樣頻率sampling_rate與基礎(chǔ)模型在訓練數(shù)據(jù)集時所使用的相同。我們正在使用這個Facebook模型進行訓練,它的模型卡明確聲明以16Khz的頻率采樣語音輸入。
  • return_tensor可以分別取值為“pt”、“tf”和“np”(分別對應于PyTorch張量、TensorFlow對象和NumPy數(shù)組)。
  • 填充對于單個音頻文件來說沒有多大意義,但當我們進行批處理時,它確實有意義,因為它會填充較短的音頻(結(jié)合額外的0秒或-1秒),使其與最長的音頻具有相同的長度。下面是用不同長度填充音頻文件的示例:
audio_file_1 = "dummy1.wav"
audio_file_2 = "dummy2.wav"
audio_array_1 = librosa.load(audio_file_1, sr=16000, mono=False)[0]
audio_array_2 = librosa.load(audio_file_2, sr=16000, mono=False)[0]
input_with_one_audio = feature_extractor(
audio_array_1,
sampling_rate=16000,
padding=True,
return_tensors="pt"
)
input_with_two_audio = feature_extractor(
[audio_array_1, audio_array_2],
sampling_rate=16000,
padding=True,
return_tensors="pt"
)
print(input_with_one_audio.input_values.shape)
print(input_with_two_audios.input_values.shape)
***** OUTPUT ****
torch.Size([1, 36409])
torch.Size([2, 37371])

既然我們知道了特征提取器模型的輸出在形狀上可能會有所不同(這取決于輸入音頻),那么,在將一批輸入推送到模型進行訓練之前,填充為什么很重要就很清楚了。處理批次時,我們可以:(a)將所有音頻填充到訓練集中最長音頻的長度,或者(b)將所有的音頻截取到最大長度。(a)的問題是,我們不必要地增加存儲這些額外填充值的內(nèi)存開銷;(b)的問題是由于截斷可能會導致一些信息丟失。

有一種更好的替代方法——在模型訓練期間使用數(shù)據(jù)收集器應用動態(tài)填充。我們很快就會看到這種用法的。

在構(gòu)建批處理(用于訓練)時,數(shù)據(jù)收集器只能對特定的輸入批應用預處理(例如填充)。

加載用于分類的基本模型

如前所述,我們將使用Facebook公司的Hubert模型對音頻進行分類。如果您對HuBERT的內(nèi)部工作機制感到好奇,請查看Jonathan Bgn為HuBERT編寫的這篇很棒的入門教程。

HubertModel可以說是一個“裸”模型類,它是唯一的一個由24個轉(zhuǎn)換器編碼器層組成的堆棧,并為這24個層中的每個層輸出原始隱藏狀態(tài)(不需要指定任何用于分類的特定的預定義參數(shù))。

bare_model = HubertModel.from_pretrained("facebook/hubert-large-ls960-ft")
last_hidden_state = bare_model(input.input_values).last_hidden_state
print(last_hidden_state.shape)
*** OUTPUT ***
torch.Size([1, 113, 1024]) # the hidden size i.e. 113 can vary depending on audio

我們需要在這個裸模型之上指定某種分類相應的頭部信息,以便可以獲取最后一個隱藏層的輸出,并將其輸入到一個最終輸出6個值(六個情感類中的每一個)的線性層中。這正是HubertForSequenceClassification所做的。它在頂部有一個分類頭,用于音頻分類等任務(wù)。

然而,與上面解釋的特征提取器配置類似,如果你從預訓練模型中獲得HubertForSequenceClassification的默認配置,那么,你會注意到,由于其默認配置的定義方式,它只適用于二進制分類任務(wù)。

model_path = ""facebook/hubert-large-ls960-ft""
hubert_model = HubertForSequenceClassification.from_pretrained(model_path)
hubert_model_config = hubert_model.config
print("Num of labels:", hubert_model_config.num_labels)
**** OUTPUT ****
Num of labels: 2

為了實現(xiàn)我們的6類型分類目標,我們需要使用PretrainedConfig來更新傳遞給Hubert模型的配置(請查看“參數(shù)微調(diào)”一節(jié)的介紹:https://huggingface.co/docs/transformers/v4.21.2/en/main_classes/configuration#transformers.PretrainedConfig.architectures)。

NUM_LABELS = 6
model_id = "facebook/hubert-base-ls960"

config = PretrainedConfig.from_pretrained(model_id, num_labels=NUM_LABELS)
hubert_model = HubertForSequenceClassification.from_pretrained(
model_id,
config=config, # because we need to update num_labels as per our dataset
ignore_mismatched_sizes=True, # to avoid classifier size mismatch from from_pretrained.
)

這里有幾點需要注意:

  • 在第5行,from_pretrained()函數(shù)從facebook/hubert-base-ls960加載模型架構(gòu)和模型權(quán)重(即所有24個轉(zhuǎn)換器層的權(quán)重+線性分類器)。

注意:如果只執(zhí)行hubert_model=HubertForSequenceClassification(),則會隨機初始化轉(zhuǎn)換器編碼器和分類器權(quán)重。

  • 將ignore_mismatched_sizes參數(shù)設(shè)置為True很重要,因為如果沒有這項設(shè)置,那么由于大小不匹配,您會得到一個錯誤(請參見下圖):作為facebook/hubert-base-ls960的一部分提供的分類器權(quán)重具有形狀(2,classifier_proj_size),而根據(jù)我們新定義的配置,權(quán)重應該具有形狀(6,classifer_proj_size)??紤]到我們將從頭開始重新訓練線性分類器層,我們可以選擇忽略不匹配的大小。

分類器大小不匹配導致的錯誤信息

凍結(jié)網(wǎng)絡(luò)層以便于微調(diào)

一般經(jīng)驗法則是,如果訓練預訓練模型的基礎(chǔ)數(shù)據(jù)集與您正在使用的數(shù)據(jù)集有顯著不同,最好在頂部解凍和重新訓練幾層。

首先,我們正在解凍頂部兩個編碼器層(最靠近分類頭)的權(quán)重,同時保持所有其他層的權(quán)重凍結(jié)。為了凍結(jié)/解凍權(quán)重,我們設(shè)置param.require_grad的值為False/True,其中param表示模型參數(shù)。

在模型訓練期間,解凍權(quán)重意味著這些權(quán)重將像往常一樣更新,以便能夠達到當前任務(wù)的最佳值。

#首先凍結(jié)所有神經(jīng)網(wǎng)絡(luò)層
for param in hubert_model.parameters():
param.requires_grad = False

#凍結(jié)兩個編碼器層
layers_freeze_num = 2
n_layers = (
4 + layers_freeze_num * 16
) # 4是指投影器和分類器的權(quán)重和偏差。
for name, param in list(hubert_model.named_parameters())[-n_layers:]:
param.requires_grad = True

注意:雖然在訓練過程的一開始就開始解凍許多層看起來很直觀,但我并不建議這樣做。實際上,我從凍結(jié)所有層開始實驗,只訓練分類器頭。因為最后的訓練結(jié)果很不理想(毫不奇怪);所以,我通過解凍兩層來恢復訓練。

加載數(shù)據(jù)集

借助于我們的自定義加載腳本crema.py,我們現(xiàn)在可以使用數(shù)據(jù)集庫中的loaddataset()方法來加載數(shù)據(jù)集。

dataset_config = {
"LOADING_SCRIPT_FILES": os.path.join(PROJECT_ROOT, "src/data/crema.py"),
"CONFIG_NAME": "clean",
"DATA_FILES": os.path.join(PROJECT_ROOT,"data/archive.zip"),
"CACHE_DIR": os.path.join(PROJECT_ROOT, "cache_crema")
}

ds = load_dataset(
dataset_config["LOADING_SCRIPT_FILES"],
dataset_config["CONFIG_NAME"],
cache_dir=dataset_config["CACHE_DIR"],
data_files=dataset_config["DATA_FILES"]
)

接下來,我們使用map()函數(shù)將數(shù)據(jù)集中的所有原始音頻(.wav格式)轉(zhuǎn)換為數(shù)組。

一般來說,map會將函數(shù)重復應用于數(shù)據(jù)集中的所有行/樣本上。

這里,函數(shù)(定義為lambda函數(shù))接受單個參數(shù)x(對應于數(shù)據(jù)集中的一行),并使用librosa.load()方法將該行中的音頻文件轉(zhuǎn)換為數(shù)組。如上所述,確保采樣率(sr)合適。

# 將原始音頻轉(zhuǎn)換為陣列
ds = ds.map(
lambda x: {
"array": librosa.load(x["file"], sr=16000, mono=False)[0]
},
num_proc=2,
)

注意:如果你在這個階段進行打?。╠s)的話,你會注意到數(shù)據(jù)集中的三個特征:

print(ds)
***** OUTPUT *****
DatasetDict({
train: Dataset({
features: ['file', 'label', 'array'],
num_rows: 7442
})
})

生成數(shù)組后,我們將再次使用map,這一次使用helper函數(shù)prepare_dataset()來準備輸入。

#將數(shù)據(jù)集處理為訓練模型所需的格式

INPUT_FIELD = "input_values"
LABEL_FIELD = "labels"

def prepare_dataset(batch, feature_extractor):
audio_arr = batch["array"]
input = feature_extractor(
audio_arr, sampling_rate=16000, padding=True, return_tensors="pt"
)

batch[INPUT_FIELD] = input.input_values[0]
batch[LABEL_FIELD] = batch[
"label"
] #colname必須是標簽,因為Trainer 類默認會查找它

return batch

在此,prepare_dataset是一個幫助函數(shù),它將處理函數(shù)應用于數(shù)據(jù)集中的每個樣本(或一組樣本,如批處理)。更具體地說,該函數(shù)做兩件事:(1)讀取batch["array"]中存在的音頻數(shù)組,并使用上面討論的feature_extractor從中提取特征,并將其存儲為一個名為input_values的新特征(除了文件、標簽和數(shù)組之外);(2)創(chuàng)建一個稱為label的新特征,其值與batch["label"]相同。

[問題]:您可能想知道每個樣本都有標簽和標簽有什么意義,特別是當它們具有相同的值時。

[原因]:默認情況下,Trainer API將查找列名標簽,我們正是考慮到此目的才這樣做的。如果您希望在這一步甚至可以完全刪除其他標簽列,那么請在創(chuàng)建加載腳本時將特征命名為“l(fā)abels”。

如果仔細觀察,你會注意到,與之前l(fā)ambda函數(shù)只接受一個輸入?yún)?shù)的映射用例不同,prepare_dataset()需要兩個參數(shù)。

記?。好慨斘覀冃枰騧ap內(nèi)的函數(shù)傳遞多個參數(shù)時,我們都必須向map傳遞fn_kwargs參數(shù)。此參數(shù)是包含要傳遞給函數(shù)的所有參數(shù)的字典。

根據(jù)其函數(shù)定義,我們需要為prepare_dataset準備兩個參數(shù):(a)數(shù)據(jù)集中的行和(b)特征提取器——因此我們必須使用以下fn_kwargsas:

# 針對所有樣本使用特征抽取器來預處理數(shù)據(jù)
ds = ds.map(
prepare_dataset,
fn_kwargs={"feature_extractor": feature_extractor},
# num_proc=2,
)
logging.info("Finished extracting features from audio arrays.")

接下來,我們將使用class_encode_column()將所有字符串標簽轉(zhuǎn)換為id(0,1,2,3,4,5,6)。

# LABEL TO ID
ds = ds.class_encode_column("label")

最后,?我們使用train_test_split()引入了訓練-測試-驗證分割。我們需要以這種方式進行兩次拆分,以獲得三個不重疊的數(shù)據(jù)集,所有這些數(shù)據(jù)集在下面的步驟8中合并為一個DatasetDict。

# 引入了訓練-測試-值分割

# 90%用于訓練,10%用于測試+驗證
train_testvalid = ds["train"].train_test_split(shuffle=True, test_size=0.1)
# 把用于測試+驗證的10%進一步對半分割:一半用于測試,一半用于校驗
test_valid = train_testvalid['test'].train_test_split(test_size=0.5)
# 如果您想擁有一個DatasetDict的話,請收集所有人的信息
ds = DatasetDict({
'train': train_testvalid['train'],
'test': test_valid['test'],
'val': test_valid['train']})

開始訓練

所有的零碎工作都準備好后,我們現(xiàn)在可以開始使用Trainer類進行訓練了。

首先,我們需要指定訓練參數(shù),這包括總迭代次數(shù)、批大小、存儲訓練模型的目錄、實驗日志記錄等。

trainer_config = {
"OUTPUT_DIR": "results",
"TRAIN_EPOCHS": 3,
"TRAIN_BATCH_SIZE": 8,
"EVAL_BATCH_SIZE": 8,
"GRADIENT_ACCUMULATION_STEPS": 4,
"WARMUP_STEPS": 500,
"DECAY": 0.01,
"LOGGING_STEPS": 10,
"MODEL_DIR": "models/test-hubert-model",
"SAVE_STEPS": 100
}

# 使用Trainer類進行微調(diào)
training_args = TrainingArguments(
output_dir=trainer_config["OUTPUT_DIR"], # output directory
gradient_accumulation_steps=trainer_config[
"GRADIENT_ACCUMULATION_STEPS"
], # 在運行優(yōu)化步驟之前累積梯度
num_train_epochs=trainer_config[
"TRAIN_EPOCHS"
], #總訓練迭代次數(shù)
per_device_train_batch_size=trainer_config[
"TRAIN_BATCH_SIZE"
], # 訓練期間每個設(shè)備的批量大小
per_device_eval_batch_size=trainer_config[
"EVAL_BATCH_SIZE"
], #用于評估的批量大小
warmup_steps=trainer_config[
"WARMUP_STEPS"
], # 學習率調(diào)度器的預熱步驟數(shù)
save_steps=trainer_config["SAVE_STEPS"], # 每100步保存檢查點
weight_decay=trainer_config["DECAY"], #權(quán)重衰減強度
logging_steps=trainer_config["LOGGING_STEPS"],
evaluation_strategy="epoch", # 在每個迭代末尾輸出指標值
report_to="wandb", # 啟動指向W&B的日志功能
)

這里也有幾個需要考慮的事項:

  • 梯度累積步驟在訓練期間想存儲大批量但內(nèi)存有限的情況下非常有用。設(shè)置gradient_accumulation_steps=4允許我們在每4個步驟后更新權(quán)重:在每個步驟中,batch_size=32個樣本被處理并累積它們的梯度。只有在累積足夠梯度的4個步驟后,才能更新權(quán)重。

其次,我們用這些訓練參數(shù)實例化Trainer類,并指定訓練和評估數(shù)據(jù)集。

#開始訓練
trainer = Trainer(
model=hubert_model, # 實例:對應于將訓練的轉(zhuǎn)換器模型
args=training_args, # 訓練參數(shù),如下所定義的
data_collator=data_collator,
train_dataset=ds["train"], # 訓練數(shù)據(jù)集
eval_dataset=ds["val"], # 評估數(shù)據(jù)集
compute_metrics=compute_metrics,
)

這里也還有幾個需要考慮的事項:

在第5行,我們使用了data_collator。我們在教程開始時簡要討論了這一點,作為動態(tài)填充輸入音頻陣列的一種方法。數(shù)據(jù)收集器初始化如下:

# 定義數(shù)據(jù)收集器以便動態(tài)填充訓練批次
data_collator = DataCollatorCTCWithPadding(
processor=feature_extractor,
padding=True
)

?DataCollatorCTCWithPadding是一個根據(jù)鏈接https://huggingface.co/blog/fine-tune-wav2vec2-english處教程改編的數(shù)據(jù)類。我強烈建議快速閱讀一下這個教程中的“設(shè)置Trainer”部分,以詳細了解這個類內(nèi)部的實現(xiàn)邏輯。

這個類中的__call__方法負責準備接收到的輸入,但沒有太多細節(jié)。它從數(shù)據(jù)集中獲取一批樣本(記住每個樣本都有5個特征:file、labels、label、array和input_values),并返回相同的批次,但是使用processor.pad對input_values應用了填充。此外,批次中的標簽將轉(zhuǎn)換為Pytorch張量。

from dataclasses import dataclass
from typing import Dict, List, Optional, Union

import torch
from transformers import Wav2Vec2Processor

INPUT_FIELD = "input_values"
LABEL_FIELD = "labels"


@dataclass
class DataCollatorCTCWithPadding:
processor: Wav2Vec2Processor
padding: Union[bool, str] = True
max_length: Optional[int] = None
max_length_labels: Optional[int] = None
pad_to_multiple_of: Optional[int] = None
pad_to_multiple_of_labels: Optional[int] = None

def __call__(
self, examples: List[Dict[str, Union[List[int], torch.Tensor]]]
) -> Dict[str, torch.Tensor]:

input_features = [
{INPUT_FIELD: example[INPUT_FIELD]} for example in examples
] # 樣本基本上對應于row0, row1,等等...
labels = [example[LABEL_FIELD] for example in examples]

batch = self.processor.pad(
input_features,
padding=self.padding,
max_length=self.max_length,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors="pt",
)
batch[LABEL_FIELD] = torch.tensor(labels)

return batch

?在第8行,我們定義了compute_metrics(),這是一種告訴Trainer在評估期間必須計算哪些指標(準確度、精度、f1、召回率等)的方法。它將評估預測(eval_pred)作為輸入,并使用metric.compute(predictions=.., references=...)將實際標簽與預測標簽進行比較。同樣,compute_metrics()的樣板代碼也是從鏈接https://huggingface.co/course/chapter3/3?fw=pt處改編而來的。

from datasets import load_metric

def compute_metrics(eval_pred):
# 定義評估指標
compute_accuracy_metric = load_metric("accuracy")
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return compute_accuracy_metric.compute(predictions=predictions, references=labels)

?注意:如果您想發(fā)揮創(chuàng)意并顯示自定義指標的話,那么,你可以自行修改一下compute_metrics()。在執(zhí)行此操作之前,您需要知道的只是eval_pred返回的內(nèi)容。在實際訓練模型之前,通過對eval/test數(shù)據(jù)集運行trainer.predict可以提前發(fā)現(xiàn)這一點。在我們的例子中,它返回實際的標簽和預測(即logits,在其上應用argmax函數(shù)以獲取預測的類型):

trainer = Trainer(model=..., args=...,...)
output = trainer.predict(ds["test"])
print(output)
**** 輸出*****
PredictionOutput(
predictions=array([
[ 0.0331, -0.0193, -0.98767, 0.0229, 0.01693, -0.0745],
[-0.0445, 0.0020, 0.13196, 0.2219, 0.94693, -0.0614],
.
.
.
], dtype=float32),
label_ids=array([0, 5, ......]),
metrics={'test_loss': 1.780486822128296, 'test_accuracy': 0.0, 'test_runtime': 1.6074, 'test_samples_per_second': 1.244, 'test_steps_per_second': 0.622}
)

核心訓練代碼

如果你仔細分析的話,你會注意到真正的訓練代碼也就是一行:

trainer.train()

# 從檢查點恢復培訓
# trainer.train("results/checkpoint-2000")

?請注意:源碼中第4行包含從檢查站繼續(xù)訓練的命令。但首先要搞清楚:什么是檢查點呢?

在訓練過程中,Trainer將創(chuàng)建模型權(quán)重的快照,并將其存儲在由TrainingArguments(output_dir="results")所定義的output_dir位置。這些文件夾通常命名為“checkpoint-XXXX”形式,其中包含模型權(quán)重、訓練參數(shù)等信息。

?檢查點

您可以分別使用save_strategy和save_steps指定創(chuàng)建這些檢查點的時間和頻率。默認情況下,檢查點將在每500個步驟后保存(save_steps=500)。我之所以提到這一點,是因為我不知道這些默認值。在一次訓練(持續(xù)7小時)中,我看到?jīng)]有在輸出目錄中創(chuàng)建任何檢查點。下面的數(shù)據(jù)是我正在使用的配置信息:

  • 訓練樣本數(shù):6697
  • epochs:5
  • 批大小:32 
  • 梯度累積步長Step:4

?經(jīng)過數(shù)小時的調(diào)試后,我發(fā)現(xiàn)在我的例子中,總步驟只有260個,而默認保存只發(fā)生在第500個步驟之后。通過在TrainingArguments()的實現(xiàn)代碼中添加一個設(shè)置(save_steps=100)的方法幫助我修復了這個問題。

在上圖的底部,你可以找到優(yōu)化步驟的總步數(shù)。

需要提醒的是,如果您想知道如何計算總步驟數(shù)(即本例中的260步),那么請注意下面的計算公式:

總訓練批次大小=批量(Batch size)梯度累積步長=324=128

總優(yōu)化步數(shù)=(訓練樣本/總訓練批量)*epochs=(6697/128)*5≈ 260。

在測試集上進行預測和記錄結(jié)果

#測試結(jié)果
test_results = trainer.predict(ds["test"])
logging.info("Test Set Result: {}".format(test_results.metrics))
wandb.log({"test_accuracy": test_results.metrics["test_accuracy"]})

trainer.save_model(os.path.join(PROJECT_ROOT, trainer_config["MODEL_DIR"]))

#把訓練后的模型數(shù)據(jù)日志保存到wandb
wandb.save(
os.path.join(PROJECT_ROOT, trainer_config["MODEL_DIR"], "*"),
base_path=os.path.dirname(trainer_config["MODEL_DIR"]),
policy="end",
)

?這里有幾件事情需要考慮:

  • 要將任何其他度量/變量記錄到權(quán)重和偏差,我們可以調(diào)用wandb.log()方法。例如,在第4行中,我們記錄下測試集的精度信息。
  • 默認情況下,wandb不會記錄經(jīng)過訓練的模型,因此它僅在訓練結(jié)束后才可在本地計算機上使用。為了顯式地存儲模型信息,我們需要調(diào)用wandb.save(設(shè)置policy="end"),表示僅在運行結(jié)束時同步文件。

結(jié)果和反思

使用不同超參數(shù)組合的所有不同模型運行的結(jié)果都記錄在我的權(quán)重和偏差儀表盤中。

?WandB儀表盤

?從學習曲線來看,我們最近的運行結(jié)果(faithful-planet-28:測試準確率=68%??紤]到只需要4小時的訓練,這還算不錯)可能會從額外的訓練迭代中受益,因為訓練和評估損失仍在減少,并且還沒有穩(wěn)定下來(或者更糟的是,開始偏離)。根據(jù)是否允許存在這種情況,可能需要解凍更多的編碼器層。

?學習曲線展示

?這里有幾點值得思考:

  •  如果我們增加訓練迭代次數(shù),那么通過使用回調(diào)技巧來提前停止訓練可能是值得考慮的方案。
# 提前停止訓練
trainer = Trainer(
callbacks=[EarlyStoppingCallback(early_stopping_patience = 10)]
)
  • 除了Cuda之外,Trainer最近還添加了對使用新Mac M1 GPU的支持(只需設(shè)置args = TrainingArguments(use_mps_device=True))。如果您正在與他們合作,請注意網(wǎng)絡(luò)上已經(jīng)有些人提到這種情況下會出現(xiàn)指標下降的問題(這是一個已知的錯誤,請參閱鏈https://github.com/huggingface/transformers/issues/17971處提到的問題)。

結(jié)論

希望您現(xiàn)在能夠更加自信地使用轉(zhuǎn)換器庫來微調(diào)深度學習模型。如果你真正在推進這個項目,那么,請與我和更廣泛的社區(qū)共同分享您的成果(以及提高準確性的步驟)。

與任何ML項目一樣,關(guān)注負責任的AI開發(fā)對于評估未來工作的影響至關(guān)重要。最近的研究表明,情緒檢測方法可能具有內(nèi)置的性別/種族偏見,并可能對現(xiàn)實世界造成傷害,這一點變得更加重要。此外,如果您正在處理敏感音頻數(shù)據(jù)(例如包含信用卡詳細信息的客戶支持電話),請應用脫敏技術(shù)來保護個人身份信息、敏感個人數(shù)據(jù)或業(yè)務(wù)數(shù)據(jù)。

像往常一樣,如果有更簡單的方法來實現(xiàn)或解釋本文中提到的一些事情,請您告訴我一下。

譯者介紹

朱先忠,社區(qū)編輯,專家博客、講師,濰坊一所高校計算機教師,自由編程界老兵一枚。


文章題目:基于Huggingface的定制音頻數(shù)據(jù)情感識別
鏈接URL:http://m.5511xx.com/article/cosedpe.html