神经网络与深度学习
1. 神经网络基础
1.1 神经元模型与激活函数
神经元是神经网络的基本单元,其模型可以类比为生物神经元。一个神经元接收多个输入信号,对这些信号进行加权求和,然后通过一个激活函数进行非线性变换,最终输出一个信号。以下是用 C++ 实现一个简单神经元的代码示例:
#include <iostream>
#include <vector>
#include <cmath>
// 定义激活函数
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
// 神经元类
class Neuron {
private:
std::vector<double> weights; // 权重
double bias; // 偏置
public:
// 构造函数
Neuron(int input_size) {
weights.resize(input_size, 0.0); // 初始化权重为0
bias = 0.0; // 初始化偏置为0
}
// 设置权重和偏置
void set_weights(const std::vector<double>& w, double b) {
weights = w;
bias = b;
}
// 前向传播,计算输出
double forward(const std::vector<double>& inputs) {
double sum = bias;
for (size_t i = 0; i < inputs.size(); ++i) {
sum += inputs[i] * weights[i];
}
return sigmoid(sum); // 使用Sigmoid激活函数
}
};
int main() {
// 创建一个输入大小为2的神经元
Neuron neuron(2);
// 设置权重和偏置
std::vector<double> weights = {0.5, -0.3};
double bias = 0.1;
neuron.set_weights(weights, bias);
// 输入信号
std::vector<double> inputs = {1.0, -2.0};
// 计算输出
double output = neuron.forward(inputs);
std::cout << "Neuron output: " << output << std::endl;
return 0;
}
代码思路:
- 激活函数:Sigmoid 函数是一个常用的激活函数,它将输入映射到 (0, 1) 区间,具有平滑的梯度,适用于神经元的非线性变换。
- 神经元类:
- 成员变量:
weights
存储输入信号的权重,bias
是偏置项。 - 构造函数:根据输入信号的数量初始化权重和偏置。
- 设置权重和偏置:通过
set_weights
函数设置权重和偏置。 - 前向传播:
forward
函数计算输入信号的加权和,加上偏置后通过激活函数输出结果。
- 成员变量:
- 主函数:
- 创建一个神经元实例,设置权重和偏置。
- 提供输入信号,调用
forward
函数计算输出。
1.2 神经网络结构与前向传播
神经网络由多个神经元组成,通常分为输入层、隐藏层和输出层。前向传播是指从输入层到输出层逐层计算的过程。以下是一个简单的两层神经网络的 C++ 实现:
#include <iostream>
#include <vector>
#include <cmath>
// 激活函数
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
// 神经元类
class Neuron {
private:
std::vector<double> weights;
double bias;
public:
Neuron(int input_size) {
weights.resize(input_size, 0.0);
bias = 0.0;
}
void set_weights(const std::vector<double>& w, double b) {
weights = w;
bias = b;
}
double forward(const std::vector<double>& inputs) {
double sum = bias;
for (size_t i = 0; i < inputs.size(); ++i) {
sum += inputs[i] * weights[i];
}
return sigmoid(sum);
}
};
// 神经网络类
class NeuralNetwork {
private:
std::vector<Neuron> hidden_layer; // 隐藏层
Neuron output_neuron; // 输出层
public:
NeuralNetwork(int input_size, int hidden_size) {
hidden_layer.resize(hidden_size, Neuron(input_size));
output_neuron = Neuron(hidden_size);
}
void set_weights(const std::vector<std::vector<double>>& hidden_weights,
const std::vector<double>& hidden_biases,
const std::vector<double>& output_weights,
double output_bias) {
for (size_t i = 0; i < hidden_layer.size(); ++i) {
hidden_layer[i].set_weights(hidden_weights[i], hidden_biases[i]);
}
output_neuron.set_weights(output_weights, output_bias);
}
double forward(const std::vector<double>& inputs) {
std::vector<double> hidden_outputs(hidden_layer.size());
for (size_t i = 0; i < hidden_layer.size(); ++i) {
hidden_outputs[i] = hidden_layer[i].forward(inputs);
}
return output_neuron.forward(hidden_outputs);
}
};
int main() {
// 创建一个输入大小为2,隐藏层大小为2的神经网络
NeuralNetwork nn(2, 2);
// 设置权重和偏置
std::vector<std::vector<double>> hidden_weights = {{0.5, -0.3}, {0.2, 0.1}};
std::vector<double> hidden_biases = {0.1, -0.2};
std::vector<double> output_weights = {0.4, -0.5};
double output_bias = 0.3;
nn.set_weights(hidden_weights, hidden_biases, output_weights, output_bias);
// 输入信号
std::vector<double> inputs = {1.0, -2.0};
// 计算输出
double output = nn.forward(inputs);
std::cout << "Neural Network output: " << output << std::endl;
return 0;
}
代码思路:
- 神经网络类:
- 成员变量:
hidden_layer
存储隐藏层的神经元,output_neuron
是输出层的神经元。 - 构造函数:根据输入层大小和隐藏层大小初始化神经网络。
- 设置权重和偏置:通过
set_weights
函数设置隐藏层和输出层的权重和偏置。 - 前向传播:
- 隐藏层的每个神经元对输入信号进行前向传播,计算隐藏层的输出。
- 将隐藏层的输出作为输出层神经元的输入,计算最终输出。
- 成员变量:
- 主函数:
- 创建一个神经网络实例,设置权重和偏置。
- 提供输入信号,调用
forward
函数计算输出。# 2. 深度学习框架
2.1 损失函数与优化算法
在深度学习中,损失函数用于衡量模型的预测值与真实值之间的差异,优化算法则用于调整模型的参数以最小化损失函数。以下是几种常见的损失函数和优化算法的实现及讲解。
均方误差损失函数
均方误差(MSE)损失函数是回归任务中常用的损失函数,它计算预测值与真实值之间差的平方的均值。
#include <iostream>
#include <vector>
#include <cmath>
// 均方误差损失函数
double mse_loss(const std::vector<double>& predictions, const std::vector<double>& targets) {
double loss = 0.0;
for (size_t i = 0; i < predictions.size(); ++i) {
loss += std::pow(predictions[i] - targets[i], 2);
}
return loss / predictions.size();
}
int main() {
// 示例:预测值和真实值
std::vector<double> predictions = {0.5, 1.2, -0.3};
std::vector<double> targets = {0.4, 1.0, -0.5};
// 计算损失
double loss = mse_loss(predictions, targets);
std::cout << "MSE Loss: " << loss << std::endl;
return 0;
}
代码思路:
- 损失计算:遍历预测值和真实值,计算每个样本的预测值与真实值之差的平方。将所有样本的损失值求和后除以样本总数,得到均方误差损失。
交叉熵损失函数
交叉熵损失函数常用于分类任务,它衡量模型输出的概率分布与真实标签的概率分布之间的差异。
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
// Softmax函数
std::vector<double> softmax(const std::vector<double>& logits) {
double max_logit = *std::max_element(logits.begin(), logits.end());
double sum = 0.0;
std::vector<double> probabilities(logits.size());
for (size_t i = 0; i < logits.size(); ++i) {
probabilities[i] = std::exp(logits[i] - max_logit);
sum += probabilities[i];
}
for (size_t i = 0; i < logits.size(); ++i) {
probabilities[i] /= sum;
}
return probabilities;
}
// 交叉熵损失函数
double cross_entropy_loss(const std::vector<double>& logits, int target_class) {
std::vector<double> probabilities = softmax(logits);
return -std::log(probabilities[target_class]);
}
int main() {
// 示例:模型输出的logits和目标类别
std::vector<double> logits = {2.0, 1.0, 0.1};
int target_class = 0;
// 计算损失
double loss = cross_entropy_loss(logits, target_class);
std::cout << "Cross-Entropy Loss: " << loss << std::endl;
return 0;
}
代码思路:
- Softmax函数:
- 对输入的logits进行归一化处理,使其转换为概率分布。
- 首先计算每个logit的最大值,用于防止数值溢出。
- 计算每个logit的指数值并归一化,得到概率分布。
- 交叉熵损失:
- 使用Softmax函数计算模型输出的概率分布。
- 计算目标类别的概率的负对数,得到交叉熵损失。
梯度下降优化算法
梯度下降是深度学习中最常用的优化算法之一,它通过计算损失函数对参数的梯度,更新参数以最小化损失。
#include <iostream>
#include <vector>
#include <cmath>
// 梯度下降优化器
class GradientDescentOptimizer {
private:
double learning_rate; // 学习率
public:
GradientDescentOptimizer(double lr) : learning_rate(lr) {}
// 更新参数
void update(std::vector<double>& params, const std::vector<double>& gradients) {
for (size_t i = 0; i < params.size(); ++i) {
params[i] -= learning_rate * gradients[i];
}
}
};
int main() {
// 示例:参数和梯度
std::vector<double> params = {1.0, 2.0};
std::vector<double> gradients = {0.5, -0.3};
double learning_rate = 0.1;
// 创建优化器
GradientDescentOptimizer optimizer(learning_rate);
// 更新参数
optimizer.update(params, gradients);
// 输出更新后的参数
std::cout << "Updated parameters: ";
for (double param : params) {
std::cout << param << " ";
}
std::cout << std::endl;
return 0;
}
代码思路:
- 优化器类:
- 成员变量:
learning_rate
存储学习率。 - 更新参数:
update
函数根据梯度和学习率更新参数。
- 成员变量:
- 主函数:
- 初始化参数和梯度。
- 创建优化器实例,调用
update
函数更新参数。
2.2 反向传播与梯度计算
反向传播是深度学习中用于计算损失函数对模型参数梯度的关键算法。它通过链式法则从输出层向输入层逐层计算梯度。
神经元的反向传播
以下是一个简单神经元的反向传播实现,包括前向传播和反向传播的代码。
#include <iostream>
#include <vector>
#include <cmath>
// 激活函数及其导数
double sigmoid(double x) {
return 1.0 / (1.0 + std::exp(-x));
}
double sigmoid_derivative(double x) {
return sigmoid(x) * (1.0 - sigmoid(x));
}
// 神经元类
class Neuron {
private:
std::vector<double> weights;
double bias;
public:
Neuron(int input_size) {
weights.resize(input_size, 0.0);
bias = 0.0;
}
void set_weights(const std::vector<double>& w, double b) {
weights = w;
bias = b;
}
// 前向传播
double forward(const std::vector<double>& inputs) {
double sum = bias;
for (size_t i = 0; i < inputs.size(); ++i) {
sum += inputs[i] * weights[i];
}
return sigmoid(sum);
}
// 反向传播
std::vector<double> backward(const std::vector<double>& inputs, double output, double target) {
double error = target - output;
double delta = error * sigmoid_derivative(output);
std::vector<double> gradients(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
gradients[i] = delta * inputs[i];
}
return gradients;
}
};
int main() {
// 创建一个输入大小为2的神经元
Neuron neuron(2);
// 设置权重和偏置
std::vector<double> weights = {0.5, -0.3};
double bias = 0.1;
neuron.set_weights(weights, bias);
// 输入信号和目标值
std::vector<double> inputs = {1.0, -2.0};
double target = 1.0;
// 前向传播
double output = neuron.forward(inputs);
std::cout << "Neuron output: " << output << std::endl;
// 反向传播
std::vector<double> gradients = neuron.backward(inputs, output, target);
// 输出梯度
std::cout << "Gradients: ";
for (double gradient : gradients) {
std::cout << gradient << " ";
}
std::cout << std::endl;
return 0;
}
代码思路:
- 激活函数及其导数:
sigmoid
函数用于前向传播,将输入映射到 (0, 1) 区间。sigmoid_derivative
函数用于计算激活函数的导数,用于反向传播。
- 神经元类:
- 前向传播:计算输入信号的加权和,加上偏置后通过激活函数输出结果。
- 反向传播:
- 计算误差:
error = target - output
。 - 计算梯度:
delta = error * sigmoid_derivative(output)
。 - 计算每个输入对应的梯度:
gradients[i] = delta * inputs[i]
。
- 计算误差:
- 主函数:
- 创建神经元实例,设置权重和偏置。
- 提供输入信号和目标值,调用
forward
函数计算输出。 - 调用
backward
函数计算梯度。# 3. C++实现神经网络
3.1 神经元类设计与实现
神经元是神经网络的基本构建块,其设计需要考虑前向传播和反向传播两个过程。以下是基于C++标准库实现的神经元类,包含详细的代码和思路讲解。
神经元类代码实现
#include <iostream>
#include <vector>
#include <cmath>
// 激活函数及其导数
double sigmoid(double x) {
return 1.0 / (1.0 + std::exp(-x));
}
double sigmoid_derivative(double x) {
return sigmoid(x) * (1.0 - sigmoid(x));
}
// 神经元类
class Neuron {
private:
std::vector<double> weights; // 权重
double bias; // 偏置
public:
// 构造函数:初始化权重和偏置
Neuron(int input_size) {
weights.resize(input_size, 0.0); // 初始化权重为0
bias = 0.0; // 初始化偏置为0
}
// 设置权重和偏置
void set_weights(const std::vector<double>& w, double b) {
weights = w;
bias = b;
}
// 前向传播:计算输出
double forward(const std::vector<double>& inputs) {
double sum = bias;
for (size_t i = 0; i < inputs.size(); ++i) {
sum += inputs[i] * weights[i];
}
return sigmoid(sum); // 使用Sigmoid激活函数
}
// 反向传播:计算梯度
std::vector<double> backward(const std::vector<double>& inputs, double output, double target) {
double error = target - output; // 计算误差
double delta = error * sigmoid_derivative(output); // 计算梯度
std::vector<double> gradients(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
gradients[i] = delta * inputs[i]; // 计算每个输入对应的梯度
}
return gradients;
}
};
代码思路
- 激活函数及其导数:
sigmoid
函数用于前向传播,将输入映射到 (0, 1) 区间。sigmoid_derivative
函数用于计算激活函数的导数,用于反向传播。
- 神经元类:
- 成员变量:
weights
:存储输入信号的权重。bias
:存储偏置项。
- 构造函数:
- 根据输入信号的数量初始化权重和偏置,权重初始化为0,偏置初始化为0。
- 设置权重和偏置:
- 通过
set_weights
函数设置权重和偏置。
- 通过
- 前向传播:
- 计算输入信号的加权和,加上偏置后通过激活函数输出结果。
- 反向传播:
- 计算误差:
error = target - output
。 - 计算梯度:
delta = error * sigmoid_derivative(output)
。 - 计算每个输入对应的梯度:
gradients[i] = delta * inputs[i]
。
- 计算误差:
- 成员变量:
3.2 神经网络类构建
神经网络由多个神经元组成,通常分为输入层、隐藏层和输出层。以下是基于C++标准库实现的简单神经网络类,包含前向传播和反向传播的代码。
神经网络类代码实现
#include <iostream>
#include <vector>
#include <cmath>
// 激活函数及其导数
double sigmoid(double x) {
return 1.0 / (1.0 + std::exp(-x));
}
double sigmoid_derivative(double x) {
return sigmoid(x) * (1.0 - sigmoid(x));
}
// 神经元类
class Neuron {
private:
std::vector<double> weights;
double bias;
public:
Neuron(int input_size) {
weights.resize(input_size, 0.0);
bias = 0.0;
}
void set_weights(const std::vector<double>& w, double b) {
weights = w;
bias = b;
}
double forward(const std::vector<double>& inputs) {
double sum = bias;
for (size_t i = 0; i < inputs.size(); ++i) {
sum += inputs[i] * weights[i];
}
return sigmoid(sum);
}
std::vector<double> backward(const std::vector<double>& inputs, double output, double target) {
double error = target - output;
double delta = error * sigmoid_derivative(output);
std::vector<double> gradients(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
gradients[i] = delta * inputs[i];
}
return gradients;
}
};
// 神经网络类
class NeuralNetwork {
private:
std::vector<Neuron> hidden_layer; // 隐藏层
Neuron output_neuron; // 输出层
public:
// 构造函数:初始化隐藏层和输出层
NeuralNetwork(int input_size, int hidden_size) {
hidden_layer.resize(hidden_size, Neuron(input_size));
output_neuron = Neuron(hidden_size);
}
// 设置权重和偏置
void set_weights(const std::vector<std::vector<double>>& hidden_weights,
const std::vector<double>& hidden_biases,
const std::vector<double>& output_weights,
double output_bias) {
for (size_t i = 0; i < hidden_layer.size(); ++i) {
hidden_layer[i].set_weights(hidden_weights[i], hidden_biases[i]);
}
output_neuron.set_weights(output_weights, output_bias);
}
// 前向传播:计算输出
double forward(const std::vector<double>& inputs) {
std::vector<double> hidden_outputs(hidden_layer.size());
for (size_t i = 0; i < hidden_layer.size(); ++i) {
hidden_outputs[i] = hidden_layer[i].forward(inputs);
}
return output_neuron.forward(hidden_outputs);
}
// 反向传播:计算梯度
std::vector<std::vector<double>> backward(const std::vector<double>& inputs, double output, double target) {
std::vector<double> hidden_outputs(hidden_layer.size());
for (size_t i = 0; i < hidden_layer.size(); ++i) {
hidden_outputs[i] = hidden_layer[i].forward(inputs);
}
double output_error = target - output;
double output_delta = output_error * sigmoid_derivative(output);
std::vector<double> output_gradients(hidden_layer.size());
for (size_t i = 0; i < hidden_layer.size(); ++i) {
output_gradients[i] = output_delta * hidden_outputs[i];
}
std::vector<std::vector<double>> hidden_gradients(hidden_layer.size());
for (size_t i = 0; i < hidden_layer.size(); ++i) {
double hidden_error = output_delta * output_neuron.weights[i];
double hidden_delta = hidden_error * sigmoid_derivative(hidden_outputs[i]);
hidden_gradients[i].resize(inputs.size());
for (size_t j = 0; j < inputs.size(); ++j) {
hidden_gradients[i][j] = hidden_delta * inputs[j];
}
}
return hidden_gradients;
}
};
代码思路
- 神经网络类:
- 成员变量:
hidden_layer
:存储隐藏层的神经元。output_neuron
:存储输出层的神经元。
- 构造函数:
- 根据输入层大小和隐藏层大小初始化神经网络。
- 设置权重和偏置:
- 通过
set_weights
函数设置隐藏层和输出层的权重和偏置。
- 通过
- 前向传播:
- 隐藏层的每个神经元对输入信号进行前向传播,计算隐藏层的输出。
- 将隐藏层的输出作为输出层神经元的输入,计算最终输出。
- 反向传播:
- 计算输出层的误差和梯度。
- 通过链式法则计算隐藏层的误差和梯度。
- 返回所有层的梯度,用于后续的参数更新。# 4. 总结
- 成员变量:
在本章中,我们详细探讨了神经网络的基础知识,包括神经元模型、激活函数、神经网络结构以及深度学习框架中的关键组件。通过这些内容,我们逐步构建了一个完整的神经网络模型,并实现了前向传播和反向传播算法。
4.1 神经元模型与激活函数
神经元是神经网络的基本单元,其设计灵感来源于生物神经元。一个神经元接收多个输入信号,对这些信号进行加权求和,并通过激活函数进行非线性变换,最终输出一个信号。激活函数是神经网络中不可或缺的一部分,它为网络引入了非线性因素,使得神经网络能够学习和模拟复杂的非线性关系。在本章中,我们使用了 Sigmoid 激活函数,它将输入映射到 (0, 1) 区间,具有平滑的梯度,适用于神经元的非线性变换。通过 C++ 实现的神经元类,我们展示了如何设置权重和偏置,并通过前向传播计算输出结果。
4.2 神经网络结构与前向传播
神经网络由多个神经元组成,通常分为输入层、隐藏层和输出层。前向传播是指从输入层到输出层逐层计算的过程。在本章中,我们构建了一个简单的两层神经网络,并通过 C++ 实现了其前向传播过程。隐藏层的每个神经元对输入信号进行前向传播,计算隐藏层的输出,然后将这些输出作为输出层神经元的输入,最终计算出整个网络的输出。通过设置权重和偏置,我们可以调整神经网络的行为,使其能够学习特定的任务。
4.3 深度学习框架中的关键组件
深度学习框架中的损失函数和优化算法是训练神经网络的关键。损失函数用于衡量模型的预测值与真实值之间的差异,而优化算法则用于调整模型的参数以最小化损失函数。在本章中,我们介绍了两种常见的损失函数:均方误差损失函数和交叉熵损失函数。均方误差损失函数适用于回归任务,而交叉熵损失函数则常用于分类任务。此外,我们还介绍了梯度下降优化算法,它通过计算损失函数对参数的梯度,更新参数以最小化损失。通过 C++ 实现的梯度下降优化器,我们展示了如何根据梯度和学习率更新参数。
4.4 反向传播与梯度计算
反向传播是深度学习中用于计算损失函数对模型参数梯度的关键算法。它通过链式法则从输出层向输入层逐层计算梯度。在本章中,我们详细介绍了神经元的反向传播过程,并通过 C++ 实现了相应的代码。在反向传播中,我们首先计算输出层的误差和梯度,然后通过链式法则计算隐藏层的误差和梯度。最终,我们得到了所有层的梯度,这些梯度将用于后续的参数更新。
4.5 神经网络类的构建与实现
在本章的最后,我们基于 C++ 标准库实现了一个简单的神经网络类,该类包含了前向传播和反向传播的功能。神经网络类的成员变量包括隐藏层和输出层的神经元。通过构造函数,我们根据输入层大小和隐藏层大小初始化神经网络。设置权重和偏置的函数允许我们调整网络的参数。在前向传播中,隐藏层的每个神经元对输入信号进行前向传播,计算隐藏层的输出,然后将这些输出作为输出层神经元的输入,最终计算出整个网络的输出。在反向传播中,我们计算输出层的误差和梯度,并通过链式法则计算隐藏层的误差和梯度。最终,我们得到了所有层的梯度,这些梯度将用于后续的参数更新。
- List item