基于权重(别名采样alias sample) 的随机选择算法

基于权重(别名采样alias sample) 的随机选择算法

转载用于收藏学习,尊重原创

算法原理讲解,原创作者写的很通俗易懂,感恩 !

当我们得到一个概率分布,如何根据这个概率分布抽样是一个常见的问题。这篇文章将介绍alias method(别名采样),这种算法的时间复杂度为O(1),当然还需要复杂度为O(n)的预处理。下面我将通过一个例子介绍别名采样算法。

问题背景
假设一共存在A,B,C,D四种情况,它们出现的概率分别为 0.3,0.1,0.1,0.5。如何实现按概率抽样呢?

比较常用的一种方法是生成一个数组:1,1,1,2,3,4,4,4,4,4,其中1对应A,2对应B,以此类推,然后随机在数组中抽取一个即可。这种方法简单易实现,但是这是在仅仅有4种情况的条件下。当情况变多,这种方法就会占用很大的空间了,所以并不适用于大规模的通用情况。

另外,可以根据它们的概率密度分布生成累积分布:0.3,0.4,0.5,1。然后生成一个0-1之间的随机数,看它落在哪个区间。然而,这时需要与临界点进行比较。我们知道,插入有序数列最好的算法的时间复杂度为O(logn),所以这种方法复杂度较大。

我们这篇文章提到的alias method可以实现以运行复杂度为O(1)的方式抽样。当然它需要预处理,预处理的时间复杂度为O(n),但是重复跑的时候,运行时间复杂度低才是重要的。

别名采样算法
首先,介绍在等概率分布和二项分布这两种模型中的抽样方法。

我们知道等概率分布抽样的时间复杂度为O(1),考虑一种情况,如果四种情况A,B,C,D出现的概率均为0.25,我们用1代表A,2代表B,以此类推,那我们只需要在1~4里随机产生一个整数,抽中哪个就是哪个,复杂度自然为O(1),这是等概率分布抽样的情况。

我们知道对符合二项分布的模型进行抽样的时间复杂度也为O(1),因为如果只有两种情况A,B时,设他们出现的概率分别为 0.2,0.8,我们用累积分布的方法,小于0.2就是A, 大于就是B,只需比较一次,所以复杂度也是O(1),这是二项分布抽样的情况。

alias method就是把这两种方法结合起来。

仍然以本文一开始提出的例子为例。本文一开始的例子中,假设一共存在A,B,C,D四种情况,它们出现的概率分别为 0.3,0.1,0.1,0.5,我们称为原始分布。在下图中,我们用绿色代表A,蓝色代表B,紫色代表C,橙色代表D,将原始分布示意如下:

www.zeeklog.com - 基于权重(别名采样alias sample) 的随机选择算法

首先我们把原概率分布乘以N(有几种情况,N就等于几),这里是N=4。得到A、B、C、D四种情况的值分别是1.2,0.4,0.4,2.0,如图所示。

www.zeeklog.com - 基于权重(别名采样alias sample) 的随机选择算法

然后我们把它们拼成等概率分布和二项分布:

www.zeeklog.com - 基于权重(别名采样alias sample) 的随机选择算法

注意拼接的过程中,每一列的值最大为1,且每一列最多有两种情况。这样对于这四列,他们被抽中的概率均为1,服从四种情况的等概率分布;对于每一列,只有最多两种情况,都符合二项分布。

做完以上处理后,我们就可以开始抽样了。首先第一步,我们以等概率分布抽四列中的一列。然后第二步,生成一个0-1之间的随机数,在第一步抽中的列里继续抽样。

举例来说,例如我们首先抽中了第四列(概率为0.25)。然后在第四列中进行二项分布抽样,如果小于0.8,是橙色,代表A,反之,就是绿色,代表D。

这两步操作的复杂度均为O(1),故总时间复杂度也为O(1)。

那么这样抽样是正确的吗,是否服从原来的概率分布呢?换句话说,在原概率分布中抽到A的概率是0.3,那使用alias方法抽到的概率还是0.3吗?

我们以抽取A情况为例。原来抽中A的概率为0.3。运用alias method方法后,抽中a的概率为抽中第一列的概率+抽中第四列且随机数小于0.2的概率,算起来为0.25+(0.25 * 0.2) = 0.3,所以完全一样。

lua 实现算法

-- 别名方法 local function prepare_weighted_random4(values, weights) assert(#values == #weights) local tinsert = table.insert local ipairs = ipairs local count = #weights local sum = 0 -- 计算总和 for _, w in ipairs(weights) do sum = sum + w end local avg = sum / count -- 平均权重 local aliases = {} -- 别名表 for _, _ in ipairs(weights) do tinsert(aliases, {1, false}) end local sidx = 1 -- 找到第1个小于平均值的索引 while sidx <= count and weights[sidx] >= avg do sidx = sidx + 1 end if sidx <= count then -- 如果 small_i > count 表示所有权重值相等,什么也不用处理 local small = {sidx, weights[sidx] / avg} local bidx = 1 -- 找到第1个大于等于平均值的索引 while bidx <= count and weights[bidx] < avg do bidx = bidx + 1 end local big = {bidx, weights[bidx] / avg} while true do aliases[small[1]] = {small[2], big[1]} -- 桶的索引即是小权重的索引,从中去掉小权重的比例,剩余的放大权重 big = {big[1], big[2] - (1-small[2])} -- 大权重去掉已放入的比例 if big[2] < 1 then -- 如果大权重剩余的比例已小于1,表示小于平均权重 small = big -- 大权重变成小权重 bidx = bidx + 1 -- 找下一个大权重的索引 while bidx <= count and weights[bidx] < avg do bidx = bidx + 1 end if bidx > count then break end big = {bidx, weights[bidx] / avg} -- 得到下一个大权重 else -- 大权重的比例大于等于1,表示不比平均权重小,继续找小权重 sidx = sidx + 1 -- 找下一个小权重索引 while sidx <= count and weights[sidx] >= avg do sidx = sidx + 1 end if sidx > count then break end small = {sidx, weights[sidx] / avg} end end end return function() local n = math.random() * count local i = math.floor(n) local odds, alias = aliases[i+1][1], aliases[i+1][2] -- 小权重比例,大权重索引 local idx if n - i > odds then idx = alias else idx = i + 1 end return values[idx], weights[idx] end end 

代码有点复杂,不过看那个返回的函数,就知道有多快。算法思路是这样的:

  1. 将权重总和切成N个桶,N就是weights的数量,桶的大小就是平均权重。
  2. 从weights中得到一个小于平均权重的列表,和一个大于等于平均权重的列表。
  3. 取出一个小权重放入桶中,桶还有一点空间用来放一个大权重的一部分。
  4. 一直重复这个过程,直到桶都填完,最终得到aliases这个数据结构。
  5. aliases的索引是小权重的索引,aliases的每个元素由两个值组成:第一个是小权重占的比例,第二个是大权重的索引。

Read more

安装 启动 使用 Neo4j的超详细教程

安装 启动 使用 Neo4j的超详细教程

最近在做一个基于知识图谱的智能生成项目。需要用到Neo4j图数据库。写这篇文章记录一下Neo4j的安装及其使用。 一.Neo4j的安装 1.首先安装JDK,配环境变量。(参照网上教程,很多) Neo4j是基于Java的图形数据库,运行Neo4j需要启动JVM进程,因此必须安装JAVA SE的JDK。从Oracle官方网站下载 Java SE JDK。我使用的版本是JDK1.8 2.官网上安装neo4j。 官方网址:https://neo4j.com/deployment-center/  在官网上下载对应版本。Neo4j应用程序有如下主要的目录结构: bin目录:用于存储Neo4j的可执行程序; conf目录:用于控制Neo4j启动的配置文件; data目录:用于存储核心数据库文件; plugins目录:用于存储Neo4j的插件; 3.配置环境变量 创建主目录环境变量NEO4J_HOME,并把主目录设置为变量值。复制具体的neo4j文件地址作为变量值。 配置文档存储在conf目录下,Neo4j通过配置文件neo4j.conf控制服务器的工作。默认情况下,不需

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程 在数字化办公日益普及的今天,企业微信作为国内领先的企业级通讯工具,其群机器人功能为团队协作带来了极大的便利。本文将手把手教你如何从零开始配置企业微信群机器人Webhook,实现自动化消息推送,提升团队沟通效率。 1. 准备工作与环境配置 在开始创建机器人之前,需要确保满足以下基本条件: * 企业微信账号:拥有有效的企业微信管理员或成员账号 * 群聊条件:至少包含3名成员的群聊(这是创建机器人的最低人数要求) * 网络环境:能够正常访问企业微信服务器 提示:如果是企业管理员,建议先在"企业微信管理后台"确认机器人功能是否已对企业开放。某些企业可能出于安全考虑会限制此功能。 2. 创建群机器人 2.1 添加机器人到群聊 1. 打开企业微信客户端,进入目标群聊 2. 点击右上角的群菜单按钮(通常显示为"..."或"⋮") 3. 选择"添加群机器人"选项 4.

Flowise物联网融合:与智能家居设备联动的应用设想

Flowise物联网融合:与智能家居设备联动的应用设想 1. Flowise:让AI工作流变得像搭积木一样简单 Flowise 是一个真正把“AI平民化”落地的工具。它不像传统开发那样需要写几十行 LangChain 代码、配置向量库、调试提示词模板,而是把所有这些能力打包成一个个可拖拽的节点——就像小时候玩乐高,你不需要懂塑料怎么合成,只要知道哪块该拼在哪,就能搭出一座城堡。 它诞生于2023年,短短一年就收获了45.6k GitHub Stars,MIT协议开源,意味着你可以放心把它用在公司内部系统里,甚至嵌入到客户交付的产品中,完全不用担心授权问题。最打动人的不是它的技术多炫酷,而是它真的“不挑人”:产品经理能搭出知识库问答机器人,运营同学能配出自动抓取竞品文案的Agent,连刚学Python两周的实习生,也能在5分钟内跑通一个本地大模型的RAG流程。 它的核心逻辑很朴素:把LangChain里那些抽象概念——比如LLM调用、文档切分、向量检索、工具调用——变成画布上看得见、摸得着的方块。你拖一个“Ollama LLM”节点,再拖一个“Chroma Vector

OpenClaw配置Bot接入飞书机器人+Kimi2.5

OpenClaw配置Bot接入飞书机器人+Kimi2.5

上一篇文章写了Ubuntu_24.04下安装OpenClaw的过程,这篇文档记录一下接入飞书机器+Kimi2.5。 准备工作 飞书 创建飞书机器人 访问飞书开放平台:https://open.feishu.cn/app,点击创建应用: 填写应用名称和描述后就直接创建: 复制App ID 和 App Secret 创建成功后,在“凭证与基础信息”中找到 App ID 和 App Secret,把这2个信息复制记录下来,后面需要配置到openclaw中 配置权限 点击【权限管理】→【开通权限】 或使用【批量导入/导出权限】,选择导入,输入以下内容,如下图 点击【下一步,确认新增权限】即可开通所需要的权限。 配置事件与回调 说明:这一步的配置需要先讲AppId和AppSecret配置到openclaw成功之后再设置订阅方式,