C++ 实现神经网络
介绍
深度学习和神经网络最近几年如火如荼,各种NN层出不穷, 这年头程序员要没听说过CNN, RNN, 还真不好意思跟人打招呼。各种库也非常多,什么pyTorch, mxnet, theano, tensorflow, keras,cntk,各种听过的没听过的满天飞。都快是全民AI学习机器学习的节奏了,怪不得Nvidia的股票一直在涨。
虽然用C++ 实现神经网络会比较麻烦,不过如果注意到以下下的trick, 在用C++ 实现神经网络时会舒服很多,代码也会非常简明,核心代码在百行左右也不是问题。
- C++ 没有内置的向量,矩阵库,可以自己先轮几个Vector, Matrix类以及各种数学运算操作,最好搞成链式操作。
- 不要用index form来推导和实现BP算法,个人比较喜欢matrix form, 公式会非常紧凑,实现起来也不太容易出错
- 顶层框架设计,比如先设计几个基类Model类,数据库类,定义几个通用的接口。实现具体的算法时,只要实现特定接口即可。
我最近实现了一把基本的神经网络,包括LR和多层NN模型, 使用C++11, Visual Studio 2017开发,源码放在了https://github.com/speedmancs/ML上。
训练程序的基本框架
在基类Model中,训练的框架逻辑在Fit方法中,主要流程就是SGD或者batch训练, 对于不同的模型,需要实现其中的ClearGradient, Eval, ComputeGradient, Update等方法
1. Eval 也就是神经网络的forward过程,前向计算得到当前的参数下的预测结果 数学上就是 计算 E=f(θ)
2. ComputeGradient 是BP 计算参数导数的过程, ∂E∂θ
3. 再得到导数之后, 再Update更新参数。 θ=θ−η∂E∂θ
void Model::Fit(const DataSet& trainingSet, const DataSet& validateSet)
{
for (int i = 0; i < m_config.train_epoch; i++)
{
for (size_t j = 0; j < trainingSet.Size(); j++)
{
if (j % m_config.batchSize == 0) ClearGradient();
size_t pred = Eval(trainingSet.GetData(j));
ComputeGradient(trainingSet.GetData(j), trainingSet.GetTarget(j));
if (j % m_config.batchSize == m_config.batchSize - 1 || j == trainingSet.Size() - 1) Update();
}
}
}
在batch模式下训练时,ComputeGradient需要把batch个样例的所有导数加起来,最后平均一下后进行update。在一个新的batch开始之前,需要调用ClearGradient对存储这些参数导数的变量清零。以logistic regression为例, 其中W,b分别是权重参数和偏置项, dW, db分别是其导数。
void LRModel::ClearGradient()
{
db = 0;
dW = 0;
}
void LRModel::Update()
{
db.Div((float)m_config.batchSize);
dW.Div((float)m_config.batchSize);
b.Sub(m_config.learning_rate, db);
W.Sub(m_config.learning_rate, dW);
}
Logistic Regression Model
有个上面的基本训练算法框架,实现具体的算法就很简单了,比如LR的模型是这样的, y 是OneHot的target, x是输入向量
Forward
Compute gradients