首页 分享 基于前馈神经网络完成鸢尾花分类

基于前馈神经网络完成鸢尾花分类

来源:花匠小妙招 时间:2024-12-18 02:16

目录

1 小批量梯度下降法

        1.0 展开聊一聊~

        1.1 数据分组

        1.2 用DataLoader进行封装

        1.3 模型构建

        1.4 完善Runner类

        1.5 模型训练

        1.6 模型评价 

        1.7 模型预测        

思考

总结

参考文献

首先基础知识铺垫~       

继续使用第三章中的鸢尾花分类任务,将Softmax分类器替换为前馈神经网络

损失函数:交叉熵损失;优化器:随机梯度下降法;评价指标:准确率;

1 小批量梯度下降法

        1.0 展开聊一聊~

        在梯度下降法中,目标函数是整个训练集上的风险函数,这种方式称为批量梯度下降法(Batch Gradient Descent,BGD)。批量梯度下降法在每次迭代时需要计算每个样本上损失函数的梯度并求和。为了减少每次迭代的计算复杂度,我们可以在每次迭代时只采集一小部分样本,计算在这组样本上损失函数的梯度并更新参数,这种优化方式称为小批量梯度下降法(Mini-Batch Gradient Descent,Mini-Batch GD)。

        梯度下降算法一般情况下主要说的是三种,批量梯度下降随机梯度下降小批量梯度下降。

        知道大家跟我一样不爱看一大段一大段的定义,巴拉巴拉一堆的,所以我大概总结了一下,快拿出你们的小本本!!!

首先,要明确梯度下降算法都是优化算法,用于求解目标函数的最优参数:

批量梯度下降(Batch Gradient Descent,BGD)随机梯度下降(Stochastic Gradient Descent,SGD)小批量梯度下降(Mini-Batch Gradient Descent,MBGD)

        批量梯度下降(BGD):最早出现的梯度下降方法是批量梯度下降。BGD在每一次迭代中使用所有训练样本来计算梯度,并更新模型参数,步骤如下:

对于每个训练样本,计算梯度。将所有梯度求平均,得到一个全局梯度。根据学习率和全局梯度更新模型参数。

优点:

收敛性较好,能够达到全局最优(目标函数是凸函数)。梯度计算相对准确,参数更新稳定。收敛速度最快,可以保证每一步都是准确地向着极值点的方向趋近,所需要的迭代次数最少。

缺点:

计算梯度时需要处理大量数据,计算开销较大。参数更新只能在整个训练集上进行,但大规模数据集通常会有大量冗余数据,所以不适用于大规模数据集。容易陷入局部最优(目标函数是非凸函数)。

        这里需要提一下~对所有样本的计算,可以利用向量运算进行并行计算来提升运算速度。

        随机梯度下降(SGD):为了解决批量梯度下降在处理大规模数据集时的计算开销问题,随机梯度下降方法被提出。SGD在每一次迭代中仅使用一个训练样本来计算梯度,并更新模型参数。具体步骤如下:

随机选择一个训练样本。计算该样本的梯度。根据学习率和该样本的梯度更新模型参数。

优点:

计算开销较小,适用于大规模数据集。参数更新频繁,可能更容易逃离局部最优。

缺点:

每次迭代只使用一个样本,但是单个样本计算出的梯度不能够很好的体现全体样本的梯度。参数更新的方向较不稳定,可能会产生参数震荡。参数更新非常的频繁,在最优点附近晃来晃去,收敛速度大大降低。

小批量梯度下降(MBGD):为了兼顾批量梯度下降和随机梯度下降的优点,小批量梯度下降方法被引入。小批量梯度下降算法又被叫做小批量随机梯度下降算法。MBGD在每一次迭代中使用一小部分训练样本(通常称为mini-batch)来计算梯度,并更新模型参数。具体步骤如下:

随机选择一小部分训练样本(mini-batch)。计算这些样本的梯度。根据学习率和这些样本的梯度更新模型参数。

优点:

兼具BGD和SGD的优点,计算开销适中,参数更新相对稳定。可以利用矩阵运算的高效性,提高计算效率。较容易并行化处理,适用于大规模数据集。

缺点:

学习率选择较为敏感,需要进行合适的调参。

        总结:三种梯度下降方法各有优劣。批量梯度下降收敛性好,但计算开销大;随机梯度下降计算开销小,但更新不稳定;小批量梯度下降在两者之间取得平衡,并且对于大规模数据集有较好的适应性。在实际应用中,根据具体问题的规模和特点选择合适的梯度下降方法。

        1.1 数据分组

        为了小批量梯度下降法,我们需要对数据进行随机分组。目前,机器学习中通常做法是构建一个数据迭代器,每个迭代过程中从全部数据集中获取一批指定数量的数据。原理图展示一下:

        首先,将数据集封装为Dataset类,传入一组索引值,根据索引从数据集合中获取数据;

        其次,构建DataLoader类,需要指定数据批量的大小和是否需要对数据进行乱序,通过该类即可批量获取数据。

import torch.utils.data as Data

class IrisDataset(Data.Dataset):

def __init__(self, mode='train', num_train=120, num_dev=15):

super(IrisDataset, self).__init__()

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)

__getitem__:根据给定索引获取数据集中指定样本,并对样本进行数据处理;

__len__:返回数据集样本个数。

train_dataset = IrisDataset(mode='train')

dev_dataset = IrisDataset(mode='dev')

test_dataset = IrisDataset(mode='test')

print("length of train set: ", len(train_dataset))

 

        1.2 用DataLoader进行封装

batch_size = 16

train_loader = Data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

dev_loader = Data.DataLoader(dev_dataset, batch_size=batch_size)

test_loader = Data.DataLoader(test_dataset, batch_size=batch_size)

        1.3 模型构建

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)

self.fc2 = nn.Linear(hidden_size, output_size)

self.act = nn.Sigmoid()

nn.init.normal_(self.fc1.weight, mean=0., std=0.01)

nn.init.constant_(self.fc1.bias, 1.0)

nn.init.normal_(self.fc2.weight, mean=0., std=0.01)

nn.init.constant_(self.fc2.bias, 1.0)

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)

        1.4 完善Runner类

class Accuracy(object):

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: 标签值, shape=[N,1]

"""

if outputs.shape[1] == 1:

outputs = torch.squeeze(outputs, axis=-1)

if self.is_logist:

preds = (outputs >= 0).to(torch.float32)

else:

preds = (outputs >= 0.5).to(torch.float32)

else:

preds = torch.argmax(outputs, dim=1).int()

labels = torch.squeeze(labels, dim=-1)

batch_correct = float((preds == labels).sum())

batch_count = len(labels)

self.num_correct += batch_correct

self.num_count += batch_count

def accumulate(self):

if self.num_count == 0:

return 0

return self.num_correct / self.num_count

def reset(self):

self.num_correct = 0

self.num_count = 0

def name(self):

return "Accuracy"

        完善RunnerV3类

import torch

class RunnerV3(object):

def __init__(self, model, optimizer, loss_fn, metric, **kwargs):

self.model = model

self.optimizer = optimizer

self.loss_fn = loss_fn

self.metric = metric

self.dev_scores = []

self.train_epoch_losses = []

self.train_step_losses = []

self.dev_losses = []

self.best_score = 0

def train(self, train_loader, dev_loader=None, **kwargs):

self.model.train()

num_epochs = kwargs.get("num_epochs", 0)

log_steps = kwargs.get("log_steps", 100)

eval_steps = kwargs.get("eval_steps", 0)

save_path = kwargs.get("save_path", "best_model.pdparams")

custom_print_log = kwargs.get("custom_print_log", None)

num_training_steps = num_epochs * len(train_loader)

if eval_steps:

if self.metric is None:

raise RuntimeError('Error: Metric can not be None!')

if dev_loader is None:

raise RuntimeError('Error: dev_loader can not be None!')

global_step = 0

for epoch in range(num_epochs):

total_loss = 0

for step, data in enumerate(train_loader):

X, y = data

logits = self.model(X)

loss = self.loss_fn(logits, y)

total_loss += loss

self.train_step_losses.append((global_step, loss.item()))

if log_steps and global_step % log_steps == 0:

print(

f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")

loss.backward()

if custom_print_log:

custom_print_log(self)

self.optimizer.step()

self.optimizer.zero_grad()

if eval_steps > 0 and global_step > 0 and

(global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):

dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)

print(f"[Evaluate] dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")

self.model.train()

if dev_score > self.best_score:

self.save_model(save_path)

print(

f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")

self.best_score = dev_score

global_step += 1

trn_loss = (total_loss / len(train_loader)).item()

self.train_epoch_losses.append(trn_loss)

print("[Train] Training done!")

@torch.no_grad()

def evaluate(self, dev_loader, **kwargs):

assert self.metric is not None

self.model.eval()

global_step = kwargs.get("global_step", -1)

total_loss = 0

self.metric.reset()

for batch_id, data in enumerate(dev_loader):

X, y = data

logits = self.model(X)

loss = self.loss_fn(logits, y).item()

total_loss += loss

self.metric.update(logits, y)

dev_loss = (total_loss / len(dev_loader))

dev_score = self.metric.accumulate()

if global_step != -1:

self.dev_losses.append((global_step, dev_loss))

self.dev_scores.append(dev_score)

return dev_score, dev_loss

@torch.no_grad()

def predict(self, x, **kwargs):

self.model.eval()

logits = self.model(x)

return logits

def save_model(self, save_path):

torch.save(self.model.state_dict(), save_path)

def load_model(self, model_path):

model_state_dict = torch.load(model_path)

self.model.load_state_dict(model_state_dict)

        注意torch环境中没有clear_grad方法,这儿需要调用zero_grad方法。 

        1.5 模型训练

        这里把RunnerV3放入runner.py存储在nndl文件夹中

import torch.optim as opt

from nndl.runner import RunnerV3

import torch.nn.functional as F

lr = 0.2

model = fnn_model

optimizer = opt.SGD(model.parameters(), lr=lr)

loss_fn = F.cross_entropy

metric = Accuracy(is_logist=True)

runner = RunnerV3(model, optimizer, loss_fn, metric)

        使用训练集和验证集进行模型训练,共训练150个epoch。在实验中,保存准确率最高的模型作为最佳模型。

log_steps = 100

eval_steps = 50

runner.train(train_loader, dev_loader,

num_epochs=150, log_steps=log_steps, eval_steps=eval_steps,

save_path="best_model.pdparams")

        可视化观察训练集损失和训练集loss变化情况,代码如下:

import matplotlib.pyplot as plt

def plot_training_loss_acc(runner, fig_name,

fig_size=(16, 6),

sample_step=20,

loss_legend_loc="upper right",

acc_legend_loc="lower right",

train_color="#e4007f",

dev_color='#f19ec2',

fontsize='large',

train_linestyle="-",

dev_linestyle='--'):

global dev_steps

plt.figure(figsize=fig_size)

plt.subplot(1, 2, 1)

train_items = runner.train_step_losses[::sample_step]

train_steps = [x[0] for x in train_items]

train_losses = [x[1] for x in train_items]

plt.plot(train_steps, train_losses, color=train_color, linestyle=train_linestyle, label="Train loss")

if len(runner.dev_losses) > 0:

dev_steps = [x[0] for x in runner.dev_losses]

dev_losses = [x[1] for x in runner.dev_losses]

plt.plot(dev_steps, dev_losses, color=dev_color, linestyle=dev_linestyle, label="Dev loss")

plt.ylabel("loss", fontsize=fontsize)

plt.xlabel("step", fontsize=fontsize)

plt.legend(loc=loss_legend_loc, fontsize='x-large')

if len(runner.dev_scores) > 0:

plt.subplot(1, 2, 2)

plt.plot(dev_steps, runner.dev_scores,

color=dev_color, linestyle=dev_linestyle, label="Dev accuracy")

plt.ylabel("score", fontsize=fontsize)

plt.xlabel("step", fontsize=fontsize)

plt.legend(loc=acc_legend_loc, fontsize='x-large')

plt.savefig(fig_name)

plt.show()

plot_training_loss_acc(runner, 'fw-loss.pdf')

 

        1.6 模型评价 

        使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及Loss情况,代码如下:

runner.load_model('best_model.pdparams')

score, loss = runner.evaluate(test_loader)

print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

        1.7 模型预测        

        同样地,也可以使用保存好的模型,对测试集中的某一个数据进行模型预测,观察模型效果。代码如下

X, label = train_dataset[0]

logits = runner.predict(X)

pred_class = torch.argmax(logits[0]).numpy()

label = label.numpy()

print("The true category is {} and the predicted category is {}".format(label, pred_class))

思考

softmax、svm、前馈神经网络三种进行比较,svm代码如下:

from sklearn.svm import SVC

from sklearn.model_selection import GridSearchCV

from sklearn.metrics import accuracy_score

def SVC_split(x_train, y_train, x_test, y_test):

svm = SVC()

param_grid = {

'C': [0.01, 0.1, 1, 10],

'kernel': ['linear', 'rbf', 'poly'],

'gamma': ['scale', 'auto']

}

grid_search = GridSearchCV(svm, param_grid=param_grid, cv=5)

grid_search.fit(x_train, y_train.ravel())

y_pred = grid_search.predict(x_test)

accuracy = accuracy_score(y_test, y_pred)

print("Accuracy on test set: {:.2f}%".format(accuracy * 100))

SVC_split(train_dataset.X,train_dataset.y ,test_dataset.X, test_dataset.y)

调用实验四的softmax函数的结果如下:

        很明显我们发现softmax回归的准确率远远低于前馈神经网络和svm,简单总结一下都有什么原因吧:

               softmax回归是一个线性模型,其只能学习到线性关系。对于复杂的非线性分类问题,Softmax回归的表达能力可能不足以捕捉到数据中的更复杂模式。相比之下,前馈神经网络具有更强大的非线性建模能力,可以通过多个隐藏层和非线性激活函数来学习到更复杂的特征表示。SVM也可以通过核函数将低维输入映射到高维空间,从而进行非线性分类。

        由此得出结论,但需要注意的是,Softmax回归在所有二分类任务上表现都会远远低于前馈神经网络和SVM。对于一些简单的线性可分问题或数据分布较为简单的情况下,Softmax回归可能表现得很好。然而,对于更复杂的问题和数据集,使用更复杂的模型如前馈神经网络和SVM通常能够获得更好的性能。

        但是有个疑问前馈神经网络和SVM究竟哪个更好一点?

        简而言之,神经网络是个“黑匣子”,优化目标是基于经验风险最小化,易陷入局部最优,训练结果不太稳定,一般需要大样本;

        而支持向量机有严格的理论和数学基础,基于结构风险最小化原则, 泛化能力优于前者,算法具有全局最优性, 是针对小样本统计的理论。

        就目前我的理论知识,好像想搞明白哪个好哪个坏可能有点难,而且模型好像很难说哪个好哪个坏,可能针对不同的数据集表现也会不一样,害,浅浅插个眼,等之后,对深度学习有了一定程度的了解的时候再回来,看看有没有一个答案吧。

总结

        到此为止前馈神经网络结束啦,有了一点搭建神经网络的经验了吧,大概流程如下:

定义网络结构:首先需要确定网络的结构,包括输入层、隐藏层和输出层的大小和数量。

初始化参数:对于每个神经元,需要初始化权重和偏置值。权重和偏置值通常是随机初始化的,以避免初始状态过于相似导致模型收敛缓慢。

定义损失函数:损失函数用来衡量预测值和真实值之间的误差。二分类问题通常使用交叉熵损失函数,回归问题可以使用均方误差损失函数。

定义优化器:优化器用于更新模型的参数,使损失函数最小化。常见的优化器包括随机梯度下降 (SGD)、Adam等。

训练模型:通过传递数据进行前向传播和反向传播,更新模型的参数。在训练过程中,需要将数据分为训练集、验证集和测试集。

模型评价:在训练完成之后,可以通过输入新数据并进行前向传播来得到预测结果。也可以进行可视化等等。

        本次实验对前馈神经网络的应用有更为明确的理解,同时针对softmax,svm,CNN的对比,在实验和搜索资料的过程中,也明白了什么模型更适合应用在什么范围内,softmax和CNN的具体的应用区别还没弄明白算是个小小的遗憾吧,等有更多深度学习经验的时候看看能不能再回来解答这个问题吧~

参考文献

torch.nn.Module所有方法总结及其使用举例_torch.nn.module cuda-CSDN博客
torch.nn — PyTorch master documentation

NNDL 实验五 前馈神经网络(3)鸢尾花分类-CSDN博客
神经网络 VS SVM_svm和神经网络的区别-CSDN博客

相关知识

神经网络与深度学习(五)前馈神经网络(3)鸢尾花分类
BP神经网络鸢尾花红酒数据分类分析与源码实现
基于BP神经网络对鸢尾花的分类的研究
搭建简单的神经网络——使用pytorch实现鸢尾花的分类
Python 基于BP神经网络的鸢尾花分类
卷积神经网络实现鸢尾花数据分类python代码实现
基于BP神经网络算法鸢尾花数据集的分类
第3章(下)基于Softmax回归完成鸢尾花分类任务
基于鸢尾花数据集实施自组织神经网络聚类分析
鸢尾花分类——神经网络详解

网址: 基于前馈神经网络完成鸢尾花分类 https://www.huajiangbk.com/newsview1159293.html

所属分类:花卉
上一篇: Game Framework 教
下一篇: 计算机网络自学指南,简直太全了!

推荐分享