机器学习:神经网络构建(下)

news/2024/12/4 1:35:30/文章来源:https://www.cnblogs.com/SXWisON/p/18585149

简介

在上一篇文章《机器学习:神经网络构建(上)》中讨论了线性层、激活函数以及损失函数层的构建方式,本节中将进一步讨论网络构建方式,并完整的搭建一个简单的分类器网络。

目录

  1. 网络Network
  2. 数据集管理器 DatasetManager
  3. 优化器 Optimizer
  4. 代码测试

网络Network

网络定义


在设计神经网络时,其基本结构是由一层层的神经元组成的,这些层可以是输入层、隐藏层和输出层。为了实现这一结构,通常会使用向量(vector)容器来存储这些层,因为层的数量是可变的,可能根据具体任务的需求而变化。

即使在网络已经进行了预训练并具有一定的参数的情况下,对于特定的任务,通常还是需要进行模型微调。这是因为不同的任务可能有不同的数据分布和要求,因此训练是构建高性能神经网络模型的重要步骤。

在训练过程中,有三个关键组件:

  1. 损失函数:神经网络的学习目标,通过最小化损失函数来优化模型参数。选择合适的损失函数对于确保模型能够学习到有效的特征表示至关重要。

  2. 优化器:优化器负责调整模型的参数以最小化损失函数。除了基本的参数更新功能外,优化器还可以提供更高级的功能,如学习率调整和参数冻结,这些功能有助于提高训练效率和模型性能。

  3. 数据集管理器:负责在训练过程中有效地管理和提供数据,包括数据的加载、预处理和批处理,以确保数据被充分利用。

对于网络的外部接口(公有方法),主要有以下几类:

  1. 网络设置:添加网络层、设置损失函数、优化器和数据集等操作,用于配置网络的结构和训练参数。
  2. 网络推理:前向传播和反向传播方法,用于在训练和测试过程中进行预测和参数更新。
  3. 网络训练:使用配置好的数据集和训练方法,执行指定次数的训练迭代,以优化网络参数。

以下是代码示例:

class Network {
private:vector<shared_ptr<Layer>> layers;shared_ptr<LossFunction> lossFunction;shared_ptr<Optimizer> optimizer;shared_ptr<DatasetManager> datasetManager;public:void addLayer(shared_ptr<Layer> layer);void setLossFunction(shared_ptr<LossFunction> lossFunc);void setOptimizer(shared_ptr<Optimizer> opt);void setDatasetManager(shared_ptr<DatasetManager> manager);MatrixXd forward(const MatrixXd& input);void backward(const MatrixXd& outputGrad);double train(size_t epochs, size_t batchSize);
};

使用shared_ptr的好处:
存储方式vector<shared_ptr>和vector相比,如果直接存储 Layer 对象,需要手动管理内存,包括分配和释放内存,这不仅容易出错,还可能导致内存泄漏或悬挂指针的问题。而使用 std::shared_ptr 可以大大简化内存管理,提高代码的健壮性和可维护性。

网络训练


网络的训练函数通常包含两个输入参数,训练的集数和批尺寸:

  • 集数epochs:指训练集被完整的迭代的次数。在每一个epoch中,网络会使用训练集中的所有样本进行参数更新。

  • 批尺寸batchSize:指在一次迭代中用于更新模型参数的样本数量。在每次迭代中,模型会计算这些样本的总梯度,并据此调整模型的参数。

因此,网络的训练函数由两层循环结构组成,外层循环结构表示完整迭代的次数,直至完成所有迭代时停止。内层循环表示训练集中样本被网络调取的进度,直至训练集中的所有数据被调用时停止。

网络的训练过程是由多次的参数迭代(更新)完成的。而参数的的迭代是以批(Batch)为单位的。具体来说,一次迭代包含如下步骤:

  1. 获取数据:从数据集管理器中获取一批的数据(包含输入和输出)
  2. 前向传播:采用网络对数据进行推理,得到预测结果,依据预测结果评估损失。
  3. 反向传播:计算损失函数关于各层参数的梯度。
  4. 参数更新:依据损失、梯度等信息,更新各层梯度。
  5. 日志更新:计算并输出每个epoch的累积误差。

代码设计如下:

double Network::train(size_t epochs, size_t batchSize) {double totalLoss = 0.0;size_t sampleCount = datasetManager->getTrainSampleCount();for (size_t epoch = 0; epoch < epochs; ++epoch) {datasetManager->shuffleTrainSet();totalLoss = 0.0;for (size_t i = 0; i < sampleCount; i += batchSize) {// 获取一个小批量样本auto batch = datasetManager->getTrainBatch(batchSize, i / batchSize);MatrixXd batchInput = batch.first;MatrixXd batchLabel = batch.second;// 前向传播MatrixXd predicted = forward(batchInput);double loss = lossFunction->computeLoss(predicted, batchLabel);// 反向传播MatrixXd outputGrad = lossFunction->computeGradient(predicted, batchLabel);backward(outputGrad);// 参数更新optimizer->update(layers);// 累计损失totalLoss += loss;}totalLoss /= datasetManager->getTrainSampleCount();// 输出每个epoch的损失等信息std::cout << "Epoch " << epoch << ", totalLoss = " << totalLoss << "\n";}return totalLoss / (epochs * (sampleCount / batchSize)); // 返回平均损失(简化示例)
}

网络的其它公有方法


下面的代码给出了网络的其它公有方法的代码实现:

void Network::addLayer(std::shared_ptr<Layer> layer) {layers.push_back(layer);
}void Network::setLossFunction(std::shared_ptr<LossFunction> lossFunc) {lossFunction = lossFunc;
}void Network::setOptimizer(std::shared_ptr<Optimizer> opt) {optimizer = opt;
}void Network::setDatasetManager(std::shared_ptr<DatasetManager> manager) {datasetManager = manager;
}MatrixXd Network::forward(const MatrixXd& input) {MatrixXd currentInput = input;for (const auto& layer : layers) {currentInput = layer->forward(currentInput);}return currentInput;
}void Network::backward(const MatrixXd& outputGrad) {MatrixXd currentGrad = outputGrad;for (auto it = layers.rbegin(); it != layers.rend(); ++it) {currentGrad = (*it)->backward(currentGrad);}
}

forward方法除了作为训练时的步骤之一,还经常用于网络推理(预测),因此声明为公有方法

backward方法只在训练时使用,在正常的使用用途中,不会被外部调用,因此,其可以声明为私有方法。

数据集管理器 DatasetManager


数据集管理器本质目的是提高网络对数据的利用率,其主要职能有:

  1. 保存数据:提供更为安全可靠的数据管理。
  2. 数据打乱:以避免顺序偏差,同时提升模型的泛化能力。
  3. 数据集划分:讲数据划分为训练集、验证集和测试集。
  4. 数据接口:使得外部可以轻松的获取批量数据。
    class DatasetManager {private:MatrixXd input;MatrixXd label;std::vector<int> trainIndices;std::vector<int> valIndices;std::vector<int> testIndices;public:// 设置数据集的方法void setDataset(const MatrixXd& inputData, const MatrixXd& labelData);// 划分数据集为训练集、验证集和测试集void splitDataset(double trainRatio = 0.8, double valRatio = 0.1, double testRatio = 0.1);// 获取训练集、验证集和测试集的小批量数据std::pair<MatrixXd, MatrixXd> getBatch(std::vector<int>& indices, size_t batchSize, size_t offset = 0);// 随机打乱训练集void shuffleTrainSet();// 获取批量数据std::pair<MatrixXd, MatrixXd> getTrainBatch(size_t batchSize, size_t offset = 0);std::pair<MatrixXd, MatrixXd> getValidationBatch(size_t batchSize, size_t offset = 0);std::pair<MatrixXd, MatrixXd> getTestBatch(size_t batchSize, size_t offset = 0);// 获取样本数量的方法size_t getSampleCount() const;size_t getTrainSampleCount() const;size_t getValidationSampleCount() const;size_t getTestSampleCount() const;};

数据集初始化


数据集初始化分为三步:数据集设置、数据集划分、数据集打乱。

// 设置数据集
void  ML::DatasetManager::setDataset(const MatrixXd& inputData, const MatrixXd& labelData) {input = inputData;label = labelData;trainIndices.resize(input.rows());std::iota(trainIndices.begin(), trainIndices.end(), 0);valIndices.clear();testIndices.clear();
}// 打乱训练集
void ML::DatasetManager::shuffleTrainSet() {std::shuffle(trainIndices.begin(), trainIndices.end(), std::mt19937{ std::random_device{}() });
}// 划分数据集为训练集、验证集和测试集
void ML::DatasetManager::splitDataset(double trainRatio, double valRatio, double testRatio) {size_t totalSamples = input.rows();size_t trainSize = static_cast<size_t>(totalSamples * trainRatio);size_t valSize = static_cast<size_t>(totalSamples * valRatio);size_t testSize = totalSamples - trainSize - valSize;shuffleTrainSet();valIndices.assign(trainIndices.begin() + trainSize, trainIndices.begin() + trainSize + valSize);testIndices.assign(trainIndices.begin() + trainSize + valSize, trainIndices.end());trainIndices.resize(trainSize);
}

对于打乱操作较频繁的场景,打乱索引是更为高效的操作;而对于不经常打乱的场景,直接在数据集上打乱更为高效。本例中仅给出打乱索引的代码示例。

数据获取


在获取数据时,首先明确所需数据集的类型(训练集或验证集)。然后,根据预设的批次大小(Batchsize),从索引列表中提取相应数量的索引,并将这些索引对应的数据存储到临时矩阵中。最后,导出数据,完成读取操作。

// 获取训练集、验证集和测试集的小批量数据
std::pair<MatrixXd, MatrixXd> ML::DatasetManager::getBatch(std::vector<int>& indices, size_t batchSize, size_t offset) {size_t start = offset * batchSize;size_t end = std::min(start + batchSize, indices.size());MatrixXd batchInput = MatrixXd::Zero(end - start, input.cols());MatrixXd batchLabel = MatrixXd::Zero(end - start, label.cols());for (size_t i = start; i < end; ++i) {batchInput.row(i - start) = input.row(indices[i]);batchLabel.row(i - start) = label.row(indices[i]);}return std::make_pair(batchInput, batchLabel);
}// 获取训练集的批量数据
std::pair<MatrixXd, MatrixXd> ML::DatasetManager::getTrainBatch(size_t batchSize, size_t offset) {return getBatch(trainIndices, batchSize, offset);
}// 获取验证集的批量数据
std::pair<MatrixXd, MatrixXd> ML::DatasetManager::getValidationBatch(size_t batchSize, size_t offset) {return getBatch(valIndices, batchSize, offset);
}// 获取测试集的批量数据
std::pair<MatrixXd, MatrixXd> ML::DatasetManager::getTestBatch(size_t batchSize, size_t offset) {return getBatch(testIndices, batchSize, offset);
}

数据集尺寸的外部接口


为便于代码开发,需要为数据集管理器设计外部接口,以便于外部可以获取各个数据集的尺寸。

size_t ML::DatasetManager::getSampleCount() const {return input.rows();
}size_t ML::DatasetManager::getTrainSampleCount() const {return trainIndices.size();
}size_t ML::DatasetManager::getValidationSampleCount() const {return valIndices.size();
}size_t ML::DatasetManager::getTestSampleCount() const {return testIndices.size();
}

优化器 Optimizer


随机梯度下降是一种优化算法,用于最小化损失函数以训练模型参数。与批量梯度下降(Batch Gradient Descent)不同,SGD在每次更新参数时只使用一个样本(或一个小批量的样本),而不是整个训练集。这使得SGD在计算上更高效,且能够更快地收敛,尤其是在处理大规模数据时。以下为随机梯度下降的代码示例:

class Optimizer {
public:virtual void update(std::vector<std::shared_ptr<Layer>>& layers) = 0;virtual ~Optimizer() {}
};class SGDOptimizer : public Optimizer {
private:double learningRate;
public:SGDOptimizer(double learningRate) : learningRate(learningRate) {}void update(std::vector<std::shared_ptr<Layer>>& layers) override;
};void SGDOptimizer::update(std::vector<std::shared_ptr<Layer>>& layers) {for (auto& layer : layers) {layer->update(learningRate);}
}

代码测试


如果你希望测试这些代码,首先可以从本篇文章,以及上一篇文章中复制代码,并参考下述图片构建你的解决方案。
description
如果你有遇到问题,欢迎联系作者!

示例1:线性回归


下述代码为线性回归的测试样例:

namespace LNR{// linear_regressionvoid gen(MatrixXd& X, MatrixXd& y);void test();
}void LNR::gen(MatrixXd& X, MatrixXd& y) {MatrixXd w(X.cols(), 1);X.setRandom();w.setRandom();X.rowwise() -= X.colwise().mean();X.array().rowwise() /= X.array().colwise().norm();y = X * w;
}void LNR::test() {std::cout << std::fixed << std::setprecision(2);size_t input_dim = 10;size_t sample_num = 2000;MatrixXd X(sample_num, input_dim);MatrixXd y(sample_num, 1);gen(X, y);ML::DatasetManager dataset;dataset.setDataset(X, y);ML::Network net;net.addLayer(std::make_shared<ML::Linear>(input_dim, 1));net.setLossFunction(std::make_shared<ML::MSELoss>());net.setOptimizer(std::make_shared<ML::SGDOptimizer>(0.25));net.setDatasetManager(std::make_shared<ML::DatasetManager>(dataset));size_t epochs = 600;size_t batch_size = 50;net.train(epochs, batch_size);MatrixXd error(sample_num, 1);error = net.forward(X) - y;std::cout << "error=\n" << error << "\n";
}

详细解释

  1. gen函数:用以生成测试数据。
  2. 网络结构:本例的网络结构中只包含一个线性层,其中输入尺寸为特征维度,输出尺寸为1。
  3. 损失函数:采用MSE均方根误差作为损失函数。

输出展示

完成训练后,网络预测值与真实值的误差如下图;容易发现,网络具有较好的预测精度。

description

示例2:逻辑回归


下述代码为逻辑回归的测试样例:

namespace LC {// Linear classificationvoid gen(MatrixXd& X, MatrixXd& y);void test();
}void LC::gen(MatrixXd& X, MatrixXd& y) {MatrixXd w(X.cols(), 1);X.setRandom();w.setRandom();X.rowwise() -= X.colwise().mean();X.array().rowwise() /= X.array().colwise().norm();y = X * w;y = y.unaryExpr([](double x) { return x > 0.0 ? 1.0 : 0.0; });
}void LC::test() {std::cout << std::fixed << std::setprecision(3);size_t input_dim = 10;size_t sample_num = 2000;MatrixXd X(sample_num, input_dim);MatrixXd y(sample_num, 1);gen(X, y);ML::DatasetManager dataset;dataset.setDataset(X, y);ML::Network net;net.addLayer(std::make_shared<ML::Linear>(input_dim, 1));net.addLayer(std::make_shared<ML::Sigmoid>());net.setLossFunction(std::make_shared<ML::LogisticLoss>());net.setOptimizer(std::make_shared<ML::SGDOptimizer>(0.05));net.setDatasetManager(std::make_shared<ML::DatasetManager>(dataset));size_t epochs = 200;size_t batch_size = 25;net.train(epochs, batch_size);MatrixXd predict(sample_num, 1);predict = net.forward(X);predict = predict.unaryExpr([](double x) { return x > 0.5 ? 1.0 : 0.0; });MatrixXd error(sample_num, 1);error = y - predict;error = error.unaryExpr([](double x) {return (x < 0.01 && x>-0.01) ? 1.0 : 0.0; });std::cout << "正确率=\n" << error.sum() / sample_num << "\n";
}

详细解释

  1. gen函数:用以生成测试数据。
  2. 网络结构:本例的网络结构中包含一个线性层及一个激活函数层,其中:线性层输入尺寸为特征维度,输出尺寸为1。
  3. 损失函数:采用对数误差作为损失函数。

输出展示
下图反映了网络预测过程中的损失变化,可以看到损失逐渐下降的趋势。
description

完成训练后,输出网络的预测结果的正确率。可以发现,网络具有较好的预测精度。
description

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/846408.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIP-18 MPLS LDP原理与配置

MPLS体系有多种标签分配协议,LDP标签分配协议是这些协议中使用较广的一种。 LDP是LSR之间协商标签含义的过程。LDP协议使用发现、会话、通告、通知四类报文进行会话的建立和标签的分发。 LDP通过标签发布方式、标签分配控制方式和标签保持方式来决定标签的发布和管理。华为数通…

centos7安装docker-ce

下载docker-ce repo curl https://download.docker.com/linux/centos/docker-ce.repo -o /etc/yum.repos.d/docker-ce.repo安装依赖 yum install -y https://mirrors.aliyun.com/docker-ce/linux/centos/7.5/x86_64/stable/Packages/containerd.io-1.4.8-3.1.el7.x86_64.rpm安装…

虚拟机苹果系统IM群发,苹果iMessage群发软件,iMessage协议版自动群发完美实现

一、PC电脑版苹果系统(Mac OS)上实现imessage群发总结为以下几种方式: /*MacOS苹果系统,正常情况下,只能安装到苹果公司自己出品的Mac电脑,俗称白苹果,不能安装到各种组装机或者其他品牌的品牌机上,黑苹果的的原理,就是通过一些 “破解补丁” 工具欺骗macOS系统,让苹果系统…

docker-配置镜像服务

镜像加速器配置 默认情况下,Docker是从Dcoker Hub上下载镜像资源,但由于该站点在国外,因此下载速度很慢,可以通过配置国内镜像加速器来解决,本文以阿里云镜像加速器为例,讲解在CentOs7系统下如何给Docker配置阿里云镜像加速器。 Docker Hub官网:https://hub.docker.com …

天梯赛练习集 L2-049 鱼与熊掌 查找

好简单,一开始随便二分一下,没想到也能过,算了时间复杂度1e10 思路:存储每个人拥有物品,查询每一个人是否两个物品都有. #include<bits/stdc++.h> using namespace std; vector<set<int>>vc; int main() {int n,m,x,y,cot;cin>>n>>m;for(int…

开源 - Ideal库 - Excel帮助类,ExcelHelper实现(四)

实现ExcelHelper第二步,设计接口包括读写操作和两类转换,通过文件路径、文件流及后缀读取Excel到DataSet,涉及工作簿Sheet与DataTable相互转换,具体代码已上传至代码库。书接上回,前面章节已经实现Excel帮助类的第一步TableHeper的对象集合与DataTable相互转换功能,今天实…

攻防世界-OldDriver

一、题目二、解题 1、观察特征:低指数,多密文 使用低指数广播攻击2、中国剩余定理剩余定理求解代码: def crt(remainders, moduli):"""中国剩余定理实现:param remainders: 余数列表 [r1, r2, ...]:param moduli: 模数列表 [m1, m2, ...]:return: 满足所有条…

Cannot find a valid baseurl for repo: base/7/x86_64

001、yum报错(base) [root@PC1 yum.repos.d]# yum install httpd ## yum安装应用报错 。002、解决方法(base) [root@PC1 home]# cd /etc/yum.repos.d (base) [root@PC1 yum.repos.d]# ls CentOS-Base.repo CentOS-CR.repo CentOS-Debuginfo.repo CentOS-fasttrack.rep…

【Comsol 6.2软件下载与安装教程】

1、安装包 「COMSOL 6.2」: 下载地址 2、安装教程(建议关闭杀毒软件和系统防护) 1) 下载并解压下载的安装包,双击Setup.exe安装,弹窗安装对话框2) 选择简体中文,点击下一步3) 选择先安装6.24) 选择我接受,点击许可证格式-许可证文件-浏览 – C…

搭建eureka集群环境以及客户端配置

先来效果图 eureka集群节点一 eureka集群节点二 节点一显示的注册服务 节点二显示的注册服务 eureka服务端配置如下所示 现在将eureka服务端放到了测试环境 192.168.31.50 192.168.31.60 服务信息 最后给出全部的eureka代码 pom文件<?xml version="1.0" encodin…

【推荐算法】推荐系统的评估

这篇文章是笔者阅读《深度学习推荐系统》第五章推荐系统的评估的学习笔记,在原文的基础上增加了自己的理解以及内容的补充,在未来的日子里会不断完善这篇文章的相关工作。离线评估在离线环境中利用已有的数据划分训练集和测试集对模型进行评估划分数据集方法机器学习常用划分…

考研打卡(34)

开局(34) 开始时间 2024-12-03 22:36:03 结束时间 2024-12-03 23:17:57为什么昨天没写,因为昨天想死,但我jio得不能每天都想死吧,所以今天该写了数据结构如果一棵二叉树的先序序列是…a…b…,中序序列是…b…a…,则_______(北京师范大学 2015年) A 节点a和节点b分别在…