深度学习C++代码配套教程(4. ANN 经典神经网络)

导航栏

深度学习C++代码 (位于 Github)
深度学习C++代码配套教程(1. 总述)
深度学习C++代码配套教程(2. 基础数据操作)
深度学习C++代码配套教程(3. 数据文件读取)
深度学习C++代码配套教程(4. ANN 经典神经网络)
深度学习C++代码配套教程(5. CNN 卷积神经网络)


经典神经网络用 java 实现只需要70 行,点击访问. 我把它翻译成了 C++, 代码行数变成了若干倍.

1. 网络结构

神经网络由多层构成, 如图 1 所示. 我们先建立单层, 然后获得整个的网络.
深度学习C++代码配套教程(4. ANN 经典神经网络)_第1张图片图 1 神经网络

2. MfAnnLayer 类

每一层包括输入、输出, 因此图 1 仅有两个 MfAnnLayer.

2.1 成员变量

成员变量说明如下:

//The size of the input.
//第 1 层的 inputSize 为条件属性数.
int inputSize;
//The size of the output.
//最后 1 层的 outputSize 为类别数.
int outputSize;

//Learning rate, 一般为 0.001 之类。
double rate;
//Mobp, 控制惯性。
double mobp;

//The activator. 
Activator* activator;

//The input data, only one row, 实质为一个向量.
MfDoubleMatrix* inputData;
//The output data, only one row
MfDoubleMatrix* outputData;

//The weights for edges
//这是训练网络主要改变的数据.
MfDoubleMatrix* weightMatrix;

//The weight change for edges.
//将它作为成员变量, 可以避免临时空间分配.
MfDoubleMatrix* weightDeltaMatrix;

//The offset. 偏移量, 也称 bias. 为一个向量.
//与图 1 中 +1 连接的部分即为 offset.
MfDoubleMatrix* offsetMatrix;
//The offset delta
MfDoubleMatrix* offsetDeltaMatrix;

//The layer node error.
//back propagation 时使用.
MfDoubleMatrix* errorMatrix;

几点说明:

  1. activator 同时管理激活 (forward) 与求导 (backPropagation). 当前使用的 sigmoid, 对iris 数据集有效, 以后用其它激活函数测试.
  2. weightMatrix 和 offsetMatrix 是网络需要训练的参数.

2.2 成员函数

成员函数申明如下:

//The default constructor. 没用.
MfAnnLayer();
//Constructor for input/output size
MfAnnLayer(int paraInputSize, int paraOutputSize, char paraActivation,
         double paraRate, double paraMobp);
//Destructor.
virtual ~MfAnnLayer();

//Convert to string for display.
string toString();
//Show weights.
void showWeight();

//Setter.
void setActivationFunction(char paraActivation);
//Reset weight and other variables.
void reset();
//Getter.
int getInputSize();
//Getter.
int getOutputSize();

//Forward calculation
MfDoubleMatrix* forward(MfDoubleMatrix* paraData);

//Back propagation calculation
MfDoubleMatrix* backPropagation(MfDoubleMatrix* paraLabel);

//Code unit test
void unitTest();

多数函数都是常见的, 不需要特别说明.

2.2.1 forward

MfDoubleMatrix* MfAnnLayer::forward(MfDoubleMatrix* paraData)
{
     
    //将数据拷贝到 inputData, back propagation 时还要用.
    inputData->cloneToMe(paraData);

    //加权和, 就是这么直接.
    outputData->timesToMe(inputData, weightMatrix);
    //再加上偏移量. 也是矩阵操作.
    outputData->addToMe(outputData, offsetMatrix);
    //激活. outputData的激活函数在构造函数中已经初始化.
    outputData->activate();

    return outputData;
}//Of forward

这里完成的就是 y = σ ( w x + b ) \mathbf{y} = \sigma(\mathbf{wx}+\mathbf{b}) y=σ(wx+b). 在矩阵操作的支持下, 代码简直和数学表达式一样简单! 说明如下:

  1. 支持不同的激活函数, 而不仅仅是sigmoid (即 σ \sigma σ).
  2. y \mathbf{y} y 是本层的输出, 如果本层并非输出层, 它就是下层的输入.

2.2.2 backPropagation

MfDoubleMatrix* MfAnnLayer::backPropagation(MfDoubleMatrix* paraErrors)
{
     
    double tempValue1, tempValue2, tempValue3, tempValue4;
    double tempErrorSum;
    for(int i = 0; i < inputSize; i ++)
    {
     
        tempErrorSum = 0;

        //Weights adjusting
        for(int j = 0; j < outputSize; j ++)
        {
     
            tempErrorSum += paraErrors->getValue(0, j) * weightMatrix->getValue(i, j);
            tempValue1 = mobp * weightDeltaMatrix->getValue(i, j)
                + rate * paraErrors->getValue(0, j) * inputData->getValue(0, i);
            weightDeltaMatrix->setValue(i, j, tempValue1);

            tempValue2 = weightMatrix->getValue(i, j);
            weightMatrix->setValue(i, j, tempValue1 + tempValue2);

            if (i == inputSize - 1)
            {
     
                // Offset adjusting
                tempValue1 = offsetDeltaMatrix->getValue(0, j);
                tempValue2 = paraErrors->getValue(0, j);
                tempValue3 = mobp * tempValue1 + rate * tempValue2;
                offsetDeltaMatrix->setValue(0, j, tempValue3);
                tempValue4 = offsetMatrix->getValue(0, j);
                offsetMatrix->setValue(0, j, tempValue4 + tempValue3);
            }//Of if
        }//Of for j

        //For the activation function.
        tempValue1 = inputData->getValue(0, i);
        errorMatrix->setValue(0, i, activator->derive(tempValue1) * tempErrorSum);
    }//Of for i

    return errorMatrix;
}//Of backPropagation

几点说明:

  1. 权值都是在原有权值基础上进行小幅变动, 后者使用 mobp 与 rate 这两个超参数控制.
  2. offset 的处理稍的不同.
  3. 以求导 derive 函数结束本层 backPropagation.

3. MfFullAnn 类

把单层堆成多层.

3.1 成员变量

//Number of layers
int numLayers;
//Learning rate
double rate;
//Mobp
double mobp;

//Activation
char activation;

//The sizes of layers
MfIntArray* layerSizes;
//All layers
MfAnnLayer** layers;

//The output for current instance
MfDoubleMatrix* currentOutput;
//The current instance
MfDoubleMatrix* currentInstance;
//The decision of the current instance
MfDoubleMatrix* currentDecision;

//Number of correctly classified instances in the current round.
//It is used for statistics especially on cross-validation.
int numCorrect;

说明如下:

  1. rate 和 mobp 用于设置每层的相应参数, 不同的层参数相同, 这个估计不用变.
  2. 激活函数现在每层都相同, 以后可以扩展下支持不同设置. 这样的话, activation 将变成一个字符串, 其长度为层数.
  3. currentOutput 和 currentDecision 均为长度为 numClasses 的向量, 而不是整数. 如 currentOutput = [0.5, 0.7, 0.6], currentDecision = [0, 1, 0]. 这样分类是正确的, 但依然有error.

3.2 成员函数

成员函数有不少.

//The default constructor. 没啥用
MfFullAnn();
//The Ann with given sizes and activation function
MfFullAnn(MfIntArray* paraSizes, char paraActivation, double paraRate, double paraMobp);
//Destructor
virtual ~MfFullAnn();

//Convert to string for display
string toString();
//Set the activation function for the given layer
void setActivationFunction(int paraLayer, char paraFunction);
//Setter
void setRate(double paraRate);
//Setter
void setMobp(double paraMobp);
//Reset weights and other variables.
void reset();

//Forward layer by layer
MfDoubleMatrix* forward(MfDoubleMatrix* paraInput);

//Back propagation
void backPropagation(MfDoubleMatrix* paraTarget);

//Train the network with only one instance
void train(MfDoubleMatrix* paraX, int paraY);
//Train with a dataset
void train(MfDoubleMatrix* paraX, MfIntArray* paraY);
//Test with an instance
bool test(MfDoubleMatrix* paraX, int paraY);
//Test with a dataset
double test(MfDoubleMatrix* paraX, MfIntArray* paraY);

//Get the number of correctly classified instances in the current round
int getNumCorrect();

//Show weight of the network, not including the offset
void showWeight();

//Code unit test
void unitTest();

//Training/testing test
void trainingTestingTest();
//Cross validation test
void crossValidationTest();

然而, 值得一提的只有两个.

3.2.1 forward 函数

MfDoubleMatrix* MfFullAnn::forward(MfDoubleMatrix* paraInput)
{
     
    MfDoubleMatrix* tempData = paraInput;
    for (int i = 0; i < numLayers; i ++)
    {
     
        tempData = layers[i]->forward(tempData);
    }//Of for i
    currentOutput->cloneToMe(tempData);

    return currentOutput;
}//Of forward

逐层 forward, 把结果拷贝给 currentOutput. tempData 只是一个指针, 相应空间在各层内分配, 实际上为该层的 outputData 变量.

3.2.2 backPropagation 函数

void MfFullAnn::backPropagation(MfDoubleMatrix* paraTarget)
{
     
	//误差.
    paraTarget->subtractToMe(paraTarget, currentOutput);
    //求导.
    currentOutput->deriveToMe(currentOutput);
    //传递到激活函数左边.
    currentOutput->cwiseProductToMe(currentOutput, paraTarget);
    MfDoubleMatrix* tempLayerErrors = currentOutput;

    for (int i = numLayers - 1; i >= 0; i --)
    {
     
        tempLayerErrors = layers[i]->backPropagation(tempLayerErrors);
    }//Of for i
}//Of backPropagation

说明如下:

  1. 计算 Y − Y ′ Y - Y' YY;
  2. 计算 Y ′ Y' Y 对应的导数, 对于 sigmoid 为 Y ′ ( 1 − Y ′ ) Y'(1 - Y') Y(1Y);
  3. 计算 Y ′ ( 1 − Y ′ ) ( Y − Y ′ ) Y'(1 - Y')(Y - Y') Y(1Y)(YY);
  4. 这里的计算都是点对点的, 即涉及的矩阵 (实际上为一维向量) 大小均相同;
  5. 做好预处理, 就一层一层向前反馈.

4. 小结

于是经典神经网络就搭建好了.
forward 时, 激活是每层向右的结束操作; backPropagation 时, 求导是每层的向左的结束操作. 也就是说, 两个方向对层的定义稍有不同. 为什么这样设计, 我暂时还没想清楚.

点击进入下一节

你可能感兴趣的:(深度学习C++代码,深度学习,c++)