iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
佛心分享-SideProject30

打造台語發音檢測系統:可憐大四生的專題實錄系列 第 10

Day 10 圖解大破程:Hugging Face 模型在訓練前資料被怎麼處理

  • 分享至 

  • xImage
  •  

今天這篇文章我來講解一下我的模型在訓練前資料被怎麼處理的。

我原本這篇想把我的模型演變歷史整理出來,不過在我講到參數時。我突然想順便講我的訓練程式在幹嘛。於是這篇就誕生了。(模型演變歷史後面幾天寫)

對模型訓練有點概念的人大概都知道按下:

trainer.train()

之後,模型才開始訓練。在它前面可是要經歷一段旅程呢。先放張圖看下我的程式在幹嘛。

流程總覽

看起來非常簡單。拿資料,資料分成聲音和標籤。再分別處理聲音和標籤,一一轉成數字。然後把每筆(聲音+標籤)包在一起,變成一筆完整的訓練資料。接著再從資料裡一次拿幾筆出來,組成 Batch。因為每個音檔長度都不一樣,所以還要把它們補成一樣長。最後,送進模型進行訓練。

就是這樣,但是魔鬼藏在細節裡,真正拆解起來會要人命。

尤其是 Step 2,先從聲音開始拆解。

聲音是怎麼變成數值資料的?

我放入訓練資料裡的是 .wav 檔。.wav 檔這個東西呢,要被模型理解之前,得先變成數值。一般的音訊播放器都會內建處理這些事情的程式,所以我們平常只要點一下播放就好了。但模型不是音訊播放器,也不是一個完整的軟體。它只是一個負責瘋狂做數學的小小模型。於是,我們就要在模型外面幫它處理啦。

我的程式會用:

speech_array, _ = librosa.load(
    audio_path,
    sr=16000,
    mono=True
)

把 WAV 音檔讀成數值資料。

librosa 是 Python 中常用的音訊分析套件,專門用來處理聲音資料。它可以讀取不同格式的音檔,把聲音轉換成程式可以處理的數值,也可以進一步進行像是音訊重採樣、頻譜分析、音高分析等工作。

不過我這次的訓練程式裡,librosa 目前負責的工作其實沒有那麼多。

16000 Hz 是語音模型中相當常見的取樣率(Sampling Rate),也是 wav2vec 2.0 預期使用的音訊取樣率。 因此,我的程式在讀取音檔時指定為 16000 Hz,mono=True 則表示統一讀成單聲道。這個過程並沒有直接修改原始音檔。

librosa.load() 會回傳兩個東西:音訊的數值資料,以及音訊讀取後使用的取樣率。

我的程式只需要前面的聲音數值,因此把它存進 speech_array。後面的取樣率雖然也被回傳,但後續沒有使用,所以用 _ 接住。

_,基本上就是收到,但不用。

音檔讀進來之後,就會變成一串像這樣的數值:

[0.012, -0.031, 0.047, ...]

也就是聲音的波形資料。

它是程式按照時間順序記錄聲音訊號在每個時間點的振幅。把這些數值畫出來,就會變成我們常看到的聲音波形。

不過,這串數字還不是最後送進模型的 input_values

wav2vec 2.0 使用的輸入格式還需經過 Hugging Face 的 Feature Extractor 處理。它會把剛從 WAV 讀出來的聲音數值,整理成 wav2vec 2.0 可以使用的輸入格式。

Feature Extractor 處理流程

Feature Extractor 聽起來很像是在幫我從聲音裡面抽出什麼特徵,例如音高、頻譜之類的。

在這邊很純粹把剛剛得到的 speech_array 丟進 processor

processor(
    speech_array,
    sampling_rate=16000
)

processor 是 Hugging Face 載入的 AutoProcessor。它裡面同時包含了處理聲音和處理文字標籤所需要的工具。當輸入的是剛剛那串聲音數值時,processor 就會把工作交給 AutoProcessor 內建的 Feature Extractor。

Feature Extractor 會依照 wav2vec 2.0 預期的方式處理這串聲音資料,包括進行 normalization(正規化)。簡單來說,就是調整不同音檔的數值分布,避免錄音音量或數值尺度的差異直接影響模型。並把資料包裝成 Hugging Face 使用的輸入格式。最後得到 input_values

我的程式再透過:

.input_values[0] 

把這筆音檔對應的聲音資料取出來。因為 processor 回傳的 input_values 外面還保留了一層資料維度,即使現在一次只處理一筆音檔也是一樣,所以 [0] 代表取出目前這唯一一筆音檔的完整聲音資料。最後存進 batch。

存入 batch 示意圖

講完聲音,講講標籤吧。

標籤是怎麼變成數字編號的?

先來圖解:

標籤處理流程

我的標籤來自於我的音檔檔名。我的程式會根據我提供的路徑掃描訓練資料夾,找出 .wav 檔。

接下來,這個標籤會跟著音檔一起進入 prepare_dataset() 進行處理。

不過,這時候的標籤還只是文字。

模型訓練時需要的是數字,所以我的程式接著做了這件事:

text = ud.normalize("NFC", batch["text"])
batch["labels"] = list(processor(text=text).input_ids)

前面的 NFC 正規化在之前的文章已經講過了,所以這裡就不再展開。簡單來說,就是先確保送進去的台羅文字使用一致的 Unicode 表示方式。

真正負責把文字交給處理工具,並取得數字編號的是這一段:

processor(text=text).input_ids

又是前面處理聲音時出現過的 processor

不過前面輸入的是聲音數值,所以它使用裡面的 Feature Extractor;這次輸入的是文字,因此會交給裡面的 Tokenizer 處理。

Tokenizer 的工作,就是根據自己載入的 vocabulary,決定文字要怎麼拆解,並將拆解後的內容轉換成模型使用的數字編號。

所以:

processor(text=text)

可以理解成把台羅文字交給 Tokenizer 處理。

處理完成後,processor 會回傳處理結果,而:

.input_ids

就是從結果中取出文字轉換後的 Token ID 編號。

最後:

batch["labels"] = list(processor(text=text).input_ids)

就是把得到的 Token IDs 轉成 Python 的 list,存進這筆資料的 labels

Step 2 結束了。

怎麼建立完整的訓練資料?

前面,我們已經分別處理完聲音和標籤。

現在每筆資料都有:

  • input_values:模型要處理的聲音
  • labels:這段聲音對應的標籤

接著把它們放進同一筆訓練資料裡,就完成了。

這是目前最簡單的步驟。

建立完整訓練資料

Step 4,一次拿一部分資料來訓練

模型不會一次把整份資料全部吃進去,而是一次從資料集中取出一小部分進行訓練。

這一小部分資料叫做 Batch。

在我的程式中:

per_device_train_batch_size=16

代表每次會取出 16 筆資料來進行訓練。

Step 5 是重頭戲

每個音檔的長度都不同,但模型一次要處理多筆資料,因此需要先把它們整理成相同的長度。這個動作叫做 Padding,也就是把較短的資料補到同一個 Batch 中最長的資料長度。

我的程式裡,負責整理這些資料的是:

@dataclass
class DataCollatorCTCWithPadding:

它會在組成 Batch 時,分別處理聲音和標籤。因為兩者的長度都可能不同,所以 Padding 的處理方式也不完全一樣。

先來張圖解。

Padding 處理圖解

每一筆音檔轉換成 input_values 後,長度不一定相同。例如同樣都是一個音節,有些人錄得比較短,有些人錄得比較長。

所以在組成 Batch 時,DataCollatorCTCWithPadding 會先把這批資料的聲音交給:

batch = self.processor.pad(
    input_features,
    padding=True,
    return_tensors="pt",
)

padding=True 會讓這一批 input_values 的長度整理一致,這樣才能一起轉成 Tensor 送進模型。

標籤也需要另外處理:

labels_batch = self.processor.pad(
    labels=label_features,
    padding=True,
    return_tensors="pt",
)

這裡看起來跟聲音很像,但標籤後面還多了一個步驟。

我的程式會利用 attention_mask 找出哪些位置是 Padding:

labels_batch.attention_mask.ne(1)

然後把這些位置改成 -100

labels = labels_batch["input_ids"].masked_fill(
    labels_batch.attention_mask.ne(1), -100
)

這裡的 -100 是用來標記這個位置原本是 Padding,因此在計算 Loss 時會被忽略。

最後,再把處理好的標籤放回這一批資料:

batch["labels"] = labels

到這裡,一個 Batch 的聲音和標籤才都整理完成,可以交給 Trainer,接著才會進入真正的模型訓練。

好耶! 終於都講解完了!

我把我的程式碼也放上 GitHub 了。

GitHub 連結https://github.com/yifyu1122/taiwanese-asr-lab

但是這次大家可不要直接拿來跑,我放上去主要是拿來展示這次文章裡講的東西。真的要拿來使用的話,還是需要依照自己的資料、模型路徑和環境修改一下。

我是微微,大家明天見!


上一篇
Day 9 終於揪出罪魁禍首!但是為什麼我介紹套件介紹到開始口音大戰。這不是畢專嗎?拜託停手!
下一篇
Day 11 揪出錯誤後呢?模型訓練終於有進展
系列文
打造台語發音檢測系統:可憐大四生的專題實錄11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言