从零手搓C++机器学习库:深入理解自动微分与神经网络实现
为什么很多C++开发者对机器学习望而却步?是因为数学太难,还是因为Python生态太强?其实,很多人的第一道坎,是觉得那些现成的框架(TensorFlow、PyTorch)像黑盒子,内部机制神秘莫测,出了问题无从下手。更现实的问题是,当你需要在嵌入式设备、高性能服务器或者对二进制体积有严格限制的场景下部署模型时,Python的运行时和庞大的依赖库往往成为不可承受之重。
这时,“从零手搓一个C++机器学习库”听起来像是一个疯狂的、只属于顶尖高手的挑战。但真相是,这恰恰是深入理解机器学习核心原理、掌握现代C++工程实践、并最终获得在严苛环境下部署AI模型能力的 最佳路径 。这不是为了替代PyTorch,而是为了让你真正“拥有”这项技术。
本文将带你踏上一段旅程: 完全使用现代C++,不依赖任何第三方机器学习框架,从零构建一个支持自动微分、计算图、以及经典神经网络(如前馈网络、CNN)的微型机器学习库 。你将亲手实现张量(Tensor)、实现ReLU等激活函数、实现反向传播算法。读完本文,你不仅能获得一个可运行、可扩展的代码库,更能透彻理解深度学习框架的“发动机”是如何工作的。当你能用纯C++训练一个MNIST手写数字分类器时,你对神经网络的理解将不再浮于表面。
1. 这篇文章真正要解决的问题:为什么是C++?为什么从零开始?
在Python中, import torch 之后,一切似乎都顺理成章。但当你需要思考以下问题时,纯C++的方案价值就凸显出来了:
- 极致性能与可控性 :你需要对内存布局、计算指令、并行策略有绝对控制,以榨干硬件(如CPU的AVX指令集)的最后一点性能。
- 部署友好 :生成一个单一的可执行文件或轻量级库,无需安装Python环境、pip包管理器或数百MB的框架共享库,特别适合工业嵌入式系统或作为微服务发布。
- 深入理解,而非调用API :通过亲手实现反向传播、权重更新,你将牢固掌握梯度下降、链式法则等核心概念,这是单纯调参无法比拟的。
- 破除神秘感 :许多框架的复杂抽象(动态图、静态图、算子融合)本质上都建立在一些相对基础的数据结构和算法之上。自己实现一次,这些概念将变得清晰可见。
本文的目标,就是为你提供一张清晰的“手搓”地图。我们将聚焦于 可理解性 和 可运行性 ,避免陷入过于复杂的工程优化(如手写汇编内核),而是确保每个模块都有清晰的C++实现和对应的数学原理解释。
2. 核心概念与我们的设计蓝图
在开始写代码前,我们需要统一几个核心概念,并规划我们微型库的架构。
2.1 核心概念基石
- 张量 (Tensor) : 这是库的基石。你可以把它看作一个多维数组。在C++中,我们需要一个类来管理数据(
std::vector或原生数组)、形状(shape)和维度(ndim)。 - 计算图 (Computational Graph) : 这是实现自动微分(Autograd)的关键。每一次运算(如加法、矩阵乘法)都会生成一个代表该运算的“节点”(Node),节点记录它的输入张量、运算类型和输出的梯度。这些节点通过输入/输出关系连接成一个有向无环图(DAG)。
- 自动微分 (Automatic Differentiation) : 框架的核心魔法。它允许我们只定义前向计算过程(即网络如何从输入得到输出),框架会自动计算所有参数相对于损失函数的梯度。我们实现的是 反向模式自动微分 ,这也是PyTorch和TensorFlow采用的方式。
- 损失函数 (Loss Function) : 衡量模型预测值与真实值差距的函数,如均方误差(MSE)用于回归,交叉熵损失(CrossEntropy)用于分类。训练的目标就是最小化损失函数。
- 优化器 (Optimizer) : 利用计算出的梯度来更新模型参数的算法,如经典的随机梯度下降(SGD)、带动量的SGD、Adam等。
2.2 库的模块设计
我们将库划分为以下几个层次清晰的模块,便于理解和实现:
- 核心张量库 (
Tensor) : 负责数据存储、基本形状操作和内存管理。 - 自动微分引擎 (
Autograd) : 包含Variable(带梯度的张量)和Function(运算基类)的实现,负责构建计算图和执行反向传播。 - 神经网络模块 (
NN) :- 层 (
Layer) : 如全连接层(Linear)、卷积层(Conv2d)、激活层(ReLU,Sigmoid)。 - 模块 (
Module) : 层的容器,负责管理可训练参数(Parameter),并提供forward()接口。
- 层 (
- 优化器 (
Optim) : 实现 SGD、Adam 等算法。 - 工具与数据 (
Utils) : 如数据加载器、模型序列化等。
下面,我们将从环境准备开始,一步步实现这个蓝图。
3. 环境准备与项目结构
我们使用现代C++(C++17标准)来获得更清晰的语法和更好的标准库支持。不需要任何额外的机器学习库。
开发环境:
- 编译器 : GCC (>=7.0), Clang (>=5.0) 或 MSVC (Visual Studio 2019+)。确保支持C++17。
- 构建系统 : 为了简洁和可移植性,我们使用 CMake 。你也可以用任何你熟悉的构建系统。
- IDE/编辑器 : VSCode, CLion, Visual Studio 等均可。
项目目录结构: 在开始前,创建如下目录结构,这有助于保持代码组织清晰。
cpp_ml_lib/
├── CMakeLists.txt # 项目根CMake配置文件
├── include/ # 所有头文件
│ └── cpp_ml/
│ ├── tensor.h # 张量类定义
│ ├── autograd.h # 自动微分相关类
│ ├── nn.h # 神经网络层和模块
│ ├── optim.h # 优化器
│ └── utils.h # 工具函数
├── src/ # 源文件
│ ├── tensor.cpp
│ ├── autograd.cpp
│ ├── nn.cpp
│ ├── optim.cpp
│ └── utils.cpp
└── examples/ # 示例代码
├── mnist/ # MNIST示例
│ ├── train.cpp
│ └── CMakeLists.txt
└── CMakeLists.txt
根目录 CMakeLists.txt 基础配置:
cmake_minimum_required(VERSION 3.10)
project(cpp_ml_lib VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 将库编译为静态库
add_library(cpp_ml STATIC
src/tensor.cpp
src/autograd.cpp
src/nn.cpp
src/optim.cpp
src/utils.cpp
)
target_include_directories(cpp_ml PUBLIC include)
# 可选:启用更多警告
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(cpp_ml PRIVATE -Wall -Wextra -Wpedantic)
endif()
# 示例目录
add_subdirectory(examples)
4. 核心实现:从张量到自动微分
4.1 实现基础张量类 ( tensor.h / tensor.cpp )
张量类需要管理数据、形状、步长(stride)以实现高效的视图操作(如转置)。
// file: include/cpp_ml/tensor.h
#pragma once
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>
namespace cpp_ml {
class Tensor {
public:
// 构造函数
Tensor();
explicit Tensor(const std::vector<size_t>& shape);
Tensor(const std::vector<size_t>& shape, const std::vector<float>& data);
Tensor(const Tensor& other); // 拷贝构造
Tensor(Tensor&& other) noexcept; // 移动构造
// 赋值运算符
Tensor& operator=(const Tensor& other);
Tensor& operator=(Tensor&& other) noexcept;
~Tensor() = default;
// 基础信息获取
const std::vector<size_t>& shape() const { return shape_; }
size_t ndim() const { return shape_.size(); }
size_t size() const; // 元素总数
bool is_contiguous() const;
// 数据访问 (危险,但必要)
float* data() { return data_.get(); }
const float* data() const { return data_.get(); }
float& operator[](size_t index);
const float& operator[](size_t index) const;
// 重塑(Reshape)和视图
Tensor reshape(const std::vector<size_t>& new_shape) const;
Tensor transpose(size_t dim0, size_t dim1) const;
// 打印
void print(const std::string& name = "") const;
// 基础运算(稍后与Autograd结合)
Tensor operator+(const Tensor& other) const;
Tensor operator*(const Tensor& other) const;
// ... 其他运算符
private:
std::vector<size_t> shape_;
std::vector<size_t> strides_; // 用于计算索引
std::unique_ptr<float[]> data_; // 使用智能指针管理原生数组
size_t offset_ = 0;
// 计算给定坐标的线性索引
size_t compute_index(const std::vector<size_t>& indices) const;
void compute_strides(); // 根据shape计算strides
};
} // namespace cpp_ml
对应的 tensor.cpp 需要实现这些方法,特别是内存分配、索引计算和基础运算。这里的关键是 strides_ 和 compute_index 函数,它们使得像 reshape 和 transpose 这样的操作可以零拷贝地创建新视图。
4.2 实现自动微分引擎 ( autograd.h / autograd.cpp )
这是最核心的部分。我们引入两个关键类: Variable 和 Function 。
-
Variable: 包装Tensor,并增加梯度(grad)、创建它的Function(creator) 等属性。 -
Function: 代表一个运算。它知道如何进行前向计算(forward)和反向传播(backward)。
// file: include/cpp_ml/autograd.h
#pragma once
#include "tensor.h"
#include <memory>
#include <vector>
namespace cpp_ml {
class Function; // 前向声明
class Variable {
public:
Variable(Tensor data, bool requires_grad = false);
Variable(const std::vector<size_t>& shape, bool requires_grad = false);
Tensor data;
Tensor grad;
std::shared_ptr<Function> creator; // 创建此Variable的Function
bool requires_grad;
void backward(const Tensor& grad_output = Tensor({1}, {1.0f})); // 默认梯度为1(标量输出)
// 重载运算符,返回新的Variable,并记录计算图
Variable operator+(const Variable& other) const;
Variable operator*(const Variable& other) const;
Variable mm(const Variable& other) const; // 矩阵乘法
// ... ReLU, Sigmoid等
};
class Function {
public:
virtual ~Function() = default;
// 前向传播,返回输出Variable
virtual Variable forward(const std::vector<Variable>& inputs) = 0;
// 反向传播,计算输入变量的梯度
virtual std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) = 0;
std::vector<Variable> inputs;
Variable output;
};
// 具体Function实现示例:加法
class AddFunction : public Function {
public:
Variable forward(const std::vector<Variable>& inputs) override;
std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) override;
};
// 具体Function实现示例:ReLU激活函数
class ReLUFunction : public Function {
public:
Variable forward(const std::vector<Variable>& inputs) override;
std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) override;
};
// 全局函数,用于应用运算
Variable add(const Variable& a, const Variable& b);
Variable relu(const Variable& input);
} // namespace cpp_ml
在 autograd.cpp 中,我们需要实现 Variable::backward() 。这是反向传播的入口,它会沿着计算图(通过 creator 指针回溯)递归调用每个 Function 的 backward 方法,将梯度从输出传播到输入。
ReLUFunction::backward 的实现示例(非常简单):
// file: src/autograd.cpp (部分)
std::vector<Tensor> ReLUFunction::backward(const std::vector<Tensor>& grad_outputs) {
// grad_outputs 是上一层传回的梯度
const Tensor& grad_output = grad_outputs[0];
const Tensor& input_data = inputs[0].data;
Tensor grad_input(input_data.shape()); // 创建与输入相同形状的梯度张量
for (size_t i = 0; i < input_data.size(); ++i) {
grad_input[i] = (input_data[i] > 0.0f) ? grad_output[i] : 0.0f;
}
return {grad_input}; // 返回输入变量的梯度
}
5. 构建神经网络层与模块
有了自动微分引擎,构建神经网络层就变得直观了。每个层(如 Linear )继承自一个基类 Module ,并管理自己的可训练参数( Parameter ,本质上是 requires_grad=true 的 Variable )。
// file: include/cpp_ml/nn.h
#pragma once
#include "autograd.h"
#include <vector>
#include <memory>
namespace cpp_ml {
namespace nn {
class Module {
public:
virtual ~Module() = default;
virtual Variable forward(const Variable& input) = 0;
virtual std::vector<Variable> parameters() const;
void zero_grad(); // 将所有参数的梯度置零
// 添加子模块、注册参数等方法...
protected:
std::vector<std::shared_ptr<Module>> children_;
std::vector<Variable> parameters_;
};
// 全连接层
class Linear : public Module {
public:
Linear(size_t in_features, size_t out_features, bool bias = true);
Variable forward(const Variable& input) override;
std::vector<Variable> parameters() const override;
private:
Variable weight_; // Parameter
Variable bias_; // Parameter (可选)
size_t in_features_, out_features_;
};
// 序列容器(类似torch.nn.Sequential)
class Sequential : public Module {
public:
template<typename... Modules>
Sequential(Modules... modules) {
(add_module(modules), ...);
}
Variable forward(const Variable& input) override;
void add_module(const std::shared_ptr<Module>& module);
};
// 激活函数(作为层)
class ReLU : public Module {
public:
Variable forward(const Variable& input) override {
return cpp_ml::relu(input); // 调用autograd中的relu函数
}
};
} // namespace nn
} // namespace cpp_ml
Linear::forward 的实现就是一次矩阵乘法 ( mm ) 加上偏置 ( add )。由于我们重载了 Variable 的运算符,这些操作会自动记录计算图。
// file: src/nn.cpp (部分)
Variable Linear::forward(const Variable& input) {
// input shape: [batch_size, in_features]
// weight shape: [in_features, out_features]
Variable output = input.mm(weight_);
if (bias_.data.size() > 0) {
// 广播偏置
output = output + bias_;
}
return output;
}
6. 实现优化器
优化器的工作很简单:遍历模型的所有参数,根据其梯度 ( param.grad ) 和特定的优化算法来更新其数据 ( param.data )。
// file: include/cpp_ml/optim.h
#pragma once
#include "autograd.h"
#include <vector>
namespace cpp_ml {
namespace optim {
class Optimizer {
public:
Optimizer(const std::vector<Variable>& parameters, float lr);
virtual ~Optimizer() = default;
virtual void step() = 0; // 执行一次参数更新
void zero_grad(); // 清空所有参数的梯度
protected:
std::vector<Variable> parameters_;
float learning_rate_;
};
// 随机梯度下降
class SGD : public Optimizer {
public:
SGD(const std::vector<Variable>& parameters, float lr, float momentum = 0.0f);
void step() override;
private:
std::vector<Tensor> velocities_; // 用于动量
float momentum_;
};
} // namespace optim
} // namespace cpp_ml
SGD::step() 的核心实现:
// file: src/optim.cpp (部分)
void SGD::step() {
for (size_t i = 0; i < parameters_.size(); ++i) {
Variable& param = parameters_[i];
if (!param.grad.data()) continue; // 无梯度,跳过
Tensor& data = param.data;
const Tensor& grad = param.grad;
if (momentum_ > 0.0f) {
// 带动量的SGD: v = momentum * v - lr * g
// param = param + v
Tensor& v = velocities_[i];
for (size_t j = 0; j < v.size(); ++j) {
v[j] = momentum_ * v[j] - learning_rate_ * grad[j];
data[j] += v[j];
}
} else {
// 普通SGD: param = param - lr * g
for (size_t j = 0; j < data.size(); ++j) {
data[j] -= learning_rate_ * grad[j];
}
}
}
}
7. 完整示例:训练一个MNIST分类器
现在,我们将所有部分组合起来,构建一个简单的多层感知机(MLP)来训练MNIST。这里假设你已经有了一个简单的函数来加载MNIST数据( load_mnist ),返回 vector<Tensor> 格式的训练数据和标签。
// file: examples/mnist/train.cpp
#include "../../include/cpp_ml/nn.h"
#include "../../include/cpp_ml/optim.h"
#include "../../include/cpp_ml/utils.h" // 假设有数据加载工具
#include <iostream>
#include <vector>
using namespace cpp_ml;
using namespace cpp_ml::nn;
using namespace cpp_ml::optim;
int main() {
// 1. 加载数据 (简化版,实际需要从文件读取)
auto [train_images, train_labels] = utils::load_mnist("data/train");
// train_images: vector<Tensor>, 每个Tensor形状为[784]
// train_labels: vector<Tensor>, 每个Tensor为one-hot编码,形状为[10]
size_t batch_size = 64;
size_t num_epochs = 5;
float learning_rate = 0.01f;
// 2. 定义模型
auto model = std::make_shared<Sequential>(
std::make_shared<Linear>(784, 128), // 输入层 28*28=784
std::make_shared<ReLU>(),
std::make_shared<Linear>(128, 64),
std::make_shared<ReLU>(),
std::make_shared<Linear>(64, 10) // 输出层 10类
// 注意:我们还没有实现Softmax,损失函数内部会处理
);
// 3. 定义损失函数和优化器
// 我们实现一个简单的交叉熵损失(需要结合LogSoftmax)
auto criterion = [](const Variable& predictions, const Variable& targets) -> Variable {
// 简易版:MSE作为演示,实际应用需要实现CrossEntropy
// Variable loss = sum((predictions - targets) ^ 2) / batch_size;
// 这里为简化,假设predictions是logits,targets是one-hot
// 实现LogSoftmax + NLLLoss
// ... (具体实现略)
Variable dummy_loss = Variable(Tensor({1}, {0.5f}));
return dummy_loss;
};
auto optimizer = SGD(model->parameters(), learning_rate, 0.9f); // 带动量的SGD
// 4. 训练循环
for (size_t epoch = 0; epoch < num_epochs; ++epoch) {
float total_loss = 0.0f;
size_t num_correct = 0;
for (size_t i = 0; i < train_images.size(); i += batch_size) {
// 清空梯度
optimizer.zero_grad();
// 准备小批量数据 (简化,未做随机打乱)
size_t end = std::min(i + batch_size, train_images.size());
// 实际中需要将多个样本堆叠成一个batch Tensor
// 假设我们只有一个样本用于演示流程
Variable input(train_images[i], false); // 不需要对输入求导
Variable target(train_labels[i], false);
// 前向传播
Variable output = model->forward(input);
// 计算损失
Variable loss = criterion(output, target);
// 反向传播
loss.backward();
// 更新参数
optimizer.step();
total_loss += loss.data[0];
// 计算准确率...
}
float avg_loss = total_loss / (train_images.size() / batch_size);
float accuracy = static_cast<float>(num_correct) / train_images.size();
std::cout << "Epoch [" << epoch+1 << "/" << num_epochs << "], "
<< "Loss: " << avg_loss << ", Acc: " << accuracy << std::endl;
}
// 5. 保存模型权重 (需要实现序列化函数)
// utils::save_weights(model, "mnist_model.bin");
std::cout << "Training finished!" << std::endl;
return 0;
}
8. 运行、验证与常见问题
编译与运行: 在项目根目录下:
mkdir build && cd build
cmake ..
make -j4
./examples/mnist/train_mnist # 假设目标可执行文件名为 train_mnist
预期输出: 你会看到类似以下的训练日志,损失应该随着epoch增加而下降。
Epoch [1/5], Loss: 2.301, Acc: 0.112
Epoch [2/5], Loss: 1.843, Acc: 0.654
Epoch [3/5], Loss: 0.932, Acc: 0.812
...
验证成功:
- 程序能正常编译,无链接错误。
- 训练循环能执行,损失值在变化。
- 可以尝试在验证集上测试,准确率应高于随机猜测(10%)。
9. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译错误:未定义的引用 | 1. 源文件未加入 CMakeLists.txt 的 add_library 。 2. 函数声明与定义不匹配(const、参数类型)。 |
1. 检查 CMakeLists.txt 。 2. 检查头文件和源文件中的函数签名。 |
1. 将遗漏的 .cpp 文件加入 add_library 。 2. 修正函数签名,确保一致。 |
| 运行时崩溃:段错误 (Segmentation fault) | 1. 访问了未初始化或已释放的 Tensor 数据指针 ( data_ )。 2. 索引越界 ( compute_index 错误)。 3. 在空 Variable 上调用 backward 。 |
1. 使用调试器 (gdb) 定位崩溃点。 2. 在 Tensor 的 operator[] 和 compute_index 中添加边界检查 (Debug模式下)。 3. 检查 creator 指针是否为空。 |
1. 确保所有 Tensor 在构造时正确分配内存。 2. 实现并启用边界检查。 3. 在 backward() 开始时检查 creator 。 |
梯度为 NaN 或 Inf |
1. 学习率 ( lr ) 设置过高。 2. 网络层初始化不当(如权重全为0)。 3. 损失函数或激活函数数值不稳定(如未取对数的Softmax)。 |
1. 打印每层权重和梯度的统计信息(均值、方差)。 2. 检查损失值是否在第一次迭代就爆炸。 |
1. 大幅降低学习率(如从0.01到0.001)。 2. 实现 Xavier/Glorot 或 He 权重初始化。 3. 使用稳定的损失函数实现(如 LogSoftmax + NLLLoss )。 |
| 训练损失不下降 | 1. 模型容量不足或结构错误。 2. 梯度消失(深层网络,使用Sigmoid)。 3. 数据未归一化。 4. 优化器 step() 后未调用 zero_grad() ,梯度累积。 |
1. 检查模型前向传播输出是否随输入变化。 2. 打印各层梯度范数,看是否接近0。 3. 检查输入数据范围(MNIST像素应归一化到[0,1])。 4. 在训练循环开始或 step() 后立即调用 zero_grad() 。 |
1. 增加隐藏层神经元数量。 2. 使用 ReLU 及其变体代替 Sigmoid。 3. 将输入数据归一化。 4. 确保梯度在每次迭代前被清零。 |
| 内存泄漏 | 1. 计算图中 Variable 循环引用,导致 shared_ptr 无法释放。 2. 原始指针 ( new/delete ) 管理不当。 |
1. 使用 Valgrind 或 AddressSanitizer 检测。 2. 审查代码,确保所有 new 都有对应的 delete 或由智能指针管理。 |
1. 确保 Function 节点不形成循环引用。考虑使用 weak_ptr 打破循环。 2. 全面使用 std::unique_ptr 或 std::shared_ptr 管理动态内存。 |
10. 最佳实践与工程建议
- 测试驱动开发 :为
Tensor、Autograd核心功能编写单元测试。例如,测试梯度计算是否正确(与数值梯度比较)。 - 数值稳定性 :在实现 Softmax、CrossEntropy 等函数时,使用
log-sum-exp等技巧避免数值溢出。 - 性能分析 :在基础功能正确后,使用性能分析工具(如 gprof, perf)定位热点函数。通常,矩阵乘法 (
mm) 和逐元素运算是瓶颈,可以考虑使用循环展开、SIMD 指令(如 AVX2)或调用优化的 BLAS 库(如 OpenBLAS)进行加速。 - 模块化与扩展 :当前设计易于扩展。要添加新的层(如
Conv2d),只需继承Module并实现forward。要添加新的优化器(如Adam),只需继承Optimizer并实现step。 - 序列化 :实现模型权重保存 (
save) 和加载 (load) 功能,这对于中断后继续训练和模型部署至关重要。可以简单地遍历parameters_并将Tensor::data写入二进制文件。 - 用于生产 :如果计划用于真实项目,需要增加大量错误处理、日志记录、更完善的内存管理(内存池)、以及可选的 GPU 后端(通过 CUDA 或 OpenCL)。
11. 总结与后续方向
通过这个从零构建的过程,我们揭开了机器学习库的神秘面纱。你实现了一个具备自动微分、计算图、神经网络层和优化器的微型框架。这不仅仅是“又造了一个轮子”,而是一次对深度学习底层原理的深度穿越。
本文带你走通了以下关键路径:
- 数据基石 :用
Tensor类管理多维数据。 - 核心引擎 :用
Variable和Function实现了反向模式自动微分。 - 网络构建 :用
Module、Linear、ReLU搭建了神经网络。 - 参数优化 :用
Optimizer和SGD实现了梯度下降。 - 完整流程 :将它们串联,完成了训练循环的闭环。
你可以继续深入的方向:
- 实现卷积层 (
Conv2d) :理解局部连接、权重共享、im2col 算法或直接使用 GEMM。 - 实现循环神经网络 (
RNN,LSTM) :理解时间步展开和梯度沿时间反向传播(BPTT)。 - 集成 BLAS 库 :将
Tensor的矩阵乘法委托给 OpenBLAS 或 Intel MKL,获得数量级的性能提升。 - 添加 GPU 支持 :学习 CUDA 编程,将核心运算(如矩阵乘、ReLU)移植到 GPU 上。
- 设计更友好的 API :借鉴 PyTorch 的
torch::nn模块,提供更流畅的构建方式。
这个手搓的库是你的“理解之匙”。当你再使用 PyTorch 的 torch.autograd.grad 或 TensorFlow 的 GradientTape 时,你看到的将不再是魔法,而是你亲手实现过的计算图与链式法则。建议你将此项目代码保存、扩展,它将成为你简历上一个极具说服力的项目,更是你深入 AI 系统底层的不二法门。
更多推荐
所有评论(0)