天天看點

【5】過拟合處理的一些技巧

文章目錄

      • 1.過拟合與欠拟合
      • 2.交叉驗證
      • 3.regularization正則化
      • 4.動量與學習量的衰減
          • 1)動量
          • 2)Learing rate decay
      • 5.Early Stopping
      • 6.Dropout

1.過拟合與欠拟合

  • Underfitting欠拟合的特點:模型的複雜度 < 資料分布的複雜度

    1)訓練集的準确率不好,loss會比較高

    2)測試集的準确率也不好,loss也會比較高

  • Overfitting過拟合的特點:模型的複雜度 > 資料分布的複雜度

    1)訓練集的準确度比較高,但是測試集的準确度比較低

    2)過分的準确降低loss,導緻效果不好

由于現在的網絡層數都比較大,是以出現過拟合的情況比較多,欠拟合的情況相對會少一點

【5】過拟合處理的一些技巧

一般來說,如何防止過拟合的一些基本的操作:

1)提供更多的資料

2)降低模型的複雜度

降低神經網絡結構的層數 或者 正則化方法

3)Dropout 去掉一部分的神經元

4)Data Enhancement 資料增強

5)Early Stopping對訓練過程做一個提前的終結

2.交叉驗證

  • train-val-test交叉驗證結構的思路:

    由于datasets.MNIST函數可以将資料樣本劃分成資料集與測試集兩類,但是這是不夠的,可以引入第三類,也就是train-val-test交叉驗證結構。可以将樣本數量較多的訓練集再度的劃分為train樣本集與val樣本集,這樣可以使用train樣本集來訓練神經網絡,然後使用val樣本集來測試來選擇一個較好的參數值,最後test資料集是作為最後的樣本得出最終的結果

    【5】過拟合處理的一些技巧
  • 參考代碼
# 導入需要的子產品
import  torch
import  torch.nn as nn
import  torch.nn.functional as F
import  torch.optim as optim
import  torchvision
from    torchvision import transforms,datasets

# 參數的初始化
batch_size=200
learning_rate=0.01
epochs=10

# 訓練集下載下傳
train_db = datasets.MNIST('datasets/mnist_data', 
                    train=True, 
                    download=True,
                    transform=transforms.Compose([
                    transforms.ToTensor(),
                    transforms.Normalize((0.1307,), (0.3081,))
]))

# 測試集下載下傳
test_db = datasets.MNIST('datasets/mnist_data', 
                    train=False, 
                    download=True,
                    transform=transforms.Compose([
                    transforms.ToTensor(),
                    transforms.Normalize((0.1307,), (0.3081,))
]))

# 将訓練集再度分成兩類,train資料集樣本數為50k,val資料集的樣本數為10k
train_db, val_db = torch.utils.data.random_split(train_db, [50000, 10000])

# 接下來進行導入資料
train_loader = torch.utils.data.DataLoader( train_db, batch_size=batch_size, shuffle=True)
test_loader  = torch.utils.data.DataLoader( test_db,  batch_size=batch_size, shuffle=True)
val_loader   = torch.utils.data.DataLoader( val_db,   batch_size=batch_size, shuffle=True)

# 列印三種資料集的樣本數量
print('train:', len(train_db),'val:', len(val_db), 'test:', len(test_db))
# 輸出為:train: 50000 val: 10000 test: 10000

# 定義神經網絡結構
class MLP(nn.Module):

    def __init__(self):
        super(MLP, self).__init__()

        self.model = nn.Sequential(
            nn.Linear(784, 200),
            nn.LeakyReLU(inplace=True),
            nn.Linear(200, 200),
            nn.LeakyReLU(inplace=True),
            nn.Linear(200, 10),
            nn.LeakyReLU(inplace=True),
        )

    def forward(self, x):
        x = self.model(x)

        return x
    
# 使用GPU來加速計算
device = torch.device('cuda:0')
net = MLP().to(device)

# 設定容器
optimizer = optim.SGD(net.parameters(), lr=learning_rate)
criteon = nn.CrossEntropyLoss().to(device)

# 一次epoch是直接訓練全部的樣本
for epoch in range(epochs):

    # 對于train資料集,主要的功能是不斷的改進神經網絡結構
    for batch_idx, (data, target) in enumerate(train_loader):
        data = data.view(-1, 28*28)
        data, target = data.to(device), target.cuda()

        logits = net(data)
        loss = criteon(logits, target)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch_idx % 100 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                       100. * batch_idx / len(train_loader), loss.item()))

    # 然後使用val資料集來挑選一個精度最高的參數值來進行下一次的epoch訓練,主要主要是監視時候出現過拟合的情況
    # 根據val樣本集輸出的情況,進行人為的選擇最低loss指的參數,來作為做好的參數,來給最後的test樣本集來做驗證
    test_loss = 0
    correct = 0
    for data, target in val_loader:
        data = data.view(-1, 28 * 28)
        data, target = data.to(device), target.cuda()
        logits = net(data)
        test_loss += criteon(logits, target).item()
		
		# data.shape,pred.shape輸出為:(torch.Size([200, 784]), torch.Size([200]))
		# 也就是data為200張28*28的圖檔
        pred = logits.data.max(1)[1]
# 得出每一個資料的預測數字      
# 輸出為:
# tensor([3, 2, 7, 0, 2, 9, 3, 5, 0, 1, 8, 8, 1, 8, 5, 0, 8, 0, 5, 9, 1, 9, 4, 8,
#        1, 0, 4, 3, 7, 7, 2, 2, 8, 7, 1, 2, 9, 9, 1, 0, 2, 3, 5, 4, 8, 8, 2, 1,
#        3, 8, 2, 6, 3, 2, 1, 1, 7, 9, 2, 1, 2, 9, 9, 3, 0, 0, 1, 9, 7, 5, 6, 4,
#        5, 3, 8, 3, 1, 6, 0, 1, 6, 1, 1, 2, 6, 6, 5, 3, 4, 9, 9, 5, 8, 3, 1, 5,
#        7, 9, 6, 1, 2, 5, 3, 4, 3, 1, 7, 2, 0, 3, 0, 3, 1, 4, 1, 6, 6, 5, 3, 1,
#        1, 6, 2, 7, 2, 8, 4, 4, 0, 8, 2, 9, 0, 1, 6, 7, 3, 1, 6, 1, 0, 5, 4, 9,
#        9, 3, 3, 0, 9, 8, 9, 7, 8, 1, 7, 6, 4, 0, 1, 8, 4, 2, 4, 2, 1, 1, 3, 4,
#        4, 8, 8, 7, 4, 8, 2, 1, 7, 6, 0, 9, 9, 6, 1, 7, 9, 4, 7, 5, 8, 7, 3, 4,
#        4, 0, 0, 5, 8, 3, 2, 1], device='cuda:0')
        correct += pred.eq(target.data).sum()

    test_loss /= len(val_loader.dataset)
    print('\nVAL set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(val_loader.dataset),
        100. * correct / len(val_loader.dataset)))

# 測試集得出最後的結果,根據上述的輸出人為的選擇一個最好的參數,去避免神經網絡訓練次數過多而導緻過拟合的情況
# 這部分代碼與val資料集的樣本是完全一模一樣的
test_loss = 0
correct = 0
for data, target in test_loader:
    data = data.view(-1, 28 * 28)
    data, target = data.to(device), target.cuda()
    logits = net(data)
    test_loss += criteon(logits, target).item()

    pred = logits.data.max(1)[1]
    correct += pred.eq(target.data).sum()

test_loss /= len(test_loader.dataset)

# 得出最後一個最終結果
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
    test_loss, correct, len(test_loader.dataset),
    100. * correct / len(test_loader.dataset)))
           

結果輸出:

Train Epoch: 0 [0/50000 (0%)]	Loss: 2.306638
Train Epoch: 0 [20000/50000 (40%)]	Loss: 2.004949
Train Epoch: 0 [40000/50000 (80%)]	Loss: 1.304654

VAL set: Average loss: 0.0048, Accuracy: 7999/10000 (80%)

Train Epoch: 1 [0/50000 (0%)]	Loss: 0.950421
Train Epoch: 1 [20000/50000 (40%)]	Loss: 0.750856
Train Epoch: 1 [40000/50000 (80%)]	Loss: 0.486089

VAL set: Average loss: 0.0024, Accuracy: 8725/10000 (87%)

Train Epoch: 2 [0/50000 (0%)]	Loss: 0.551328
Train Epoch: 2 [20000/50000 (40%)]	Loss: 0.393225
Train Epoch: 2 [40000/50000 (80%)]	Loss: 0.364113

VAL set: Average loss: 0.0019, Accuracy: 8911/10000 (89%)

Train Epoch: 3 [0/50000 (0%)]	Loss: 0.349589
Train Epoch: 3 [20000/50000 (40%)]	Loss: 0.392292
Train Epoch: 3 [40000/50000 (80%)]	Loss: 0.436958

VAL set: Average loss: 0.0017, Accuracy: 9017/10000 (90%)

Train Epoch: 4 [0/50000 (0%)]	Loss: 0.258544
Train Epoch: 4 [20000/50000 (40%)]	Loss: 0.338164
Train Epoch: 4 [40000/50000 (80%)]	Loss: 0.359373

VAL set: Average loss: 0.0016, Accuracy: 9062/10000 (91%)

Train Epoch: 5 [0/50000 (0%)]	Loss: 0.273114
Train Epoch: 5 [20000/50000 (40%)]	Loss: 0.253162
Train Epoch: 5 [40000/50000 (80%)]	Loss: 0.311780

VAL set: Average loss: 0.0015, Accuracy: 9127/10000 (91%)

Train Epoch: 6 [0/50000 (0%)]	Loss: 0.250342
Train Epoch: 6 [20000/50000 (40%)]	Loss: 0.315225
Train Epoch: 6 [40000/50000 (80%)]	Loss: 0.349633

VAL set: Average loss: 0.0014, Accuracy: 9168/10000 (92%)

Train Epoch: 7 [0/50000 (0%)]	Loss: 0.279099
Train Epoch: 7 [20000/50000 (40%)]	Loss: 0.223900
Train Epoch: 7 [40000/50000 (80%)]	Loss: 0.316459

VAL set: Average loss: 0.0014, Accuracy: 9201/10000 (92%)

Train Epoch: 8 [0/50000 (0%)]	Loss: 0.349246
Train Epoch: 8 [20000/50000 (40%)]	Loss: 0.241840
Train Epoch: 8 [40000/50000 (80%)]	Loss: 0.193532

VAL set: Average loss: 0.0013, Accuracy: 9231/10000 (92%)

Train Epoch: 9 [0/50000 (0%)]	Loss: 0.238849
Train Epoch: 9 [20000/50000 (40%)]	Loss: 0.193404
Train Epoch: 9 [40000/50000 (80%)]	Loss: 0.204639

VAL set: Average loss: 0.0013, Accuracy: 9251/10000 (93%)


Test set: Average loss: 0.0012, Accuracy: 9305/10000 (93%)
           

由于設定的epoch過小,神經網絡沒有完全訓練好,是以目前loss還是不斷地在降低,還沒有出現過拟合的情況,是以目前還不需要人為的挑選參數,來給最後的test樣本集來進行訓練。

3.regularization正則化

問題:

正則化的操作是為了使得參數分布減小,進而避免過拟合的情況,但是為什麼參數範數接近于0的時候,其模型的複雜度會減少?

答案:

因為将一個高維函數的高維部分的參數下降為接近于0,是以正則化在另一方面也稱為Weight Decay

正則化分為兩類:劃分的依據是範數的不同

1) L1-regularizationimage

【5】過拟合處理的一些技巧

2) L2-regularization(較常用)

【5】過拟合處理的一些技巧

對于L2-regularization來說,pytorch有直接API接口可以直接使用,比較的友善,而L1-regularization需要人為進行添加。

# L2-regularization的使用參考
device = torch.device('cuda:0')
net = MLP().to(device)
# 其實就是添加了一個參數weight_decay = 0.01
optimizer = optim.SGD(net.parameters(),lr = learning_rate,weight_decay = 0.01)
criteon = nn.CrossEntropyLoss().to(device)
# weight_decay = 0.01的含義:相當于設定了L2-regularization公式中的λ為0.01
# 由于net.parameters()已經得到了神經網絡的全部參數,w’=w-▽w,然後使得參數的二範數|w|——>0
           

需要注意的是,正則化的操作是為了避免網絡結果過于的複雜是以需要限制其參數不能過大,也就是避免過拟合的情況,如果網絡并沒有出現過拟合的情況而使用正則化操作會使得訓練的效果差很多

# L1-regularization的使用參考
regulatization_loss = 0          # 周遊參數計算出參數的1範數,根據公式也就是模的相加
for param in model.parameters():
    regulatization_loss += torch.sum(torch.abs(param))   # 對參數取絕對值求和
classify_loss = criteon(logits,target)    # 計算出結果的交叉熵
# 交叉熵 + 參數的1範數損失 = 總的loss, 現在降低這個loss就可以改善網絡
# 根據上述的公式,0.01就是L1-regularization公式中的λ
# 以下這行代碼就是模仿公式的結果
loss = classify_loss + 0.01 * regulatization_loss   

# 基本的三部曲,向後傳播更新參數
optimizer.zero_grad()
loss.backward()
optimizer.step()
           

4.動量與學習量的衰減

1)動量
【5】過拟合處理的一些技巧

其中的zk可以了解為上一次梯度更新的方向,增加的動量的機制相當于不僅僅考慮了目前梯度的方向,還考慮了之前的梯度的方向,也就是綜合的結合了兩次梯度的變化資訊來權衡。

  • No momentum
    【5】過拟合處理的一些技巧
  • With appr. momentum
    【5】過拟合處理的一些技巧
    參考代碼:
# 方法1
# 同樣的,pytorch中的SGD優化器也提供的一個接口,使用起來還是比較的友善
optimizer = torch.optim.SGD(model.parameters().args.lr,
                               momentum = args.momentum,   # 僅僅是添加了了着一個參數
                               weight_decay = args.weight_decay)

# 方法2
# 0.8表示新一輪産生的資料與前一輪産生的資料之間的使用比例,也就是動量操作
nn.BatchNorm2d(128, 0.8),  
# 是以在構造神經網絡的時候可以使用BN層來間接使用動量操作
# 參考解釋:https://blog.csdn.net/t20134297/article/details/104960101/
           
2)Learing rate decay
【5】過拟合處理的一些技巧

當梯度的長時間保持不變的時候,也有可以是learning rate的問題,有時我們調整了一下學習率就可能讓loss跳轉,是以可以動态的設定learning rate,訓練過程一直保持同樣的learning rate有時進行的會是非常的緩慢的

【5】過拟合處理的一些技巧
【5】過拟合處理的一些技巧

參考代碼:

# 方法1
# 在SGD容器中設定了正則化操作與動量操作
optimizer = torch.optim.SGD(model.parameters().args.lr,
                               momentum = args.momentum,
                               weight_decay = args.weight_decay)
# ReduceLOonPlateau當loss已經平坦了很長時間之後,可以通過減小learning rate來進一步減小loss
# 接口函數為:class ReduceLROnPlateau(builtins.object)
#  |  ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, 
#                      threshold=0.0001, threshold_mode='rel', cooldown=0, 
#                      min_lr=0, eps=1e-08, verbose=False)
scheduler = ReduceLOonPlateau(optimizer,'min')
# mode='min':表示模式是減小lr
# patience=10:表示如果10個epoch都沒有減小,則降低learing rate
# factor=0.1:表示降低learing rate的倍數,lr = factor*lr# 

# 訓練過程
for epoch in xrange(args.start_epoch,args.epochs):
    train(train_loader,model,criterion,optimizer,epoch)
    result_avg,loss_val = validate(val_loader,model,criterion,epoch)
    # 每調用一次就監聽一次loss,若平淡10次就會做出改變
    scheduler.step(loss_val)
           
# 方法2:
# 此方法不需要監聽,而是沒經過一定數量的訓練次數就直接降低learning rate
# Assuming optimizer uses lr = 0.05 for all groups
# lr = 0.05   if epoch < 30
# lr = 0.005  if 30 <= epoch < 60
# lr = 0.0005 if 60 <= epoch < 90
# ......
scheduler = StepLR(optimizer,step_size = 30,gamma = 0.1)
for epoch in range(100):
    scheduler.step()
    train(...)
    validate(...)
           

5.Early Stopping

思想很簡單,就是使用val資料要進行驗證,如果其準确度已經開始下降時,可以提前的終止訓練,而使用目前認為最後的一組參數,其實這個思想前面的推文也有涉及,其簡單的步驟歸結為:

1)Validation set to select parameters :val資料集驗證來挑選參數

2)Monitor validation perforamce :人工監視來檢視性能

3)Stop at the highest val perf:在最高的準确度時停止實驗

【5】過拟合處理的一些技巧

6.Dropout

Dropout的思想是去除掉一部分是神經元,也就是通過簡化神經網絡結構來避免過拟合的情況

  • Learning less to learn better
  • Each connection has p = (0,1) to lose
    【5】過拟合處理的一些技巧

    其與正則化的思想是有部分類似的,但是正則化的思想是使用2範數迫使參數的複雜度降低|w|->0,既使得參數的總範數接近于0

    而Dropout是想不需要使用全部的參數,可以簡化一部分也就是斷了部分參數之間的輸入輸出練習 ∑w->0,既使得有效的參數越小越好

    Dropout可以是的loss函數變化的比較平和,因為其使得有效的參數量減少了,避免了網絡結構學習了噪聲對最後的結果造成了幹擾

# Dropout的使用方法如下:
net_dropped = torch.nn.Sequential(
    torch.nn.Linear(784,200),
    # 層(784,200)與層(200,200)之間的連線有一定幾率斷掉
    torch.nn.Dropout(0.5),     # drop 50% of the neuron
    torch.nn.ReLU(),
    torch.nn.Linear(200,200),
    # 層(200,200)與層(200,10)之間的連線有一定幾率斷掉
    torch.nn.Dropout(0.5),     # drop 50% of the neuron
    torch.nn.ReLU(),
    torch.nnLinear(200,10),
)
# 注意,此代碼是使用pytorch的接口,如果是tensorflow是參數意義是相反的
# tf.nn.dropout(keep_prob)
# 代碼遷移可能會出現這種情況

# 在訓練的時候會使用Dropout,但是在val樣本測試的時候需要恢複
# 也就是test的時候全部的連接配接都會考慮,不會考慮Dropout的這個行為
for epoch in range(epochs):
    net_dropped.train()
    for batch_idx,(data,target) in enumerate(train_loader):
        ...
        # 關鍵代碼,切換,連接配接全部用上
        net_dropped.eval()
        test_loss = 0
        correct = 0
        for data,target in test_loader:
            ...
# 一定要注意,在val來測試的時候需要加上net_dropped.eval()一句,否則效果會差一點
           

繼續閱讀