- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在上一篇文章《机器学习:神经网络构建(上)》中讨论了线性层、激活函数以及损失函数层的构建方式,本节中将进一步讨论网络构建方式,并完整的搭建一个简单的分类器网络.
在设计神经网络时,其基本结构是由一层层的神经元组成的,这些层可以是输入层、隐藏层和输出层。为了实现这一结构,通常会使用向量(vector)容器来存储这些层,因为层的数量是可变的,可能根据具体任务的需求而变化.
即使在网络已经进行了预训练并具有一定的参数的情况下,对于特定的任务,通常还是需要进行模型微调。这是因为不同的任务可能有不同的数据分布和要求,因此训练是构建高性能神经网络模型的重要步骤.
在训练过程中,有三个关键组件:
损失函数:神经网络的学习目标,通过最小化损失函数来优化模型参数。选择合适的损失函数对于确保模型能够学习到有效的特征表示至关重要.
优化器:优化器负责调整模型的参数以最小化损失函数。除了基本的参数更新功能外,优化器还可以提供更高级的功能,如学习率调整和参数冻结,这些功能有助于提高训练效率和模型性能.
数据集管理器:负责在训练过程中有效地管理和提供数据,包括数据的加载、预处理和批处理,以确保数据被充分利用.
对于网络的外部接口(公有方法),主要有以下几类:
以下是代码示例:
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)为单位的。具体来说,一次迭代包含如下步骤:
代码设计如下:
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方法只在训练时使用,在正常的使用用途中,不会被外部调用,因此,其可以声明为私有方法.
数据集管理器本质目的是提高网络对数据的利用率,其主要职能有:
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();
}
随机梯度下降是一种优化算法,用于最小化损失函数以训练模型参数。与批量梯度下降(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);
}
}
如果你希望测试这些代码,首先可以从本篇文章,以及上一篇文章中复制代码,并参考下述图片构建你的解决方案。 如果你有遇到问题,欢迎联系作者! 。
下述代码为线性回归的测试样例:
namespace LNR{
// linear_regression
void 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";
}
详细解释 。
gen
函数:用以生成测试数据。输出展示 。
完成训练后,网络预测值与真实值的误差如下图;容易发现,网络具有较好的预测精度.
下述代码为逻辑回归的测试样例:
namespace LC {
// Linear classification
void 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";
}
详细解释 。
gen
函数:用以生成测试数据。输出展示 下图反映了网络预测过程中的损失变化,可以看到损失逐渐下降的趋势.
完成训练后,输出网络的预测结果的正确率。可以发现,网络具有较好的预测精度.
最后此篇关于机器学习:神经网络构建(下)的文章就讲到这里了,如果你想了解更多关于机器学习:神经网络构建(下)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
简介 在上一篇文章《机器学习:神经网络构建(上)》中讨论了线性层、激活函数以及损失函数层的构建方式,本节中将进一步讨论网络构建方式,并完整的搭建一个简单的分类器网络。 目录 网络Netwo
简介 在本篇文章中,我们采用逻辑回归作为案例,探索神经网络的构建方式。文章详细阐述了神经网络中层结构的实现过程,并提供了线性层、激活函数以及损失函数的定义(实现方法)。 目录 背景介绍
简介 在前两篇文章中,我们详细探讨了如何利用采样数据来估计回归曲线。接下来,在本节中,我们将深入讨论如何处理分类问题。 章节安排 背景介绍 数学方法 程序实现 背景介绍 线
简介 在上一篇文章《机器学习:线性回归(上)》中讨论了二维数据下的线性回归及求解方法,本节中我们将进一步的将其推广至高维情形。 章节安排 背景介绍 最小二乘法 梯度下降法 程序
PyCaret是一个开源、低代码Python机器学习库,能够自动化机器学习工作流程。它是一个端到端的机器学习和模型管理工具,极大地加快了实验周期,提高了工作效率。PyCaret本质上是围绕几个机器学习
在我的研究进展中,我现在已经将寄生虫从图像中分离出来。寄生虫看起来像蠕虫。我希望 MATLAB 读取所有输入图像,查找类似深紫色图像的蠕虫,如果检测到,则给出检测到的答复。我尝试使用直方图比较,但我认
目前我正在尝试了解机器学习算法的工作方式,但我没有真正了解的一件事是预测标签的计算准确度与视觉混淆矩阵之间的明显差异。我会尽量解释清楚。 这是数据集的片段(这里你可以看到 9 个样本(在真实数据集中大
第一章 绪论 机器学习 : 致力于研究如何通过计算的手段,利用经验来改善系统自身的性能。在计算机系统中, “经验” 通常以“数据“形式存在,因此,机器学习所研究的主要内容,是关于在计算
1. 算法原理(K-Nearest Neighbor) 本质是通过距离判断两个样本是否相似,如果距离够近就认为他们足够相似属于同一类别 找到离其最近的 k 个样本,并将这些样本称
前言 K-means是一种经典的无监督学习算法,用于对数据进行聚类。K-means算法将数据集视为具有n个特征的n维空间,并尝试通过最小化簇内平方误差的总和来将数据点划分为簇。本文将介绍K-m
目录 前言 介绍LightGBM LightGBM的背景和起源 L
前言 可以说掌握了机器学习,你就具备了与机器对话,充分利用机器为人类服务的能力。在人工智能时代,这将成为一项必备技能,就好比十年前你是编程大牛,二十年前你英语超好一样。因此,无论你是什么专业的
几个贯穿始终的概念 当我们把人类学习简单事物的过程抽象为几个阶段,再将这些阶段通过不同的方法具体化为代码,依靠通过计算机的基础能力-- 计算 。我们就可以让机器能够“学会”一些简单的事物。
1、选题背景 人脸识别技术是模式识别和计算机视觉领域最富挑战性的研究课题之一,也是近年来的研究热点,人脸性别识别作为人脸识别技术
每当我们在公有云或者私有云发布训练好的大数据模型,为了方便大家辨识、理解和运用,参照huggingface所制定的标准制作一个Model Card展示页,是种非常好的模型展示和组织形式。 下面就是一
2. 支持向量机 对偶优化 拉格朗日乘数法可用于解决带条件优化问题,其基本形式为: \[\begin{gather} \min_w f(w),\\ \mathrm{s.t.} \quad
我正在尝试运行以下代码: https://github.com/opencv/opencv/blob/master/samples/dnn/classification.cpp 我在这里找到所有经过预
我是机器学习新手。当我使用 scikit-learn 模块中的波士顿数据集练习具有默认参数的决策树回归模型时。 在此链接解决方案( How to Build a Decision tree Regre
我有用于训练的数据。当我将其输入神经网络时,该数据出现 3% 的错误。 我知道这些数据有一定的过度代表性 - 例如,第 5 类的示例大约是其他类的十分之一。 我的作业指出,我可以通过偏置训练数据(即删
我在 Python 的多类分类中使用 SVM 时遇到问题。事实上,问题在于性别分类(来自图像),其中训练数据集仅包含“y=1”或“ y=-1”作为类标签(二进制)。但是,在预测中,如果是男性,我必须预
我是一名优秀的程序员,十分优秀!