天池大賽:街景字符編碼識別——Part4:模型訓練與驗證

街景字符編碼識別

更新流程↓
Task01:賽題理解
Task02:數據讀取與數據擴增
Task03:字符識別模型
Task04:模型訓練與驗證
Task05:模型集成
你是真的狗?
婧妹
正 盜 的 光
比賽鏈接


Part4:模型訓練與驗證





0. 簡介

一個成熟合格的深度學習訓練流程至少具備以下功能:

  • 在訓練集上進行訓練,並在驗證集上進行驗證。
  • 模型可以保存最優的權重,並讀取權重。
  • 記錄下訓練集和驗證集的精度,便於調參。

本章將從構建驗證集模型訓練和驗證模型保存與加載模型調參四個部分進行介紹講解。



1. 構建驗證集

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

 1.1. 過擬合與欠擬合

在這裏插入圖片描述

  • 擬合(Fitting):就是說這個曲線能不能很好的描述某些樣本,並且有比較好的泛化能力
  • 過擬合(Overfitting):模型把數據學習的太徹底,以至於把噪聲數據的特徵也學習到了,這樣就會導致在後期測試的時候不能夠很好地識別數據,即不能正確的分類,模型泛化能力太差
  • 欠擬合(UnderFitting):模型沒有很好地捕捉到數據特徵,不能夠很好地擬合數據,或者是模型過於簡單無法擬合或區分樣本。
    在這裏插入圖片描述
    在這裏插入圖片描述




  1.1.1. 防止過擬合方法介紹

  防止過擬合方法主要有:

  1. 正則化方法。正則化方法包括L0正則、L1正則和L2正則,而正則一般是在目標函數之後加上對於的範數。但是在機器學習中一般使用L2正則

  2. 數據增強(Data augmentation),增大數據的訓練量,還有一個原因就是我們用於訓練的數據量太小導致的,訓練數據佔總數據的比例過小。

  3. 重新清洗數據,導致過擬合的一個原因也有可能是數據不純導致的,如果出現了過擬合就需要我們重新清洗數據。

  4. 提前終止法(Early stopping),對模型進行訓練的過程即是對模型的參數進行學習更新的過程,這個參數學習的過程往往會用到一些迭代方法,如梯度下降(Gradient descent)學習算法。提前終止法便是一種迭代次數截斷的方法來防止過擬合的方法,即在模型對訓練數據集迭代收斂之前停止迭代來防止過擬合。

  5. 丟棄法(Dropout)。這個方法在神經網絡裏面很常用。丟棄法是ImageNet中提出的一種方法,通俗一點講就是丟棄法在訓練的時候讓神經元以一定的概率不工作。具體看下圖:標準 Dropout 的示例。左側爲全連接網絡,右側的網絡以 0.5 的概率丟棄神經元。輸出層並沒有應用 Dropout。

左側爲全連接網絡,右側的網絡以 0.5 的概率丟棄神經元。輸出層並沒有應用 Dropout



   1.1.1.1. L1正則化

  可以參考L1和L2正則化(嶺迴歸和LASSO)進行了解。
  在原始的代價函數後面加上一個L1正則化項,即全部權重w的絕對值的和,再乘以λn\frac{\lambda }{n} (這裏不像L2正則化項那樣,須要再乘以1/2)。
C=C0+λnwwC=C_{0}+\frac{\lambda }{n} \sum_{w}^{}|w|
  先計算導數:
Cw=C0w+λnsgn(w)\frac{\partial C}{\partial w}=\frac{\partial C_{0}}{\partial w} +\frac{\lambda }{n}sgn(w)
  上式中sgn(w)sgn(w)表示ww的符號,那麼權重ww的更新規則爲:
ww=wηλnsgn(w)ηC0ww\rightarrow {w}'=w-\frac{\eta \lambda }{n}sgn(w)-\eta\frac{\partial C_{0}}{\partial w}
  比原始的更新規則多出了ηC0w\eta\frac{\partial C_{0}}{\partial w}這一項。

  ww爲正時,sgn(w)>0sgn(w)>0, 則更新後的ww變小。
  當ww爲負時,sgn(w)>0sgn(w)>0, 則更新後的ww變大——因此它的效果就是讓ww往0靠,使網絡中的權重儘可能爲0,也就相當於減小了網絡複雜度,防止過擬合。

  另外,上面沒有提到一個問題,當ww爲0時怎麼辦?當ww等於0時,W|W|是不可導的。所以我們僅僅能依照原始的未經正則化的方法去更新ww,這就相當於去掉 ηλsgn(w)n\frac{\eta\lambda sgn(w)}{n} 這一項,所以我們能夠規定sgn(0)=0sgn(0)=0,這樣就把w=0w=0的情況也統一進來了。(在編程的時候,令sgn(0)=0,sgn(w>0)=1,sgn(w<0)=1sgn(0)=0,sgn(w>0)=1,sgn(w<0)=-1

   1.1.1.2. L2正則化(權重衰減)

  L2正則化就是在代價函數後面再加上一個正則化項:
C=C0+λ2nww2C=C_{0}+\frac{\lambda }{2n} \sum_{w}^{}w^{2}
  C0C_{0}代表原始的代價函數,後面那一項就是L2正則化項。它是這樣來的:全部參數ww的平方和,除以訓練集的樣本大小n。

  λλ就是正則項係數,權衡正則項與C0C_{0}項的比重。 另外另一個係數1/2,1/2經常會看到,主要是爲了後面求導的結果方便,後面那一項求導會產生一個2,與1/2相乘剛好湊整。L2正則化項是怎麼避免過擬合的呢?我們推導一下看看,先求導:
Cw=C0w+λnw\frac{\partial C}{\partial w}=\frac{\partial C_{0}}{\partial w}+\frac{\lambda }{n}w

Cb=C0b\frac{\partial C}{\partial b}=\frac{\partial C_{0}}{\partial b}
  能夠發現L2正則化項對 b 的更新沒有影響,可是對於w的更新有影響:
wwηC0wηλwn=(1ηλn)wηC0ww\rightarrow w-\eta\frac{\partial C_{0}}{\partial w}-\eta\frac{\lambda w}{n}=(1-\frac{\eta \lambda}{n})w-\eta\frac{\partial C_{0} }{\partial w}
  在不使用L2正則化時。求導結果中ww前係數爲 1,經變化後ww前面係數爲 1ηλ/n1−ηλ/n ,由於ηηλλnn都是正的。所以1ηλ/n1−ηλ/n小於1,它的效果是減小ww,這也就是權重衰減(weight decay)的由來。

  當然考慮到後面的導數項,ww終於的值可能增大也可能減小。

  另外,必須提一下,對於基於mini-batch的隨機梯度下降,wwbb更新的公式跟上面給出的有點不同:
w(1ηλn)wηmxCxww\rightarrow (1-\frac{\eta \lambda }{n})w-\frac{\eta }{m}\sum_{x}^{}\frac{\partial C_{x}}{\partial w}bbηmxCxbb\rightarrow b-\frac{\eta }{m}\sum_{x}^{}\frac{\partial C_{x}}{\partial b}

  對照上面ww的更新公式。能夠發現後面那一項變了,變成全部導數加和,乘以ηη再除以mmmm是一個mini-batch中樣本的個數。

  在此我們僅僅是解釋了L2正則化項有讓ww“變小”的效果,可是還沒解釋爲什麼ww“變小”能夠防過擬合?

  一個所謂“顯而易見”的解釋就是:更小的權值ww,從某種意義上說,表示網絡的複雜度更低,對數據的擬合剛剛好(這個法則也叫做奧卡姆剃刀),而在實際應用中,也驗證了這一點,L2正則化的效果往往好於未經正則化的效果。當然,對於非常多人(包含我)來說,這個解釋似乎不那麼顯而易見,所以這裏加入一個略微數學一點的解釋(引自知乎):

過擬合的時候,擬合函數的係數往往非常大,爲什麼?例如以下圖所看到的,過擬合。就是擬合函數須要顧忌每個點。終於形成的擬合函數波動非常大。在某些非常小的區間裏,函數值的變化非常劇烈。

  這就意味着函數在某些小區間裏的導數值(絕對值)非常大,由於自變量值可大可小,所以僅僅有係數足夠大,才幹保證導數值非常大。而L2正則化是通過約束參數的範數使其不要太大,所以能夠在一定程度上降低過擬合情況。


   1.1.1.3. 數據增強(Data augmentation)

  在深度學習方法中,海量的訓練數據,意味着能夠用更深的網絡,訓練出更好的模型。所以,能夠在原始數據上做些改動,得到很多其它的數據,以圖片數據集舉例,能夠做各種變換,如:

  • 將原始圖片旋轉一個小角度

  • 加入隨機噪聲

  • 一些有彈性的畸變(elastic distortions),論文《Best practices for convolutional neural networks applied to visual document analysis》對MNIST做了各種變種擴增。

  • 截取(crop)原始圖片的一部分,比方DeepID中,從一副人臉圖中,截取出了100個小patch作爲訓練數據,極大地添加了數據集。


   1.1.1.4. 提前終止法(Early stopping)

  對模型進行訓練的過程即是對模型的參數進行學習更新的過程,這個參數學習的過程往往會用到一些迭代方法,如梯度下降(Gradient descent)學習算法。提前終止法便是一種迭代次數截斷的方法來防止過擬合的方法,即在模型對訓練數據集迭代收斂之前停止迭代來防止過擬合。

  提前終止法的具體做法是,在每一個Epoch結束時(一個Epoch集爲對所有的訓練數據的一輪遍歷)計算驗證集的正確率,當正確率不再提高時,就停止訓練。這種做法很符合直觀感受,因爲正確率都不再提高了,在繼續訓練也是無益的,只會提高訓練的時間。
  那麼該做法的一個重點便是怎樣才認爲驗證集正確率不再提高了呢?並不是說驗證集正確率一降下來便認爲不再提高了,因爲可能經過這個Epoch後,正確率降低了,但是隨後的Epoch又讓正確率又上去了,所以不能根據一兩次的連續降低就判斷不再提高。一般的做法是,在訓練的過程中,記錄到目前爲止最好的驗證集正確率,當連續10次Epoch(或者更多次)沒達到最佳正確率時,則可以認爲正確率不再提高了。此時便可以停止迭代了(Early Stopping)。這種策略也稱爲“No-improvement-in-n”,n即Epoch的次數,可以根據實際情況取。


   1.1.1.5. 丟棄法(Dropout)

  L1、L2正則化是通過改動代價函數來實現的,而丟棄法則是通過改動神經網絡本身來實現的,它是在訓練網絡時用的一種技巧(trike),它的流程例如以下:

在這裏插入圖片描述 在這裏插入圖片描述
假設我們要訓練上圖這個網絡,在訓練開始時,我們隨機地“刪除”一部分的隱層單元,視它們爲不存在 保持輸入輸出層不變,依照BP算法更新上圖神經網絡中的權值(虛線連接的單元不更新,由於它們被“暫時刪除”了)。

  以上就是一次迭代的過程,在第二次迭代中,也用相同的方法,僅僅只是這次刪除的那一部分隱層單元,跟上一次刪除掉的肯定是不一樣的。我們每一次迭代都是“隨機”地去刪掉一部分,直至訓練結束。
  以上就是 丟棄法t,它爲什麼有助於防止過擬合呢?能夠簡單地這樣解釋,運用了 丟棄法的訓練過程,相當於訓練了非常多個僅僅有部分隱層單元的神經網絡,每個這種半數網絡,都能夠給出一個分類結果,這些結果有的是正確的,有的是錯誤的。隨着訓練的進行,大部分半數網絡都能夠給出正確的分類結果。那麼少數的錯誤分類結果就不會對終於結果造成大的影響。
  刪除神經單元,不工作,通常keep_prob取0.5,在編程時可以利用TensorFlow中 DropoutWrappera函數。在訓練過程引入丟棄策略,其Dropout層保留節點比例(keep_prob),每批數據輸入時神經網絡中的每個單元會以1-keep_prob的概率不工作,防止過擬合。

lstmCell = tf.contrib.rnn.DropoutWrapper(cell=lstmCell, output_keep_prob=0.5)



  1.1.2. 防止過擬合方法介紹

  解決欠擬合方法主要有:

  1. 添加其他特徵項,有時候我們模型出現欠擬合的時候是因爲特徵項不夠導致的,可以添加其他特徵項來很好地解決。例如,“組合”、“泛化”、“相關性”三類特徵是特徵添加的重要手段,無論在什麼場景,都可以照葫蘆畫瓢,總會得到意想不到的效果。除上面的特徵之外,“上下文特徵”、“平臺特徵”等等,都可以作爲特徵添加的首選項。
  2. 添加多項式特徵,這個在機器學習算法裏面用的很普遍,例如將線性模型通過添加二次項或者三次項使模型泛化能力更強。例如上面的圖片的例子。
  3. 減少正則化參數,正則化的目的是用來防止過擬合的,但是現在模型出現了欠擬合,則需要減少正則化參數。



 1.2. 數據集劃分

  • 訓練集(Train Set):模型用於訓練和調整模型參數。
  • 驗證集(Validation Set):用來驗證模型精度和調整模型超參數。
  • 測試集(Test Set):驗證模型的泛化能力。

  因爲訓練集和驗證集是分開的,所以模型在驗證集上面的精度在一定程度上可以反映模型的泛化能力。在劃分驗證集的時候,需要注意驗證集的分佈應該與測試集儘量保持一致,不然模型在驗證集上的精度就失去了指導意義。

  既然驗證集這麼重要,那麼如何劃分本地驗證集呢。在一些比賽中,賽題方會給定驗證集;如果賽題方沒有給定驗證集,那麼參賽選手就需要從訓練集中拆分一部分得到驗證集。驗證集的劃分有如下幾種方式:
在這裏插入圖片描述

  • 留出法(Hold-Out)
    直接將訓練集劃分成兩部分,新的訓練集和驗證集。這種劃分方式的優點是最爲直接簡單;缺點是隻得到了一份驗證集,有可能導致模型在驗證集上過擬合。留出法應用場景是數據量比較大的情況。

  • 交叉驗證法(Cross Validation,CV)
    將訓練集劃分成K份,將其中的K-1份作爲訓練集,剩餘的1份作爲驗證集,循環K訓練。這種劃分方式是所有的訓練集都是驗證集,最終模型驗證精度是K份平均得到。這種方式的優點是驗證集精度比較可靠,訓練K次可以得到K個有多樣性差異的模型;CV驗證的缺點是需要訓練K次,不適合數據量很大的情況。

  • 自助採樣法(BootStrap)
    通過有放回的採樣方式得到新的訓練集和驗證集,每次的訓練集和驗證集都是有區別的。這種劃分方式一般適用於數據量較小的情況。

  當然這些劃分方法是從數據劃分方式的角度來講的,在現有的數據比賽中一般採用的劃分方法是留出法交叉驗證法。如果數據量比較大,留出法還是比較合適的。當然任何的驗證集的劃分得到的驗證集都是要保證訓練集-驗證集-測試集的分佈一致,所以如果不管劃分何種的劃分方式都是需要注意的。(這裏的分佈一般指的是與標籤相關的統計分佈,如果標籤是帶有時序信息,則驗證集和測試集的時間間隔應該保持一致。)



2. 模型訓練和驗證

  在本節我們目標使用Pytorch來完成CNN的訓練和驗證過程,CNN網絡結構與之前的章節中保持一致。我們需要完成的邏輯結構如下:

  • 構造訓練集和驗證集。
  • 每輪進行訓練和驗證,並根據最優驗證集精度保存模型。
# 將自定義的Dataset封裝成一個Batch Size大小的Tensor,用於後面的訓練。
# 訓練集封裝 批量處理數據
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())	#Adam優化算法
best_loss = 1000.0
for epoch in range(20):
    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')
        
	print('Epoch: ', epoch)

  train()validate()predict()代碼如下:

# 訓練函數
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()

# 驗證函數
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)

# 預測函數
def predict(test_loader, model, tta=10):
    model.eval()
    test_pred_tta = None

    # TTA 次數
    for _ in range(tta):
        test_pred = []

        with torch.no_grad():
            for i, (input, target) in enumerate(test_loader):
                #                 if use_cuda:
                #                     input = input.cuda()

                c0, c1, c2, c3, c4, c5 = model(input)
                output = np.concatenate([
                    c0.data.numpy(),
                    c1.data.numpy(),
                    c2.data.numpy(),
                    c3.data.numpy(),
                    c4.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




3. 模型保存與加載

  在Pytorch中模型的保存和加載非常簡單,比較常見的做法是保存和加載模型參數:

# 保存模型爲文件model.pt
torch.save(model_object.state_dict(), 'model.pt')

# 讀取文件model.pt載入模型
model.load_state_dict(torch.load(' model.pt'))



4. 模型調參

  深度學習原理少但實踐性非常強,基本上很多的模型的驗證只能通過訓練來完成。同時深度學習有衆多的網絡結構和超參數,因此需要反覆嘗試。訓練深度學習模型需要GPU的硬件支持,也需要較多的訓練時間,如何有效的訓練深度學習模型逐漸成爲了一門學問。
深度學習有衆多的訓練技巧,比較推薦的閱讀鏈接有:

  本節挑選了常見的一些技巧來講解,並針對本次賽題進行具體分析。與傳統的機器學習模型不同,深度學習模型的精度與模型的複雜度、數據量、正則化、數據擴增等因素直接相關。所以當深度學習模型處於不同的階段(欠擬合、過擬合和完美擬合)的情況下,大家可以知道可以什麼角度來繼續優化模型。
在參加本次比賽的過程中建議大家以如下邏輯完成:

  1. 初步構建簡單的CNN模型,不用特別複雜,跑通訓練、驗證和預測的流程。
  2. 簡單CNN模型的損失會比較大,嘗試增加模型複雜度,並觀察驗證集精度。
  3. 在增加模型複雜度的同時增加數據擴增方法,直至驗證集精度不變。

在這裏插入圖片描述




5.參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章