随机、优先与权重(续)
随机、优先与权重(续)
写完上文《》后,我顺便写了一个 Python 版本的 ——毕竟我们这个人工智能组的主要编程语言是 Python。在测试的时候,我发现一个很糟糕的问题。 Damping 和 Invert 这两个主要算法,居然有缺陷,因为之前的测试逻辑不严谨,边界处理的问题没有暴露出来。 首先是以前的 damping 并不会选中第一个元素。这是一个很简单的计算问题,做一下平移就能解决
class Damping[T](val random: Random) extends Poker[T] { override def select(cards: Seq[T]): Option[Int] = { if (cards == null || cards.isEmpty) { return None } if (cards.size == 1) { return Some(0) } val range = Math.log(cards.size + 1) val value = Math.exp(random.nextDouble() * range) Some(Math.floor(value).intValue() - 1) } }
不过没有第一时间发现这个问题,确实是因为测试逻辑写的不严谨。过去的测试逻辑是执行完算法逻辑后,遍历计数器字典,打印结果。但是如果某个元素一次也没有被选中过,就不会出现在字典中,正确的逻辑应该是:
it should "select more elements while more nearer front user damping" in { val counter: mutable.Map[Int, Int] = new mutable.TreeMap[Int, Int]() val croupier = Croupier.damping[Int] for (_ <- 0 until 100000) { val element = croupier.randSelect(elements) element should not be None element.foreach { value => counter.put(value, counter.getOrElse(value, 0) + 1) } } for (num <- elements) { info(s"damping select $num times ${ counter.getOrElse(num, 0)}") } }
当然,在scaled/rank算法中,如果某一个元素的权重是0,那么它也不会被选中,这是符合设计的。 最后,我观察了 invert 的计算结果,感觉这个曲线还是过于陡峭了,对于(我自己)常见的业务来说,是一个太过激进的做法。于是我把 invert 改成了反向的damping。 相关的内容我已经在前文中做了修改,jaskell core、jaskell dotty 和 jaskell java8的相关修改也已经发布。不过测试代码不严谨导致问题没有及时发现,这倒是值得记一笔。 顺便说一下 Python 版本 pycroupier,这个版本的实现比较简单,因为 python 是动态类型,很多在静态类型中的设计都没有意义。rank/scale合并为rank,并且rank只要求是一个函数(或者实现了 __call__(self, item)方法的对象)。同样在Scala和Java版中用来构造 croupier 的静态(object)方法,也写成了函数。 再就是draw_one 抽取一个元素,draw 则提取一组,不提供分组为(selected, rest)的操作,因为 Python 的默认 list 并不提供只读保证,Python 的使用者也习惯了利用高度的动态特性。如果确实需要,可以将原有的 list 复制之后使用,这也是我实现 select 的方法。 也因为这个原因,使用 ZipRanked 类型时要注意,此时使用的 rank 函数需要返回整数,因为 ZipRank 要构造一个键为权重的字典,如果使用浮点数 rank,那么意喻着rank值来自一个稠密的实数区间,恐怕 zip 就失去了意义。 最后,pycroupier 的安装和使用非常简单,从 pypi 就可以安装:
pip install pycroupier
导入 pycroupier 包就可以使用
In [1]: import pycroupier In [2]: data = ["b", "e", "h", "d", "f"] In [3]: croupier = pycroupier.damping() In [4]: croupier.draw(data, 2) Out[4]: [d, b] In [5]:
上面这段代码演示了在 IPython 里执行 damping 选择的效果。 最后,如果大家有深入了解过 Python 的标准库,它提供了 shuffle、choice这样的基本操作,可以用于构造对列表的随机操作,但是这些操作都是基于平均概率的。