iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
佛心分享-SideProject30

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

Day 3|原本以為可以撿現成,最後還是要自己訓練

  • 分享至 

  • xImage
  •  

上一篇講到,我最後選擇從教育部《臺灣台語常用詞辭典》取得台語詞彙與語音資料。但真正把資料下載下來之後,我才發現,有資料跟資料可以直接拿來用,是兩回事。

歡迎來到資料清洗地獄 ( ´•︵•` )

這是半年前我第一次整理資料時遇到的問題。當時我拿到的東西,大致上就是 kautian.ods 加上大量 WAV 音檔,但這些東西根本還沒有接在一起。

所以 Day 3 就來講講,我是怎麼把字典和 WAV 檔配對起來的。

字典對照表 kautian.ods

教育部《臺灣台語常用詞辭典》的相關資源中,提供了辭典文字資料與詞條音檔。

其中一個我後來大量使用的檔案,就是 kautian.ods。我把它當成資料索引表。裡面有辭典的詞條資訊,也有我後面需要用來連結語音資料的欄位,例如羅馬字與音檔相關資訊。

實際下載下來的 WAV 長什麼樣?

如果你以為下載完資料之後,資料夾裡會直接出現:

kám.wav
káu.wav
tshia̍h.wav

那你想得太美好了。

我當時實際拿到的是按照音檔編號分散在不同資料夾裡的 WAV。

下一步工作到這裡已經很清晰了。要怎麼把它們提取出來呢?

我初步的想法是按照臺羅拼音符號表,把單音節分類並重新命名,方便後續查詢。因此我開始按照臺羅的音節結構建立資料夾,分成:

接下來就開始寫 Python 啦!

資料整理程式:move.py

它做的事情非常單純:

雖然過程看起來很簡單,但是細節可不少,做錯一步就要重來。

找到符合的資料

大家觀察前面的分類表應該會發現,臺羅的韻母有不同的組合形式。因此在用羅馬字進行篩選時,不能只看單一字母。

例如我要找 i,如果直接用「以 i 結尾」來篩選,iiaiuio 都可能被一起抓進來。我一開始沒有注意到這個問題,等分類結果跑完才發現資料混在一起,只好回頭修改篩選條件,再重新整理一次。

找到符合條件的資料後,程式會從 ODS 取得對應的音檔編號。由於 ODS 裡的編號可能帶有 (1),而實際下載下來的 WAV 也使用這種命名方式,因此還需要依照原始資料的命名規則處理。我也是一開始忘記有 (1)

找到音檔後,再把它重新命名成對應的臺羅,例如 kám.wav,並複製到前面建立好的分類資料夾。

跑的過程還發生靈異事件:log 顯示複製失敗,但是實際上已經複製完了。

先測一下原本的模型

接下來,我開始測試原本打算使用的 wav2vec2-large-xls-r-300m-tsm-asr-v6

在模型本身的驗證結果中,Validation WER 是 0.666

這裡的 WER(Word Error Rate)是 ASR 很常見的評估指標,用來衡量辨識結果和標準答案之間有多少錯誤。它會考慮三種情況:替換(Substitution)、刪除(Deletion)以及插入(Insertion),計算方式是:

WER = (S + D + I) / N

其中 N 是標準答案的詞數。

所以 WER 越低越好,0 代表完全沒有錯誤。

不過,我自己的測試並沒有直接使用 WER,而是另外建立了字典單音節的 Exact Match Accuracy。也就是只有當模型輸出的整個臺羅音節完全符合標準答案時,才算辨識正確。

在我的測試條件下,Exact Match Accuracy 只有 56.78%

這兩個數字不能直接互換,因為它們衡量的是不同東西。WER 是以錯誤率的方式衡量整體辨識結果,而我的 Exact Match 是直接判斷這一個單音節到底有沒有完全辨識正確。

我後來進一步用自己寫的 assessment.py 把辨識結果拆成聲母、韻腹、韻尾與聲調,開始分析模型到底錯在哪裡。程式會把標準答案與模型輸出拆成這些音節組件後再比較。

這樣分析之後,我發現錯誤並不是平均分布在所有音節上,而是會集中在某些特徵。

首先很明顯的是聲調混淆。例如第 1 調辨識成第 7 調有 246 筆,第 8 調辨識成第 4 調有 151 筆,第 7 調辨識成第 1 調也有 137 筆。這代表模型在辨識聲調時,並不是單純隨機猜錯,而是存在特定的混淆方向。

聲母也出現類似的情況,例如 kh → th14 筆t → p14 筆。這些結果讓我開始注意到,模型可能不是「整個音節都辨識錯」,而是在某些音節組件上特別容易出現混淆。

韻尾的情況則更加明顯,其中 h → 空 出現了 169 筆。也就是說,有一部分原本帶有 -h 韻尾的音節,模型輸出時直接沒有辨識出這個韻尾。

這些統計結果讓我第一次比較具體地看到這個模型的弱點:錯誤其實有規律,而且不同的音節組件有不同的問題。

所以接下來,我沒有只停在錯誤統計,而是挑出實際的音節進一步分析。

前面的錯誤統計讓我注意到,kh 這類聲母存在明顯的混淆。因此我挑了 khà 這兩個音節,進一步看看模型到底是怎麼辨識它們的。

我先把標準 khà、標準 ,以及一段學生錄音送進模型。單純看最後的 ASR 結果,三者的結果其實就已經很有意思:

標準 khà 被辨識成 tha-,標準 被辨識成 kà-ah,反而是學生錄音辨識成了 khà

但如果只看最後輸出的文字,我還是很難知道模型到底在哪裡判斷錯了。於是我決定不要只看模型最後吐出的答案,而是往模型裡面看。

Hidden States 要怎麼看?

Wav2Vec2 在處理音訊的過程中,會經過多層神經網路,每一層都會產生一組 hidden states。這些數值不是模型直接告訴我的「這個音是 kh」或「這個音是 k」,而是模型在處理音訊時形成的內部表徵。

我在載入模型時開啟 output_hidden_states=True,就可以取得這些中間結果:

outputs = model(**inputs)

ai_features = outputs.hidden_states[-1]

這裡的 hidden_states[-1] 就是模型最後一層的 hidden states。

它不是一個單獨的數字,而是會隨著音訊時間變化的一組向量。為了先做簡單的整體比較,我把整段音訊的時間軸取平均,讓每個音檔最後都變成一個固定長度的特徵向量。

接下來的問題就變成:拿到這些向量之後,要怎麼比較兩段音訊在模型裡的表徵是不是接近?

Cosine Similarity 是什麼?

Cosine Similarity(餘弦相似度)是一種比較兩個向量方向有多接近的方法。

假設有兩個向量 A 和 B:

A:學生錄音經過模型後得到的特徵向量

B:標準音檔經過模型後得到的特徵向量

計算方式是:

Cosine Similarity = A · B
                   ───────
                   |A| |B|

就是先算兩個向量的內積,再除以兩個向量各自的長度。結果越接近 1,代表兩個向量的方向越接近。

所以這次我拿學生錄音的向量,分別和標準 khà、標準 的向量做比較。

結果是:

學生錄音與標準 khà0.9395

學生錄音與標準 0.9587

也就是說,按照這次實驗的比較方式,學生錄音的模型表徵反而更接近標準

這個結果沒有直接告訴我學生的 kh 發得對不對,反而讓我發現一件事:模型最後的 ASR 結果,和它內部的特徵表徵,並不一定能直接拿來解釋成我們想要的語音學特徵。

既然整段音訊壓成一個向量之後,還是看不出模型到底在哪個時間點做出判斷,我決定再把時間軸拆開來看。

把模型的判斷拉回時間軸

我把模型的最後輸出再往下拆。Wav2Vec2 的 logits 是一個隨著時間變化的矩陣,每個時間步都有一組對應各個 token 的分數。我用 argmax 找出每個時間點分數最高的 token,再把 token ID 轉回臺羅字母。

logits = model(**inputs).logits[0]

predicted_ids = torch.argmax(logits, dim=-1).tolist()

tokens = processor.tokenizer.convert_ids_to_tokens(predicted_ids)

接著根據音檔長度與模型產生的時間步數,估算每個 frame 對應的時間位置,最後把 token 和時間點一起輸出。

time_per_frame = duration / total_frames

for idx, token in enumerate(tokens):
    current_time_ms = idx * time_per_frame * 1000

這樣得到的就不只是最後一句 ASR 結果,而是可以看到模型在不同時間點分別偏向哪些 token。

例如學生錄音的結果中,可以看到:

0.0 ms     → k
363.8 ms   → à
525.4 ms   → -
606.3 ms   → a
687.1 ms   → h

這兩個實驗我也整理成 GitHub 裡的 Day 3 範例程式,有興趣的話可以自己準備一個音檔跑跑看,看看模型處理音訊後會留下什麼樣的 Hidden States,以及不同時間點會偏向哪些 token。

GitHubhttps://github.com/yifyu1122/taiwanese-asr-lab

結果確實出來了,但我看著這些時間點,還是很難回答原本的問題。

我知道模型在什麼時間點偏向 khà,卻不知道這些 token 背後對應的聲音究竟發生了什麼變化。換句話說,我看到的是模型的判斷結果,而不是聲音本身。

這也是我在 review 時被指出來的問題:如果我要研究 khk 這種實際的發音差異,應該回到原始音訊本身,而不是一直從 ASR 模型的輸出往下拆。

老師當時建議我可以進一步輸出頻譜圖(Spectrogram),直接從聲音的頻率分布觀察差異。

不過,這個方向在當時並沒有真的做完。

因為同一次 review 中,老師更直接指出了前面的 baseline 結果:我用字典單音節測試得到的 Exact Match Accuracy 只有 56.78%。對當時的目標來說,這個結果太低了。

老師給我的選項很明確:換模型,或是 fine-tuning。

於是我原本先分析現成模型的弱點,再想辦法補足的規劃,在這裡出現了轉折。最後我選擇不換模型,而是開始嘗試 fine-tuning

至於接下來會不會真的能用這些資訊判斷發音,當時的我還沒有答案。那時候我只知道,原本的模型表現不夠好,必須先想辦法讓它學會我真正想處理的台語資料。

今天的分享就到這裡,我是微微,我們明天見!


上一篇
Day 2|去年嫌訓練資料太貴,今年才發現學生只要 3,000 元
下一篇
Day 4|人生中第一次微調:99.34%,然後我發現事情不太對
系列文
打造台語發音檢測系統:可憐大四生的專題實錄11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言