CV入門賽最全思路&上分技巧匯總!
賽題數(shù)據(jù)及背景
本文分為以下幾部分:
如何優(yōu)化官方baseline的效果? 其它解題思路的整理和分析 字符級目標(biāo)檢測的優(yōu)化技巧整理
在這里要特別感謝多位前排選手對于比賽技巧的無私分享,那么不多bb,下面直接進(jìn)入正題
一、如何優(yōu)化官方baseline的效果?
本次入門賽的官方baseline入門材料,已經(jīng)提供了完整的實(shí)踐思路:Task1 賽題理解[1]、Task2 數(shù)據(jù)讀取與數(shù)據(jù)擴(kuò)增[2]、Task3 字符識別模型[3]、Task4 模型訓(xùn)練與驗(yàn)證[4]、Task5 模型集成[5],本質(zhì)上,baseline的思路就是將賽題轉(zhuǎn)換為了一個(gè)定長的字符識別問題,用包含多個(gè)輸出的分類問題來進(jìn)行求解。

1.1 改進(jìn)版baseline
那么如何進(jìn)行進(jìn)一步優(yōu)化呢?在比賽進(jìn)行的過程中,我在天池進(jìn)行了一次如何調(diào)參上分[6]的直播分享。
直播對應(yīng)的代碼可以在我們的《動(dòng)手學(xué)CV項(xiàng)目》[7]的2.5節(jié)找到。
這份代碼相當(dāng)于一個(gè)加強(qiáng)版的baseline,簡短來說,介紹了以下幾點(diǎn):
重新回顧baseline的代碼 階段性下降的學(xué)習(xí)率調(diào)整策略 分析了很多人提交出0.3-0.4分成績的原因和解決方案 加入數(shù)據(jù)增強(qiáng)策略
這份代碼我相信是幫到了一些剛?cè)腴T的同學(xué)的,提交的成績大概在0.75分左右。
那么在這樣一個(gè)baseline的基礎(chǔ)上,如何進(jìn)一步的優(yōu)化呢?
1.2 改進(jìn)backbone
baseline中我們的網(wǎng)絡(luò)結(jié)構(gòu)是這樣定義的:
class SVHN_Model1(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
model_conv = models.resnet18(pretrained=True)
model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
model_conv = nn.Sequential(*list(model_conv.children())[:-1]) # 去除最后一個(gè)fc layer
self.cnn = model_conv
self.fc1 = nn.Linear(512, 11)
self.fc2 = nn.Linear(512, 11)
self.fc3 = nn.Linear(512, 11)
self.fc4 = nn.Linear(512, 11)
self.fc5 = nn.Linear(512, 11)
def forward(self, img):
feat = self.cnn(img)
#print(feat.shape)
feat = feat.view(feat.shape[0], -1)
c1 = self.fc1(feat)
c2 = self.fc2(feat)
c3 = self.fc3(feat)
c4 = self.fc4(feat)
c5 = self.fc5(feat)
return c1, c2, c3, c4, c5
我們可以對使用的backbone網(wǎng)絡(luò)進(jìn)行一系列的改進(jìn):
由resnet18換為更大的resnet50 為每一個(gè)分類模塊加上一層全連接隱藏層 為隱含層添加dropout
由resnet18換為resnet50,更深的模型就擁有更好的表達(dá)能力,添加一層隱含層同樣起到了增加模型擬合能力的作用,與此同時(shí)為隱含層添加dropout來進(jìn)行一個(gè)balance,一定程度上防止過擬合。(這只是我個(gè)人對于baseline的改進(jìn)方案,不一定是最優(yōu)的)
改進(jìn)后的模型定義代碼如下:
class SVHN_Model2(nn.Module):
def __init__(self):
super(SVHN_Model2, self).__init__()
# resnet18
model_conv = models.resnet18(pretrained=True)
model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
model_conv = nn.Sequential(*list(model_conv.children())[:-1]) # 去除最后一個(gè)fc layer
self.cnn = model_conv
self.hd_fc1 = nn.Linear(512, 128)
self.hd_fc2 = nn.Linear(512, 128)
self.hd_fc3 = nn.Linear(512, 128)
self.hd_fc4 = nn.Linear(512, 128)
self.hd_fc5 = nn.Linear(512, 128)
self.dropout_1 = nn.Dropout(0.25)
self.dropout_2 = nn.Dropout(0.25)
self.dropout_3 = nn.Dropout(0.25)
self.dropout_4 = nn.Dropout(0.25)
self.dropout_5 = nn.Dropout(0.25)
self.fc1 = nn.Linear(128, 11)
self.fc2 = nn.Linear(128, 11)
self.fc3 = nn.Linear(128, 11)
self.fc4 = nn.Linear(128, 11)
self.fc5 = nn.Linear(128, 11)
def forward(self, img):
feat = self.cnn(img)
feat = feat.view(feat.shape[0], -1)
feat1 = self.hd_fc1(feat)
feat2 = self.hd_fc2(feat)
feat3 = self.hd_fc3(feat)
feat4 = self.hd_fc4(feat)
feat5 = self.hd_fc5(feat)
feat1 = self.dropout_1(feat1)
feat2 = self.dropout_2(feat2)
feat3 = self.dropout_3(feat3)
feat4 = self.dropout_4(feat4)
feat5 = self.dropout_5(feat5)
c1 = self.fc1(feat1)
c2 = self.fc2(feat2)
c3 = self.fc3(feat3)
c4 = self.fc4(feat4)
c5 = self.fc5(feat5)
return c1, c2, c3, c4, c5
改進(jìn)后的模型訓(xùn)起來會(huì)慢很多,不過增加了模型的表達(dá)力,自然效果也會(huì)好一些。此外,你也可以嘗試一些更"state-of-the-arts"的模型,比如SENet,EfficientNet等。
1.3 數(shù)據(jù)增強(qiáng)優(yōu)化
關(guān)于數(shù)據(jù)增強(qiáng),我們在直播中已經(jīng)探討過了,從原理上分析我們更應(yīng)該用一些基于空間、位置相關(guān)的數(shù)據(jù)增強(qiáng),比如Randomcrop,平移,旋轉(zhuǎn)等。而顏色空間相關(guān)的變換,也可以嘗試,但很可能會(huì)起到副作用。
數(shù)據(jù)增強(qiáng)是非常普遍的訓(xùn)練技巧了,肯定要用,但對這個(gè)賽題的結(jié)果提升不會(huì)很顯著。關(guān)于這部分,這位小伙伴寫的比賽實(shí)驗(yàn)記錄[8]對相關(guān)的實(shí)驗(yàn)進(jìn)行了很詳細(xì)的記錄,大家感興趣可以閱讀一下~
1.4 和文本長度相關(guān)的探索
baseline方案將識別問題轉(zhuǎn)化為了定長識別問題,那么定多長合適?就是個(gè)值得思考的問題,有的小伙伴通過一個(gè)小腳本進(jìn)行了統(tǒng)計(jì),訓(xùn)練集中的樣本的字符長度分別為1,2,3,4,5,6的樣本數(shù)量分別為4636,16262,7813,1280,8,1。

可以看到,數(shù)據(jù)集中長度為5和6的圖片都是可以忽略不計(jì)的,因此主動(dòng)放棄這部分極少數(shù)情況的case可以很好的為模型“減負(fù)”,從而獲得更好的效果。
baseline模型設(shè)定定長len=5,不妨嘗試下len=4,會(huì)帶來進(jìn)一步的提升。
我們的這個(gè)方案需要CNN自己找到文字在圖中的位置,還要自己判斷出字符的個(gè)數(shù)并完成正確的識別,感覺上是相當(dāng)難的任務(wù),之所以能夠work是因?yàn)閳鼍跋鄬碚f比較簡單。
如果你做了一些更進(jìn)一步得分析工作你會(huì)發(fā)現(xiàn),模型對于長度不同的圖片的效果是不一樣的。更詳細(xì)來說,根據(jù)我的訓(xùn)練日志的記錄,訓(xùn)練時(shí)第3個(gè)字符的loss是相對比較小的,而預(yù)測時(shí)長度為3的圖片出現(xiàn)的預(yù)測錯(cuò)誤是比較多的,主要是漏檢。
我分析這是因?yàn)椋▊€(gè)人觀點(diǎn)可能存在誤導(dǎo)):對于輸出第一個(gè)字符結(jié)果的fc layer來說,所有圖片它都會(huì)參于訓(xùn)練,而輸出第二個(gè)字符結(jié)果的fc layer,只有當(dāng)圖片字符數(shù)>=2,它才會(huì)參于“有效”訓(xùn)練,以此類推。所以越是對應(yīng)靠后的fc layer,訓(xùn)練的越不充分,越容易過擬合導(dǎo)致實(shí)際效果越差。
因此可以圍繞不同長度的圖片效果不同來做些文章,依然是這篇比賽實(shí)驗(yàn)記錄[9]
他嘗試了兩個(gè)方案,可供大家參考,提供些思路:
損失加權(quán) 樣本加權(quán)
其中第二個(gè)方案,樣本加權(quán)看起來是更合理來解決這個(gè)問題的,我們可以通過重復(fù)采樣,提高較長的字符數(shù)量的圖片出現(xiàn)的比例,來讓對應(yīng)第3個(gè)字符和第4個(gè)字符對應(yīng)的輸出層訓(xùn)練的更充分。
這是個(gè)蠻有新意的角度,你是否還可以碰撞出其它的idea來解決這個(gè)問題呢~
1.5 集成學(xué)習(xí)
對于這個(gè)比賽來說,比較適合baseline的集成方案是:
首先將單模型盡可能訓(xùn)到最高,然后單模型的輸出使用TTA(test time augmentation),可以大幅提升單模型的預(yù)測效果。
然后訓(xùn)練多個(gè)有差異化的模型,將多個(gè)單模型的預(yù)測結(jié)果進(jìn)行投票,得到最終的預(yù)測結(jié)果。單模型間的差異化可以從數(shù)據(jù)的角度通過重新劃分訓(xùn)練集和驗(yàn)證集來達(dá)到,也可以從模型的角度,使用不同的backbone來達(dá)到。從原理上講,單模型間越是獨(dú)立和具有差異化,融合的效果就越好。
1.6 讓baseline進(jìn)入Top 2%的6行代碼
最后再偷偷告訴你一個(gè),只需修改6行代碼,就能讓目前的優(yōu)化后baseline單模型進(jìn)入Top 2%的方法。
不知大家有沒有發(fā)現(xiàn),測試集相比于訓(xùn)練數(shù)據(jù),要更簡單。
具體地,體現(xiàn)在測試集最終的分?jǐn)?shù)反而要比驗(yàn)證集高一些,如果你直接觀察數(shù)據(jù),也可以看出來,測試集的圖片中字符在圖片中的占比更大,而訓(xùn)練集中圖片的字符占比更小。直觀感受就是訓(xùn)練數(shù)據(jù)和測試集對應(yīng)的場景不一致,訓(xùn)練集的字符感覺更“遠(yuǎn)”,預(yù)處難度也更高。
提示到這里,你可以先停下來思考下,如果是你,會(huì)如何來針對這個(gè)問題進(jìn)行優(yōu)化呢?
驗(yàn)證集圖片示例:

測試集圖片示例:

對于這個(gè)問題,很多人很自然的想到了進(jìn)行目標(biāo)檢測,這也是幾乎所有前排選手的一致選擇。但是,我們的baseline就沒有一戰(zhàn)之力了嗎?當(dāng)然不是,我個(gè)人用resnet50作為backbone將單模型的分?jǐn)?shù)訓(xùn)到了0.91,相當(dāng)于正式賽Top2%的分?jǐn)?shù),而且因?yàn)槲覜]有時(shí)間做太多的超參數(shù)調(diào)整實(shí)驗(yàn),這個(gè)成績也并沒有達(dá)到baseline的上限。
那么baseline要如何相應(yīng)的進(jìn)行改造來解決這個(gè)問題呢?
文字描述出來就是:訓(xùn)練時(shí)把場景拉近,測試基本保持不變,這樣一定程度上讓訓(xùn)練和測試的數(shù)據(jù)的場景更加一致,從而讓模型學(xué)到的預(yù)測能力完全發(fā)揮出來。
可以有很多方案來達(dá)到這個(gè)目的,最簡單有效的方法僅僅需要修改數(shù)據(jù)增強(qiáng)相關(guān)的6行代碼,我用TODO作為后綴標(biāo)注出來,代碼如下:
train_loader = torch.utils.data.DataLoader(
SVHNDataset(train_path, train_label,
transforms.Compose([
transforms.Resize((80, 160)), # TODO
transforms.RandomCrop((64, 128)), # TODO
transforms.ColorJitter(0.3, 0.3, 0.2),
transforms.RandomRotation(5),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])),
batch_size=40,
shuffle=True,
num_workers=2,
)
val_loader = torch.utils.data.DataLoader(
SVHNDataset(val_path, val_label,
transforms.Compose([
transforms.Resize((80, 160)), # TODO
transforms.CenterCrop((64, 128)), # TODO
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])),
batch_size=40,
shuffle=False,
num_workers=2,
)
test_loader = torch.utils.data.DataLoader(
SVHNDataset(test_path, test_label,
transforms.Compose([
transforms.Resize((68, 136)), # TODO
transforms.RandomCrop((64, 128)), # TODO
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])),
batch_size=40,
shuffle=False,
num_workers=2,
)
這個(gè)優(yōu)化雖然很trick,但是效果顯著,大家可以思考下這6行代碼的修改是如何帶來成績的巨大提升的。
二、不同解題思路的整理與分析
除了baseline的思路以外,還有多種不同的解題思路,這里簡單進(jìn)行總結(jié)。
2.1 CRNN
純識別的思路,除了baseline的定長字符識別方案外,還可以用CRNN來做,屬于一種端到端的不定長字符識別的解決方案。
關(guān)于CRNN,賽事組織者 阿水 已經(jīng)為我們提供了一個(gè)CRNN baseline[10],感謝去可以學(xué)習(xí)一下~
2.2 檢測+識別
除了兩種端到端的識別方案,還可以引入目標(biāo)檢測來解題,根據(jù)具體使用中檢測框粒度的不同,還可以細(xì)分為三種不同的方案:
方案一:文本行檢測+文本行識別
方案二:字符級目標(biāo)檢測+字符識別模型
方案三:純目標(biāo)檢測方案
根據(jù)我最近兩周持續(xù)對前排選手進(jìn)行的騷擾+在線乞討收集到的情報(bào)來看,前排選手使用的普遍是方案三,但是方案一、方案二也有人用,而且成績不差。也就是說,對于這個(gè)賽題,從實(shí)際結(jié)果上看這三個(gè)方案上限差不多。
方案一是更標(biāo)準(zhǔn)的字符識別類問題的解決方案,如果我們的問題不是數(shù)字之間無關(guān)聯(lián)的門牌號識別,而是比如場景文字識別,那么方案一由于可以對不同字符間的關(guān)聯(lián)進(jìn)行建模,效果將會(huì)顯著優(yōu)于其它方案,但是本賽題這種優(yōu)勢無法發(fā)揮出來。
而方案三作為一種端到端的解決方案,思路更直接,整個(gè)訓(xùn)練流程更簡單,更容易在有限的比賽時(shí)間內(nèi)優(yōu)化出好的效果,再加上有眾多簡單好用的開源庫,因此也是絕大多數(shù)前排選手選擇的原因。
三、字符級目標(biāo)檢測的優(yōu)化技巧整理
本文的最后一部分,再針對大家使用最多的字符級目標(biāo)檢測的方案,進(jìn)行一些簡單的整理。
網(wǎng)絡(luò)框架選擇方面,前排普遍采用的是YOLOv3-v5的版本,還有一位選手使用的CenterNet獲得了非常好的效果。
除了模型訓(xùn)練的各種小的trick以外,如何對模型結(jié)果進(jìn)行后處理,以及如何融合多個(gè)模型的結(jié)果,會(huì)對最終結(jié)果有很大影響。關(guān)于這部分,眾多選手都在天池的論壇熱心分享了自己的經(jīng)驗(yàn),細(xì)節(jié)太多很難一一列舉,我也有很多要學(xué)習(xí)的地方,這里就不班門弄斧了,感興趣的小伙伴趕快去學(xué)習(xí)吧~ 分別包括:
天池街景字符識別總結(jié)[11]、第五名 yolov4 加 投票法方案[12]、街景字符編碼識別-第6名 線上0.938 方案分享[13]、yolov5加全局nms 第八名方案分享[14]、零基礎(chǔ)入門CV賽事-分享一些適合新手的0.92+的上分技巧吧[15]、真正零基礎(chǔ),單模型非融合,上93的最簡單技巧[16]、參賽歷程以及方案分享[17]、零基礎(chǔ)CV賽--街景字符識別,小小白分享,從0.002~0.926[18]
寫在最后
如果覺得有收獲,可否給我們的《動(dòng)手學(xué)CV項(xiàng)目》[7]項(xiàng)目點(diǎn)個(gè)star呢,我的老火雞~
(小聲叨叨,目前項(xiàng)目只更新了兩章,但大家天天私信催我的目標(biāo)檢測的入門教程已經(jīng)在做了)
參考資料
賽題理解: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.21.2ce879dekK1l1d&postId=108659
[2]數(shù)據(jù)讀取與數(shù)據(jù)擴(kuò)增: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.6.2ce879dekK1l1d&postId=108150
[3]字符識別模型: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.3.2ce879dekK1l1d&postId=108711
[4]模型訓(xùn)練與驗(yàn)證: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.18.2ce879dekK1l1d&postId=108780
[5]模型集成: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.24.2ce879dekK1l1d&postId=108656
[6]如何調(diào)參上分: https://tianchi.aliyun.com/course/video?spm=5176.12586971.1001.1.11be79delsbf6G&liveId=41169
[7]動(dòng)手學(xué)CV項(xiàng)目: https://github.com/datawhalechina/dive-into-cv-pytorch
[8]比賽實(shí)驗(yàn)記錄之?dāng)?shù)據(jù)增強(qiáng): https://github.com/tiantianheheaa1995/tianchi_CV_SVHN?spm=5176.12282029.0.0.45b24adcxaghR6
[9]比賽實(shí)驗(yàn)記錄之定長識別問題: https://github.com/tiantianheheaa1995/tianchi_CV_SVHN?spm=5176.12282029.0.0.45b24adcxaghR6
[10]CRNN-baseline: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.12.2ce879deix0lKP&postId=111274
[11]天池街景字符識別總結(jié): https://tianchi.aliyun.com/forum/postDetail?spm=5176.12586969.1002.36.2ce879deix0lKP&postId=118992
[12]第五名 yolov4 加 投票法方案: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.30.2ce879deix0lKP&postId=118791
[13]街景字符編碼識別-第6名 線上0.938 方案分享: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.42.2ce879deix0lKP&postId=118810
[14]yolov5加全局nms 第八名方案分享: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.27.2ce879deiwn8Cw&postId=116822
[15]零基礎(chǔ)入門CV賽事-分享一些適合新手的0.92+的上分技巧吧: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.36.2ce879deiwn8Cw&postId=116724
[16]真正零基礎(chǔ),單模型非融合,上93的最簡單技巧: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.27.2ce879deix0lKP&postId=118780
[17]參賽歷程以及方案分享: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.33.2ce879deix0lKP&postId=118799
[18]零基礎(chǔ)CV賽--街景字符識別,小小白分享,從0.002~0.926: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.48.2ce879deix0lKP&postId=118789
