官方鏈接: https://tianchi.aliyun.com/competition/entrance/531795/introduction
Task1:賽題理解
賽題數(shù)據(jù):
賽題來源自Google街景圖像中的門牌號數(shù)據(jù)集(The Street View House Numbers Dataset, SVHN),并根據(jù)一定方式采樣得到比賽數(shù)據(jù)集。
該數(shù)據(jù)來自真實場景的門牌號。訓練集數(shù)據(jù)包括3W張照片,驗證集數(shù)據(jù)包括1W張照片,每張照片包括顏色圖像和對應的編碼類別和具體位置;為了保證比賽的公平性,測試集A包括4W張照片,測試集B包括4W張照片。
需要注意的是本賽題需要選手識別圖片中所有的字符,為了降低比賽難度,我們提供了訓練集、驗證集和測試集中字符的位置框。

官方提供的數(shù)據(jù)

| Field | Description |
|---|---|
| top | 坐標左上角X |
| height | 字符高度 |
| left | 左上角最表Y |
| width | 字符寬度 |
| label | 字符編碼 |
json數(shù)據(jù)
"000000.png": {
"height": [219, 219], #字符高度
"label": [1, 9], #字符編碼
"left": [246, 323], #左上角最表Y
"top": [77, 81], #坐標左上角X
"width": [81, 96] #字符寬度
},
.....
評價指標:
選手提交結(jié)果與實際圖片的編碼進行對比,以編碼整體識別準確率為評價指標。
任何一個字符錯誤都為錯誤,最終評測指標結(jié)果越大越好,具體計算公式如下:
Score=編碼識別正確的數(shù)量/測試集圖片數(shù)量
file_name, file_code
0010000.jpg,451
0010001.jpg,232
0010002.jpg,45
0010003.jpg,67
0010004.jpg,191
0010005.jpg,892
思路:參考他人https://blog.csdn.net/qq_40317204/article/details/106218501?fps=1&locationNum=2
思路一:定長字符識別(入門思路)
可將賽題抽象為定長字符識別問題,將識別長度定為數(shù)據(jù)集中最長的字符長度,對于達不到識別長度的字符通過進行填充:

思路二:不定長字符識別(專業(yè)字符識別思路)
在字符識別問題當中,有特定的研究方法,典型的有CRNN字符識別模型。
思路三:先檢測再識別(專業(yè)分類問題思路)
由于本次數(shù)據(jù)集中已經(jīng)給出字符框的位置和大小,無需檢測,只需識別即可。
但是對于專業(yè)的分類問題,總要先構(gòu)建(字符)檢測模型,再構(gòu)建識別模型。典型物體檢測模型有SSD或YOLO。
Task2:數(shù)據(jù)讀取與數(shù)據(jù)擴增
2.1 工具
- Python
- Pytorch
- Pillow (圖像讀取操作)
- OpenCV (圖像讀取操作)
2.2 圖像處理
Pillow
Pillow是Python圖像處理函數(shù)庫PIL的一個分支,Pillow提供了常見的圖像讀取和處理的操作,而且可以與ipython notebook無縫集成,是應用比較廣泛的庫
from PIL import Image
from matplotlib import pyplot as plt
im = Image.open('/Users/pqq/Desktop/CV/data/mchar_train/000012.png')
plt.imshow(im)
plt.show()

應用模糊濾鏡
首先可以利用系統(tǒng)自帶的畫圖工具轉(zhuǎn)為jpg格式
實現(xiàn)應用模糊濾鏡
from PIL import Image
from matplotlib import pyplot as plt
im2 = Image.open('/Users/pqq/Desktop/CV/data/mchar_train/000012.png')
im2 = im.filter(ImageFilter.BLUR)
im2.save('2.jpg','jpeg')
plt.imshow(im2)

圖片放縮
im.thumbnail((w//2,h//2))
OpenCV
OpenCV是一個跨平臺的計算機視覺庫,最早由Intel開源得來,擁有眾多的計算機視覺、數(shù)字圖像處理和機器視覺等功能。OpenCV在功能上比Pillow更強大
import cv2
# 導入Opencv庫
img = cv2.imread(‘cat.jpg’)
# Opencv默認顏色通道順序是BRG,轉(zhuǎn)換一下
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
import cv2
img = cv2.imread(‘cat.jpg’)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 轉(zhuǎn)換為灰度圖
import cv2
img = cv2.imread(‘cat.jpg’)
img =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 轉(zhuǎn)換為灰度圖
# Canny邊緣檢測
edges = cv2.Canny(img, 30, 70)
cv2.imwrite(‘canny.jpg’, edges)
OpenCV包含了眾多的圖像處理的功能,OpenCV包含了你能想得到的只要與圖像相關(guān)的操作。此外OpenCV還內(nèi)置了很多的圖像特征處理算法,如關(guān)鍵點檢測、邊緣檢測和直線檢測等。
OpenCV官網(wǎng):https://opencv.org/
OpenCV Github:https://github.com/opencv/opencv
OpenCV 擴展算法庫:https://github.com/opencv/opencv_contrib
2.3 數(shù)據(jù)拓展
在深度學習中數(shù)據(jù)擴增方法非常重要,數(shù)據(jù)擴增可以增加訓練集的樣本,同時也可以有效緩解模型過擬合的情況,也可以給模型帶來的更強的泛化能力。
-
數(shù)據(jù)擴增為什么有用?
在深度學習模型的訓練過程中,數(shù)據(jù)擴增是必不可少的環(huán)節(jié)?,F(xiàn)有深度學習的參數(shù)非常多,一般的模型可訓練的參數(shù)量基本上都是萬到百萬級別,而訓練集樣本的數(shù)量很難有這么多。
其次數(shù)據(jù)擴增可以擴展樣本空間,假設現(xiàn)在的分類模型需要對汽車進行分類,左邊的是汽車A,右邊為汽車B。如果不使用任何數(shù)據(jù)擴增方法,深度學習模型會從汽車車頭的角度來進行判別,而不是汽車具體的區(qū)別。
- 有哪些數(shù)據(jù)擴增方法?
數(shù)據(jù)擴增方法有很多:從顏色空間、尺度空間到樣本空間,同時根據(jù)不同任務數(shù)據(jù)擴增都有相應的區(qū)別。
對于圖像分類,數(shù)據(jù)擴增一般不會改變標簽;
對于物體檢測,數(shù)據(jù)擴增會改變物體坐標位置;
對于圖像分割,數(shù)據(jù)擴增會改變像素標簽。
2.3.2 常見數(shù)據(jù)拓展方法
在常見的數(shù)據(jù)擴增方法中,一般會從圖像顏色、尺寸、形態(tài)、空間和像素等角度進行變換。當然不同的數(shù)據(jù)擴增方法可以自由進行組合,得到更加豐富的數(shù)據(jù)擴增方法。
以torchvision為例,常見的數(shù)據(jù)擴增方法包括:
- transforms.CenterCrop 對圖片中心進行裁剪
- transforms.ColorJitter 對圖像顏色的對比度、飽和度和零度進行變換
- transforms.FiveCrop 對圖像四個角和中心進行裁剪得到五分圖像
- transforms.Grayscale 對圖像進行灰度變換
- transforms.Pad 使用固定值進行像素填充
- transforms.RandomAffine 隨機仿射變換
- transforms.RandomCrop 隨機區(qū)域裁剪
- transforms.RandomHorizontalFlip 隨機水平翻轉(zhuǎn)
- transforms.RandomRotation 隨機旋轉(zhuǎn)
- transforms.RandomVerticalFlip 隨機垂直翻轉(zhuǎn)

2.3.3 常見數(shù)據(jù)拓展庫
-
torchvision
https://github.com/pytorch/vision
pytorch官方提供的數(shù)據(jù)擴增庫,提供了基本的數(shù)據(jù)數(shù)據(jù)擴增方法,可以無縫與torch進行集成;但數(shù)據(jù)擴增方法種類較少,且速度中等; -
imgaug
https://github.com/aleju/imgaug
imgaug是常用的第三方數(shù)據(jù)擴增庫,提供了多樣的數(shù)據(jù)擴增方法,且組合起來非常方便,速度較快; -
albumentations
https://albumentations.readthedocs.io
是常用的第三方數(shù)據(jù)擴增庫,提供了多樣的數(shù)據(jù)擴增方法,對圖像分類、語義分割、物體檢測和關(guān)鍵點檢測都支持,速度較快。
2.4 Pytorch讀取數(shù)據(jù)
import os, sys, glob, shutil, json
import cv2
from PIL import Image
import numpy as np
import torch
from torch.utils.data.dataset import Dataset
import torchvision.transforms as transforms
class SVHNDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
self.img_path = img_path
self.img_label = img_label
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
img = Image.open(self.img_path[index]).convert('RGB')
if self.transform is not None:
img = self.transform(img)
# 原始SVHN中類別10為數(shù)字0
lbl = np.array(self.img_label[index], dtype=np.int)
lbl = list(lbl) + (5 - len(lbl)) * [10]
return img, torch.from_numpy(np.array(lbl[:5]))
def __len__(self):
return len(self.img_path)
train_path = glob.glob('../input/train/*.png')
train_path.sort()
train_json = json.load(open('../input/train.json'))
train_label = [train_json[x]['label'] for x in train_json]
data = SVHNDataset(train_path, train_label,
transforms.Compose([
# 縮放到固定尺寸
transforms.Resize((64, 128)),
# 隨機顏色變換
transforms.ColorJitter(0.2, 0.2, 0.2),
# 加入隨機旋轉(zhuǎn)
transforms.RandomRotation(5),
# 將圖片轉(zhuǎn)換為pytorch 的tesntor
# transforms.ToTensor(),
# 對圖像像素進行歸一化
# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
]))
Dataset:對數(shù)據(jù)集的封裝,提供索引方式的對數(shù)據(jù)樣本進行讀取
DataLoder:對Dataset進行封裝,提供批量讀取的迭代讀取
加入DataLoder后,數(shù)據(jù)讀取代碼改為如下:
import os, sys, glob, shutil, json
import cv2
from PIL import Image
import numpy as np
import torch
from torch.utils.data.dataset import Dataset
import torchvision.transforms as transforms
class SVHNDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
self.img_path = img_path
self.img_label = img_label
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
img = Image.open(self.img_path[index]).convert('RGB')
if self.transform is not None:
img = self.transform(img)
# 原始SVHN中類別10為數(shù)字0
lbl = np.array(self.img_label[index], dtype=np.int)
lbl = list(lbl) + (5 - len(lbl)) * [10]
return img, torch.from_numpy(np.array(lbl[:5]))
def __len__(self):
return len(self.img_path)
train_path = glob.glob('../input/train/*.png')
train_path.sort()
train_json = json.load(open('../input/train.json'))
train_label = [train_json[x]['label'] for x in train_json]
train_loader = torch.utils.data.DataLoader(
SVHNDataset(train_path, train_label,
transforms.Compose([
transforms.Resize((64, 128)),
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=10, # 每批樣本個數(shù)
shuffle=False, # 是否打亂順序
num_workers=10, # 讀取的線程個數(shù)
)
for data in train_loader:
break
Task3: 字符識別模型
CNN:卷積 圖像 (本章學習內(nèi)容)
RNN:遞歸 時間序列
CNN介紹
卷積神經(jīng)網(wǎng)絡(簡稱CNN)是一類特殊的人工神經(jīng)網(wǎng)絡,是深度學習中重要的一個分支。CNN在很多領域都表現(xiàn)優(yōu)異,精度和速度比傳統(tǒng)計算學習算法高很多。特別是在計算機視覺領域,CNN是解決圖像分類、圖像檢索、物體檢測和語義分割的主流模型。
CNN每一層由眾多的卷積核組成,每個卷積核對輸入的像素進行卷積操作,得到下一次的輸入。隨著網(wǎng)絡層的增加卷積核會逐漸擴大感受野,并縮減圖像的尺寸。

CNN是一種層次模型,輸入的是原始的像素數(shù)據(jù)。CNN通過卷積(convolution)、池化(pooling)、非線性激活函數(shù)(non-linear activation function)和全連接層(fully connected layer)構(gòu)成。
如下圖所示為LeNet網(wǎng)絡結(jié)構(gòu),是非常經(jīng)典的字符識別模型。兩個卷積層,兩個池化層,兩個全連接層組成。卷積核都是5×5,stride=1,池化層使用最大池化。

通過多次卷積和池化,CNN的最后一層將輸入的圖像像素映射為具體的輸出。如在分類任務中會轉(zhuǎn)換為不同類別的概率輸出,然后計算真實標簽與CNN模型的預測結(jié)果的差異,并通過反向傳播更新每層的參數(shù),并在更新完成后再次前向傳播,如此反復直到訓練完成 。
與傳統(tǒng)機器學習模型相比,CNN具有一種端到端(End to End)的思路。在CNN訓練的過程中是直接從圖像像素到最終的輸出,并不涉及到具體的特征提取和構(gòu)建模型的過程,也不需要人工的參與。
CNN發(fā)展
隨著網(wǎng)絡結(jié)構(gòu)的發(fā)展,研究人員最初發(fā)現(xiàn)網(wǎng)絡模型結(jié)構(gòu)越深、網(wǎng)絡參數(shù)越多模型的精度更優(yōu)。比較典型的是AlexNet、VGG、InceptionV3和ResNet的發(fā)展脈絡。

LeNet-5(1998)

AlexNet(2012)

VGG-16(2014)

Inception-v1 (2014)

ResNet-50 (2015)

Pytorch構(gòu)建CNN模型
在Pytorch中構(gòu)建CNN模型非常簡單,只需要定義好模型的參數(shù)和正向傳播即可,Pytorch會根據(jù)正向傳播自動計算反向傳播。
在本章我們會構(gòu)建一個非常簡單的CNN,然后進行訓練。這個CNN模型包括兩個卷積層,最后并聯(lián)6個全連接層進行分類。
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
# 定義模型
class SVHN_Model1(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
# CNN提取特征模塊
self.cnn = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
nn.ReLU(),
nn.MaxPool2d(2),
)
#
self.fc1 = nn.Linear(32*3*7, 11)
self.fc2 = nn.Linear(32*3*7, 11)
self.fc3 = nn.Linear(32*3*7, 11)
self.fc4 = nn.Linear(32*3*7, 11)
self.fc5 = nn.Linear(32*3*7, 11)
self.fc6 = nn.Linear(32*3*7, 11)
def forward(self, img):
feat = self.cnn(img)
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)
c6 = self.fc6(feat)
return c1, c2, c3, c4, c5, c6
model = SVHN_Model1()
訓練代碼
# 損失函數(shù)
criterion = nn.CrossEntropyLoss()
# 優(yōu)化器
optimizer = torch.optim.Adam(model.parameters(), 0.005)
loss_plot, c0_plot = [], []
# 迭代10個Epoch
for epoch in range(10):
for data in train_loader:
c0, c1, c2, c3, c4, c5 = model(data[0])
loss = criterion(c0, data[1][:, 0]) + \
criterion(c1, data[1][:, 1]) + \
criterion(c2, data[1][:, 2]) + \
criterion(c3, data[1][:, 3]) + \
criterion(c4, data[1][:, 4]) + \
criterion(c5, data[1][:, 5])
loss /= 6
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_plot.append(loss.item())
c0_plot.append((c0.argmax(1) == data[1][:, 0]).sum().item()*1.0 / c0.shape[0])
print(epoch)
訓練完成后我們可以將訓練過程中的損失和準確率進行繪制,如下圖所示。從圖中可以看出模型的損失在迭代過程中逐漸減小,字符預測的準確率逐漸升高。

Task4:模型訓練與驗證
結(jié)合上節(jié)知識(構(gòu)建了一個簡單地CNN),本章深度學習加深以下幾個方面:
- 在訓練集上進行訓練,并在驗證集上進行驗證;
- 模型可以保存最優(yōu)的權(quán)重,并讀取權(quán)重;
- 記錄下訓練集和驗證集的精度,便于調(diào)參。
4.1 構(gòu)造驗證集
知識:
過擬合:過擬合是指為了得到一致假設而使假設變得過度嚴格。

在機器學習模型(特別是深度學習模型)的訓練過程中,模型是非常容易過擬合的。深度學習模型在不斷的訓練過程中訓練誤差會逐漸降低,但測試誤差的走勢則不一定。
在模型的訓練過程中,模型只能利用訓練數(shù)據(jù)來進行訓練,模型并不能接觸到測試集上的樣本。因此模型如果將訓練集學的過好,模型就會記住訓練樣本的細節(jié),導致模型在測試集的泛化效果較差,這種現(xiàn)象稱為過擬合(Overfitting)。與過擬合相對應的是欠擬合(Underfitting),即模型在訓練集上的擬合效果較差。

如圖所示:隨著模型復雜度和模型訓練輪數(shù)的增加,CNN模型在訓練集上的誤差會降低,但在測試集上的誤差會逐漸降低,然后逐漸升高,而我們?yōu)榱俗非蟮氖悄P驮跍y試集上的精度越高越好。
在一般情況下,參賽選手也可以自己在本地劃分出一個驗證集出來,進行本地驗證。訓練集、驗證集和測試集分別有不同的作用:
-
訓練集(Train Set):模型用于訓練和調(diào)整模型參數(shù);
-
驗證集(Validation Set):用來驗證模型精度和調(diào)整模型超參數(shù);
-
測試集(Test Set):驗證模型的泛化能力。
驗證集的劃分:

-
留出法(Hold-Out)
直接將訓練集劃分成兩部分,新的訓練集和驗證集。這種劃分方式的優(yōu)點是最為直接簡單;缺點是只得到了一份驗證集,有可能導致模型在驗證集上過擬合。留出法應用場景是數(shù)據(jù)量比較大的情況。
-
交叉驗證法
將訓練集劃分成K份,將其中的K-1份作為訓練集,剩余的1份作為驗證集,循環(huán)K訓練。這種劃分方式是所有的訓練集都是驗證集,最終模型驗證精度是K份平均得到。這種方式的優(yōu)點是驗證集精度比較可靠,訓練K次可以得到K個有多樣性差異的模型;CV驗證的缺點是需要訓練K次,不適合數(shù)據(jù)量很大的情況。
-
自助采樣法(BootStrap)
通過有放回的采樣方式得到新的訓練集和驗證集,每次的訓練集和驗證集都是有區(qū)別的。這種劃分方式一般適用于數(shù)據(jù)量較小的情況。
4.2 模型的訓練與驗證
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=10,
shuffle=True,
num_workers=10,
)
val_loader = torch.utils.data.DataLoader(
val_dataset,
batch_size=10,
shuffle=False,
num_workers=10,
)
model = SVHN_Model1()
criterion = nn.CrossEntropyLoss (size_average=False)
optimizer = torch.optim.Adam(model.parameters(), 0.001)
best_loss = 1000.0
for epoch in range(20):
print('Epoch: ', epoch)
train(train_loader, model, criterion, optimizer, epoch)
val_loss = validate(val_loader, model, criterion)
# 記錄下驗證集精度
if val_loss < best_loss:
best_loss = val_loss
torch.save(model.state_dict(), './model.pt')
每個epoch的訓練代碼:
def train(train_loader, model, criterion, optimizer, epoch): # 切換模型為訓練模式
model.train()
for i, (input, target) in enumerate(train_loader):
c0, c1, c2, c3, c4, c5 = model(data[0])
loss = criterion(c0, data[1][:, 0]) + \
criterion(c1, data[1][:, 1]) + \
criterion(c2, data[1][:, 2]) + \
criterion(c3, data[1][:, 3]) + \
criterion(c4, data[1][:, 4]) + \
criterion(c5, data[1][:, 5])
loss /= 6
optimizer.zero_grad()
loss.backward()
optimizer.step()
每個epoch的訓練代碼:
def validate(val_loader, model, criterion): # 切換模型為預測模型
model.eval()
val_loss = []
# 不不記錄模型梯度信息 with torch.no_grad():
for i, (input, target) in enumerate(val_loader):
c0, c1, c2, c3, c4, c5 = model(data[0])
loss = criterion(c0, data[1][:, 0]) + \
criterion(c1, data[1][:, 1]) + \
criterion(c2, data[1][:, 2]) + \
criterion(c3, data[1][:, 3]) + \
criterion(c4, data[1][:, 4]) + \
criterion(c5, data[1][:, 5])
loss /= 6
val_loss.append(loss.item())
return np.mean(val_loss)
補充知識點:
Epoch, Batch, Iteration
- epoch:訓練時,所有訓練數(shù)據(jù)集都訓練過一次。
- batch_size:在訓練集中選擇一組樣本用來更新權(quán)值。1個batch包含的樣本的數(shù)目,通常設為2的n次冪,常用的包括64,128,256。 網(wǎng)絡較小時選用256,較大時選用64。
-
iteration?:訓練時,1個batch訓練圖像通過網(wǎng)絡訓練一次?(一次前向傳播+一次后向傳播),每迭代一次權(quán)重更新一次;測試時,1個batch測試圖像通過網(wǎng)絡一次?(一次前向傳播)。所謂iterations就是完成一次epoch所需的batch個數(shù)
計算示例
4.3 模型保存和加載
torch.save(model_object.state_dict(), 'model.pt')
model.load_state_dict(torch.load(' model.pt'))
4.4 調(diào)參
深度學習原理少但實踐性非常強,基本上很多的模型的驗證只能通過訓練來完成。同時深度學習有眾多的網(wǎng)絡結(jié)構(gòu)和超參數(shù),因此需要反復嘗試。訓練深度學習模型需要GPU的硬件支持,也需要較多的訓練時間,如何有效的訓練深度學習模型逐漸成為了一門學問。
深度學習有眾多的訓練技巧,比較推薦的閱讀鏈接有:
- http://lamda.nju.edu.cn/weixs/project/CNNTricks/CNNTricks.html
- http://karpathy.github.io/2019/04/25/recipe/
本節(jié)挑選了常見的一些技巧來講解,并針對本次賽題進行具體分析。與傳統(tǒng)的機器學習模型不同,深度學習模型的精度與模型的復雜度、數(shù)據(jù)量、正則化、數(shù)據(jù)擴增等因素直接相關(guān)。所以當深度學習模型處于不同的階段(欠擬合、過擬合和完美擬合)的情況下,大家可以知道可以什么角度來繼續(xù)優(yōu)化模型。
在參加本次比賽的過程中,我建議大家以如下邏輯完成:
1.初步構(gòu)建簡單的CNN模型,不用特別復雜,跑通訓練、驗證和預測的流程;
2.簡單CNN模型的損失會比較大,嘗試增加模型復雜度,并觀察驗證集精度;
3.在增加模型復雜度的同時增加數(shù)據(jù)擴增方法,直至驗證集精度不變。

Task5:模型集成
5.1 集成學習方法
在機器學習中的集成學習可以在一定程度上提高預測精度,常見的集成學習方法有Stacking、Bagging和Boosting,同時這些集成學習方法與具體驗證集劃分聯(lián)系緊密。
由于深度學習模型一般需要較長的訓練周期,如果硬件設備不允許建議選取留出法,如果需要追求精度可以使用交叉驗證的方法。
下面假設構(gòu)建了10折交叉驗證,訓練得到10個CNN模型。

那么在10個CNN模型可以使用如下方式進行集成:
對預測的結(jié)果的概率值進行平均,然后解碼為具體字符;
對預測的字符進行投票,得到最終字符。
5.2 深度學習中的集成學習
5.3.1 Dropout
Dropout可以作為訓練深度神經(jīng)網(wǎng)絡的一種技巧。在每個訓練批次中,通過隨機讓一部分的節(jié)點停止工作。同時在預測的過程中讓所有的節(jié)點都其作用。

Dropout經(jīng)常出現(xiàn)在在先有的CNN網(wǎng)絡中,可以有效的緩解模型過擬合的情況,也可以在預測時增加模型的精度。
加入Dropout后的網(wǎng)絡結(jié)構(gòu)如下
# 定義模型
class SVHN_Model1(nn.Module):
def __init__(self):
super(SVHN_Model1, self).__init__()
# CNN提取特征模塊
self.cnn = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
nn.ReLU(),
nn.Dropout(0.25),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
nn.ReLU(),
nn.Dropout(0.25),
nn.MaxPool2d(2),
)
#
self.fc1 = nn.Linear(32*3*7, 11)
self.fc2 = nn.Linear(32*3*7, 11)
self.fc3 = nn.Linear(32*3*7, 11)
self.fc4 = nn.Linear(32*3*7, 11)
self.fc5 = nn.Linear(32*3*7, 11)
self.fc6 = nn.Linear(32*3*7, 11)
def forward(self, img):
feat = self.cnn(img)
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)
c6 = self.fc6(feat)
return c1, c2, c3, c4, c5, c6
5.3.2 TTA
測試集數(shù)據(jù)擴增(Test Time Augmentation,簡稱TTA)也是常用的集成學習技巧,數(shù)據(jù)擴增不僅可以在訓練時候用,而且可以同樣在預測時候進行數(shù)據(jù)擴增,對同一個樣本預測三次,然后對三次結(jié)果進行平均。
def predict(test_loader, model, tta=10):
model.eval()
test_pred_tta = None
# TTA 次數(shù)
for _ in range(tta):
test_pred = []
with torch.no_grad():
for i, (input, target) in enumerate(test_loader):
c0, c1, c2, c3, c4, c5 = model(data[0])
output = np.concatenate([c0.data.numpy(), c1.data.numpy(),
c2.data.numpy(), c3.data.numpy(),
c4.data.numpy(), c5.data.numpy()], axis=1)
test_pred.append(output)
test_pred = np.vstack(test_pred)
if test_pred_tta is None:
test_pred_tta = test_pred
else:
test_pred_tta += test_pred
return test_pred_tta
5.3.3 Snapshot
假設我們訓練了10個CNN則可以將多個模型的預測結(jié)果進行平均。但是加入只訓練了一個CNN模型,如何做模型集成呢?
在論文Snapshot Ensembles中,作者提出使用cyclical learning rate進行訓練模型,并保存精度比較好的一些checkopint,最后將多個checkpoint進行模型集成。

由于在cyclical learning rate中學習率的變化有周期性變大和減少的行為,因此CNN模型很有可能在跳出局部最優(yōu)進入另一個局部最優(yōu)。在Snapshot論文中作者通過使用表明,此種方法可以在一定程度上提高模型精度,但需要更長的訓練時間。

5.4 結(jié)果后處理
在不同的任務中可能會有不同的解決方案,不同思路的模型不僅可以互相借鑒,同時也可以修正最終的預測結(jié)果。
在本次賽題中,可以從以下幾個思路對預測結(jié)果進行后處理:
統(tǒng)計圖片中每個位置字符出現(xiàn)的頻率,使用規(guī)則修正結(jié)果;
單獨訓練一個字符長度預測模型,用來預測圖片中字符個數(shù),并修正結(jié)果。
5.5 本章小節(jié)
在本章中我們講解了深度學習模型做集成學習的各種方法,并以此次賽題為例講解了部分代碼。以下幾點需要同學們注意:
集成學習只能在一定程度上提高精度,并需要耗費較大的訓練時間,因此建議先使用提高單個模型的精度,再考慮集成學習過程;
具體的集成學習方法需要與驗證集劃分方法結(jié)合,Dropout和TTA在所有場景有可以起作用。


