零基礎入門CV賽事- 街景字符編碼識別

官方鏈接: 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ù)集樣本展示

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


官方提供的數(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 工具

  1. Python
  2. Pytorch
  3. Pillow (圖像讀取操作)
  4. 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()
000012.png

應用模糊濾鏡
首先可以利用系統(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)
圖片格式轉(zhuǎn)換

圖片放縮

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ù)擴增方法包括:

  1. transforms.CenterCrop 對圖片中心進行裁剪
  2. transforms.ColorJitter 對圖像顏色的對比度、飽和度和零度進行變換
  3. transforms.FiveCrop 對圖像四個角和中心進行裁剪得到五分圖像
  4. transforms.Grayscale 對圖像進行灰度變換
  5. transforms.Pad 使用固定值進行像素填充
  6. transforms.RandomAffine 隨機仿射變換
  7. transforms.RandomCrop 隨機區(qū)域裁剪
  8. transforms.RandomHorizontalFlip 隨機水平翻轉(zhuǎn)
  9. transforms.RandomRotation 隨機旋轉(zhuǎn)
  10. 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),本章深度學習加深以下幾個方面:

  1. 在訓練集上進行訓練,并在驗證集上進行驗證;
  2. 模型可以保存最優(yōu)的權(quán)重,并讀取權(quán)重;
  3. 記錄下訓練集和驗證集的精度,便于調(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

  1. epoch:訓練時,所有訓練數(shù)據(jù)集都訓練過一次。
  2. batch_size:在訓練集中選擇一組樣本用來更新權(quán)值。1個batch包含的樣本的數(shù)目,通常設為2的n次冪,常用的包括64,128,256。 網(wǎng)絡較小時選用256,較大時選用64。
  3. 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的硬件支持,也需要較多的訓練時間,如何有效的訓練深度學習模型逐漸成為了一門學問。

深度學習有眾多的訓練技巧,比較推薦的閱讀鏈接有:

本節(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在所有場景有可以起作用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容