典CNN在滿文識別中的實戰(zhàn):從數(shù)據(jù)集構(gòu)造到泛化陷阱)
簡介面向滿文文字識別與計算機視覺研究者的圖像分類資源以666類多字體滿文單詞圖像為基礎(chǔ)圍繞AlexNet、VGG19、GoogLeNet三種經(jīng)典深度網(wǎng)絡(luò)給出完整的模型構(gòu)建、訓(xùn)練和可視化分析過程。整個資料包約220.5MB共2000個文件其中jpg圖像占據(jù)主體另有9個Python腳本用于數(shù)據(jù)加載與模型對比1份PPTX展示文稿匯總了實驗結(jié)果并含簡要說明文檔結(jié)構(gòu)清晰便于按需查閱。目前已有224人學(xué)習(xí)下載適合作為OCR方向課程設(shè)計或科研入門的參考樣例。學(xué)習(xí)者可從腳本中看到數(shù)據(jù)預(yù)處理、網(wǎng)絡(luò)微調(diào)和特征可視化的具體寫法從PPTX中獲取不同模型的準(zhǔn)確率對比與可視化結(jié)果從而快速復(fù)現(xiàn)滿文單詞識別實驗深入理解多模型遷移學(xué)習(xí)在少數(shù)民族文字識別任務(wù)中的實際表現(xiàn)。 去年年初接過一個公共文化數(shù)字化方向的圖像識別需求館藏有一批滿文資料需要盡快轉(zhuǎn)成可檢索的文本。我原本以為這類拼音文字識別不算太難上手之后才發(fā)現(xiàn)滿文這個任務(wù)的麻煩程度遠(yuǎn)超預(yù)期。滿文是拼音文字一個詞由多個音素字母橫向連寫而成字母在詞首、詞中、詞尾的位置不同寫法也會跟著變化視覺上很像阿拉伯文的連寫形態(tài)。更棘手的是手上的數(shù)據(jù)是多種字體混合的——古籍刻本、鉛印本、手寫抄本、現(xiàn)代字庫渲染都有字形風(fēng)格差異極大。于是我們把問題拆成了最樸素的圖像分類任務(wù)滿文數(shù)據(jù)集666類中的多種字體單詞圖像分別用AlexNet、VGG19、GoogLeNet三個經(jīng)典CNN去識別。這篇文章就復(fù)盤一下從數(shù)據(jù)集構(gòu)造、模型選型到訓(xùn)練排錯的完整過程尤其適合正在做低資源文字識別、冷門OCR或小樣本圖像分類的讀者參考。1. 為什么拿三個“老牌CNN”去啃滿文單詞識別1.1 滿文識別到底難在哪先說文字本身。滿文屬于音素文字系統(tǒng)常見說法是6個元音字母加22個輔音字母共28個基礎(chǔ)音位。但這只是字母層面的數(shù)量落到字形上同一個字母在詞首、詞中、詞尾往往有獨立寫法學(xué)界對滿文字形變體的統(tǒng)計普遍認(rèn)為超過130個。也就是說模型看到的不是28個簡單符號而是130多種局部形狀這些形狀還要按詞內(nèi)順序橫向連寫組合出成百上千種整體外形。滿文還有個綽號叫“圈點滿文”——大量字母之間的區(qū)分全靠一個圈或一個點的位置差異。比如某些輔音字母唯一區(qū)別是右側(cè)有沒有圈、圈點在哪個位置。這種級別的細(xì)節(jié)放到古籍掃描件里就是災(zāi)難紙張泛黃、筆畫斷裂、圈點模糊人眼有時候都得湊近看。數(shù)據(jù)里再混入手寫體和印刷體同一個詞的筆順、連筆、筆畫粗細(xì)又會繼續(xù)放大差異。這也是我堅持把它當(dāng)成細(xì)粒度圖像分類問題而不是普通OCR來看待的原因。1.2 為什么偏偏選了這三個模型市面上比這三個更強的網(wǎng)絡(luò)一抓一把ResNet、EfficientNet、Vision Transformer都能跑。但我做這個項目的選型邏輯很樸素第一這個方向缺可復(fù)現(xiàn)的baseline。查文獻能發(fā)現(xiàn)少數(shù)民族文字識別論文里最常出現(xiàn)的就是AlexNet、VGG19、GoogLeNet這一代經(jīng)典網(wǎng)絡(luò)。用它們建立基線后續(xù)無論誰提出新方法都能放到同一套指標(biāo)下比較不用各自憑空報數(shù)。第二實際部署環(huán)境限制了模型規(guī)模。館藏單位通常只有普通辦公電腦沒有GPU集群。AlexNet和GoogLeNet的參數(shù)量都在可控范圍訓(xùn)練和推理成本不高VGG19即使參數(shù)量很大也還能靠著顯存勉強跑起來。第三數(shù)據(jù)量是硬約束。完整數(shù)據(jù)集只有8萬多張還要分成666類類別多且分布不均。Transformer沒有海量數(shù)據(jù)做預(yù)訓(xùn)練很難在這種規(guī)模下發(fā)揮優(yōu)勢硬遷移ImageNet上的ViT權(quán)重也無法撐起細(xì)粒度字形特征的學(xué)習(xí)。這三個網(wǎng)絡(luò)的結(jié)構(gòu)差異剛好覆蓋了三種思路AlexNet是典型的“深度卷積全連接”奠基結(jié)構(gòu)VGG19把卷積網(wǎng)絡(luò)推到極深的堆疊路線GoogLeNet則在寬度和多尺度特征上做文章。用同一份滿文數(shù)據(jù)集橫向?qū)Ρ人鼈兡芤淮涡钥辞搴脦最悊栴}。2. 數(shù)據(jù)集不是下載的是自己拼出來的2.1 666個類別怎么定項目啟動時最尷尬的一件事沒有現(xiàn)成的滿文數(shù)據(jù)集。公開渠道能下載到的都是漢字或英文OCR數(shù)據(jù)集滿文這塊幾乎是空白。我和團隊只能自己造。類別選的是滿漢詞典和高頻詞表里出現(xiàn)頻次靠前的666個詞盡量覆蓋名詞、動詞、數(shù)詞、格助詞等不同語法形態(tài)。每個類別的圖像數(shù)并不均勻少的只有60來張多的能到260張整份數(shù)據(jù)加起來大約8.2萬張長尾分布非常明顯。這其實是低資源文字?jǐn)?shù)據(jù)集的常態(tài)——常用詞樣本多冷門詞樣本少。如果動手之前沒意識到這一點模型后面偏科是必然的。2.2 字體來源與圖像預(yù)處理這批數(shù)據(jù)的“多種字體”主要來自四個渠道古籍刻本掃描、鉛印出版物掃描、人工手寫樣本、現(xiàn)代TrueType滿文字體渲染圖。四種渠道的字形風(fēng)格差異很大——刻本有木紋噪聲鉛印體筆畫均勻手寫體有連筆字庫渲染最規(guī)整。原始圖像預(yù)處理沒有做太復(fù)雜的操作。先把彩色掃描圖轉(zhuǎn)灰度因為筆畫顏色對文字識別幾乎沒有幫助然后做一次降噪去掉掃描產(chǎn)生的孤立噪點再做簡單的對比度增強讓筆畫更突出。這里我刻意沒有做全局二值化——二值化會把灰度抗鋸齒信息直接丟掉筆畫邊緣在非標(biāo)準(zhǔn)字體下會變得毛糙反而影響后續(xù)識別。預(yù)處理最關(guān)鍵的一步是尺寸歸一。滿文單詞是典型的橫向排布圖像大多是細(xì)長條高約64像素寬卻可能到300甚至500像素。而AlexNet、VGG19、GoogLeNet的常見輸入都是224×224正方形。最省事的辦法是直接resize但那樣單詞會被壓成矮胖形狀圈點細(xì)節(jié)全糊。我最后采用“等比縮放白邊padding”單詞圖像按比例縮放到合適大小周邊補白邊湊成224×224再送進網(wǎng)絡(luò)。這樣字形比例不丟模型也能正常吃標(biāo)準(zhǔn)輸入。2.3 切分原則按字體切而不是隨機切這是整份數(shù)據(jù)組織里我認(rèn)為最重要的一次決策。很多人做圖像分類時習(xí)慣直接隨機切訓(xùn)練集和測試集但這個項目隨機切會出大問題同一種字體的圖像會同時出現(xiàn)在訓(xùn)練集和驗證集里。由于字體風(fēng)格特征太明顯模型完全可能靠“這是鉛印體”“這是手寫體”這種淺層線索做出判斷驗證準(zhǔn)確率虛高一到真實掃描場景就崩。所以訓(xùn)練集、驗證集、測試集的劃分是按字體來源做的比如字庫渲染和部分鉛印體進訓(xùn)練集手寫體、刻本掃描和另一部分鉛印體進驗證集和測試集盡量保證測試集里有訓(xùn)練時沒見過的字體組合。這個做法會讓測試準(zhǔn)確率比隨機切分低不少但它才是評價泛化能力的正確方式。我后面專門留了一份隨機切分的數(shù)據(jù)做對照結(jié)果非常有說服力實驗部分會具體說。3. 三套網(wǎng)絡(luò)的改動與訓(xùn)練配置3.1 灰度圖像的首層通道問題torchvision里的預(yù)訓(xùn)練模型都是按RGB三通道設(shè)計的。滿文圖像只有單通道如果直接把灰度圖復(fù)制成三通道送進去等于讓網(wǎng)絡(luò)用三倍顯存去學(xué)完全相同的特征還會引入冗余。更穩(wěn)妥的做法是把預(yù)訓(xùn)練首層卷積核從3通道融合成1通道。這里有個成熟的小技巧對ImageNet預(yù)訓(xùn)練權(quán)重首層卷積核形狀是[輸出通道數(shù), 3, 卷積核高, 卷積核寬]直接對通道維求平均把RGB三個通道的卷積核合并成一個平均核偏置保持不變。代碼量很小import torch from torchvision import models def fuse_first_conv(model): state model.state_dict() # AlexNet / VGG19 的首層在 features.0.weightGoogLeNet 在 conv1.weight weight_key next(k for k in state if k.startswith(features.0) or k.startswith(conv1.)) w state[weight_key] # [C, 3, K, K] fused w.mean(dim1, keepdimTrue) # [C, 1, K, K] state[weight_key] fused model.load_state_dict(state) return model這個方法比灰度圖復(fù)制成三通道更省內(nèi)存實測在滿文數(shù)據(jù)上幾乎沒有精度損失。對AlexNet和VGG19改的是features[0]對GoogLeNet改的是conv1。改完首層后還需要把最后的全連接層替換成666類輸出。3.2 三個模型的差異性在哪里模型主干卷積層數(shù)約參數(shù)量全連接層處理本項目測試集準(zhǔn)確率AlexNet5約6000萬三層全連接約62.8%VGG1916約1.43億三層全連接約70.4%GoogLeNet22約670萬全局平均池化約76.1%這張表能看出不少東西。VGG19的深度讓它有更強的特征表達能力但它那三個全連接層占了絕大部分參數(shù)在8萬張級別的數(shù)據(jù)上過擬合壓力很大。AlexNet五層卷積結(jié)構(gòu)最樸素訓(xùn)練最快但特征抽象程度明顯不夠。GoogLeNet用Inception模塊在同一層里做多尺度卷積再用1×1卷積大幅降維參數(shù)總量只相當(dāng)于VGG19的零頭最后用全局平均池化替代全連接這種結(jié)構(gòu)在低資源場景下幾乎是為抗過擬合量身定做的。3.3 訓(xùn)練配置與數(shù)據(jù)增強運行環(huán)境是PyTorch 1.13單張RTX 3090。優(yōu)化器用SGDmomentum0.9weight decay5e-4初始學(xué)習(xí)率0.01訓(xùn)練80個epoch前5個epoch做線性warmup后續(xù)跟余弦退火。batch size設(shè)128。驗證集準(zhǔn)確率連續(xù)15個epoch不提升就提前停止。數(shù)據(jù)增強我用RandomAffine但旋轉(zhuǎn)角度只給±3°平移給0.05縮放0.9到1.1。滿文圈點位置對角度極度敏感旋轉(zhuǎn)超過5°基本就是在制造錯誤樣本。我還以p0.25的概率加了RandomErasing模擬古籍掃描時局部筆畫被污漬遮擋的情況。有一點要特別提醒不要對滿文單詞圖像做隨機裁剪增強。漢字那種方形單字可以隨機裁但滿文單詞是橫向連寫的隨機裁很容易把詞的頭部或尾部切掉幾個不同詞被裁完可能長得差不多模型越訓(xùn)越糊涂。GoogLeNet還有一個特殊細(xì)節(jié)torchvision默認(rèn)加載的模型在訓(xùn)練階段會輸出主logits和兩個輔助logits總損失按loss_main 0.3 × (loss_aux1 loss_aux2)計算。輔助分類器對這個數(shù)據(jù)集的梯度回傳有明顯正則作用開著比關(guān)掉整體高約1.5個點。但推理時一定要調(diào)model.eval()否則輔助分支還會參與計算白白浪費時間。4. 結(jié)果不是越高越好泛化才是4.1 按字體劃分的真實成績經(jīng)過上面這些處理和訓(xùn)練配置三個模型在按字體劃分的測試集上的表現(xiàn)以及隨機切分對照組的成績匯總?cè)缦履P桶醋煮w劃分測試集準(zhǔn)確率隨機切分對照組準(zhǔn)確率AlexNet62.8%95.2%VGG1970.4%96.7%GoogLeNet76.1%97.3%兩組數(shù)據(jù)放在一起對比非常刺激。隨機切分時三套模型都輕松上95%看起來好像“滿文識別已經(jīng)搞定了”。但按字體劃分后AlexNet直接掉到62.8%說明之前高出的那一大部分準(zhǔn)確率并不是真的認(rèn)識滿文單詞而是認(rèn)出了字體風(fēng)格。這正驗證了前面堅持按來源切分的必要性。GoogLeNet在這份數(shù)據(jù)上領(lǐng)先其他兩個模型5個多點。我的判斷有三層原因一是Inception結(jié)構(gòu)用多種卷積核同時關(guān)注局部細(xì)節(jié)和整體輪廓對多字體帶來的尺度變化更魯棒二是1×1卷積降維配合全局平均池化參數(shù)少在8萬張數(shù)據(jù)上不容易過擬合三是輔助分類器帶來的隱式正則效果讓梯度更平穩(wěn)地回傳。4.2 錯誤集中在哪里準(zhǔn)確率之外更能說明問題的是混淆矩陣。我把所有誤分類對拉出來看發(fā)現(xiàn)錯誤高度集中在幾類情況。第一是字形相近的單詞對。滿文里有一批詞區(qū)別只在詞末字母的圈點位置。鉛印體還算清楚刻本掃描一旦筆畫斷裂模型基本只能靠猜。第二是手寫體表現(xiàn)明顯更差。手寫連筆會讓字母邊界糊在一起這三個模型對連續(xù)連筆的容忍度都不高。第三是長尾類別問題。樣本數(shù)只有六七十張的冷門詞準(zhǔn)確率普遍比高頻詞低十幾個百分點。我還特意用訓(xùn)練時完全沒見過的字體做了次小測試。比如只用字庫渲染圖訓(xùn)練然后用刻本掃描圖測試GoogLeNet準(zhǔn)確率掉了將近20個點。這個結(jié)果不意外但它提醒我多字體混合識別本質(zhì)上比單字體識別難上一個維度。真實場景里不可能只遇到訓(xùn)練過的字體這個損失必須從一開始就納入預(yù)期。5. 訓(xùn)練過程中踩過的幾個坑5.1 字體泄露最隱蔽的坑這個坑前面反復(fù)提但還是要單獨說一遍因為它實在太隱蔽了。第一次跑實驗時我在驗證集上看到94%的準(zhǔn)確率一度以為項目可以提前交差。后來把模型拿到另一批手寫掃描件上測試準(zhǔn)確率驟降到68%。復(fù)盤時才發(fā)現(xiàn)驗證集隨機切分后同一種字體同時出現(xiàn)在訓(xùn)練和驗證里模型在驗證時就是把“字形風(fēng)格”當(dāng)成了主要特征。按字體來源重新劃分后準(zhǔn)確率才回到真實水平。如果你的任務(wù)也是多來源、多風(fēng)格的數(shù)據(jù)強烈建議先按來源分組再切分。寧可訓(xùn)練集看起來少一些也不要讓評估結(jié)果自欺欺人。5.2 細(xì)長條圖像被resize得面目全非滿文單詞圖像是橫長條。最初我圖省事直接把短邊resize成224×224長邊被壓扁結(jié)果三個模型全在55%左右徘徊。改成等比縮放加白邊padding后準(zhǔn)確率立刻提升。這里還可以再優(yōu)化一步單詞本身寬高比差別很大等比縮放后有的圖實際占的面積很小白白浪費計算量。我后來在同一套流程里加了動態(tài)適配先統(tǒng)計所有訓(xùn)練圖寬高比的分布把常見比例對應(yīng)的縮放尺寸算出來讓單詞主體盡量多占像素其余空白繼續(xù)padding。這一步對細(xì)長型文字的OCR類任務(wù)幾乎是通用方案。5.3 全連接分類頭成了過擬合重災(zāi)區(qū)AlexNet和VGG19最后的全連接層是為1000類ImageNet設(shè)計的改成666類后參數(shù)依然龐大。實驗中發(fā)現(xiàn)VGG19的驗證loss通常在40個epoch后開始反彈而GoogLeNet的驗證loss能一路壓到最后。后來我把AlexNet和VGG19的fc6維度從4096降到2048dropout從0.5提到0.6準(zhǔn)確率反而小幅提升0.3到0.8個點訓(xùn)練時間也縮短了。這說明在中小規(guī)模數(shù)據(jù)集上模型容量不是越大越好。對小數(shù)據(jù)文字識別全局平均池化這類無參降維方案明顯更省心。5.4 數(shù)據(jù)增強不是萬能藥我一度希望通過瘋狂增強來彌補數(shù)據(jù)不足結(jié)果旋轉(zhuǎn)加強到10°之后三個模型準(zhǔn)確率集體下降——滿文字母一旦帶圈點轉(zhuǎn)幾度后圈點位置就可能和另一個字母的形態(tài)重合。這個教訓(xùn)讓我明白對局部符號極其敏感的文字?jǐn)?shù)據(jù)增強必須保守。可加的是輕微平移、縮放、擦除和噪聲不可加的是大角度旋轉(zhuǎn)和隨意裁剪。做任何增強前最好先自己看一批增強后的圖確認(rèn)它看起來還是“那個單詞”再決定是否上線。6. 如果繼續(xù)往下做我會怎么走6.1 從分類到序列化識別詞級分類只是baseline。滿文是拼音文字一個單詞的內(nèi)部結(jié)構(gòu)本質(zhì)上是音素序列天然適合序列模型。后續(xù)真要落地更合理的方向是把單詞的字母序列預(yù)測出來用CRNN或Transformer encoder-decoder做端到端識別而不是讓網(wǎng)絡(luò)從666類里硬選一個。分類只能處理固定詞表序列生成能推到?jīng)]有見過的詞對真實古籍的開放詞表適配性高得多。代價是標(biāo)注成本上漲——每張圖都要標(biāo)字母級序列但這是技術(shù)路線上必然要邁的一步。6.2 合成數(shù)據(jù)是低資源文字識別的生命線如果讓我重新做一個滿文識別項目第一步不會急著找更多人工標(biāo)注而是先把合成數(shù)據(jù)流水線搭起來。現(xiàn)在滿文字庫和輸入法已經(jīng)比較成熟可以用腳本批量渲染任意詞表在不同字體、不同字號、不同噪聲下的圖像幾個小時就能生成幾十萬張。合成數(shù)據(jù)雖然和真實掃描存在域差距但用來解決長尾類別樣本不足的問題綽綽有余。跑通之后再配合少量真實數(shù)據(jù)做領(lǐng)域適應(yīng)性價比極高。6.3 一點個人體會這個項目最大的收獲不是“GoogLeNet贏了”而是讓我確認(rèn)了一件事在低資源文字識別里數(shù)據(jù)組織方式對最終結(jié)果的影響常常比模型結(jié)構(gòu)更明顯。按字體切分、等比padding、保守增強——每一項調(diào)整帶來的收益都比單純換網(wǎng)絡(luò)更大。如果你也在做類似的文字圖像分類別急著上大模型先把數(shù)據(jù)的來源、劃分方式和預(yù)處理研究透。這幾個點做好了哪怕只用GoogLeNet這種上一代網(wǎng)絡(luò)也能在真實場景里扛住一陣子。如果后續(xù)算力和數(shù)據(jù)量都上來了再從這個強baseline出發(fā)往序列化模型和合成數(shù)據(jù)方向迭代路會順很多。本文還有配套的精品資源點擊獲取