随机、优先与权重——非平均概率的选择工具

这篇具有很好参考价值的文章主要介绍了随机、优先与权重——非平均概率的选择工具。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

随机、优先与权重

动机

除了汇编语言这样的另类,常规的编程语言几乎都提供了按平均概率生成整数或者浮点数的标准库。这也是应用开发中非常基本的功能。不过,有时候我们需要一些关于随机性的更复杂的功能。
这种复杂性主要来自两个方面:非平均的随机分布和随机结果的使用方式。

非平均概率

标准库的随机算法,通常都是以一个平均概率的,生成(0,1)之间浮点数的函数,或者以一个生成[0, MAX_INT)区间的整数值的方法为基础,构造相关的算法(Java标准库的Random类型,nextInt 和 nextDouble是分别实现的,互不依赖),这就使得相关的随机数生成总是在已知区间内的平均概率。有时候我们会希望以非平均概率生成随机数。例如我手上有一个推荐系统,在我们从数据集中抽取推荐内容发送到客户端时,并不希望每个内容都有平均的被选择机会,肯定是希望权重更高,更靠前的内容,有更大的几率被选中。
一种常见的方法是计算出每个内容的权重——weight、rank、score或者其它什么名字,整数或者浮点数——然后根据这个权重去选择内容。但其实严格的按权重计算概率是一个相对比较奢侈的做法,很多时候我们未必需要一个严格按照权重分布的随机数,而是仅仅要求一个有序列表中的元素,排在更前面的元素总是有更大的几率被选中——这正是推荐系统常见的需求。

随机数的使用

很奇怪,C++ 的 STL 都提供了 random_shuffle 这样的针对集合的随机算法,但是Java没有,不但Java的标准库没有,scala的标准库,甚至 Apache Commons,都没有从一个容器中随机选择若干元素的方法,Apache Commons 的 RandomUtils 中包含 nextBoolean、nextBytes这样的随机内容构造,但是就是没有从一个有限集合中做随机选择的支持。但是对我的日常工作来说,随机数一个非常重要的用途,就是利用随机行为,生成一个数据列表的子集。有时候我需要一个从固定集合中反复采样,有时候我希望做一个随机分割,还有时候,我还要处理只读和可变的容器。
针对这两个方面的缺失,我回顾了这些年来遇到的各种相关问题,在 Jaskell Core 和 Jaskell Java 8 中各自加入了一组随机工具,用于对 Java 的 List 和 Scala 的 Seq、ListBuffer 做随机选择操作。

实现

随机算法和选择算法分离

我没有计划自己重写一个平均概率的随机算法,各种语言的标准库,一般来说提供的是足够高质量的伪随机数发生器,要超过这些实现并不容易。相反,基于这些基础工具,实现实用的非平均概率算法,是个可行的选择。
另一方面,如何根据随机数生成器构造出不同的选择行为,其实是独立于随机数生成器的逻辑。初步想到的包括:

  • 从列表中随机计算出一个索引位置
  • 从列表中返回一个随机元素,不改变原列表
  • 从列表中随机返回n个元素,不改变原列表
  • 从只读列表(这里指Scala的Seq)中取出一个元素,返回被选择的元素和列表的剩余部分
  • 从只读列表中选取n个元素,返回生成的随机元素集合和列表的剩余部分
  • 从可变列表中随机选取一个元素,返回元素时从原列表中删除
  • 从可变列表中随机选取n个元素,返回元素集合时从原列表中删除

以上这些功能,都应该能正确处理传入的参数的边界条件,包括空值和空列表,返回值也应该是安全的。
基于这些需要,我把选择组件和随机数发生器分开,前者名为 Croupier ,即赌场发牌的荷官,后者名为 Poker,即扑克牌 。对于 Croupier ,其行为是固定的,发牌过程是否公平(遵循平均概率)或者“作弊”,由 Poker 决定,如果我们选择了一个“不公平”的 Poker,那么发牌结果就是非平均概率的。
这样,我们就可以通过组合不同分布的随机数发生器,构造出不同需要的随机选择行为。这些组件不像我在近期工作中写的非平均概率算法那么紧凑,但是更通用。
后续的章节主要介绍 Scala 版本的实现——下班写点儿 Scala 是我的一个很重要的消遣。相比之下 Jaskell Java8 中的实现主要是为了未来可能的工作需要,一些重要的点我会提及。
为了保持一致性和使用上的安全便利,所有的随机方法返回值都是 Optional 或者 Seq ,确保不会产生空值问题。

Poker

因为 Croupier 的接口上使用了 Poker ,所以我们先从 Poker 开始了解。
Poker 其实就是根据给定的列表生成随机数。所以,它的接口定义是一个 SAM

trait Poker[T]{
	def select(cards: Seq[T]): Int
}

如果只是为了使用平均概率,它就只是简单的调用 Random 对象的 nextInt

  override def select(cards: Seq[T]): Option[Int] = {
    if (cards == null || cards.isEmpty) {
      return None
    }
    if (cards.size == 1) {
      return Some(0)
    }
    Some(random.nextInt(cards.size))
  }

在这里,我还处理了边界条件,除此之外只有一个 nextInt 调用。但是这里我们仍然要传入待抽取的集合而非仅仅它的尺寸,因为某些随机算法需要更详细的内容信息。
这个方法是 Fair 类型的 Poker 实现,这个类型中还有一些辅助方法,包括默认构造函数,和传入 Random 对象或随机种子数的构造方法等,这里不一一列举了,其它的几个 Poker 实现类型,我也都提供了对应的功能接口。这里我们主要讨论 select 方法的不同实现,对 Rank 和 Weight 的运用。但是在此之前,我先介绍 Croupier 类型。

Croupier

在讨论各种不同的 Poker 实现之前,我们先了解一下 Croupier ,这样我们可以更好的了解 Poker 的作用,和这种组合方式的设计目标。
在设计角度,Croupier 是我们最终使用的选择算法对象,它的接口体现了这个算法库的功能接口,这里我们先看一下它有哪些方法:

class Croupier[T](val poker: Poker[T]) {
  def randIndex(seq: Seq[T]): Option[Int] = poker.select(seq)

  def randDraw(seq: Seq[T]): (Option[T], Seq[T]) 
  def randDraw(seq: Seq[T], size: Int): (Seq[T], Seq[T])
  def randDraw(list: mutable.ListBuffer[T]): Option[T] 
  def randDraw(data: mutable.ListBuffer[T], size: Int): Seq[T] 

  def randSelect(seq: Seq[T], size: Int): Seq[T]
  def randSelect(seq: Seq[T]): Option[T] 

}

我暂时忽略了方法的实现。在 Croupier 构造时,需要传入某个具体的 Poker ,我们还在 object Croupier 中提供了对应的构造方法。Croupier有7个对象方法,对应了我们之前提到的七个功能点,下面我们逐个介绍:

随机索引

根据 Poker ,返回列表中一个随机的索引,是 randIndex 方法:

def randIndex(seq: Seq[T]): Option[Int] = poker.select(seq)

这里没有太多要解释的,它只是直接执行 poker 的 select 方法。不过后面六个方法,都在直接或间接的使用它,而这个东西下面,是不同的 Poker 实现,也是我们后面的章节要着重介绍的内容。

随机选中一个元素

如果我们不需要从列表中删除被选择的元素,只是把它拿出来,那么下面这个 randSelect 很适合:

def randSelect(seq: Seq[T]): Option[T] = randIndex(seq).map(seq)

随机选中一组元素的方法稍复杂一些,我们放在 randDraw之后介绍

随机抽取一个元素

通过 randDraw(seq: Seq[T]): (Option[T], Seq[T]) 方法,我们可以从 seq 中抽取一个元素,因为 seq 是个只读类型,所以返回值是一个 tuple,同时携带了被选择的元素和剩余部分:

  def randDraw(seq: Seq[T]): (Option[T], Seq[T]) =
    poker.select(seq) match {
      case Some(idx) =>
        (Some(seq(idx)), seq.take(idx) ++ seq.drop(idx + 1))
      case None =>
        (None, seq)
    }

因为 poker 中已经正确处理的边界条件,通过对其结果的模式匹配,我们很容易保证 randDraw 的正确性。

从只读列表中随机选取多个元素

从只读列表中抽取多个元素,通过向 randDraw 传入抽取个数实现:

  def randDraw(seq: Seq[T], size: Int): (Seq[T], Seq[T]) = {
    if (seq == null) {
      return (Seq(), Seq())
    }

    @tailrec
    def helper(data: Seq[T], seq: Seq[T], size: Int): (Seq[T], Seq[T]) = {
      if (size == 0 || seq.isEmpty) {
        return (data, seq)
      }
      randDraw(seq) match {
        case (Some(element), rest: Seq[T]) =>
          helper(data :+ element, rest, size - 1)
        case (None, _) =>
          (data, seq)
      }
    }

    helper(Seq(), seq, size)
  } 

这里我用了一个带尾递归的辅助函数,其逻辑也不复杂,反复在递归过程中调用抽取单个元素的 randDraw 即可。主要需要注意的是边界条件的处理。
返回值同时包含了被选择的列表和剩余的列表,这对于应用项目使用和Croupier类型内部的互相调用,都是很有用的设计。

从可变列表中随机抽取一个元素

如果传入了可变的 ListBuffer,randDraw 返回随机选中的元素同时,将其从列表中删除:

  def randDraw(list: mutable.ListBuffer[T]): Option[T] =
    poker.select(list.toSeq)
      .map(idx => list.remove(idx))
从可变列表中随机抽取 n 个元素

同样,可变列表的随机抽取,也有多元素版本:

  def randDraw(data: mutable.ListBuffer[T], size: Int): Seq[T] = {
    if (data == null) {
      return Seq()
    }

    for {
      _ <- 0 until Math.min(data.size, size)
      element <- randDraw(data)
    } yield element
  }

有兴趣的同行可以试试把这个函数改写成尾递归,思路类似def randDraw(seq: Seq[T], size: Int) ,但是要注意边界条件的处理。

随机选择 n 个元素

因为 randSelect 不修改原列表,直接执行 n 次 randSelect(seq:Seq[T])可能会出现重复元素,但是有了 randDraw,我们可以利用它实现 randSelect:

  def randSelect(seq: Seq[T], size: Int): Seq[T] = {
    randDraw(seq, size)._1
  }
回顾

以上是 Croupier 的全部功能,和它如何使用 Poker ,下面我们讨论 Poker 的各种非平均概率实现,首先从大家比较熟悉的按权重选择开始。

非平均概率算法

按权重选择

如果可以对元素计算出一个整数权重,然后根据这个权重决定这个元素在整个概率空间内的分布,就是我们熟悉的一类随机算法,它的做法,应用开发人员应该都不陌生,简单的说先构造一个新的列表,包含每个元素的权重值,然后根据这个权重值的总和,用平均概率做 nextInt 计算,然后查找这个生成结果对应的元素索引。
为了将算法通用化,这里定义一个新的 trait:

trait Scale[T] {
  def weight(item: T): Int
}

显然,这个 SAM 类型大多数情况可以简化为一个 lambda,但是一个明确的定义可以建立更清晰的表达,这个接口是用来给人阅读的。
那么,最简单的情况下,列表和列表元素的权重都不大的情况下,我们可以把索引为i的元素的权重n展开成一段长为n,内容为i的列表,然后将这些列表连起来构成一个大的列表,在其中按平均概率选择一项,它的值就是我们要生成的随机索引值:

  override def select(cards: Seq[T]): Option[Int] = {
	// 省略边界条件

    val steps: Seq[Int] = for {
      idx <- cards.indices
      w = scale.weight(cards(idx))
      value <- Seq.fill(w)(idx)
    } yield value

    val top = steps.size
    val score = random.nextInt(top)
    Some(steps(score))
  }

这段代码来自 Jaskell Core 中的 LiteScaled 类型。充分利用了 scala 的 list comprehension 。比较离谱的是我实现 java 8版本时,stream api 版本比用 for 循环更啰嗦,先看for循环,还是比较朴素的:

        List<Integer> steps = new ArrayList<>();
        for (int idx=0; idx < cards.size(); idx ++) {
            int weight = scale.weight(cards.get(idx));
            for(int i=0; i < weight; i++){
                steps.add(idx);
            }
        }

Stream 版本:

        List<Integer> steps = IntStream
                .range(0, cards.size())
                .mapToObj(idx -> {
                    Integer[] pair = new Integer[2];
                    pair[0] = idx;
                    pair[1] = scale.weight(cards.get(idx));
                    return pair;
                }).flatMap(pair -> IntStream.range(0, pair[1])
                        .mapToObj(x -> pair[0]))
                .collect(Collectors.toList());

真的就是……何必呢……不但 stream 不方便,java 还没有 tuple——所以几乎所有功能稍微丰富一点儿的 Java 框架或库,都有自己的 tuple 和 pair 实现。
这个 Scale Lite 非常容易理解,但是如果 weight 中有一些值很大的,可能会构造出一个比待选择的列表大很多的索引列表,这就不太经济了。这种情况下我们可以用更通用的 Scaled 算法:

  override def select(cards: Seq[T]): Option[Int] = {
	// 省略边界条件处理

    val steps: Seq[Int] = cards
      .map(scale.weight)
      .foldLeft(Seq[Int](0)) { (result: Seq[Int], r: Int) =>
        result :+ (result.last + r)
      }
    val top = steps.last
    val score = random.nextInt(top)
    for (idx <- cards.indices) {
      if (steps(idx) <= score && steps(idx + 1) > score) {
        return Some(idx)
      }
    }
    Some(cards.size - 1)
  }

这个实现的思路是,生成一个从0开始的整数列表,列表中每个值都是当前位置之前所有元素的权重之和——所以第一个元素是0,而这个权重列表的元素个数会比元素列表大 1。然后以最终的总和为上限生成随机数,找出不大于这个随机数的最大位置,就是我们要找的索引。
这个算法比较紧凑,而且空间复杂度有限,通常情况是足够用的。如果我们需要处理特别长的列表,可以考虑把遍历改成二分查找,因为权重值的累加列表是单调递增的,二分查找对这种大列表很有效。
二分查找是很基本的算法,就不多介绍了——嗯,其实我有次面试没写对。
BinaryScaled 的 select 实现如下:

  override def select(cards: Seq[T]): Option[Int] = {
    // 省略边界条件处理

    val steps: Seq[Int] = cards
      .map(scale.weight)
      .foldLeft(Seq[Int](0)) { (result: Seq[Int], r: Int) =>
        result :+ (result.last + r)
      }
    val top = steps.last
    val score = random.nextInt(top)

    var idx: Int = steps.size / 2
    var low = 0
    var high = steps.size - 1
    while (true) {
      if (steps(idx) <= score && steps(idx + 1) > score) {
        return Some(idx)
      }
      if (score < steps(idx)) {
        high = idx
        idx -= math.max((idx - low) / 2, 1)
      } else {
        low = idx
        idx += math.max((high - idx) / 2, 1)
      }
    }

    Some(cards.size - 1)
  }

如果待选择的数据集比较大,二分查找应该会更有效。这里我没有做性能测试,经验来看只要列表有几千个元素,这个差异就可以表现出来了,更何况我们如果要从列表中选择多个元素的话,这个计算会反复进行。
另一方面,如果我们有一个相当大的列表,但是其中涉及的权重值只有很少的几个整数,也就是说大量的元素有相同的权重,另一种算法可能更有效:我们先将元素按权重分组,用权重和元素索引的集合构建一个集合,然后按每个组的总权重(权重值乘以元素个数)进行按权重选择,再从被选择的索引集合中,按平均概率选择出最终的结果。
这个类型在 jaskell core 中称为 ZipScaled

  override def select(cards: Seq[_ <: T]): Option[Int] = {
	// 省略边界条件处理	

    val group = new mutable.TreeMap[Int, Seq[Int]]()
    val steps = cards.map(scale.weight)
    for (position <- steps.indices) {
      val w = steps(position)
      group.put(w, group.getOrElse(w, Seq()).appended(position))
    }
    val pairs: Seq[(Int, Int)] = 
			(for ((weight, positions) <- group) 
				yield (weight, weight * positions.size)
					).toSeq

    scaled.select(pairs)
      .map(pairs)
      .map({ weight =>
        group(weight._1)
      })
      .flatMap({ positions =>
        val result = fair.select(positions).map(positions)
        result
      })
  }
按 Rank 选择

针对离散的整数权重,我们可以很容易的构造出不同性能表现的选择实现,但是有很多业务系统使用浮点数评分,这里我们称之为 Rank,对应的提取接口:

 trait Ranker[T] {
  def rank(item: T): Double
}

随机生成浮点数时,几乎不会出现它精确的等于一个预定值。所以ZipScale的优化方式对浮点数并不很有效,我这里只提供了类似标准 Scaled 算法的 Ranked 算法:

  override def select(cards: Seq[T]): Option[Int] = {
    if (cards == null || cards.isEmpty) {
      return None
    }
    if (cards.size == 1) {
      return Some(0)
    }

    val steps: Seq[Double] = cards
      .map(ranker.rank)
      .foldLeft(Seq[Double](0)) { (result: Seq[Double], r: Double) =>
        result :+ (r + result.last)
      }
    val top = steps.last
    val score = random.nextDouble() * top
    for (idx <- cards.indices) {
      if (steps(idx) <= score && steps(idx + 1) > score) {
        return Some(idx)
      }
    }
    Some(cards.size - 1)
  }

它同样是构造累加和“阶梯”,然后找到对应的位置,因此也有二分查找的版本 BinaryRanked。不过它的代码和 BinaryScaled 几乎一样,这里就不展开讨论了,

概率递降选择

有时候我们按权重做随机选择,其实并不真的需要严格的按权重计算概率,仅仅需要一个有序的概率变化,确保权重更高的元素有更大的几率被选中就可以。那么,非常容易由 Rank 得到启发,我们只要构造一个开头最大,后面单调递减的浮点数序列,让它按照这些浮点数项作为概率分布作为选择就可以。方法有很多,我提供了一个比较简单的 damping 实现:

class Damping[T](val random: Random) extends Poker[T] {
  override def select(cards: Seq[T]): Option[Int] = {
	//省略边界条件处理

    val range = Math.log(cards.size + 1)
    val value = Math.exp(random.nextDouble() * range)
    Some(Math.floor(value).intValue() - 1)
  }
}

在 Scale 和 Rank 算法中隐含的逻辑是:steps看作是一个递增函数的话,相邻两项的差值是这个函数的差分,我们其实是将这些差分值作为概率分布进行随机计算。直接取元素索引作为steps的话,它的差分就是常数1,而Damping先计算列表尺寸的对数,那么每一个索引的对数,构成了一个越来越“紧密”的刻度尺,其差分值数列是以 log(n+1) - log(n) 递减的。当我们以平均概率计算出一个在此区间内的浮点数,再通过 e^v 还原到线性空间时,越靠前的索引,其被选中的概率会显著的大于靠后的索引值。
即使算上文档注释、空行、import和我们这里省略掉的边界条件,Dampling.scala 也只有26行。核心的计算函数只有三行。但是这个算法非常有效,对于很多业务系统,它已经足够用。

概率递增选择

我其实还没有在业务系统中遇到过需要按递增的概率做随机选择的情况,不过作为对偶,我也顺手写了一个递增算法:

class Invert[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)
    val score = cards.size - value
    Some(Math.floor(score).intValue() + 1)
  }

如果能理解 damping,这个invert也就很容易理解,它只是一个反向的 damping。

工具函数

最终,我们有了提供选择逻辑的 Croupier 类型,和实现不同随机算法的各种 Poker ,通过一组 object 方法,我们提供了快捷的使用入口:

object Croupier {

  def fair[T]: Croupier[T] = new Croupier(new Fair(new Random()))

  def fair[T](random: Random): Croupier[T] = new Croupier[T](new Fair(random))

  def fair[T](seed: Long): Croupier[T] = new Croupier[T](new Fair(new Random(seed)))

  def damping[T]: Croupier[T] = new Croupier(new Damping(new Random()))

 	//..

  def invert[T]: Croupier[T] = new Croupier(new Invert(new Random()))

 	//..

  def byWeight[T](scale: Scale[T]) = new Croupier[T](new Scaled[T](scale, new Random()))

 	//..

  def byWeightLite[T](scale: Scale[T]) = new Croupier[T](new LiteScaled[T](scale, new Random()))

 	//..

  def byWeightBinary[T](scale: Scale[T]) = new Croupier[T](new BinaryScaled[T](scale, new Random()))

 	//..

  def byRank[T](ranker: Ranker[T]) = new Croupier[T](new Ranked[T](ranker, new Random()))

 	//..

  def byRankBinary[T](ranker: Ranker[T]) = new Croupier[T](new BinaryRanked[T](ranker, new Random()))

 	//..

  def byZipScale[T](scale: Scale[T]) = new Croupier[T](new ZipScaled[T](scale, new Random()))

 	//..
}

每一个构造方法,我都提供了三个不同的构造方法:直接构造,或者传入一个既有的 Random 对象,或者传入一个随机种子。这样可以满足将来对不同随机行为的选择需求。当然使用者也可以实现自己的 Poker,组装到 Croupier 中使用。下面我们以一段测试代码为例,演示一下使用方法:

  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)}")
    }
  }

因为这是个随机逻辑,我很难通过精确的 Assertion 判断其有效性,所以这里我把返回结果打印出来观察。各种功能的测试逻辑大同小异,都是构造测试集和 Croupier,将随机选择的结果写入计数器字典,做简单的检查后,打印观察详细情况。所以就不一一展开了。文章来源地址https://www.toymoban.com/news/detail-414587.html

到了这里,关于随机、优先与权重——非平均概率的选择工具的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 什么是机器学习?监督学习的定义、概率论的基本概念以及模型选择、过拟合与欠拟合的问题。常见的监督学习算法,包括朴素贝叶斯(Naive Bayes)、决策树(Decision Tree)支持向量机随机森林

    作者:禅与计算机程序设计艺术 什么是机器学习?从定义、发展历程及目前的状态来看,机器学习由3个主要分支组成:监督学习(Supervised Learning),无监督学习(Unsupervised Learning)和强化学习(Reinforcement Learning)。这三类学习都可以使计算机系统根据输入数据自动分析和改

    2024年02月09日
    浏览(52)
  • 路由时权重和优先级的区别

    在路由时,权重(Weight)和优先级(Priority)是两个不同的概念,它们在路由策略中起着不同的作用: 1、权重(Weight): 2、优先级(Priority): 在一些路由策略中,这两个概念可能会结合使用。例如,可以先根据优先级排序规则,然后在每个优先级中根据权重选择目标。这

    2024年01月16日
    浏览(38)
  • 概率论--随机事件与概率--贝叶斯公式--随机变量

    目录 随机事件与概率 概念 为什么要学习概率论 随机事件与随机事件概率 随机事件 随机事件概率 贝叶斯公式  概念 条件概率 概率乘法公式 贝叶斯公式  举个栗子 随机变量   随机变量的定义 随机变量的分类 离散型随机变量 连续型随机变量 概念 随机事件是指在一次试验

    2024年02月11日
    浏览(45)
  • 概率 随机变量 条件概率 贝叶斯定理

    随机变量x是一个变化的量,它的变化是由于偶然/随机性引起的。可以将随机变量看成一个函数,它由实验结果赋值。例如:在抛硬币的实验中把正面朝上定义为x1=0,反面朝上为x2=1。 一般用小写字母表示随机变量,如 x text x x 。一旦试验完成,它的取值就用斜体的 x x x 表示

    2024年02月10日
    浏览(46)
  • 概率图论:了解概率分布、概率独立性和随机化

    作者:禅与计算机程序设计艺术 概率图模型(Probabilistic Graphical Model)(PGM)是现代统计学习中的一个重要工具,它通过描述变量间的依赖关系和概率分布来对复杂系统进行建模。概率图模型由两部分组成:一是概率模型,它定义了变量之间的联合概率分布;二是结构模型,它

    2024年02月14日
    浏览(35)
  • java基础入门-21-【阶段综合案例(带权重的随机&每日一记)】

    获取姓氏: https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0dfrom=kg0 男生名字: http://www.haoming8.cn/baobao/10881.html 获取女生名字: http://www.haoming8.cn/baobao/7641.html allnames1.txt Test1

    2024年02月13日
    浏览(54)
  • 概率统计笔记:二维随机变量及其联合概率分布

    定义3 设 ( X , Y ) (X,Y) ( X , Y ) 为二维随机变量,对任意的 ( x , y ) ∈ R 2 (x,y)∈R^2 ( x , y ) ∈ R 2 ,称 F ( x , y ) = P ( X ≤ x , Y ≤ y ) F(x,y)=P(X≤x,Y≤y) F ( x , y ) = P ( X ≤ x , Y ≤ y ) 为随机变量 ( X , Y ) (X,Y) ( X , Y ) 的

    2023年04月08日
    浏览(42)
  • 红包算法关于---随机分发和平均分发

    😀前言 本片文章讲解了 群发普通红包的算法 🏠个人主页:[尘觉主页](https://blog.csdn.net/apple_67445472?type=blog) 🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉 在csdn获奖荣誉: 🏆csdn城市之星2名 ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣

    2023年04月22日
    浏览(44)
  • 概率论_概率公式中的逗号( , ) 竖线( | ) 分号( ; )及其优先级

    目录 1.概率公式中的分号(;)、逗号(,)、竖线(|) 2.各种概率相关的基本概念 2.1 联合概率 2.2 条件概率(定义) 2.3 全概率(乘法公式的加强版) 2.4 贝叶斯公式 贝叶斯定理的公式推导  ;  分号 代表前后是两类东西,以概率P(x;θ)为例,分号前面是x样本,分号后边是模型参数。 分号

    2024年02月05日
    浏览(43)
  • 宋浩概率论笔记(三)随机向量/二维随机变量

    第三更:本章的内容最重要的在于概念的理解与抽象,二重积分通常情况下不会考得很难。此外,本次暂且忽略【二维连续型随机变量函数的分布】这一章节,非常抽象且难度较高,之后有时间再更新。 目录 1.1二维随机变量及其分布函数 1.2二维离散型随机变量的联合分布与

    2024年02月14日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包