我已经阅读了不少关于神经网络及其通过反向传播进行训练的内容,主要是通过这门Coursera课程,并从这里和这里进行了额外的阅读。我以为自己已经很好地掌握了核心算法,但当我尝试构建一个通过反向传播训练的神经网络时,效果却不尽如人意,我不确定原因是什么。
代码是用C++编写的,目前还没有进行向量化处理。
我想构建一个简单的2输入神经元、1隐藏神经元、1输出神经元的网络来模拟AND函数。只是为了在转向更复杂的例子之前理解这些概念的工作原理。当我手动输入权重和偏置值时,我的前向传播代码是有效的。
float NeuralNetwork::ForwardPropagte(const float *dataInput){ int number = 0; // 将输入数据写入输入层 for ( auto & node : m_Network[0]) { node->input = dataInput[number++]; } // 对于网络中的每一层 for ( auto & layer : m_Network) { // 对于层中的每个神经元 for (auto & neuron : layer) { float activation; if (layerIndex != 0) { neuron->input += neuron->bias; activation = Sigmoid( neuron->input); } else { activation = neuron->input; } for (auto & pair : neuron->outputNeuron) { pair.first->input += static_cast<float>(pair.second)*activation; } } } return Sigmoid(m_Network[m_Network.size()-1][0]->input);}
这些变量的命名有些不太好,但基本上,neuron->outputNeuron是一个对的向量。第一个是指向下一神经元的指针,第二个是权重值。neuron->input是神经网络方程中的“z”值,即所有权重*激活值 + 偏置的总和。Sigmoid函数如下所示:
float NeuralNetwork::Sigmoid(float value) const{ return 1.0f/(1.0f + exp(-value));}
这两个函数似乎按预期工作。在网络上的每次传递后,所有’z’或’neuron->input’值都会重置为零(或在反向传播后)。
然后我按照下面的伪代码训练网络。训练代码会运行多次。
for trainingExample=0 to m // m = 训练样本数 执行前向传播计算hyp(x) 计算最后一层的成本delta delta = y - hyp(x) 使用输出层的delta计算所有层的delta 遍历网络根据此值调整权重 重置网络
实际代码如下:
void NeuralNetwork::TrainNetwork(const std::vector<std::pair<std::pair<float,float>,float>> & trainingData){ for (int i = 0; i < 100; ++i) { for (auto & trainingSet : trainingData) { float x[2] = {trainingSet.first.first,trainingSet.first.second}; float y = trainingSet.second; float estimatedY = ForwardPropagte(x); m_Network[m_Network.size()-1][0]->error = estimatedY - y; CalculateError(); RunBackpropagation(); ResetActivations(); } }}
反向传播函数如下所示:
void NeuralNetwork::RunBackpropagation(){ for (int index = m_Network.size()-1; index >= 0; --index) { for(auto &node : m_Network[index]) { // 再次说明,"outputNeuron"是下一层神经元及其相关权重的列表 for (auto &weight : node->outputNeuron) { weight.second += weight.first->error*Sigmoid(node->input); } node->bias = node->error; // 我不知道如何调整偏置,一些公式似乎指向这个。是否正确? } }}
成本计算如下:
void NeuralNetwork::CalculateError(){ for (int index = m_Network.size()-2; index > 0; --index) { for(auto &node : m_Network[index]) { node->error = 0.0f; float sigmoidPrime = Sigmoid(node->input)*(1 - Sigmoid(node->input)); for (auto &weight : node->outputNeuron) { node->error += (weight.first->error*weight.second)*sigmoidPrime; } } } }
我随机化权重并在数据集上运行它:
x = {0.0f,0.0f} y =0.0f x = {1.0f,0.0f} y =0.0f x = {0.0f,1.0f} y =0.0f x = {1.0f,1.0f} y =1.0f
当然,我不应该用同一数据集进行训练和测试,但我只是想让基本的反向传播算法运行起来。当我运行这段代码时,我看到权重/偏置如下:
Layer 0 Bias 0.111129 NeuronWeight 0.058659 Bias -0.037814 NeuronWeight -0.01820Layer 1 Bias 0.016230 NeuronWeight -0.104935Layer 2 Bias 0.080982
训练集运行后,输出层的delta的均方误差看起来像这样:
Error: 0.156954Error: 0.152529Error: 0.213887Error: 0.305257Error: 0.359612Error: 0.373494Error: 0.374910Error: 0.374995Error: 0.375000... 一直保持这个值...
最终的权重看起来像这样:(它们总是大致达到这个值)
Layer 0 Bias 0.000000 NeuronWeight 15.385233 Bias 0.000000 NeuronWeight 16.492933Layer 1 Bias 0.000000 NeuronWeight 293.518585Layer 2 Bias 0.000000
我承认,这可能看起来是学习神经网络的一种迂回方式,而且当前的实现非常不优化。但有人能发现我做出了无效假设,或者实现或公式有误的地方吗?
编辑
感谢关于偏置值的反馈,我停止将偏置应用于输入层,并停止将输入层通过Sigmoid函数。此外,我的Sigmoid prime函数是无效的。但网络仍然无法正常工作。我已经更新了上面的错误和输出,显示现在的情况。
回答:
我解决了我的问题(除了上述初始的偏置/Sigmoid prime问题)。我开始从权重中减去,而不是添加到权重中。在我查阅的资料中,他们在delta值计算中有一个负号,而我没有,但我保留了他们将否定值添加到权重的格式。此外,我对如何处理权重感到困惑,并误读了一个来源,该来源说将其分配给错误。现在我明白了直觉是将其视为正常权重,但乘以偏置常数1而不是z。在我添加这些更改后,迭代训练集大约1000次可以模拟简单的位运算表达式,如OR和AND。