共计 7514 个字符,预计需要花费 19 分钟才能阅读完成。
前言
明天提一个比拟轻松的话题,简略探讨数据集大小对深度学习训练的影响。
不晓得大家有没有看过这篇文章:Don’t use deep learning your data isn’t that big
是的,有人对深度学习的局限性提供了一个证据:那就是当你数据比拟少的时候,深度学习的作用相比拟于其余传统的办法并没有什么劣势,相同成果还不如传统的办法。
提出这个说法的作者利用两种办法进行了测试,测试的数据集是 MNIST,测试计算机是否能正确辨认 0 和 1,采纳的办法别离是:
- 5 层的深度神经网络,活函数是双曲正切函数;
- 另一种办法应用的是李加索变量抉择办法,这种办法思维就是筛选 10 个边际 p 值最小的像素来进行(用这些值做回归就能够了);
而后得出一个论断:
那就是应用李加索办法的体现要优于神经网络。What?
注释
那么回到正题,下面的说法到底对不对,咱们在数据比拟小的时候是否正确地进行深度学习的训练从而达到比较满意的成果?
咱们都晓得,神经网络相当于一个有限深的万能函数,咱们输出变量 x 而后失去后果y,两头经验了很多简单的计算过程。实践上,通过传统算法能够解决的问题通过深度学习都能够解决,然而如果神经网络足够深的时候,尽管这个网络的性能很弱小,然而如果数据不够,很容易达到过拟合的景象,从而达不到咱们要求的成果。
那么数据集过小是否能够通过深度学习来做,咱们来测试一下。
一维信号
咱们测试数据很简略,不是咱们平时应用的三通道 RGB 图(3 x 256 x 256),而是一般的一通道一维信号(1 x 168)。
上方是咱们的一维信号,532nm 和 1064mn 别离对应两种不同的信号,咱们只须要对一种信号处理器可。信号的格局是.mat 文件,也就是 matlab 文件。
下面的文件中,train 数据集是 161 x 168,第一行是 x 轴的坐标咱们不必理睬只须要 y 轴的数据,而后每 40 个数据组是一类也就是 2-41、42-81、82-121、122-161,一共四类。而 test 数据集是 81×168,第一行同样是 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 博客」。
老潘也会整顿一些本人的私藏,心愿能帮忙到大家,点击神秘传送门获取。