前言

今天提一个比较轻松的话题,简单探讨数据集大小对深度学习训练的影响。
不知道大家有没有看过这篇文章:Don't use deep learning your data isn't that big
深度学习中数据集很小是一种什么样的体验-LMLPHP

是的,有人对深度学习的局限性提供了一个证据:那就是当你数据比较少的时候,深度学习的作用相比较于其他传统的方法并没有什么优势,相反效果还不如传统的方法。

提出这个说法的作者利用两种方法进行了测试,测试的数据集是MNIST,测试计算机是否能正确识别0和1,采用的方法分别是:

  • 5层的深度神经网络,活函数是双曲正切函数;
  • 另一种方法使用的是李加索变量选择方法,这种方法思想就是挑选10个边际p值最小的像素来进行(用这些值做回归就可以了);

然后得出一个结论:
深度学习中数据集很小是一种什么样的体验-LMLPHP

那就是使用李加索方法的表现要优于神经网络。What?

正文

那么回到正题,上面的说法到底对不对,我们在数据比较小的时候能否正确地进行深度学习的训练从而达到比较满意的效果?

我们都知道,神经网络相当于一个无限深的万能函数,我们输入变量x然后得到结果y,中间经历了很多复杂的计算过程。理论上,通过传统算法可以解决的问题通过深度学习都可以解决,但是如果神经网络足够深的时候,虽然这个网络的功能很强大,但是如果数据不够,很容易达到过拟合的现象,从而达不到我们要求的效果。

那么数据集过小是否可以通过深度学习来做,我们来测试一下。

一维信号

我们测试数据很简单,不是我们平常使用的三通道RGB图(3 x 256 x 256),而是普通的一通道一维信号(1 x 168)。

深度学习中数据集很小是一种什么样的体验-LMLPHP

上方是我们的一维信号,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博客」。
老潘也会整理一些自己的私藏,希望能帮助到大家,点击神秘传送门获取。

03-02 08:42