ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Miller 随机化实战指南:分布采样、蓄水池抽样与 n-gram 造词

Miller 随机化实战指南:分布采样、蓄水池抽样与 n-gram 造词 Miller 随机化实战指南分布采样、蓄水池抽样与 n-gram 造词【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址: https://gitcode.com/gh_mirrors/mi/miller本文以 Miller 官方文档中的 randomizing-examples.md 为主线系统讲解如何用 Miller 完成三类典型随机化任务从指定概率分布生成随机数并可视化、从词表中随机抽取样本、以及用 n-gram 统计模型自动生成仿造单词。读完本文你将掌握urand函数族与--seed可复现机制、seqgen/histogram/bar/sample等动词的链式用法并能直接复制文中命令跑出可复现的结果。随机数基础urand函数族与--seed可复现机制在进行任何随机化操作之前先理解 Miller 的随机数底层设施。Miller 的随机数能力全部集中在put/filter的 DSL 函数urand系列中其实现位于 pkg/bifs/random.go函数签名语义依据源码urand()无参返回[0,1)区间均匀分布的浮点数基于lib.RandFloat64()urand32()无参返回均匀分布的 32 位无符号整数uint32值转为 int 输出urandint(lo, hi)两个整数参数返回[lo, hi]闭区间内的均匀随机整数参数顺序可颠倒源码会先取lomin, himax1再做floorurandrange(a, b)两个数值参数返回[a, b)开区间内的均匀随机浮点数a (b-a)*randurandelement(array)一个数组参数等概率返回数组中的随机一个元素空数组会报错从源码看Miller 的随机数底层是对 Go 标准库math/rand的薄封装见 pkg/lib/rand.go。关键的可复现机制在这里默认种子是每次运行都不同var defaultSeed time.Now().UnixNano() ^ int64(os.Getpid())因此默认情况下两次运行随机结果不同。--seed标志可强制可复现SeedRandom(seed)会以用户给定种子重建随机源。该标志在 pkg/cli/option_parse.go#L3975-L3996 中解析要求参数为十进制或十六进制整数如0xcafefeed其 help 文本明确写着withnof the form12345678or0xcafefeed. Forput/filterurand,urandint, andurand32。因此如果你希望随机化脚本产生完全一致的输出比如用于回归测试、文档生成或演示务必通过--seed指定种子生产环境中的模拟实验则通常省略它。从指数分布生成随机数并可视化文档中的第一个例子展示了一个完整的生成—变换—统计—可视化流水线生成 10 万对服从指数分布的随机变量对其中一个及其和做直方图再用 ASCII 条形图展示。完整脚本位于 docs/src/expo-sample.sh# Generate 100,000 pairs of independent and identically distributed # exponentially distributed random variables with the same rate parameter # (namely, 2.5). Then compute histograms of one of them, along with # histograms for their sum and their product. # # Here Im using a specified random-number seed so this example always # produces the same output for this web document: in everyday practice we # wouldnt do that. mlr -n \ --seed 0 \ --opprint \ seqgen --stop 100000 \ then put # https://en.wikipedia.org/wiki/Inverse_transform_sampling func expo_sample(lambda) { return -log(1-urand())/lambda } $u expo_sample(2.5); $v expo_sample(2.5); $s $u $v; \ then histogram -f u,s --lo 0 --hi 2 --nbins 50 \ then bar -f u_count,s_count --auto -w 20这条命令链由以下积木拼装而成-n与--seed 0-n表示不读任何输入文件纯生成模式--seed 0固定随机种子保证每次运行输出完全一致文档注释明确提醒日常使用中通常不需要固定种子。--opprint使用 pretty-printed 表格输出格式。seqgen --stop 100000生成 100000 条记录字段为i0、i1、……、i99999作为循环骨架。put步骤定义了一个逆变换采样inverse transform sampling函数expo_sample(lambda)——若urand()在(0,1]上均匀分布则-log(1-urand())/lambda服从参数为lambda的指数分布。随后调用两次生成独立同分布样本$u、$v并计算其和$s。注意脚本注释提到还要对乘积做直方图但实际命令行中只对u,s做了histogram未计算乘积。histogram -f u,s --lo 0 --hi 2 --nbins 50对u和s分别做 50 个桶、值域[0,2)的直方图输出bin_lo、bin_hi、u_count、s_count列。bar -f u_count,s_count --auto -w 20把计数列渲染成宽度 20 的 ASCII 条形图纯粹用于可视化——文档指出你完全可以把这一步替换为输出 CSV 交给自己的绘图工具。运行sh expo-sample.sh后在仓库docs/src目录下执行输出形如bin_lo bin_hi u_count s_count 0 0.04 [64]*******************#[9554] [326]#...................[3703] 0.04 0.08 [64]*****************...[9554] [326]*****...............[3703] 0.08 0.12 [64]****************....[9554] [326]*********...........[3703] 0.12 0.16 [64]**************......[9554] [326]************........[3703] 0.16 0.2 [64]*************.......[9554] [326]**************......[3703] 0.2 0.24 [64]************........[9554] [326]*****************...[3703] 0.24 0.28 [64]**********..........[9554] [326]******************..[3703] 0.28 0.32 [64]*********...........[9554] [326]******************..[3703] 0.32 0.36 [64]********............[9554] [326]*******************.[3703] 0.36 0.4 [64]*******.............[9554] [326]*******************#[3703] 0.4 0.44 [64]*******.............[9554] [326]*******************.[3703] 0.44 0.48 [64]******..............[9554] [326]*******************.[3703] 0.48 0.52 [64]*****...............[9554] [326]******************..[3703] 0.52 0.56 [64]*****...............[9554] [326]******************..[3703] 0.56 0.6 [64]****................[9554] [326]*****************...[3703] 0.6 0.64 [64]****................[9554] [326]******************..[3703] 0.64 0.68 [64]***.................[9554] [326]****************....[3703] 0.68 0.72 [64]***.................[9554] [326]****************....[3703] 0.72 0.76 [64]***.................[9554] [326]***************.....[3703] 0.76 0.8 [64]**..................[9554] [326]**************......[3703] 0.8 0.84 [64]**..................[9554] [326]*************.......[3703] 0.84 0.88 [64]**..................[9554] [326]************........[3703] 0.88 0.92 [64]**..................[9554] [326]************........[3703] 0.92 0.96 [64]*...................[9554] [326]***********.........[3703] 0.96 1 [64]*...................[9554] [326]**********..........[3703] 1 1.04 [64]*...................[9554] [326]*********...........[3703] 1.04 1.08 [64]*...................[9554] [326]********............[3703] 1.08 1.12 [64]*...................[9554] [326]********............[3703] 1.12 1.16 [64]*...................[9554] [326]********............[3703] 1.16 1.2 [64]*...................[9554] [326]*******.............[3703] 1.2 1.24 [64]#...................[9554] [326]******..............[3703] 1.24 1.28 [64]#...................[9554] [326]*****...............[3703] 1.28 1.32 [64]#...................[9554] [326]*****...............[3703] 1.32 1.36 [64]#...................[9554] [326]****................[3703] 1.36 1.4 [64]#...................[9554] [326]****................[3703] 1.4 1.44 [64]#...................[9554] [326]****................[3703] 1.44 1.48 [64]#...................[9554] [326]***.................[3703] 1.48 1.52 [64]#...................[9554] [326]***.................[3703] 1.52 1.56 [64]#...................[9554] [326]***.................[3703] 1.56 1.6 [64]#...................[9554] [326]**..................[3703] 1.6 1.64 [64]#...................[9554] [326]**..................[3703] 1.64 1.68 [64]#...................[9554] [326]**..................[3703] 1.68 1.72 [64]#...................[9554] [326]*...................[3703] 1.72 1.76 [64]#...................[9554] [326]*...................[3703] 1.76 1.8 [64]#...................[9554] [326]*...................[3703] 1.8 1.84 [64]#...................[9554] [326]#...................[3703] 1.84 1.88 [64]#...................[9554] [326]#...................[3703] 1.88 1.92 [64]#...................[9554] [326]#...................[3703] 1.92 1.96 [64]#...................[9554] [326]#...................[3703] 1.96 2 [64]#...................[9554] [326]#...................[3703]注意观察两个分布形状的差异指数分布自身的u_count单调递减符合指数分布密度函数形状而两个独立指数随机变量之和s_count呈先升后降的单峰形态——这正是伽马Erlang分布的特征验证了抽样与求和的统计正确性。这个例子体现了 Miller 的核心设计哲学seqgen负责造数据骨架put负责逐条变换histogram/bar负责聚合与展示所有积木通过then链式连接每一步的输出恰好是下一步的输入。从词表中随机选择单词sample -k第二种随机化任务是从列表中随机抽取样本。仓库提供了英文词表 docs/src/data/english-words.txt约 21 万行每行一个单词。先看它的前几行a aa aal aalii aam aardvark aardwolf aba abac abaca然后执行如下命令随机抽取10 个长度为 4 到 8 个字符的单词mlr --from docs/src/data/english-words.txt --nidx \ filter -S nstrlen($1);4nn8 then sample -k 10文档中的原命令为mlr --from data/english-words.txt --nidx filter -S nstrlen($1);4nn8 then sample -k 10相对docs/src目录。一次可能的运行输出thionine birchman mildewy avigate addedly abaze askant aiming insulant coinmate逐段拆解这条命令--from ... --nidx以无索引nidx格式读取词表每个单词成为记录$1且没有字段名。filter -S过滤记录-S表示把 DSL 源码中出现的数值字面量当作字符串处理配合strlen比较字符个数而非字节中文等宽字符场景下有区别。过滤条件nstrlen($1);4nn8先求单词长度再保留长度在 4 到 8 之间的单词。sample -k 10随机抽取 10 条记录。sample动词在 pkg/transformers/sample.go 中实现其 usage 文本明确说明它是蓄水池采样reservoir sampling即无放回子抽样并支持-g参数按类别分组抽样。-k指定抽取条数通过VerbGetIntArg解析见 pkg/transformers/sample.go#L81-L85。由于是无放回抽样同样的输入与种子下抽取结果不重复且整体均匀。由于没有指定--seed每次运行结果不同若需要固定抽样结果只需在命令前加上--seed即可。用 n-gram 随机生成胡话单词第三个例子最有意思读取词表统计字母到字母的转移频次然后按该频次分布随机拼接新词——生成诸如bromancebrotherromance与sporkspoonfork这类混合词风格的仿造词。相关脚本位于仓库 docs/src/ngrams/ 目录。原理直方图 → 概率质量函数 → 累积分布函数 → 采样核心思路在 docs/src/ngrams/ngfuncs.mlr 中四个函数把统计与采样串成一条流水线compute_sum_from_histo(map histo)对直方图字母前缀 → 后继字母 → 计数求和得到总计数compute_pmf_from_histo(map histo)将计数归一化为概率质量函数PMFcompute_cmf_from_pmf(map pmf)把 PMF 逐项累加为累积分布函数CMFsample_from_cmf(var cmf)生成u urand()在 CMF 上找到第一个u c的键并返回——这就是逆变换采样在离散分布上的应用。sample_from_cmf的实现极为精简func sample_from_cmf(var cmf) : str { u urand(); output ; for (k, c in cmf) { output k; if (u c) { break; } } return output; }主处理脚本三个阶段的词法建模主脚本 docs/src/ngrams/ngrams.mlr 把建模分成词首、词中、词尾三段避免生成像childhoo这样开头像词、结尾不像词的产物词首链start_histos对词的开头若干字母建模。以n5、输入abcdefghij为例start_histo[1]记录_ - astart_histo[2]记录a - b依此类推用_作为任意起始符号词中链middle_histo统计连续的 n-1 个字母到第 n 个字母的转移即abcd - e、bcde - f等词尾链end_histo单独统计词的结尾字母转移保证生成的词在结尾处也符合真实词的形态。同时脚本还用len_histo记录了输入词长的分布生成新词时按该分布抽取目标长度。处理流程由begin/end块驱动见 docs/src/ngrams/ngrams.mlr 的 MAIN PROCESSING 段begin块设置默认参数n4、ocount15、olen0表示按输入长度分布抽样verbosefalse并调用init()对每条记录的每个字段调用ingest_word()累加各类直方图第一个end块调用compute_cmfs()把直方图转为 CMF可选dump查看内部状态第二个end块循环ocount次调用emit_word()生成并打印单词。生成侧的关键处理是emit_word_aux()先按长度分布取目标长度然后依次走词首链凑足n个字母、再用词中链续长、最后用词尾链收尾。由于词中链与词尾链不一定总能衔接emit_word()做了最多 100 次尝试的兜底见 docs/src/ngrams/ngrams.mlr 的emit_word函数。运行命令文档中的运行命令为mlr --nidx --from docs/src/ngrams/gsl-2000.txt \ put -q -f docs/src/ngrams/ngfuncs.mlr -f docs/src/ngrams/ngrams.mlr原文写作mlr --nidx --from ./ngrams/gsl-2000.txt put -q -f ./ngrams/ngfuncs.mlr -f ./ngrams/ngrams.mlr相对docs/src目录。要点输入为 docs/src/ngrams/gsl-2000.txt通用服务列表的 2000 常用词表另有 short-wordlist.txt、one-word-list.txt 可替换put -q静默模式不打印每条记录的变换过程-f可多次使用把ngfuncs.mlr库函数与ngrams.mlr主逻辑依次加载进同一个 DSL 程序。一次运行输出示例15 个自动生成的仿造词burse serious land seasure clainst tray wherhoose stry jourt strue partist ornear devel praction roup可以看到输出词在拼写上高度模仿英语的字母转移规律有些如serious、land甚至直接命中真实词。包装脚本ngrams.sh仓库还提供了 bash 包装脚本 docs/src/ngrams/ngrams.sh把上述命令封装成更友好的 CLIUsage: ngrams.sh [options] {word-list files} Options: -n {n} The n for n-grams; default 5. -o {o} Number of words to produce; default 40. -l {l} Only make words of length l. Default: sample from input-length distribution. -v Verbose processing; default off. If no wordlists are provided, stdin is read.脚本内部通过mlr --nidx put -q -s n$n -s ocount$ocount -s olen$olen -s verbose$verbose -f .../ngfuncs.mlr -f .../ngrams.mlr $wordlist把选项以-s设置 DSLO 变量的方式注入 Miller 程序。脚本头部还演示了固定长度输出的进阶玩法先用shuf -n 10000随机采样词表再对每个生成的词切片拼接产出devo-bils-obug-auna式的混合词用于制造复合词汇效果。随机化工具箱速查任务推荐组合关键点固定随机种子保证可复现--seed 0十进制或0x十六进制在 pkg/cli/option_parse.go 解析默认种子为时间戳异或 PID每次运行不同均匀随机数urand()、urandrange(a,b)urand返回[0,1)urandrange返回[a,b)随机整数 / 随机取元素urandint(lo,hi)、urandelement(array)urandint为闭区间urandelement对空数组报错任意分布采样-log(1-urand())/lambda等逆变换采样可定义func后多次调用参考 docs/src/expo-sample.sh无放回随机抽样sample -k N可加-g分组蓄水池采样见 pkg/transformers/sample.go基于词频的随机造词n-gram 直方图 CMF 采样参考 docs/src/ngrams/ngfuncs.mlr 与 docs/src/ngrams/ngrams.mlr随机序列骨架seqgen --stop N生成i0..N-1的纯记录流配合-n不读输入文件可复现性提醒任何依赖urand系列的命令只要给定相同的--seed、相同的输入与相同的处理链输出就完全一致这使 Miller 非常适合嵌入文档生成、CI 回归测试与蒙特卡洛模拟等需要确定性的场景。【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址: https://gitcode.com/gh_mirrors/mi/miller创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进