QuickCheck:生成平衡样本的嵌套数据结构的任意实例

Posted

技术标签:

【中文标题】QuickCheck:生成平衡样本的嵌套数据结构的任意实例【英文标题】:QuickCheck: Arbitrary instances of nested data structures that generate balanced specimens 【发布时间】:2013-04-11 21:40:15 【问题描述】:

tl;dr:如果您的数据类型允许太多嵌套,您如何编写不会爆炸的Arbitrary 实例?您如何保证这些实例产生真正随机的数据结构样本?

我想生成随机树结构,然后在我用我的库代码修改这些结构之后测试这些结构的某些属性。 (注意:我正在编写子类型算法的实现,即给定类型的层次结构,类型 A 是类型 B 的子类型。这可以通过在层次结构中包含多重继承和初始化后更新而变得任意复杂. 不支持这两种方法的经典方法是舒伯特编号,我知道的最新结果是Alavi et al. 2008。)

我们以玫瑰树为例,遵循Data.Tree

data Tree a = Node a (Forest a)
type Forest a = [Tree a]

Arbitray 的一个非常简单(并且不要在家尝试这个)的实例是:

instance (Arbitrary a) => Arbitrary (Tree a) where
    arbitrary = Node <$> arbitrary <$> arbitrary

由于根据类型约束,a 已经有一个 Arbitrary 实例,而 Forest 将有一个实例,因为 [] 也是一个实例,这看起来很简单。它不会(通常)由于非常明显的原因而终止:由于它生成的列表任意长,结构变得太大,并且它们很可能不适合内存。甚至更保守的方法:

arbitrary = Node <$> arbitrary <*> oneof [arbitrary,return []]

出于同样的原因,再次无法正常工作。可以调整 size 参数,以减少列表的长度,但即使这样也不能保证 终止,因为它仍然是多个连续的掷骰子,结果可能非常糟糕(并且我想要有 100 个孩子的奇数节点。)

这意味着我需要限制整个树的大小。这不是那么直截了当。 unordered-containers 很简单:只需使用 fromList。这在这里并不容易:你如何将列表随机地变成一棵树,并且不会以某种方式产生偏见(即不支持左分支或非常左倾的树。)

列表中的某种广度优先构造(Data.Tree 提供的函数都是预购的)会很棒,我想我可以写一个,但结果证明它不是微不足道的。由于我现在使用的是树,但稍后会使用更复杂的东西,我想我可能会尝试找到一个更通用、更简单的解决方案。有没有,或者我必须求助于编写我自己的非平凡的Arbitrary 生成器?在后一种情况下,我实际上可能只是求助于单元测试,因为这看起来工作量太大。

【问题讨论】:

Quickcheck manual 对此进行了解释并提供了有用的示例(尽管它们可能适用于 QuickCheck v1);请参阅标题为 "The size of test data" 和 "Generating recursive data types" 的部分。 这太好了,谢谢。我没有通读整本手册,但我当然应该阅读。我想这真的很难(对我来说)谷歌搜索。我认为这应该可以解决我的问题。 【参考方案1】:

使用sized:

instance Arbitrary a => Arbitrary (Tree a) where
arbitrary = sized arbTree

arbTree :: Arbitrary a => Int -> Gen (Tree a)
arbTree 0 = do
    a <- arbitrary
    return $ Node a []
arbTree n = do
    (Positive m) <- arbitrary
    let n' = n `div` (m + 1)
    f <- replicateM m (arbTree n')
    a <- arbitrary
    return $ Node a f

(改编自the QuickCheck presentation)。

附:也许这会生成过度平衡的树...

【讨论】:

很好的答案,谢谢。这基本上与手册中@sacundim 链接的内容一致。我希望这对其他人有用,因为答案似乎相对简单。 是的,我尝试从二叉树中调整它(部分原因是我意识到这也可以帮助我完成我的项目;)) 对“太平衡”问题的评论:不是以这种方式使用mapM,而是可以生成任意长度列表的随机分区,并将其作为生成孩子的基础。 Oleg 的随机列表洗牌可能会提供帮助。【参考方案2】:

您可能想要使用在 Haskell Symposium 2012 上发表的论文“Feat: Functional Enumeration of Algebraic Types”中介绍的库。它是关于 Hackage 的测试专长,这里有一段介绍它的演讲视频: http://www.youtube.com/watch?v=HbX7pxYXsHg

【讨论】:

【参考方案3】:

正如 Janis 提到的,您可以使用包 testing-feat,它创建任意代数数据类型的枚举。这是创建无偏均匀分布生成器的最简单方法 对于不超过给定大小的所有树。

下面是你将它用于玫瑰树的方法:

import Test.Feat (Enumerable(..), uniform, consts, funcurry)
import Test.Feat.Class (Constructor)
import Data.Tree (Tree(..))
import qualified Test.QuickCheck as QC

-- We make an enumerable instance by listing all constructors
-- for the type. In this case, we have one binary constructor:
-- Node :: a -> [Tree a] -> Tree a
instance Enumerable a => Enumerable (Tree a) where
    enumerate = consts [binary Node]
      where
        binary :: (a -> b -> c) -> Constructor c
        binary = unary . funcurry

-- Now we use the Enumerable instance to create an Arbitrary
-- instance with the help of the function:
-- uniform :: Enumerable a => Int -> QC.Gen a
instance Enumerable a => QC.Arbitrary (Tree a) where
    QC.arbitrary = QC.sized uniform
    -- QC.shrink = <some implementation>

Enumerable 实例也可以使用 TemplateHaskell 自动生成:

deriveEnumerable ''Tree

【讨论】:

此示例 sn-p 似乎与 2018 年 5 月 26 日的 testing-feat 1.1.0.0 不对应。您的示例似乎仅在破坏 API 更改前约 1.5 个月出现。也许这只是修复进口的问题,例如consts 现在仅在 Test.Feat.Class 中,依此类推。但是对 Test.Feat.Class 的弃用要求人们使用 Control.Enumerable 来代替。 导入 Constructor 不会出现在 1.1.0.0 中的任何位置,而是类型别名 type Constructor = Enumerate 在 0.4.0.3 中。有一个examples 目录,但缺​​少像这个这样简单的示例。

以上是关于QuickCheck:生成平衡样本的嵌套数据结构的任意实例的主要内容,如果未能解决你的问题,请参考以下文章

Swift函数式编程五(QuickCheck)

Swift函数式编程五(QuickCheck)

解决正负样本数据不平衡

数据预处理-非平衡样本的处理方式(SMOTE--待补充)

R语言中样本平衡的几种方法

sklearn.datasets.make_classification 无法生成平衡类