iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
佛心分享-SideProject30

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

Day 4|人生中第一次微調:99.34%,然後我發現事情不太對

  • 分享至 

  • xImage
  •  

前面把模型下載下來、確認可以跑之後,下一步當然就是把自己的資料真的拿去訓練。我一開始也是很擔心自己沒經驗,沒什麼先備知識怎麼辦。不過好像比想像中簡單,畢竟我在開始之前就知道要在Google Colab上訓練。

所以當時我就直接把我整理好的單音節資料夾拿去當訓練資料了,一共 5,014 筆單音節資料。

訓練時使用的主要設定是:

Batch size:16
Gradient accumulation:2
Learning rate:1e-4
Warmup steps:200
Epoch:4
FP16:開啟
Logging:每 20 steps
Checkpoint:每 200 steps

這些東西第一次看的時候其實滿容易讓人困惑,所以我也簡單記錄一下它們到底是在控制什麼。

Batch size 可以先理解成模型一次拿幾筆資料來處理。我這次設定 16,也就是一次處理 16 筆資料。

但因為 GPU 記憶體有限,我沒有直接把 Batch size 開得很大,而是另外設定 Gradient accumulation 為 2。它的概念是先處理兩個 batch,把梯度累積起來,再進行一次模型參數更新。

所以可以簡單想成:

16 筆
 ↓
累積梯度

16 筆
 ↓
再累積梯度
 ↓
更新模型

因此有效的 batch size 可以粗略理解成 16 × 2,也就是 32。

Learning rate 則是 1e-4。它可以理解成模型每次根據錯誤調整參數時的步伐大小。數值太大可能會讓模型更新得太激烈,太小則可能需要很久才能學到東西。

Warmup steps 設定 200,意思是在訓練剛開始的階段,先讓 learning rate 逐步進入訓練狀態,而不是一開始就直接用完整的設定值。

Epoch 設定 4,可以簡單理解成讓模型完整看過這批訓練資料四次。

FP16 則是讓訓練使用較低精度的浮點數進行部分計算。在 GPU 資源有限的情況下,可以降低部分記憶體需求,也可能讓訓練速度更快。

至於 Logging 和 Checkpoint,就比較像是在記錄訓練過程。每 20 steps 記錄一次訓練資訊,每 200 steps 儲存一次 checkpoint,方便我觀察訓練狀況,也避免模型只剩最後一個結果可以看。

這次訓練總共跑了 4 個 Epoch。訓練過程中可以看到 loss 整體從一開始的 3.588271 降到後期約 0.4 左右。例如第 20 step 是 3.588271,到第 780 step 時記錄值已經降到 0.388170。訓練完成後,Trainer 紀錄的整體 train_loss 則是 0.697841。

例如:

Step    Training Loss
20      3.588271
40      1.832244
60      1.186142
80      0.978693
100     0.974713
120     0.661884
...
440     0.485474
480     0.446555
540     0.403206
580     0.396850
640     0.416293
700     0.394183
780     0.388170

最後訓練完成時,紀錄中的 global_step785,training loss 是 0.697841,整次訓練約花了 908 秒

到這裡看起來都滿正常。

然後我把模型拿去辨識。

結果:99.34%

第一次看到結果的時候,我其實滿開心的。

整體辨識準確率:99.34%。

我第一次真的把自己的資料拿去微調,結果竟然可以辨識到 99.34%。

而且這不是拿別人的預訓練模型直接跑,是我自己拿 5,014 筆台語單音節資料去做微調之後得到的結果。

當時我的第一個想法甚至不是「哇,模型泛化能力很好」。

而是:

欸?怎麼不是 100%?

因為這批資料本身就是拿來訓練模型的。

我原本甚至覺得,既然模型已經看過這些資料,那應該至少要接近 100%,結果居然還有幾筆辨識錯誤。

所以我開始把錯誤抓出來看。

結果看到一些很有意思的東西。

聲調方面出現:

第 4 調 → 第 8 調:2 筆
第 5 調 → 第 2 調:1 筆
第 1 調 → 第 2 調:1 筆

聲母方面則有:

th → ph:2 筆
t  → 空:2 筆
h  → m:1 筆
s  → k:1 筆
p  → t:1 筆

韻尾方面也出現:

p  → k:3 筆
t  → p:3 筆
ng → n:1 筆
ng → 空:1 筆

這時候我開始發現一件滿有趣的事情。即使整體 Accuracy 已經是 99.34%,模型還是會在某些很細的地方犯錯。而這些錯誤剛好也是我之後想拿來做發音分析的東西。例如 th 被判成 ph,這不是整個詞完全不知道是什麼,而是某個聲音單位出現了混淆;ng 被判成 n,也是類似的問題。

因為我的專題最後不可能永遠只處理一個音節一個音節的語音。實際的台語詞彙會由多個音節組成,這些音節在實際語音中也會連在一起,還可能受到連讀與變調影響。所以我接著建立了一個新的多音節測試資料集,想直接看看原本使用單音節資料微調的模型,遇到多音節語音之後會發生什麼事情。這次我建立了 188 筆多音節測試資料,然後把剛才那個主要使用單音節資料微調的模型拿來測試。結果出來之後,我直接看到一個非常明顯的落差:99.34% → 70.74%。

我當時看到這個結果大概就是:「蛤?差這麼多?」(訓練大失敗!!!!)

但這個 70.74% 反而開始讓我覺得有東西可以研究了。因為如果只看第一次的 99.34%,我很容易得到一個很直覺的印象:單音節好像已經沒什麼問題了。但換成多音節之後,模型的表現明顯下降,代表模型面對的根本不是同一個問題。於是我開始把這 188 筆資料裡的錯誤一筆一筆抓出來看。結果發現,70.74% 並不是單純「整個詞都辨識錯」。有些是音素錯,例如 thui-lí 被辨識成 hui-lí;有些則是聲調錯,例如 tsong-ha̍p 被辨識成 tsông-ha̍p。但我後來發現一個非常常見,而且對我的專題特別麻煩的問題,就是音節黏在一起。

例如標準答案是 lí-ke,模型卻輸出 líke;sat-bó 變成 satbó;sī-li̍k 變成 sīli̍k;ông-lâi 則變成 ônglâi。模型很多時候其實已經抓到大致上的聲音內容,卻沒有保留我需要的音節邊界。

這對我的專題來說就不是一個小問題了。因為我後面想做的事情,是把台羅進一步拆成音節,再往聲母、韻母、韻尾和聲調去分析。如果模型連多音節之間的邊界都不穩定,那後面要做的音素分析自然也會受到影響。

70.74% 到底錯在哪裡?

這次看到 70.74% 之後,我沒有只看 Accuracy,而是開始把這批多音節測試資料的錯誤一筆一筆抓出來。因為如果只知道「188 筆裡有一部分辨識錯誤」,其實還是很難知道模型到底出了什麼問題。我真正想知道的是:它究竟是完全聽不懂,還是其實已經辨識到一部分內容,只是在某些地方出錯?

例如 bai-óo-lín.wav,標準答案是:

bai-óo-lín

但模型預測成

báiu-lín

這就不是單純某一個字母辨識錯而已。原本三個音節的結構被模型改變了,中間的音節甚至沒有按照原本的形式保留下來。

再例如 thui-lí.wav

標準:thui-lí
預測:hui-lí

這次就比較容易看出來是音素層級的錯誤,原本的 th 被辨識成了 h

還有 lí-ke.wav

標準:lí-ke
預測:líke

這個錯誤一開始看起來好像沒那麼嚴重,畢竟模型似乎還是辨識到了兩個音節的內容。但對我的專題來說,這其實是很麻煩的問題,因為原本的音節邊界消失了。

而且這種情況不是只有一筆。像是:

sat-bó
→ satbó
sī-li̍k
→ sīli̍k
ngē-táu
→ ngētáu
ông-lâi
→ ônglâi
im-kan
→ imkan

模型很多時候並不是完全不知道這段語音裡面有哪些聲音,而是當多個音節連在一起之後,它會把原本分開的音節黏在一起。

這件事情對我的專題特別重要。因為我後面並不是只需要知道「這個詞大概是什麼」,而是要把模型輸出的台羅繼續拆成音節,再進一步分析聲母、韻母、韻尾和聲調。如果 lí-ke 變成 líke,那我後面就會遇到一個很基本的問題:我要怎麼知道哪一段是第一個音節,哪一段是第二個音節?

所以我開始發現,70.74% 背後其實不是單純的「模型準確率下降」。模型在多音節語音裡遇到的問題,比我原本想像的複雜很多。

不只是音節黏在一起

除了音節邊界之外,我也看到一些音素和聲調上的錯誤。

例如:

tsong-ha̍p
→ tsông-ha̍p

這是聲調上的錯誤。

另外也有:

lo̍h-hā
→ lo-hā

以及:

tsiànn-sin
→ tsiàn-sin

這類比較接近音素或韻尾資訊的錯誤。

還有:

tsîng-tī
→ tsîng-pī

這時候我開始覺得,不能把所有錯誤都直接塞進一個 Accuracy 裡面。

因為「辨識錯誤」其實可以是完全不同的事情。有些是音節數不對,有些是音節黏合,有些是聲母或其他音素錯誤,有些則是聲調錯誤。如果最後全部只寫成:

正確 / 錯誤

那我只知道模型錯了,卻不知道它為什麼錯。

而這件事情對我的專題尤其重要。因為我不是要做一個單純把語音轉成文字的 ASR,而是希望利用 ASR 的結果繼續往發音分析走。如果連錯誤本身都沒有辦法拆開來看,那後面根本沒有辦法知道模型提供的資訊到底能不能拿來做發音分析。

這時候,我開始重新看第一次的 99.34%

做到這裡,我又回頭看了一次第一次微調的結果。

第一次拿 5,014 筆單音節資料微調之後,我得到的是 99.34%。當時我只是想知道模型在這批單音節資料上的辨識表現,所以看到這個結果時,我原本覺得單音節應該已經做得滿好了。

但後來我整理實驗紀錄時,才發現第一次實驗還有一個很大的問題:那 5,014 筆資料當時根本沒有正式切成 training set 和 test set。

也就是說,我拿去訓練模型的資料,後來又拿同一批資料來計算辨識準確率。

我看到這件事情的時候是真的覺得

大事不妙。

因為這代表 99.34% 不能直接拿來當成模型遇到新資料時的正式表現。這個問題是我後來整理實驗時才發現的,所以現在回頭看,第一次的 99.34% 應該更保守地解讀:它可以讓我看到模型確實把這批單音節資料學得非常好,但不能拿來證明模型對沒看過的資料也有同樣的準確率。

而這也讓前面的 70.74% 變得更值得注意。至少在這次多音節測試裡,我是另外建立了一批資料來測試原本的單音節模型,所以我開始看到一個非常明顯的現象:

模型在單音節資料上表現很好,換成多音節語音之後,問題馬上出現。

原來我要處理的不是只有模型

第一次微調之前,我想的其實很簡單:先把資料整理好,然後把模型拿去訓練。如果結果不好,就繼續調參數、換模型,想辦法把 Accuracy 拉高。

但 70.74% 之後,我開始發現事情沒有這麼單純。

如果我的訓練資料主要是單音節,而我要讓模型處理多音節語音,那模型當然可能會遇到問題。更不用說台語實際說話時還會出現連讀和變調,這些情況都可能讓字典裡的原始讀音和實際語音之間產生差異。

所以我開始重新思考一個問題

如果我希望模型最後能處理實際的台語語音,那我到底應該讓它學什麼?

今天先分享到這裡,我有發現這篇文好像有點AI修過頭了。(好不擅長處理這種文章)希望我最後可以改善啦。
我是微微,明天繼續講故事,掰掰!


上一篇
Day 3|原本以為可以撿現成,最後還是要自己訓練
下一篇
Day 5|Colab 額度不夠啦!為什麼3050筆電訓一次模型要16小時ヽ(#`Д´)ノ 我需要免費GPU
系列文
打造台語發音檢測系統:可憐大四生的專題實錄11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言