目录
3. 尝试基于MNIST手写数字识别数据集,设计合适的前馈神经网络进行实验,并取得95%以上的准确率。
4.5实践:基于前馈神经网络完成鸢尾花分类
继续使用神经网络与深度学习(四)线性分类中3.3实践中的鸢尾花数据集,并将Softmax分类器替换为前馈神经网络进行分类任务。
- 损失函数:交叉熵损失;
- 优化器:随机梯度下降法;
- 评价指标:准确率。
深入研究鸢尾花数据集
【统计学习方法】感知机对鸢尾花(iris)数据集进行二分类_征途黯然.的博客-CSDN博客
所有属性之间的关系图:
4.5.1 小批量梯度下降法
【批量梯度下降法】在梯度下降法中,目标函数是整个训练集上的风险函数,这种方式称为批量梯度下降法(Batch Gradient Descent,BGD)。 批量梯度下降法在每次迭代时需要计算每个样本上损失函数的梯度并求和。当训练集中的样本数量N很大时,空间复杂度比较高,每次迭代的计算开销也很大。
【小批量梯度下降法】为了减少每次迭代的计算复杂度,我们可以在每次迭代时只采集一小部分样本,计算在这组样本上损失函数的梯度并更新参数,这种优化方式称为小批量梯度下降法(Mini-Batch Gradient Descent,Mini-Batch GD)。
第次迭代时,随机选取一个包含
个样本的子集
,计算这个子集上每个样本损失函数的梯度并进行平均,然后再进行参数更新。
其中为批量大小(Batch Size)。
通常不会设置很大,一般在1∼1001∼100之间。在实际应用中为了提高计算效率,通常设置为2的幂
。
在实际应用中,小批量随机梯度下降法有收敛快、计算开销小的优点,因此逐渐成为大规模的机器学习中的主要优化算法。此外,随机梯度下降相当于在批量梯度下降的梯度上引入了随机噪声。在非凸优化问题中,随机梯度下降更容易逃离局部最优点。
小批量随机梯度下降法的训练过程如下:
4.5.1.1 数据分组
为了使用小批量梯度下降法,我们需要对数据进行随机分组。目前,机器学习中通常做法是构建一个数据迭代器,每个迭代过程中从全部数据集中获取一批指定数量的数据。
(1)首先,将数据集封装为Dataset类,传入一组索引值,根据索引从数据集合中获取数据;
(2)其次,构建DataLoader类,需要指定数据批量的大小和是否需要对数据进行乱序,通过该类即可批量获取数据。
在实践过程中,通常使用进行参数优化。在pytorch中,使用torch.utils.data.DataLoader加载minibatch的数据,torch.utils.data.DataLoader API可以生成一个迭代器,其中通过设置batch_size
参数来指定minibatch的长度,通过设置shuffle参数为True,可以在生成minibatch
的索引列表时将索引顺序打乱。
4.5.2 数据处理
构造IrisDataset类进行数据读取,继承自torch.utils.data.Dataset类。torch.utils.data.Dataset是用来封装 Dataset的方法和行为的抽象类,通过一个索引获取指定的样本,同时对该样本进行数据处理。当继承torch.utils.data.Dataset来定义数据读取类时,实现如下方法:
__getitem__
:根据给定索引获取数据集中指定样本,并对样本进行数据处理;__len__
:返回数据集样本个数。
代码实现如下:
import torch
import numpy as np
import torch.utils.data
from nndl.load_data import load_data
class IrisDataset(torch.utils.data.Dataset):
def __init__(self, mode='train', num_train=120, num_dev=15):
super(IrisDataset, self).__init__()
# 调用第三章中的数据读取函数,其中不需要将标签转成one-hot类型
X, y = load_data(shuffle=True)
if mode == 'train':
self.X, self.y = X[:num_train], y[:num_train]
elif mode == 'dev':
self.X, self.y = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
else:
self.X, self.y = X[num_train + num_dev:], y[num_train + num_dev:]
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
def __len__(self):
return len(self.y)
torch.manual_seed(12)
train_dataset = IrisDataset(mode='train')
dev_dataset = IrisDataset(mode='dev')
test_dataset = IrisDataset(mode='test')
# 打印训练集长度
print("length of train set: ", len(train_dataset))
运行结果:
注:其中load_data代码如下所示:
import torch
import numpy as np
from sklearn.datasets import load_iris
# 加载数据集
def load_data(shuffle=True):
# 加载原始数据
X = np.array(load_iris().data, dtype=np.float32)
y = np.array(load_iris().target, dtype=np.int32)
X = torch.tensor(X)
y = torch.tensor(y)
# 数据归一化
X_min = torch.min(X, dim=0)
X_max = torch.max(X, dim=0)
X = (X - X_min.values) / (X_max.values - X_min.values)
# 如果shuffle为True,随机打乱数据
if shuffle:
idx = torch.randperm(X.shape[0])
X = X[idx]
y = y[idx]
return X, y
4.5.2.2 用DataLoader进行封装
# 批量大小
batch_size = 16
# 加载数据
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = torch.utils.data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size)
4.5.3 模型构建
构建一个简单的前馈神经网络进行鸢尾花分类实验。其中输入层神经元个数为4,输出层神经元个数为3,隐含层神经元个数为6。代码实现如下:
import torch.nn as nn
from torch.nn.init import constant_, normal_, uniform_
# 定义前馈神经网络
class Model_MLP_L2_V3(nn.Module):
def __init__(self, input_size, output_size, hidden_size):
super(Model_MLP_L2_V3, self).__init__()
# 构建第一个全连接层
self.fc1 = nn.Linear(input_size, hidden_size)
normal_(self.fc1.weight, mean=0.0, std=0.01)
constant_(self.fc1.bias, val=1.0)
# 构建第二全连接层
self.fc2 = nn.Linear(hidden_size, output_size)
normal_(self.fc2.weight, mean=0.0, std=0.01)
constant_(self.fc2.bias, val=1.0)
# 定义网络使用的激活函数
self.act = nn.Sigmoid()
def forward(self, inputs):
outputs = self.fc1(inputs)
outputs = self.act(outputs)
outputs = self.fc2(outputs)
return outputs
fnn_model = Model_MLP_L2_V3(input_size=4, output_size=3, hidden_size=6)
4.5.4 完善Runner类
基于RunnerV2类进行完善实现了RunnerV3类。其中训练过程使用自动梯度计算,使用DataLoader
加载批量数据,使用随机梯度下降法进行参数优化;模型保存时,使用state_dict
方法获取模型参数;模型加载时,使用set_state_dict
方法加载模型参数.
由于这里使用随机梯度下降法对参数优化,所以数据以批次的形式输入到模型中进行训练,那么评价指标计算也是分别在每个批次进行的,要想获得每个epoch整体的评价结果,需要对历史评价结果进行累积。这里定义Accuracy
类实现该功能。
import torch
class Accuracy():
def __init__(self, is_logist=True):
"""
输入:
- is_logist: outputs是logist还是激活后的值
"""
# 用于统计正确的样本个数
self.num_correct = 0
# 用于统计样本的总数
self.num_count = 0
self.is_logist = is_logist
def update(self, outputs, labels):
"""
输入:
- outputs: 预测值, shape=[N,class_num]
- labels: 标签值, sh