《人工神经网络训练方法——随机查找 》介绍的随机查找方法,有点盲人摸象,所以继续介绍主流的后向传播(BackPropagation)算法。
填坑
先给随机查找做个优化!上篇中的激活函数统一使用 ReLU,其实这是不好的,输出层可以改为 Sigmoid 或 Tanh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 inline double ActivationFunction_ReLU(double x) { return std::max(0.0, x); } inline double ActivationFunction_Sigmoid(double x) { return 1.0 / (1 + exp(-x)); } inline double ActivationFunction_Tanh(double x) { return (tanh(x) + 1.0) / 2; } double AnnRun(const double x[2], double* w) { double f = ActivationFunction_ReLU(x[0] * w[0] + x[1] * w[1] - w[2]); double g = ActivationFunction_ReLU(x[0] * w[3] + x[1] * w[4] - w[5]); return ActivationFunction_Sigmoid(f * w[6] + g * w[7] - w[8]); }
原因很简单,我们已经知道 Xor 的结果不是 0 就是 1,用 ReLU 是可能大于 1 的,而 Sigmoid 和 Tanh 不会大于 1。
后向传播
理论学习:《如何直观地解释 back propagation 算法?》
原理:求导
训练时,x 和 y 都是固定的,要求的是 a 和 b,所以问题是:当 y 偏离了 delta_y,求 a 和 b 应该修正多少?
分别对 a 和 b 求偏导,则:
所以
1 2 delta_a = delta_y / x delta_b = delta_y
代码不会骗人,来一个简化的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // BackPropagation.cpp // #include <iostream> void Train(double& a, double& b, double input, double expect_output, double learning_rate) { double delta_y = expect_output - (input * a + b); if (input != 0) { a += (delta_y / input) * learning_rate; } b += delta_y * learning_rate; } int main() { // 要求的函数是:y = 2 * x + 3 const double input[4] = {0, 1, 2, 3}; const double expect_output[4] = {3, 5, 7, 9}; // 初始化状态是:y = 1 * x + 4 double a = 1.0; double b = 4.0; std::cout << "Initial: y = " << a << " * x + " << b << "\n"; // 两轮就搞定了 for (int t = 0; t < 2; ++t) { for (int i = 0; i < 4; ++i) { Train(a, b, input[i], expect_output[i], 1); } } std::cout << "Trained: y = " << a << " * x + " << b << "\n"; return 0; }