2021 年春夏,我與隊友們組成四人隊伍,參加玉山銀行在 T-Brain 平台上主辦的「你,識字嗎?玉山人工智慧公開挑戰賽 2021 夏季賽」,取得了第 20 名的成績(Top 4%)。
在這則筆記裡,我將摘要我的競賽解法,並且簡要教學這次比賽的重要技巧—手寫文字圖片合成。在主辦單位舉辦的得獎者分享會之中,我從勝出隊伍身上獲得新知、反省自己的解法不足之處,因此,我也會在這篇筆記記錄我從得獎者的分享學到的技巧。這篇筆記提及的技巧,將著重於探討如何有效率地處理資料標籤錯誤、以及如何讓合成圖片更加逼真。
本次比賽的 PyTorch 程式碼,以及本篇筆記的手寫文字圖片合成教學 Python 程式碼,都公佈在 我的 GitHub 與大家交流。
目錄
競賽介紹
主辦單位提供將近七萬張包含手寫中文字的圖片,每張圖片也提供一個該圖片所對應到中文字的標籤,參賽者的任務,就是透過深度學習自動辨識圖片中的文字。競賽規定需要分辨的中文字總共有 800 個,如果手寫圖片中的字無法辨識、或者不屬於 800 字中的任何一類,就將它分類成「其他類別」(標註成 “isnull”)。
隊伍成績
競賽解題方法摘要
這次競賽,我主要用 Albumentations 套件進行圖像相關操作、並用 PyTorch + PyTorch Lightning 建立模型,Python 程式碼都分享在 我的 GitHub,以下摘要對比賽成績有幫助的其中幾項重點。
VGG 風格的 CNN 深度學習模型
既然是影像辨識問題,基本上會從 CNN 類型的模型出發。我參考了史丹佛大學 CS231 的 專題研究,他使用 VGGnet 在中文字手寫辨識任務獲得良好的成效。因此,我根據他的實驗流程,嘗試了多種 VGG 風格的模型,Cross-Validation 後找出最佳網路深度與神經元數量架構,作為我的基礎模型。我自己也嘗試過 ResNet 或 DenseNet 等 torchvision 支援的影像辨識模型,但準確度並沒有明顯地更好。
事實上,我們最終將每個隊友分別訓練的多種模型(主要是 ResNet + VGGnet)的輸出進行簡單平均的 Ensemble,這個小技巧確實讓我們的最終模型準確度有不小的提升。
資料增強(Data Augmentation)
雖然前面提到資料集總共有將近七萬張圖片,但是我們需要辨識出的中文字種類總共有 800 個,平均每個字只有大約 80 張圖片,更別說資料不平衡、造成部分的中文字只有約 20 張圖片。資料有限又想避免過擬合,資料增強在此特別重要。
本次競賽中,我實驗過後對提升辨識準確度的資料增強有以下幾種:(使用 Albumentations 套件實作)
- RGBShift
- RandomBrightnessContrast
- ElasticTransform
- ShiftScaleRotate
在我的 圖片合成教學 Notebook,有上述 Albumentations 資料增強的簡單示範。若是還不熟悉資料增強是什麼的讀者,也推薦你閱讀我寫過的 資料增強教學與 Albumentations 介紹。
錯誤標籤修正技巧
本次競賽最大的困難點在於其提供的資料集,有很多錯誤的標籤。例如,明明圖片裡的中文字一看就知道是「同」,資料卻標註成「司」,至少 10% 的圖片是這種標示錯誤。資料原本就不多,還有這麼多錯誤,如果不修正這些標籤,就難以訓練出有效的模型。但是,將近七萬筆資料,若是要全部手動重新標注資料,太過於費時費工。因此,我們用了幾種方法,來提升修正錯誤標籤的效率。
空白圖片:辨識「只有背景」的分類器
我們發現競賽中,有很多的錯誤標籤是「只有背景」的圖片,明明圖片裡什麼手寫字都沒有,資料卻給予我們它是某個字的錯誤標籤。合理的狀況,這種「只有背景」圖片應該被歸類為「其他類別」(”isnull”)。
對此,我的強力隊友想出了好方法來清資料:創造一個簡單的二元分類器,分辨圖片內是「有手寫字存在」或者「只有背景」。這個分類器因為分辨的任務本身非常簡單,因此只要快速地抽樣一小群資料,手動標註是否符合「只有背景」,就能訓練出足夠準確的二元分類器。
辨別出哪些是「只有背景」後,我們就能自己決定要把「只有背景」重新標註成「其他類別」(”isnull”);或者,可以剔除「只有背景」的圖片資料,以節省模型訓練的時間。
善用混淆矩陣
我們在檢視錯誤標籤的過程中,發現很多正確標籤跟錯誤標籤長得完全不像的狀況,例如:
- 圖片裡的手寫字是「股」,資料標籤卻是「票」
- 圖片裡的手寫字是「公」,資料標籤卻是「司」
- 圖片裡的手寫字是「銀」,資料標籤卻是「行」
這類的錯誤不像是人類肉眼辨識文字時的誤差,比較可能是標註人員為字詞切割圖片時出錯、或者相鄰字詞的輸入順序錯誤。當我們要手動處理錯誤標籤時,這反而是福音,因為它是更明顯、更「確定」的錯誤。舉例而言,如果一張手寫中文字圖片的真實標籤是「股」:
- 一個正常的影像辨識模型可能會把它誤認為形象相似的字,包括「設」、「役」、「說」等等
- 相對的,影像辨識模型只有很低的可能會辨識成形象差很多的字,例如「票」、「市」、「台」等等
在這類情況,用混淆矩陣(Confusion Matrix)就可以簡便的看出:模型高頻率預測錯誤的字詞中,是否存在預測與答案形象相差很多的中文字。如果有,我們可以優先把這些情形挑出來、手動修正錯誤標籤。簡言之,錯誤標籤太多,我們利用混淆矩陣找出最誇張的錯誤標籤、優先修正。
模型的高信心
深度學習模型預測輸出的 Softmax 層,我們可以解讀成辨識結果是某個字的機率,機率越大、就表示模型的信心越高。因此,在我們認為「錯誤標籤很多」的前提下,對於模型預測有高信心(預測機率大於 95%)、但是與答案不符的情形,我們優先懷疑是「標籤錯誤」,接著,用肉眼判斷是否模型的辨識內容是否正確,如果是,就依照模型預測修改標籤。
這個方法,如果沒有肉眼辨識的過程,直接將原本資料標籤用模型辨識結果覆蓋過去的話,其實就是機器學習競賽常見的 Pseudo Labeling 技巧。筆者好豪在另一場比賽也使用了這項技巧,有興趣的讀者可以閱讀我在 2021 年初的另一場影像辨識競賽心得。
中文手寫圖片合成
這篇筆記所要提的最重要的方法,也是名列前茅隊伍都有使用的方法,就是圖片合成。
透過網路搜尋,我們能找到不少開源的手寫中文圖片資料集,再加上前面提過,原資料集中有許多「只有背景」的圖片,這兩者就可以合成新的手寫圖片、增加模型訓練資料,也算是另類的資料增強。
在此列舉值得一試的開源手寫中文圖片資料集:
除了用手寫字合成,我們也在資料中觀察到,原始資料集內有很多圖片裡其實是印刷體字,或許在銀行業務的場景,辨識印刷品內的文字也是常見的運用。因此,合成圖片時,使用印刷體字也對提升辨識準確度有幫助,我們還能加入不同的字型、增加合成圖片的多樣性,例如以下的開源字型:
實際上該如何做到圖片合成,筆者接下來將非常簡短地教學。
手寫中文字,圖片合成基本教學
筆者的圖片合成小花招是利用一個簡單的概念:手寫字的圖片,通常都是「淺色背景 + 深色字跡」的組合。
圖片中的每個像素(pixel),都是 RGB 三原色組成,因此圖片可以在電腦裡表示成形狀為 圖片高度 x 圖片寬度 x 3
的矩陣資料結構。並且,矩陣內的元素越接近 0 表示顏色越深、越接近 255 表示顏色越淺。以前述的開源圖片資料集說明:除了有手寫字筆跡的位置是深色(接近 RGB(0, 0, 0)
)以外,其他沒有字跡的位置(也就是背景)都是純白(RGB(255, 255, 255)
)。
因此,要進行「淺色背景 + 深色字跡」的合成,只要將兩張圖片的矩陣取逐元最小值(element-wise minimum),也就是讓兩張圖片分別比較 RGB 三個通道、在有字跡的位置優先取深色的值,就 OK 了。
使用 np.min()
的 Pseudo-Code 會是像這樣的:
# resize(): 轉換大小,使兩張圖片長與寬相同
np.min(
[resize(handwrite_img), resize(background_img)],
axis=0
)
詳細的合成教學示範 Python 程式碼,請參考 筆者的 GitHub。
需要補充的是:使用 np.min()
只是我所找到簡單、又不會出太多差錯的方法,Python 當然還有其他方法可以合成圖片,例如,PIL 函式庫的 paste()
,有興趣的讀者可以參考 Syashin 的部落格文章。
向勝出隊伍學習
參加比賽,除了磨練自己的經驗,更重要的是要把握機會向高手學習、反省自己為何跟不上第一名的腳步。在這個小節,我將記下幾個得獎者所分享、但是我們隊伍沒有做得很好的重要技巧。
更「像」的手寫中文字圖片合成
圖片合成的方法並不一定能提升模型準確度。問題在於,機器如果看得出來圖片是被合成出來的,那麼模型可能會對「是否為合成」的線索過擬合、而不是學習到我們希望模型看到的文字形體本身。
以本篇教學的圖片合成示範為例,很明顯看到字跡的邊緣出現了不少突兀的鋸齒狀,而一般的手寫筆跡邊緣應該要呈現柔和的墨水暈開效果才正常。這個不自然的合成,夠強的 CNN 深度學習模型會辨識出來、反而會使「是否為合成」的線索造成模型的偏誤(bias),這就是我這次在圖片合成做得不夠好的地方。
對此問題,前幾名得獎者分享了幾種作法,幫助合成圖片更加逼真:
- 使用 Gaussian Blur 效果對手寫字圖片的字跡加粗、並柔化邊緣
- 將手寫字圖片的字跡轉換成原始資料集常出現的藍筆顏色
- 使用生成對抗網路(GAN)幫原本沒有背景的手寫字圖片增加背景、並且整體更像是真實手寫圖片
這些技巧中,最讓我驚嘆的是勝出隊伍們想到的 GAN 運用,合成圖片遇到的「不希望機器看出來是合成」難題,就是 GAN 的 Discriminator + Generator 在處理的問題!勝出隊伍們的分享提到 CycleGAN 與 StarGAN V2 在此字跡仿真的運用都有良好的效果,以下圖片是第三名隊伍(隊名:歐肯)分享的 GAN 合成成果:
更極端的是,在冠軍隊伍(隊名:三杯波波隊)的分享中,他認為只要合成圖片做得夠像、夠好,在訓練資料就可以更大膽加入大量合成圖片。他用來訓練最佳模型的資料集,合成圖片竟然比原始資料還多、而且合成圖片數量是原始資料圖片量的三倍之多!
資料不平衡
既然是影像辨識問題,令人頭痛的點當然是長得很像的字容易辨識錯誤,例如「土」與「士」、或者「股」與「設」。這時,資料不平衡會是很大的問題。
在模型分辨不出圖片裡的字究竟是「土」還是「士」的時候,如果模型在訓練資料的經驗中,「土」比「士」更常出現,那模型就只會因為「土」的出現頻率較高—不是因為字跡的視覺特徵更相像—給「土」更高的預測機率,這就是每個字的圖片數量不同(資料不平衡)造成的模型偏誤。
這是個明顯的問題,而我在這次競賽中,卻沒有實驗處理資料不平衡能帶來多少模型成效改進,自己需要檢討。
信心不足就分類成「其他類別」
本次競賽共有 800 個字要辨識,再加上「其他類別」(”isnull”),是總共有 801 個類別的分類問題。這樣的預測任務設定有個重要的問題:什麼時候該將預測結果歸類成「其他類別」?
前面也提過,我們的分類模型輸出層是 Softmax,801 個字各自輸出一個機率、所有字的加總是 100%,實際上線進行預測的時候,單純是看 801 個字中,哪個字的預測機率最高(取最大值)、就把它當成模型預測手寫字的結果。
然而,在一張圖片裡面的字跟 800 字中的任何字都不相像時,這個做法容易出現問題。當模型對圖片裡的字不熟悉(字太醜、或者該字確實不屬於模型認得的 800 字),模型可能會給多個字不大不小的機率,使得取機率最大值之後、模型很難給出「其他類別」的辨識結果。
用一個假想例子來說明,如果圖片裡的字是「骰」,這個字在競賽中,不被包含在官方定義的 800 字中,因此標準答案應該是「其他類別」。但是,因為模型跟這個字不熟悉,可能會產生如下的預測結果:
- 「股」:22.9%
- 「設」:17.0%
- 「體」:12.3%
- 「投」:9.8%
- …
- 「其他類別」(”isnull”):14.9%
由於模型只要是有點像的字、都會給予不小的分數,我們在 Softmax 層取機率最大值的做法,很難正確地給出「其他類別」的辨識結果。
得獎者們解決這個問題的方式,是使用門檻值(Threshold),例如,如果辨識結果中,模型不認為任何一個字的機率大於 30%,那就辨識為「其他類別」。當然,這裡的 30% 門檻值只是假設,實際上辨識任務究竟該把門檻值設為多少,也算是超參數搜尋的環節之一,需要透過 Cross-Validation、做實驗找出來最佳門檻值。
嘗試更多模型
雖然這次競賽的勝敗關鍵在於上述的資料標籤糾錯以及合成圖片技巧,勝出隊伍仍分享了他們實驗後,發現能改善預測成效的模型。筆者在此記下我向他們學到的模型知識:
- 有多個隊伍使用 EfficientNet 達到很好的影像辨識成效
- Weight Decay 的調整、使用 Ranger Optimizer、以及半監督式學習方法 Noisy Student,可以讓此模型進一步最佳化
- 為了辨識中文手寫圖片設計的 HCCR 模型
- ArcFace 人臉辨識模型
筆者在競賽初期,比較了 VGG、ResNet、DenseNet 等等常見 CNN 模型後,發現其中一種較好,就選定模型、之後不再調整模型。經過我自己的反省,在競賽的後半部、我們隊伍慢慢解決資料的原生問題之後,我應該重新進行模型選擇、並且嘗試更多模型,因為處理過後的圖片與標籤,已經與我當初選模型時所用的資料分布大相逕庭,最一開始的模型與超參數選擇在處理資料後或許不再會是最佳模型。
結語
非常感謝玉山銀行與 T-Brain 舉辦這場競賽,主辦單位還舉辦了賽後的交流會,我才有機會向各界高手學習、找到自己該改進的方向。
當然也要感謝隊友:冠廷、銘翔、彥庭,尤其是大家花很多時間合作處理的資料標籤糾錯。儘管我們採用這篇筆記提到的幾種方法、試著改善處理錯誤標籤的效率,無法避免的肉眼辨識加上人工標籤工作,依然是相當費時費工。在測試賽的幾天,晚上全隊伍還在盯著圖片檢查標籤、然後用新資料重新訓練模型,回想起來還滿熱血的。在此也特別感謝彥庭,幫隊伍扛起了用 Flask 與 GCP 架 API 的任務,可以到 彥庭的 GitHub 找到他分享的 Flask 程式碼。
在這場比賽,讓我學到最多的不是更複雜先進的深度學習模型、而是「把玩資料」的實驗精神,從高手身上我了解到,在這場比賽的勝出關鍵,是要妥善處理標籤的錯誤、並且找出圖片合成的好方法讓引入的外部資料集與原資料集更加相像,尤其冠軍隊伍玩圖片合成的方式,確實讓人跌破眼鏡!
對影像辨識、機器學習競賽、以及 PyTorch 有興趣嗎?推薦你繼續閱讀:
- 資料科學競賽,能獲得什麼?
- 最讚的 PyTorch 免費初學課程,由臉書 AI 團隊教學! — Udacity 課程心得
- Data Augmentation 教學:Image + Keypoints 同步資料增強
- 2021 年 AIdea 機器學習競賽,動態足壓影像辨識,Top 5% 作法分享
- 《Kaggle 競賽攻頂秘笈 – 掌握 Grandmaster 制勝的關鍵技術》
如果你喜歡這篇文章,歡迎點選下方按鈕加入書籤或分享,也歡迎追蹤 好豪的 Facebook 粉絲專頁,我會持續分享更多資料科學的知識與工作心得。