平均一个很长的列表[双]而不在 Scala 中获得无穷大

Posted

技术标签:

【中文标题】平均一个很长的列表[双]而不在 Scala 中获得无穷大【英文标题】:Averaging a very long List[Double] Without getting infinity in Scala 【发布时间】:2018-09-03 05:18:47 【问题描述】:

我有一个很长的双精度列表,我需要平均,但我无法在双精度数据类型中对它们求和,所以当我去除法时,我仍然得到无穷大。

 def applyToMap(list: Map[String, List[Map[String, String]]], f: Map[String, String]=>Double): Map[String,Double]=
   val mSLD = list.mapValues(lm=>lm.map(f))
   mSLD.mapValues(ld=> ld.sum/ld.size)


这给我留下了一个 Map[String, Double] 都是 Key -> Infinity

【问题讨论】:

你确定你真的溢出了Double吗?这通常有点难以实现......您可能会在这里和那里失去一些精确度,但是很难获得如此长的合理大小的值列表,以至于sum 溢出到Infinity。你确定不是f返回Infinity 【参考方案1】:

您可以使用 fold 来计算平均值。而不是sum / size,你应该用n计算你的方式,并为每个项目调整你的累加器acc = (acc * n/(n+1)) + (item * 1/(n+1))

这是一般的 scala 代码:

val average = seq.foldLeft((0.0, 1)) ((acc, i) => ((acc._1 + (i - acc._1) / acc._2), acc._2 + 1))._1

取自here。

如果列表真的很长,你可能仍然很难精确,因为你会被一个逐渐很大的数字除。为了真正安全起见,您应该将列表分成子列表,并计算子列表的平均值。确保子列表的长度都相同,或者根据它们的大小进行加权平均。

【讨论】:

(acc /(n+1) *n) + (item/(n+1)) 在达到无穷大(虽然可能会失去精度)方面不安全吗? 如果列表和问题所暗示的一样长,那很多乘法和除法会引入相当多的数字漂移...... @userunknown 是的。执行此操作的最佳方法取决于数字与数据类型的最小值或最大值的接近程度。我在对您的回答的评论中提供了另一种选择。 @AndreyTyukin 确实如此。我认为,一般来说,平均值(每个组的平均值通过所述折叠计算)方法可能是最好的。不过,弄清楚如何最好地拆分它可能会很棘手。 也许正在寻找两个较小 N 的最大幂?然后拆分,直到子列表的总和不能超过合理的double值?在找到最大部分的平均值后,对其余部分重复并跟踪如何加权其余部分(可能还有其余部分......)。【参考方案2】:

对实施 gandaliters 解决方案感兴趣,我想出了以下方法(由于我不是 Doubles 的知名朋友,我试图找到一个易于遵循的带有 Bytes 的数字序列)。首先,我在 75..125 范围内生成 10 个字节,以接近 MaxByte,但低于每个值,平均为 100,用于简单控制:

val rnd = util.Random   
val is=(1 to 10).map (i => (rnd.nextInt (50)+75).toByte)
// = Vector(99, 122, 99, 105, 102, 104, 122, 99, 87, 114)

第一个算法在除法之前相乘(这增加了超过 MaxByte 的危险),在乘法之前第二个除法,这会导致舍入错误。

def slidingAvg0 (sofar: Byte, x: Byte, cnt: Byte): (Byte, Byte) = 
    val acc : Byte = ((sofar * cnt).toByte / (cnt + 1).toByte + (x/(cnt + 1).toByte).toByte).toByte
    println (acc)
    (acc.toByte, (cnt + 1).toByte)


def slidingAvg1 (sofar: Byte, x: Byte, cnt: Byte): (Byte, Byte) = 
    val acc : Byte = (((sofar / (cnt + 1).toByte).toByte * cnt).toByte + (x/(cnt + 1).toByte).toByte).toByte
    println (acc)
    (acc.toByte, (cnt + 1).toByte)

这是 scala 中的 foldLeft:

((is.head, 1.toByte) /: is.tail)  case ((sofar, cnt), x) => slidingAvg0 (sofar, x, cnt)
110
21
41
2
18
32
8
16
0

scala> ((is.head, 1.toByte) /: is.tail)  case ((sofar, cnt), x) => slidingAvg1 (sofar, x, cnt)
110
105
104
100
97
95
89
81
83

由于 10 个值太少,无法依赖接近 100 的平均值,因此我们将总和视为 Int:

is.map (_.toInt).sum
res65: Int = 1053

漂移非常显着(应该是 105,是 0/83)

发现是否可以从 Bytes/Int 转移到 Doubles 是另一个问题。而且我不是 100% 有信心,我的大括号反映了评估顺序,但是恕我直言,对于相同优先级的乘法/除法,它是从左到右的。

原来的公式是:

acc = (acc * n/(n+1)) + (item * 1/(n+1))
acc = (acc /(n+1) *n) + (item/(n+1)) 

【讨论】:

这太棒了!我没有考虑过评估顺序——我给出的公式是为了弄清楚发生了什么,而不是为了保持精确。我认为如果数字可能接近最大值或最小值,最好先评估“n/(n+1)”,然后将其乘以 acc (等)。当然,这个数字需要是浮点数,否则它只会四舍五入,一切都会消失。 @gandaliter:嗯,great 被夸大了。看看几乎每一步的平均值是如何下降的?要么这样的技术需要仔细研究,要么应该考虑更好地使用均匀长度的子列表。 我指的是你的分析而不是结果!我怀疑这很奇怪,因为在每个阶段所有内容都被制成一个字节。如果 scala 只是地板翻倍到字节,那么难怪它每次都会变小。【参考方案3】:

如果我正确理解了 OP,那么数据量似乎不是问题,否则它将不适合内存。 所以我只关注数据类型。

总结

我的建议是使用BigDecimal 而不是Double。 特别是如果您要添加相当高的值。 唯一显着的缺点是性能和少量杂乱的语法。 或者,您必须预先重新调整输入,但这会降低精度,并且需要特别注意后期处理。

Double 在一定程度上中断

scala> :paste
// Entering paste mode (ctrl-D to finish)

val res0 = (Double.MaxValue + 1) == Double.MaxValue
val res1 = Double.MaxValue/10 == Double.MaxValue
val res2 = List.fill(11)(Double.MaxValue/10).sum
val res3 = List.fill(10)(Double.MaxValue/10).sum == Double.MaxValue
val res4 = (List.fill(10)(Double.MaxValue/10).sum + 1) == Double.MaxValue

// Exiting paste mode, now interpreting.

res0: Boolean = true
res1: Boolean = false
res2: Double = Infinity
res3: Boolean = true
res4: Boolean = true

在您的 scala REPL 中查看这些简单的 Double 算术示例:

    Double.MaxValue + 1 将在数字上取消并且不会添加任何内容,因此它仍然与 Double.MaxValue 相同 Double.MaxValue/10 行为符合预期,不等于 Double.MaxValue11 次添加Double.MaxValue/10 将导致Infintiy 溢出 为 10 次添加 Double.MaxValue/10 不会破坏算术并再次计算为 Double.MaxValue 求和的Double.MaxValue/10 的行为与Double.MaxValue 完全相同

BigDecimal 适用于所有规模,但速度较慢

scala> :paste
// Entering paste mode (ctrl-D to finish)

val res0 = (BigDecimal(Double.MaxValue) + 1) == BigDecimal(Double.MaxValue)
val res1 = BigDecimal(Double.MaxValue)/10 == BigDecimal(Double.MaxValue)
val res2 = List.fill(11)(BigDecimal(Double.MaxValue)/10).sum
val res3 = List.fill(10)(BigDecimal(Double.MaxValue)/10).sum == BigDecimal(Double.MaxValue)
val res4 = (List.fill(10)(BigDecimal(Double.MaxValue)/10).sum + 1) == BigDecimal(Double.MaxValue)

// Exiting paste mode, now interpreting.

res0: Boolean = false
res1: Boolean = false
res2: scala.math.BigDecimal = 197746244834854727000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
res3: Boolean = true
res4: Boolean = false

现在将这些结果与上面来自Double 的结果进行比较。 如您所见,一切正常。

重新缩放会降低精度并且可能很乏味

在使用天文或微观尺度时,可能会发生数字快速溢出或下溢的情况。 然后与基本单位以外的其他单位合作来弥补这一点是适当的。 例如。用公里代替米。 但是,在公式中乘以这些数字时,您必须特别小心。

10km * 10km ≠ 100 km^2

而是

10,000 m * 10,000 m = 100,000,000 m^2 = 100 Mm^2

所以请记住这一点。

另一个陷阱是在处理非常多样化的数据集时,其中数字以各种规模和数量存在。 缩小输入域时,您将失去精度,并且可能会取消少量数字。 在某些情况下,不需要考虑这些数字,因为它们的影响很小。 但是,当这些小数字高频率存在并一直被忽略时,最终会引入很大的误差。

所以也要记住这一点;)

希望对你有帮助

【讨论】:

以上是关于平均一个很长的列表[双]而不在 Scala 中获得无穷大的主要内容,如果未能解决你的问题,请参考以下文章

为什么我在UWP列表框中获得双击事件而不是单个事件?

每小时时间序列上列的平均值

如何使用 Pytorch 中的截断反向传播(闪电)在很长的序列上运行 LSTM?

在元组列表(SCALA)中将所有具有相同日期的双精度加起来

颤振错误:RangeError(索引):无效值:不在0..2范围内,包括:3

使用Numpy而不在数组中获得额外的尺寸