经过前面的学习,相信大家已经对于前馈神经网络的原理、过程、实现和评估等非常熟悉了,本次我们将使用现实中的一个非常著名的数据集来进行前馈神经网络的应用实验,更加深刻地感受神经网络强大的能力。
鸢尾花数据集可以说是分类界最为著名的数据集之一了。鸢尾花数据集又称费舍尔鸢尾花数据集或安德森的鸢尾花数据集。是英国统计学家和生物学家罗纳德·费舍尔在其 1936 年的论文《在分类问题中使用多重测量作为线性判别分析的一个例子》中使用并著名的多变量 数据集。这三个物种中的两个是在加斯佩半岛采集的, “都来自同一牧场,同一天采摘,由同一个人用同一仪器同时测量”。
该数据集由来自三种鸢尾花(鸢尾花(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginca))的 50 个样本组成。从每个样品中测量了四个特征:萼片(sepal)和花瓣(petal)的长度和宽度,以厘米为单位。基于这四个特征的结合,Fisher 开发了一个线性判别模型来区分物种。费舍尔的论文发表在《优生学年鉴》上,其中讨论了所含技术在颅相学领域的应用。
该数据集中并不直接包含三种花的图片,而是使用前面介绍的四种测量值加上花的种类组成。下图是这三种花:
下图是这三种画的测量指标和分类数据分布:
我们当然可以从网上下载得到Iris数据集并进行读取来使用,但是有一种更方便的方法:直接从库里调(毕竟这个数据集太经典太著名了)。我们这边直接使用sklearn.datasets
中的Iris数据集即可。调用和可视化的代码如下,由于直接作图没法直接画出来四维的内容,我们同样使用该库中的PCA降维方法使得数据能够呈现:
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
iris_raw = load_iris()
xs,ys = iris_raw.data,iris_raw.target
pca = PCA(2)
xs = pca.fit_transform(xs)
plt.scatter(xs[ys==0][:,0],xs[ys==0][:,1],c='#80e0d8',alpha=.8,label='setosa') # 50块钱的颜色
plt.scatter(xs[ys==1][:,0],xs[ys==1][:,1],c='#664f97',alpha=.8,label='versicolor') # 5块钱的颜色
plt.scatter(xs[ys==2][:,0],xs[ys==2][:,1],c='#e3c7c5',alpha=.8,label='virginica') # 5毛钱的颜色
plt.xticks([])
plt.yticks([])
plt.legend()
plt.title('Iris数据集分布')
plt.show()
代码及效果如下:
iris_raw = load_iris()
xs,ys = iris_raw.data,iris_raw.target
plt.scatter(xs[ys==0][:,0],xs[ys==0][:,1],c='#80e0d8',alpha=.8,label='setosa') # 50块钱的颜色
plt.scatter(xs[ys==1][:,0],xs[ys==1][:,1],c='#664f97',alpha=.8,label='versicolor') # 5块钱的颜色
plt.scatter(xs[ys==2][:,0],xs[ys==2][:,1],c='#e3c7c5',alpha=.8,label='virginica') # 5毛钱的颜色
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.legend()
plt.title('Iris数据集前两个特征分布')
plt.show()
由于本次我们将使用Softmax进行分类,因此有几个类别就会有几个输出神经元,于是原来的种类编码需要转化为独热编码(one-hot)并将输入和输出均变成tensor然后进行数据集划分等操作。torch自带转化函数如下:
数据转化和分割代码如下:
import torch
from torch.nn.functional import one_hot
xs,ys = iris_raw.data,iris_raw.target
xs, ys = torch.tensor(xs,dtype=torch.long), torch.tensor(ys,dtype=torch.long)
ys = one_hot(ys)
dataset = DatasetGenerator()
dataset.copy_from(torch.hstack([xs,ys]))
dataset.shuffle()
dataset_train, dataset_test = dataset.train_test_split()
dataset_eval,dataset_test = dataset_test[:-10],dataset_test[-10:]
Iris数据集由于量不大,训练难度并不会太高,然而当今的很多数据集(如coco2017)都有非常大量的数据。如果还像一开始将所有数据计算后训练进行一次参数更新,计算的复杂度将会非常高。为了减少计算量、达到更加快速收敛的目的,我们可以在每次迭代时只取一部分样本进行计算,基于这一部分样本的损失进行优化,这样的优化方式称为小批量梯度下降。
那么如何在torch中实现这一功能呢?torch中内置一个名为Dataloader的数据集迭代器,通过其能够进行批量数据的构建,本题中我们取批量数为16,代码如下:
from torch.utils.data import DataLoader
dataloader_train = DataLoader(dataset_train,16)
for item in dataloader_train:
print(item)
观察输出能够发现,Dataloader将原始数据集分成16个为一批的结果。
同时在使用中,我们还发现有个DataLoader2,这个还是实验性功能,不要用哦:
构建完成后我们需要在训练过程中微调训练的结构,具体的内容将在本文后面部分提及。
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.inp = torch.nn.Linear(4,6)
self.ac1 = torch.nn.Sigmoid()
self.lay = torch.nn.Linear(6,3)
self.ac2 = torch.nn.Softmax(1)
def forward(self, x):
return self.ac2(self.lay(self.ac1(self.inp(x))))
crit = torch.nn.CrossEntropyLoss
opti = torch.optim.SGD
def acc(test_x, y, y_pred, loss): # 统一接口
return (1.0 / y.shape[0]) * torch.sum(torch.argmax(y)==torch.argmax(y_pred))
runner = Runner(Model,crit,opti,lr=0.1)
由于增加了minibatch,因此我们需要修改模型中对于训练部分的代码,其他部分保持不变:
def train(self, dataset_train, split_x_y_pos, epochs, dataset_eval, epochs_display):
if self.imodel is None:
self.imodel = eval('self.model({})'.format(','.join(self.kwargs.get('model_init_params', []))))
icriterion = self.criterion()
ioptimizer = self.optimizer(
self.imodel.parameters(),
self.kwargs.get('lr',0.01),
self.kwargs.get('momentum',0)
)
accum_loss = []
for i in range(1, epochs + 1):
for item in dataset_train:
train_x, train_y = item[:,:split_x_y_pos],item[:,split_x_y_pos:]
out = self.imodel(train_x)
loss = icriterion(out, train_y)
ioptimizer.zero_grad()
loss.backward()
ioptimizer.step()
accum_loss.append(loss.data.item())
if i % epochs_display == 0:
print('[{} / {}] loss = {}'.format(i,epochs,accum_loss[-1]), end = ' ')
self.eval(dataset_eval,split_x_y_pos)
return accum_loss
我们使用评价测试集进行测试:
runner.eval(dataset_test,-3)
得到结果:
由于数据过少,我们这边预测使用所有数据,并给出正确与否的可视化:
y_pred = runner.predict(dataset.collections[:,:-3])
runner.test(dataset.collections,-3,acc,'acc')
y_pred = torch.argmax(y_pred,axis=1)
y = torch.argmax(dataset.collections[:,-3:],axis=1)
plt.scatter(dataset.collections[torch.where(y==y_pred)[0],0],dataset.collections[torch.where(y==y_pred)[0],1],c='#1d998f',alpha=.8,label='true')
plt.scatter(dataset.collections[torch.where(y!=y_pred)[0],0],dataset.collections[torch.where(y!=y_pred)[0],1],c='#cc0000',alpha=.8,label='false')
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.legend()
plt.title('Iris数据集预测结果')
plt.show()
我们通过修改模型,仅仅将最后一层的softmax改为sigmoid,其他参数均不变,得到最终的结果:
模型预测时准确率也有显著下滑:
可见显然Softmax效果更好。为什么呢?通过Softmax函数就可以将多分类的输出值转换为范围在[0, 1]和为1的概率分布,因此比简单的前馈更加准确。Softmax训练的深度特征,会把整个超空间或者超球,按照分类个数进行划分,保证类别是可分的。
MNIST 手写数字数据库具有 60,000 个示例的训练集和 10,000 个示例的测试集。 它是 NIST 提供的更大集合的子集。 数字已经过尺寸标准化,并在固定尺寸的图像中居中。对于想要在现实世界数据上尝试学习技术和模式识别方法,同时在预处理和格式化方面花费最少的人来说,它是一个很好的数据库。 ——MNIST 官网介绍
和前面介绍的Iris数据集不同的是,这里面的手写数字都是一张张28×28的灰度图,同时每个图片都有其对应的数字编号:
尽管该数据集更适合在下一张的卷积神经网络中进行使用和实验,但是由于MNIST中图片并不是很大,所以我们同样可以尝试使用前馈神经网络,将每个像素全连接后继续进行前馈神经网络的实验。构建的模型代码及效果如下:
# 数据集创建和处理
dataset = mnist.MNIST(
'./mnist',
download=False if os.path.exists('./mnist/MNIST') else True
)
xs = dataset.data.float().view(-1,784)
ys = one_hot(dataset.targets).float()
dataset = DatasetGenerator()
dataset.copy_from(torch.hstack([xs,ys]))
dataset.shuffle()
dataset_train,dataset_test = dataset.train_test_split()
dataloader = DataLoader(dataset_train,32,True)
# 模型构建
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.hidden = nn.Linear(784,1176) # 28 * 28 = 784
self.activ1 = nn.Sigmoid()
self.output = nn.Linear(1176,10)
self.activ2 = nn.Softmax()
def forward(self,x):
return self.activ2(self.output(self.activ1(self.hidden(x))))
# 损失函数、优化器、正确计算、runner
crit = torch.nn.CrossEntropyLoss
opti = torch.optim.SGD
def acc(test_x, y, y_pred, loss): # 统一接口
return (1.0 / y.shape[0]) * torch.sum(torch.argmax(y,axis=1)==torch.argmax(y_pred,axis=1))
runner = Runner(Model,crit,opti,lr=0.1)
runner.train(dataloader,-10,10,None,1)
runner.test(dataset_test,-10,acc,'acc')
由于我可怜的商务本实在是运行不动这个非常非常慢的网络,这边只训练10代并输出结果。
没想到准确率还不错,结果如下:
准确率居然有90%多!分析下来应该是由于数据集各种图片非常非常多,加上小批量梯度下降,让模型的准确度变得很高。
已经做了好几周的前馈神经网络了,这个网络包含了很多入门的概念和以后都将会用到的很多技术(神经元、损失函数、激活函数等等)。
最后,作为总结,思维导图奉上: