输入“/”快速插入内容

101N0202 Micrograd C++ 代码自发实现

2024年8月26日修改
1.
引言
本文是对micrograd C++版本的代码解读。micrograd项目是Andrej Karpathy 101N课程的一部分,旨在构建一个轻量级的自动微分引擎,帮助我们理解自动微分,梯度下降,反向传播和神经网络训练的基本原理。原项目是使用python实现,我们自发对101n课程做了扩展,使用C++复现了micrograd的核心代码,以便让大家了解C++和Python在代码实现方式上的差异。下面让我开始一步步对代码解读。
📌
阅读 Tips:阅读本文需要准备一些高数微积分基础知识。C++11 基础知识。
2.
代码主要结构:
整个代码只有一个micrograd.cpp 文件,通过面向对象的方式实现了Python版本中的各个模块:
RNG 类: 自定义的随机数生成器,保证相同的seed情况下,生成的数据数相同。
Value 类: 整个自动求导引擎的核心。它存储一个标量值及其梯度,并定义了基本的数学运算
Module 类: 定义了神经网络模块的基本接口,是Neuron Layer MLP 的基类。
Neuron:实现单个神经元。
Layer 类:实现神经网络层。
MLP 类:实现了一个多层感知机。
main 方法:主程序入口,使用上面的对象和工具,构建了一个具有 2 个输入节点,16 个隐藏层节点和 3 个输出节点的神经网络,跑通了整个训练流程。
以上C++对象,和Python版本实现一一对应,都能在Python版本中找到相应的实现对象。
3.
代码解读
3.1
RNG 类:
代码块
class RNG {
private:
uint64_t state; // 随机数生成器的内部状态
public:
// 构造函数,初始化随机数生成器的状态
RNG(uint64_t seed) : state(seed) {}
// 生成一个 32 位的随机整数
uint32_t random_u32() {
// 使用 xorshift 算法生成随机数
state ^= (state >> 12) & 0xFFFFFFFFFFFFFFFF;
state ^= (state << 25) & 0xFFFFFFFFFFFFFFFF;
state ^= (state >> 27) & 0xFFFFFFFFFFFFFFFF;
// 返回生成的 32 位随机数
return static_cast<uint32_t>((state * 0x2545F4914F6CDD1D) >> 32);
}
// 生成一个 [0, 1) 区间内的随机浮点数
float random() {
// 将 32 位随机数右移 8 位后,除以 16777216.0 来生成 [0, 1) 区间内的随机浮点数
return (random_u32() >> 8) / 16777216.0f;
}
// 生成一个 [a, b) 区间内的随机浮点数
float uniform(float a = 0.0f, float b = 1.0f) {
// 使用 [0, 1) 的随机数生成 [a, b) 区间内的随机浮点数
return a + (b - a) * random();
}
};
// 生成训练集、验证集和测试集
std::tuple<std::vector<std::pair<std::vector<double>, int>>,
std::vector<std::pair<std::vector<double>, int>>,
std::vector<std::pair<std::vector<double>, int>>>
gen_data(RNG &random, int n = 100) {
// 定义存储数据点的向量
std::vector<std::pair<std::vector<double>, int>> pts;
// 生成 n 个随机数据点
for (int i = 0; i < n; ++i) {
// 生成 x 和 y 坐标,范围在 [-2.0, 2.0)
float x = random.uniform(-2.0f, 2.0f);
float y = random.uniform(-2.0f, 2.0f);
// 根据 x 和 y 的位置确定标签
int label = (x < 0) ? 0 : (y < 0) ? 1 : 2;
// 将生成的点(向量和标签)存入 pts 向量中
pts.emplace_back(std::vector<double>{x, y}, label);
}
// 计算训练集、验证集和测试集的大小 (80%, 10%, 10%)
int tr_size = static_cast<int>(0.8 * n); // 训练集大小
int val_size = static_cast<int>(0.1 * n); // 验证集大小
// 将数据分为训练集、验证集和测试集
std::vector<std::pair<std::vector<double>, int>> tr(pts.begin(), pts.begin() + tr_size); // 训练集
std::vector<std::pair<std::vector<double>, int>> val(pts.begin() + tr_size, pts.begin() + tr_size + val_size); // 验证集
std::vector<std::pair<std::vector<double>, int>> te(pts.begin() + tr_size + val_size, pts.end()); // 测试集
// 返回训练集、验证集和测试集
return std::make_tuple(tr, val, te);
}