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

前言

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

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

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

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

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

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

正文

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

咱們都知道,神經網絡至關於一個無限深的萬能函數,咱們輸入變量x而後獲得結果y,中間經歷了不少複雜的計算過程。理論上,經過傳統算法能夠解決的問題經過深度學習均可以解決,可是若是神經網絡足夠深的時候,雖然這個網絡的功能很強大,可是若是數據不夠,很容易達到過擬合的現象,從而達不到咱們要求的效果。測試

那麼數據集太小是否能夠經過深度學習來作,咱們來測試一下。優化

一維信號

咱們測試數據很簡單,不是咱們日常使用的三通道RGB圖(3 x 256 x 256),而是普通的一通道一維信號(1 x 168)。設計

signal

上方是咱們的一維信號,532nm和1064mn分別對應兩種不一樣的信號,咱們只須要對一種信號處理器可。信號的格式是.mat文件,也就是matlab文件。3d

上面的文件中,train數據集是161 x 168,第一行是x軸的座標咱們不用理會只須要y軸的數據,而後每40個數據組是一類也就是 2-4一、42-8一、82-12一、122-161,一共四類。而test數據集是81x168,第一行一樣是x座標咱們無論,每20個數據組是一類(和train數據組順序上類別是同樣的)。也就是說咱們一共有四類信號要進行分類。

label分別爲:0、一、二、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博客」。
老潘也會整理一些本身的私藏,但願能幫助到你們,點擊神祕傳送門獲取。

相關文章
相關標籤/搜索