本项目旨在实现cuda完成基本的网络训练流程,以达到熟悉cuda/c++编程的目标,
目前完成了
- tensor的实现与基础运算符的自动微分,完成了线性层、relu,softmax层的正向模拟与反向传播,损失函数:mse与crossentropy的计算与反传
- 借鉴K神完成了与pytorch的相同的随机数生成器,可以固定一样的权重,用于对比此项目和pytorch的计算
- adam优化器和梯度下降优化器对梯度进行更新
- 可以对mnist数据集进行训练与推理
参考仓库:
https://github.com/leeroee/NN-by-Numpy
https://github.com/Phoenix8215/BuildCudaNeuralNetworkFromScratch
https://github.com/SmartFlowAI/LLM101n-CN/blob/master/micrograd/micrograd.cpp
仿照pytorch, 首先要定义tensor, 也是网络运算的基本单元
- 初始化: 正态分布,Xavier, 全零初始化
- 运算符重载以及基本的运算: 矩阵之间的逐元素计算或者矩阵与标量之间的逐元素计算,以及右移运算符重载
- 自动微分的实现,记录张量的生成路径、梯度和反向传播函数,用于梯度沿着路径反向传播
目前只实现二维张量, 定义如下(省略版)
template<typename T>
class Tensor{
public:
bool requires_grad;//是否需要计算梯度
//数据形状
int rows, cols;
//数据指针
std::shared_ptr<T> data_host;
std::shared_ptr<T> data_device;
std::shared_ptr<Tensor<T>> grad; //当前节点的梯度(requires_grad==true时)
std::unordered_set<std::shared_ptr<Tensor<T>>> prev; //前驱节点, 也就是当前节点是由哪些节点计算的得到的
std::function<void(std::shared_ptr<Tensor<T>>)> grad_fn = [this](std::shared_ptr<Tensor<T>>) { };; //当前节点的反向传播函数,用来更新前驱节点的梯度
Tensor(int rows, int cols):rows(rows), cols(cols){}{};
//开辟空间
void allocate(){};
//数据转移
void copyHostToDevice(){};
void copyDeviceToHost(){};
//运算符重载
Tensor operator+(Tensor& other){}
Tensor operator-(Tensor& other){}
}forward:
backward:
这里的左乘右乘可能会一头雾水,说白了就是要对应元素相乘,矩阵的元素用偏导数的形式写出来就会懂了。
pytorch的线性层为什么不是y=W*x+b 而是 y = x*W^T+b?
说白了就是把列向量当成行向量
行优先符合主流的编程规范,比如sum, max, 所以pytorch将优先的行向量作为特征,行数为批次, 并且按道理存数据时,单个数据内存要连续,如果行数为特征,列数为批次的话,单个数据的不同特征内存不连续
所以本项目实现的是 y = x*W^T+b , 当然作为对比 y=W*x+b 也实现了
反向传播之一:softmax函数 SoftMax反向传播推导,简单易懂,包教包会
forward:
backward:
其中:
-
$y_i$ 是真实值 -
$\hat{y}_i$ 是预测值 -
$n$ 是数据点的数量
对
多类分类问题,对于一个单一样本的交叉熵损失函数,假设真实标签是
对于多类交叉熵损失函数,对预测概率
十分钟搞明白Adam和AdamW,SGD,Momentum,RMSProp,Adam,AdamW
遇到的一些坑以及解决方法
- MSE的反向传播要除以元素个数,否则回传的梯度过大,导致梯度爆炸
- 线性层的定义注意是Y=XW+b, 为了符合编程行优先,对注意各个参数的偏导数计算
- 神经网络权重的初始化,正态分布,如果方差过大同样导致梯度爆炸,参考torch的初始化,0均值0.01方差
- shared_ptr和函数声明周期一样,函数结束自动释放内存,如果要保存就要在其他地方指向这块内存!
- 在adam更新梯度时,同样进行了tensor的运算,但不保存梯度!!计算更新量时中间变量数量级比较小,所以使用double, 权重参数可能会使用float, 故要进行重载运算符
- 对于mnist数据集, 如果bs=1,也就是一维一维的进行训练,无法收敛
TODO
- 使用CmakeLists.txt对项目进行配置
- cuda端和cpu端的区分,控制Tensor在cpu上计算或者在gpu上计算
- 模型的导出与读取
- softmax二维的backward
- 更多层的实现