为了从“第一原理”的角度更好地理解机器学习,我正在实现自己的 ML 相关功能。 目前我正在尝试实现 SoftMax:
IEnumerable<double> SoftMax(IEnumerable<double> vector)
{
var exps = vector.Select(v => Math.Exp(v));
var sumExps = exps.Sum();
return exps.Select(exp => exp / sumExps);
}
如果我正确理解 SoftMax,如果我对这个函数的结果求和,即
SoftMax(vector).Sum()
,输出应该始终是 1
。
然而,在几乎所有情况下,这都会返回一个略大于或略小于
1
的值。 一般来说,类似于 1.0568102998178908
或 0.9758570985704772
。
我听说这并不罕见(这基本上是一个溢出问题),并且可以通过从输入到
Math.Exp()
的值中减去输入的最大元素来修复。 所以我根据建议想出了这个实现:
IEnumerable<double> SoftMax(IEnumerable<double> vector)
{
var maxVal = vector.Max();
var exps = vector.Select(v => Math.Exp(v - maxVal));
var sumExps = exps.Sum();
return exps.Select(exp => exp / sumExps);
}
在测试我的实现时,我传递了随机的双精度集合,如下所示:
IEnumerable<double> vector = Enumerable.Range(0, 100).Select(n => new Random().NextDouble());
var softMaxVec = SoftMax(vector);
Console.WriteLine(softMaxVec.Sum());
但是我改进的实现仍然给我带来同样的问题。 我做错了什么? 或者我是否误解了 SoftMax 的工作原理?
问题出在您的测试代码中。
IEnumerable<double> vector = Enumerable.Range(0, 100).Select(n => new Random().NextDouble());
var softMaxVec = SoftMax(vector);
Console.WriteLine(softMaxVec.Sum());
由于延迟执行,如果多次迭代同一向量,则会得到不同的数字。例如
Random random = new Random();
IEnumerable<double> randomNumbers = Enumerable.Range(0, 3).Select(_ => random.NextDouble());
Console.WriteLine("First Iteration:");
foreach (var number in randomNumbers)
{
Console.WriteLine(number);
}
Console.WriteLine('\n')
foreach (var number in randomNumbers)
{
Console.WriteLine(number);
}
产生
0.63632565114476
0.440262559540692
0.254312404549826
0.566282945483123
0.348965338128137
0.983789030920616
在您的 softmax 实现中,您多次迭代向量,并且每次都有效地使用不同的随机数。
只需使用硬编码的测试用例来测试它,例如
SoftMax(new List<double> { 0.443426089567796, 0.234234, 0.343454, 0.3 });
这表明你的 softmax 工作得很好。一般来说,为了实现测试稳定性,编写使用随机输入的测试用例并不是一个好主意。