神经网络中的反向传播算法
Backpropagation algorithm in neural network
我在神经网络中实现反向传播时遇到了一些麻烦。此实现使用来自 Andrew Ng 的 Coursera 机器学习课程幻灯片中的想法(这里是 link https://www.coursera.org/course/ml)。我认为我已经理解了算法,但是代码中有一些细微的错误。
我使用的网络有 1 个输入层、1 个隐藏层和 1 个输出层。它们分别有 2 + 1、2 + 1、1 个神经元(+1 表示偏差)。
当我尝试实现逻辑与和逻辑或时,一切都很好,网络学会了给出正确的值。但后来我尝试实现 XNOR (a XNOR b = NOT (a XOR b)).
我用了 4 个例子:
- 0 0 1
- 0 1 0
- 1 0 0
- 1 1 1
但是突然间,在这个函数上梯度没有任何变化。一开始我用随机的小数字(从 -0.01 到 0.01)初始化权重。输出接近 0.5。然后我在做梯度下降。任何输入的输出仍然始终接近 0.5。
我想知道如何解决这个问题。
代码如下:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
// contains matrix and Vector classes.
// Vector is just like std::valarray, but is compatible with my matrix.
#include "matrix.hpp"
size_t L;
std::vector< Vector<double> > layers;
std::vector< matrix<double> > theta;
struct Example
{
Vector<double> x;
Vector<double> y;
};
using TrainingSet = std::vector<Example>;
TrainingSet examples;
double g(double x)
{
return 1 / (1 + exp(-x));
}
void forwardPropagate(Vector<double> x)
{
for ( size_t i = 1; i < layers[0].size(); ++i )
layers[0][i] = x[i - 1];
for ( size_t i = 0; i < L - 1; ++i )
{
auto z = theta[i] * layers[i];
for ( size_t j = 1; j < layers[i + 1].size(); ++j )
layers[i + 1][j] = g(z[j - 1]);
}
}
void backwardPropagate(Vector<double> y, std::vector< matrix<double> >& delta)
{
auto err = layers.back().slice(1) - y;
for ( int i = L - 2; i >= 0; --i )
{
delta[i] += asMatrix(err) * asMatrix(layers[i]).transpose();
auto gdz = layers[i] * (Vector<double>(layers[i].size(), 1.0) - layers[i]);
auto tmp = theta[i].transpose() * err * gdz;
err = tmp.slice(1);
}
}
double costFunction(const TrainingSet& examples)
{
double result = 0.0;
for ( const auto& example : examples )
{
std::cout << layers.back()[1] << '\n';
forwardPropagate(example.x);
for ( size_t k = 1; k < layers.back().size(); ++k )
{
auto h = layers.back()[k];
auto y = example.y[k - 1];
result += y * log(h) + (1 - y) * log(1 - h);
}
}
return (-result) / examples.size();
}
void computeGradient(std::vector< matrix<double> >& delta, const TrainingSet& examples)
{
for ( auto& m : delta )
m.fillWith(0);
for ( auto example : examples )
{
forwardPropagate(example.x);
backwardPropagate(example.y, delta);
}
for ( auto& m : delta )
m /= examples.size();
}
void gradientDescentStep(const std::vector< matrix<double> >& gradient)
{
const double alpha = 0.01;
for ( size_t i = 0; i < L - 1; ++i )
theta[i] -= alpha / examples.size() * gradient[i];
}
double gradientDescent(const TrainingSet& examples)
{
const double eps = 0.0000001;
double prev, cur;
cur = costFunction(examples);
size_t iterations = 0;
const size_t max_iterations = 200000000;
std::vector< matrix<double> > delta;
delta.reserve(L - 1);
for ( size_t i = 0; i < L - 1; ++i )
delta.emplace_back(theta[i].rows(), theta[i].cols());
do
{
prev = cur;
computeGradient(delta, examples);
gradientDescentStep(delta);
cur = costFunction(examples);
} while ( fabs(cur - prev) >= eps && iterations++ < max_iterations );
std::cout << "Made " << iterations << " iterations\n";
return cur;
}
int main()
{
std::ifstream fin("input.txt");
std::istream& in = fin;
std::cout.sync_with_stdio(false);
in >> L;
std::vector<size_t> architecture(L);
for ( size_t i = 0; i < L; ++i )
in >> architecture[i];
layers.reserve(L);
for ( size_t i = 0; i < L; ++i )
{
layers.emplace_back(1 + architecture[i]);
layers.back()[0] = 1;
}
const double eps = 0.01;
theta.reserve(L - 1);
for ( size_t i = 0; i < L - 1; ++i )
{
theta.emplace_back(layers[i + 1].size() - 1, layers[i].size());
theta[i].randomInitialize(eps);
}
size_t number_of_examples;
in >> number_of_examples;
examples.reserve(number_of_examples);
for ( size_t i = 0; i < number_of_examples; ++i )
{
auto x = Vector<double>(architecture.front());
auto y = Vector<double>(architecture.back());
for ( size_t j = 0; j < architecture.front(); ++j )
in >> x[j];
for ( size_t j = 0; j < architecture.back(); ++j )
in >> y[j];
examples.emplace_back(Example{x, y});
}
for ( auto example : examples )
{
forwardPropagate(example.x);
std::cout << layers.back()[1] << '\n';
}
for ( size_t i = 0; i < theta.size(); ++i )
std::cout << "θ[" << i << "] = " << theta[i];
gradientDescent(examples);
for ( size_t i = 0; i < theta.size(); ++i )
std::cout << "θ[" << i << "] = " << theta[i];
std::cout << "\n\n\n";
for ( auto example : examples )
{
forwardPropagate(example.x);
std::cout << layers.back()[1] << '\n';
}
return 0;
}
我终于意识到哪里不对了。问题不在代码本身。问题是这种具有 XOR 的网络配置的成本函数具有局部最小值。所以,我来到那里并被困住了。
解决办法是在随机方向上迈出一步,直到你走出局部最小值。它可以让你很快达到全局最小值。
一般来说,随机初始化的权重在~[-3, 3]的范围内。初始较大的误差(通过具有较大的权重)有助于 "jump" 权重收敛到适当的区域。是的,XOR 问题具有局部最小值,但您的网络应该能够轻松地收敛到只有几个隐藏节点的正确答案。
你不应该 "need" 走出局部最小值,它应该很容易收敛到正确的最优值。网络结构为 [2,2,1] 的异或问题具有如此小的权重,您基本上可以 "random initialization" 您的权重相对快速地达到正确的最优值。 (因为搜索space小)
注意/编辑 如果您的网络只有 2 个隐藏节点,这是一个很好的改变,它会卡在局部最小值处。即使是 ~[2,7,1] 大小的网络也应该能够在没有随机步骤的情况下收敛。
我在神经网络中实现反向传播时遇到了一些麻烦。此实现使用来自 Andrew Ng 的 Coursera 机器学习课程幻灯片中的想法(这里是 link https://www.coursera.org/course/ml)。我认为我已经理解了算法,但是代码中有一些细微的错误。
我使用的网络有 1 个输入层、1 个隐藏层和 1 个输出层。它们分别有 2 + 1、2 + 1、1 个神经元(+1 表示偏差)。 当我尝试实现逻辑与和逻辑或时,一切都很好,网络学会了给出正确的值。但后来我尝试实现 XNOR (a XNOR b = NOT (a XOR b)).
我用了 4 个例子:
- 0 0 1
- 0 1 0
- 1 0 0
- 1 1 1
但是突然间,在这个函数上梯度没有任何变化。一开始我用随机的小数字(从 -0.01 到 0.01)初始化权重。输出接近 0.5。然后我在做梯度下降。任何输入的输出仍然始终接近 0.5。
我想知道如何解决这个问题。
代码如下:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
// contains matrix and Vector classes.
// Vector is just like std::valarray, but is compatible with my matrix.
#include "matrix.hpp"
size_t L;
std::vector< Vector<double> > layers;
std::vector< matrix<double> > theta;
struct Example
{
Vector<double> x;
Vector<double> y;
};
using TrainingSet = std::vector<Example>;
TrainingSet examples;
double g(double x)
{
return 1 / (1 + exp(-x));
}
void forwardPropagate(Vector<double> x)
{
for ( size_t i = 1; i < layers[0].size(); ++i )
layers[0][i] = x[i - 1];
for ( size_t i = 0; i < L - 1; ++i )
{
auto z = theta[i] * layers[i];
for ( size_t j = 1; j < layers[i + 1].size(); ++j )
layers[i + 1][j] = g(z[j - 1]);
}
}
void backwardPropagate(Vector<double> y, std::vector< matrix<double> >& delta)
{
auto err = layers.back().slice(1) - y;
for ( int i = L - 2; i >= 0; --i )
{
delta[i] += asMatrix(err) * asMatrix(layers[i]).transpose();
auto gdz = layers[i] * (Vector<double>(layers[i].size(), 1.0) - layers[i]);
auto tmp = theta[i].transpose() * err * gdz;
err = tmp.slice(1);
}
}
double costFunction(const TrainingSet& examples)
{
double result = 0.0;
for ( const auto& example : examples )
{
std::cout << layers.back()[1] << '\n';
forwardPropagate(example.x);
for ( size_t k = 1; k < layers.back().size(); ++k )
{
auto h = layers.back()[k];
auto y = example.y[k - 1];
result += y * log(h) + (1 - y) * log(1 - h);
}
}
return (-result) / examples.size();
}
void computeGradient(std::vector< matrix<double> >& delta, const TrainingSet& examples)
{
for ( auto& m : delta )
m.fillWith(0);
for ( auto example : examples )
{
forwardPropagate(example.x);
backwardPropagate(example.y, delta);
}
for ( auto& m : delta )
m /= examples.size();
}
void gradientDescentStep(const std::vector< matrix<double> >& gradient)
{
const double alpha = 0.01;
for ( size_t i = 0; i < L - 1; ++i )
theta[i] -= alpha / examples.size() * gradient[i];
}
double gradientDescent(const TrainingSet& examples)
{
const double eps = 0.0000001;
double prev, cur;
cur = costFunction(examples);
size_t iterations = 0;
const size_t max_iterations = 200000000;
std::vector< matrix<double> > delta;
delta.reserve(L - 1);
for ( size_t i = 0; i < L - 1; ++i )
delta.emplace_back(theta[i].rows(), theta[i].cols());
do
{
prev = cur;
computeGradient(delta, examples);
gradientDescentStep(delta);
cur = costFunction(examples);
} while ( fabs(cur - prev) >= eps && iterations++ < max_iterations );
std::cout << "Made " << iterations << " iterations\n";
return cur;
}
int main()
{
std::ifstream fin("input.txt");
std::istream& in = fin;
std::cout.sync_with_stdio(false);
in >> L;
std::vector<size_t> architecture(L);
for ( size_t i = 0; i < L; ++i )
in >> architecture[i];
layers.reserve(L);
for ( size_t i = 0; i < L; ++i )
{
layers.emplace_back(1 + architecture[i]);
layers.back()[0] = 1;
}
const double eps = 0.01;
theta.reserve(L - 1);
for ( size_t i = 0; i < L - 1; ++i )
{
theta.emplace_back(layers[i + 1].size() - 1, layers[i].size());
theta[i].randomInitialize(eps);
}
size_t number_of_examples;
in >> number_of_examples;
examples.reserve(number_of_examples);
for ( size_t i = 0; i < number_of_examples; ++i )
{
auto x = Vector<double>(architecture.front());
auto y = Vector<double>(architecture.back());
for ( size_t j = 0; j < architecture.front(); ++j )
in >> x[j];
for ( size_t j = 0; j < architecture.back(); ++j )
in >> y[j];
examples.emplace_back(Example{x, y});
}
for ( auto example : examples )
{
forwardPropagate(example.x);
std::cout << layers.back()[1] << '\n';
}
for ( size_t i = 0; i < theta.size(); ++i )
std::cout << "θ[" << i << "] = " << theta[i];
gradientDescent(examples);
for ( size_t i = 0; i < theta.size(); ++i )
std::cout << "θ[" << i << "] = " << theta[i];
std::cout << "\n\n\n";
for ( auto example : examples )
{
forwardPropagate(example.x);
std::cout << layers.back()[1] << '\n';
}
return 0;
}
我终于意识到哪里不对了。问题不在代码本身。问题是这种具有 XOR 的网络配置的成本函数具有局部最小值。所以,我来到那里并被困住了。
解决办法是在随机方向上迈出一步,直到你走出局部最小值。它可以让你很快达到全局最小值。
一般来说,随机初始化的权重在~[-3, 3]的范围内。初始较大的误差(通过具有较大的权重)有助于 "jump" 权重收敛到适当的区域。是的,XOR 问题具有局部最小值,但您的网络应该能够轻松地收敛到只有几个隐藏节点的正确答案。
你不应该 "need" 走出局部最小值,它应该很容易收敛到正确的最优值。网络结构为 [2,2,1] 的异或问题具有如此小的权重,您基本上可以 "random initialization" 您的权重相对快速地达到正确的最优值。 (因为搜索space小)
注意/编辑 如果您的网络只有 2 个隐藏节点,这是一个很好的改变,它会卡在局部最小值处。即使是 ~[2,7,1] 大小的网络也应该能够在没有随机步骤的情况下收敛。