我刚刚创建了我的第一个神经网络,它使用梯度方法和反向传播学习算法。激活函数使用的是双曲正切函数。代码经过了充分的单元测试,所以我满怀希望这个网络能够正常工作。接着我决定创建一个集成测试,并尝试教我的网络解决一些非常简单的函数。基本上我在测试权重是否有改进(因为这是一个非常小的网络——只有一个输入和一个神经元,所以只有一个权重)。
// 大于1的值的负号组合 [TestCase(8, 4)] // 失败原因1 [TestCase(-8, 4)] // 失败原因1 [TestCase(8, -4)] // 失败原因1 [TestCase(-8, -4)] // 失败原因1 // 小于1的值的负号组合 [TestCase(.8, .4)] // 通过 [TestCase(-.8, .4)] // 失败原因2 [TestCase(.8, -.4)] // 失败原因2 [TestCase(-.8, -.4)] // 通过 // 一个值大于1,另一个值小于1的负号组合 [TestCase(-.8, 4)] // 失败原因2 [TestCase(8, -.4)] // 失败原因2 // 一个值大于1,另一个值小于1的组合 [TestCase(.8, 4)] // 通过 [TestCase(8, .4)] // 失败原因1 public void ShouldImproveLearnDataSetWithNegativeExpectedValues(double expectedOutput, double x) { var sut = _netBuilder.Build(1, 1); // 一个输入,只有一个层和一个输出 sut.NetSpeedCoefficient = .9; for (int i = 0; i < 400; i++) { sut.Feed(new[] { x }, new[] { expectedOutput }); } var postFeedOutput = sut.Ask(new[] { x }).First(); var postFeedDifference = Math.Abs(postFeedOutput - expectedOutput); postFeedOutput.Should().NotBe(double.NaN); postFeedDifference.Should().BeLessThan(1e-5); }
我非常失望,因为大多数测试用例都失败了(只有标记为’// 通过’的3个通过了)。我深入研究了代码,发现了一些有趣的事实。
- 双曲正切函数的最大值是1。因此,无论权重乘以输入的总和有多大,神经元的输出绝对值总是<=1。换句话说,如果函数的返回绝对值大于1,网络将永远无法学会解决该函数。这解释了所有预期输出为8、-8的测试用例失败的原因。
- 在其中一个数字为负的测试用例中,最终权重也应该是负的。首先它会减少,但它永远不会变成负数。它要么在0附近停止,要么在0附近来回跳动。
神经网络只能解决输入值和预期输出值在0到1之间的问题,还是我的实现有问题?
回答:
神经网络可以有其他输出。如果你想要离散输出(分类),可以使用Softmax回归。如果你想要连续输出(回归),那么你需要在你的输出范围(最小值,最大值)和(0,1)之间创建一个双射映射。在大多数情况下,映射f:(min,max)->(0,1),f(x) = (x-min)/(max-min)就足够了。
在其中一个数字为负的测试用例中,最终权重也应该是负的
为什么最终权重也应该是负的?
你可以使用任何数字作为输入。(虽然将特征标准化到较小的范围通常是好做法,通常是使它们具有0均值和1标准差)