深度學習中資料集很小是一種什麼樣的體驗

老潘部落格發表於2021-03-01

前言

今天提一個比較輕鬆的話題,簡單探討資料集大小對深度學習訓練的影響。
不知道大家有沒有看過這篇文章:Don't use deep learning your data isn't that big
QQ20180605-002523@2x

是的,有人對深度學習的侷限性提供了一個證據:那就是當你資料比較少的時候,深度學習的作用相比較於其他傳統的方法並沒有什麼優勢,相反效果還不如傳統的方法。

提出這個說法的作者利用兩種方法進行了測試,測試的資料集是MNIST,測試計算機是否能正確識別0和1,採用的方法分別是:

  • 5層的深度神經網路,活函式是雙曲正切函式;
  • 另一種方法使用的是李加索變數選擇方法,這種方法思想就是挑選10個邊際p值最小的畫素來進行(用這些值做迴歸就可以了);

然後得出一個結論:
unnamed-chunk-7-1

那就是使用李加索方法的表現要優於神經網路。What?

正文

那麼回到正題,上面的說法到底對不對,我們在資料比較小的時候能否正確地進行深度學習的訓練從而達到比較滿意的效果?

我們都知道,神經網路相當於一個無限深的萬能函式,我們輸入變數x然後得到結果y,中間經歷了很多複雜的計算過程。理論上,通過傳統演算法可以解決的問題通過深度學習都可以解決,但是如果神經網路足夠深的時候,雖然這個網路的功能很強大,但是如果資料不夠,很容易達到過擬合的現象,從而達不到我們要求的效果。

那麼資料集過小是否可以通過深度學習來做,我們來測試一下。

一維訊號

我們測試資料很簡單,不是我們平常使用的三通道RGB圖(3 x 256 x 256),而是普通的一通道一維訊號(1 x 168)。

signal

上方是我們的一維訊號,532nm和1064mn分別對應兩種不同的訊號,我們只需要對一種訊號處理器可。訊號的格式是.mat檔案,也就是matlab檔案。

上面的檔案中,train資料集是161 x 168,第一行是x軸的座標我們不用理會只需要y軸的資料,然後每40個資料組是一類也就是 2-41、42-81、82-121、122-161,一共四類。而test資料集是81x168,第一行同樣是x座標我們不管,每20個資料組是一類(和train資料組順序上類別是一樣的)。也就是說我們一共有四類訊號要進行分類。

label分別為:0、1、2、3.

我們的訓練資料量只有160組,而測試資料量也只有80組。

資料讀取

我們採用的深度學習庫是Pytorch,利用的python上的scipy庫,scipy是一個線性函式處理庫,當然我們只是使用它對mat檔案的讀取功能。

建立一個檔案讀取.py,引入以下標頭檔案。

import torch
import torch.utils.data as data
import scipy.io
import os
import os.path as osp

然後我們編寫檔案讀取類.py

# 將原始資料轉化為訓練需要的資料格式
def to_tensor(data):
    data = torch.from_numpy(data).type(torch.float32)
    data = data.unsqueeze(0)
    return data


# 讀取資料類
class LineData(data.Dataset):

    def __init__(self, root, name=532, train=True, transform=to_tensor):
        self.root = os.path.expanduser(root)
        self.name = name
        self.train = train
        self.transform = transform
        self.classes = [0, 1, 2, 3]

        if not osp.exists('datasets'):
            raise FileExistsError('Missing Datasets')

        if self.train:
            self.train_datas = []
            self.train_labels = []

            dataset_dir = osp.join(self.root, 'train_{}nm.mat'.format(self.name))
            train_data = scipy.io.loadmat(dataset_dir)['lineIntensity']
            data_length = len(train_data) - 1              # 161 - 1 = 160

            if self.transform:

                for i in range(data_length):                   # 0 - 159
                    self.train_datas.append(transform(train_data[i+1]))        # i+1 => 1 - 160
                    self.train_labels.append(self.classes[int(i / 40)])
            else:
                raise ValueError('We need tranform function!')

        if not self.train:
            self.test_datas = []
            self.test_labels = []

            dataset_dir = osp.join(self.root, 'test_{}nm.mat'.format(self.name))
            test_data = scipy.io.loadmat(dataset_dir)['lineIntensity']
            data_length = len(test_data) - 1              # 81 - 1 = 80

            if self.transform:

                for i in range(data_length):                   # 0 - 79
                    self.test_datas.append(transform(test_data[i+1]))         # i+1 => 1 - 80
                    self.test_labels.append(self.classes[int(i / 20)])
            else:
                raise ValueError('We need tranform function!')

    def __getitem__(self, index):
        """
        Args:
            index (int): Index

        Returns:
            tuple: (image, target) where target is index of the target class.
        """
        if self.train:
            data, target = self.train_datas[index], self.train_labels[index]
        else:
            data, target = self.test_datas[index], self.test_labels[index]

        return data, target

    def __len__(self):
        if self.train:
            return len(self.train_datas)
        else:
            return len(self.test_datas)

編寫神經網路

寫好檔案讀取程式碼後,我們來設計一下神經網路,因為資料量很少,所以我們的神經網路的層數也應該下降。否則很容易出現過擬合的現象。

我們首先設計5層的神經網路,兩個卷積層,一個池化層,兩個線性層,啟用函式使用Relu:

每個資料的長度為168
模型:兩個個卷積層、兩個線性層
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv1d(1, 10, 3)    # (168 - 3)/1 + 1 = 166 (* 10)
        self.pool = nn.MaxPool1d(2, 2)      # (166 - 2)/2 + 1= 83 (* 10)
        self.conv2 = nn.Conv1d(10, 20, 3)   # (83 - 3)/1 + 1 = 81 (* 20)
        self.fc1 = nn.Linear(81*20, 100)
        self.fc2 = nn.Linear(100, 4)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = F.relu(self.conv2(x))
        x = x.view(-1, 81*20)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

訓練以及測試

設計好神經網路後,我們來進行訓練吧!

首先編寫訓練塊的程式碼,我們使用的優化策略是SGD隨機下降法(帶有動能),預設的學習率設定為0.001,驗證方式採用經典的分類常用的驗證法CrossEntropyLoss

我們首先進行訓練然後進行驗證準確率,準確率就是每次測試這四種訊號正確次數和總次數的比。

# 主程式頁面

import torch
import torch.nn as nn
import torch.utils.data
import torch.optim as optim
from model import Net
from data_utils import LineData

root = 'datasets'    # 資料所在目錄,相對目錄地址
train_name = '532'   # 或者 '1064'

# device = torch.device('cuda:0')

# 讀取檔案類
trainset = LineData(root, name=train_name)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True)

testset = LineData(root, name=train_name, train=False)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=True)

net = Net()
# net = net.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 下面的epoch改成 2 可以達到100%的準確率
epoch_sum = 1
# 訓練
for epoch in range(epoch_sum):

    loss_sum = 0.0
    for i, data in enumerate(trainloader, 0):

        inputs, labels = data
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        loss_sum += loss.item()
        if i % 10 == 9:
            print('[epoch:{} num:{}] loss:{}'.format(epoch, i, loss_sum / 20))
            loss_sum = 0.0

print('Finished Training')

# 驗證
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        inputs, labels = data
        outputs = net(inputs)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 80 test images: %d %%' % (
    100 * correct / total))

pass

好了,開始訓練,由於資料量很少,所以在CPU上訓練即可。

第一次訓練,epoch為1,lr為0.001:

[epoch:0 num:9] loss:1.693671927541118e+16
[epoch:0 num:19] loss:53694975.30745087
[epoch:0 num:29] loss:6.2672371854667905e+28
[epoch:0 num:39] loss:51403236.52956776
Finished Training
Accuracy of the network on the 80 test images: 25 %

結果看起來很不好嘛...準確率25%,看起來是猜的,損失波動很劇烈,應該是我們學習率調的太高了,接下來調整一下學習率。

第二次訓練,epoch為1,lr為0.0001:

[epoch:0 num:9] loss:133432.54784755706
[epoch:0 num:19] loss:67940.00796541572
[epoch:0 num:29] loss:109.18773172795773
[epoch:0 num:39] loss:1.1358043849468231
Finished Training
Accuracy of the network on the 80 test images: 25 %

loss下降很平緩,但是epoch看起來不夠導致loss下降沒有徹底,準確率依然很低,讓我們來調整一下epoch。

第三次訓練,epoch為5,lr為0.0001:

[epoch:0 num:9] loss:3024598166.2773805
[epoch:0 num:19] loss:3117157163.829549
[epoch:0 num:29] loss:258.4028107881546
[epoch:0 num:39] loss:0.6990358293056488
[epoch:1 num:9] loss:0.6830220401287079
[epoch:1 num:19] loss:66.56461009383202
[epoch:1 num:29] loss:0.7117315053939819
[epoch:1 num:39] loss:0.6977931916713714
[epoch:2 num:9] loss:0.6974189281463623
[epoch:2 num:19] loss:0.6898959457874299
[epoch:2 num:29] loss:0.7101178288459777
[epoch:2 num:39] loss:0.6914324820041656
[epoch:3 num:9] loss:0.686737447977066
[epoch:3 num:19] loss:0.6972651600837707
[epoch:3 num:29] loss:0.7028001189231873
[epoch:3 num:39] loss:0.6998239696025849
[epoch:4 num:9] loss:0.6997098863124848
[epoch:4 num:19] loss:0.6969940900802613
[epoch:4 num:29] loss:0.696108078956604
[epoch:4 num:39] loss:0.6910847663879395
Finished Training
Accuracy of the network on the 80 test images: 25 %

loss下降到一定級別沒有再下降,而準確率依然很迷,有可能還是因為學習率過高而導致loss一直卡在一個範圍無法徹底下降,我們再試著嘗試下降一下學習率。

第四次訓練,epoch為2,lr為0.00001:

[epoch:0 num:9] loss:200.58453428081702
[epoch:0 num:19] loss:5.724525341391564
[epoch:0 num:29] loss:0.2976263818090047
[epoch:0 num:39] loss:0.05558242934057489
[epoch:1 num:9] loss:0.0004892532759185996
[epoch:1 num:19] loss:0.00012833428763769916
[epoch:1 num:29] loss:9.479262493137242e-05
[epoch:1 num:39] loss:3.948449189010717e-05
Finished Training
Accuracy of the network on the 80 test images: 100 %

完美,看來我們摸索出了合適的學習率(0.00001),經過10次測試,準確率分別為:100%、100%、100%、100%、100%、100%、100%、100%、100%、98%。

如果我將epoch從2換成1,則是100%、77%、100%、100%、100%、86%、100%、100%、100%、100%。

epoch從1換成3則是:100%、100%、100%、100%、100%、100%、100%、100%、100%、100%。

我們如果修改一下神經網路層為3層全連線層,lr為0.00001效果會很差,即使訓練10個以上的epochy也不會達到100%的準確率,但是如果將lr下降到0.000001,準確率則就會達到100%了:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(168, 1000)
        self.fc2 = nn.Linear(1000, 100)
        self.fc3 = nn.Linear(100,4)

    def forward(self, x):
        x = x.view(-1, 168)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

結論

通過上面的測試,看起來200數量以內的資料集,只要設計層合理,學習率合適,那麼準確率也是可以達到比較好的效果。

其實所說的過擬合常常是因為我們設計的神經網路層數過深,但是資料沒有那麼多,神經網路就會充分“榨乾”那些訓練資料,過度吸收那些訓練集的資訊,導致在測試的時候沒有那麼準確,說以如果資料集過少,可以通過減少層數的方法來減輕錯誤。

但是如果資料包含的資訊很豐富,但是資料量很少,這時候光調整層數就不夠了,我們需要一些資料增強的技術擴充資料集,從而“餵飽”神經網路,不至於讓神經網路出現異常。當然,資料集擴充是針對含資訊量很豐富的資訊來實現的,如果資訊都像我們之前使用的一維訊號一樣,一般就沒有必要擴充了。

撩我吧

  • 如果你與我志同道合於此,老潘很願意與你交流;
  • 如果你喜歡老潘的內容,歡迎關注和支援。
  • 如果你喜歡我的文章,希望點贊? 收藏 ? 評論 ? 三連一下~

想知道老潘是如何學習踩坑的,想與我交流問題~請關注公眾號「oldpan部落格」。
老潘也會整理一些自己的私藏,希望能幫助到大家,點選神祕傳送門獲取。

相關文章