Project1

标题: 【通用】【附带范例】获取服从正态分布的随机整数的方法 [打印本页]

作者: 正太君    时间: 2015-1-23 11:33
标题: 【通用】【附带范例】获取服从正态分布的随机整数的方法
本帖最后由 正太君 于 2015-2-3 16:01 编辑

【引言】【问题的提出】

前几天青草叔@oOTESOo 提出了一个问题:怎样才能使得武器的属性值是在一个正整数的区间范围里随机的,而且随机数取到区间的靠中间的值的概率最大,取到边缘的概率最小...举个例子,武器的攻击力在5-15这11个整数中随机取值,怎样做才能使得攻击力取到10的概率最大,取到9和11次之……取到5和15的概率最小?
按照青草叔的意思,他希望构建一个随机数生成函数,使得生成的随机数服从正态分布...
【资料】【正态分布的知识】

若随机变量服从一个位置参数为μ、尺度参数为σ的概率分布,且其概率密度函数为

则正态随机变量服从的分布就称为正态分布,记作X~N(μ,σ2)...
正态分布概率密度函数的图像:

正态分布概率密度函数有一个3σ原则,P(μ-3σ<X≤μ+3σ)≈99.7%,几乎占据了全部的概率...
所有的概率密度函数有一个性质:

【分析】【构建服从正态分布的方法】

服从正态分布的函数是一个连续型随机变量,而我们的攻击力取值是正整数,如何将离散型的正整数点分配到连续型分布函数中呢?我们假定武器的攻击力在(m到n)这(n-m+1)个整数中随机取值p(其中m≤p≤n),我们将闭区间[μ-3σ,μ+3σ]平均分成(n-m),所有的数量关系以及随机变量X按照正态分布取到p时的概率推导如下图所示(这里σ取1)...
【编译】【将数学思路编译成脚本】

按照这个推导得到【随机变量按照正态分布率取到p的概率】的方法,翻译成脚本如下(这是一个XP、VX、VA通用脚本):
RUBY 代码复制
  1. #==============================================================================
  2. # ■ 服从正态分布的随机数
  3. #------------------------------------------------------------------------------
  4. #   使用方法:zhengtai_rand(m, n)
  5. #    可以按正态分布率取到闭区间[m,n]之间的整数...
  6. #------------------------------------------------------------------------------
  7. #   作者:聪仔
  8. #    转载请保留脚本来源:本脚本来自rpg.blue
  9. #==============================================================================
  10. module Kernel
  11. #==============================================================================  
  12. # 常数精确度设置
  13. # EI:自然常数e
  14. # PI:圆周率π
  15. #------------------------------------------------------------------------------
  16.   EI = 2.71828183
  17.   PI = 3.14159265
  18. #==============================================================================
  19.  
  20.   #--------------------------------------------------------------------------
  21.   # ● 随机变量按照正态分布率取到p的概率
  22.   #    (系统处理速度会随着m、n的间距增大而减慢)
  23.   #--------------------------------------------------------------------------
  24.   def zhengtai(m, n, p)
  25.     return if !m.is_a?(Integer) or !n.is_a?(Integer) or !p.is_a?(Integer)
  26.     return if m > n or p > n or m > p
  27.     return if n - m <= 5 # 数值m和n的距离不应小于5,否则没必要满足正态分布
  28.     return EI ** (-4.5 * ((n + m - 2.0 * p) / (n - m)) ** 2.0) * 6.0 / ((n - m) * ((2.0 * PI) ** 0.5))
  29.   end
  30.   #--------------------------------------------------------------------------
  31.   # ● 按照正态分布率取随机数
  32.   #--------------------------------------------------------------------------
  33.   def zhengtai_rand(m, n)
  34.     return unless zhengtai(m, n, n - m -1)
  35.     # 初始化局部变量
  36.     gailv_arr, range_arr = [], []
  37.     a, b = 0, 0
  38.     # 把m到n之间取得各个p值的区间的概率存入数组
  39.     (m..n).each{|i| gailv_arr.push(zhengtai(m, n, i))}
  40.     # 叠加数组各个区间,得到形如[0...m][m...m+p1][m+p1...m+p1+p2][m+p1+p2...m+p1+p2+p3]
  41.     # 的范围数组,这些数组的各个区间满足正态分布,且区间之和≈1
  42.     gailv_arr.each_with_index{|i, index|
  43.     b = gailv_arr[index]
  44.     range_arr.push(a...(a + b)) if index != 0 and !b.nil?
  45.     # 数组gailv_arr叠加
  46.     a += b unless b.nil?}
  47.     range_arr.unshift(0...gailv_arr[0])
  48.     random_value = rand until (0...a) === random_value
  49.     # 随机数random_value落在range_arr的哪个区间,就取这个区间对应的数组序号+范围起点m
  50.     return m + range_arr.index(range_arr.find{|i| i === random_value})
  51.   end
  52.   #--------------------------------------------------------------------------
  53.   # ● 正态分布效果测试
  54.   #   (系统需要处理较长时间,请耐心等待)
  55.   #    此测试方法是根据P叔的测试方法改写的...
  56.   #--------------------------------------------------------------------------
  57.   def zhengtai_test
  58.     a = Sprite.new
  59.     a.bitmap = Bitmap.new(544, 416)
  60.     color = Color.new(0, 0, 255)
  61.     b = Array.new.fill(416, 0..256)
  62.     10000.times do
  63.       x = zhengtai_rand(0, 256)
  64.       b[x] -= 1
  65.       a.bitmap.set_pixel(x, b[x], color)
  66.     end
  67.     p "生成完毕..."
  68.   end   
  69. end


【范例】【得到服从正态分布的随机数及其验证】

得到随机数...

验证...

范例下载(以VX为例): Project3.rar (433.57 KB, 下载次数: 101)
这样一来就可以利用脚本命令
RUBY 代码复制
  1. value = zhengtai_rand(m, n)

在闭区间[m,n]的范围内生成服从正态分布概率的随机整数了...
作者: 永燃的狂炎    时间: 2015-1-23 13:14
聪聪真厉害
作者: oOTESOo    时间: 2015-1-23 13:56
不错哦,这个是聪聪自己想出来的,很了不起啊,有兴趣的话还可以研究下那个什么Box-Muller方法,可是非常简单呢!
作者: chd114    时间: 2015-1-23 16:06
在函数内取随机值?
作者: 怪蜀黍    时间: 2015-1-23 16:20
  1.   #--------------------------------------------------------------------------
  2.   # ● 正态分布效果测试
  3.   #   (系统需要处理较长时间,请耐心等待)
  4.   #    此测试方法是根据P叔的测试方法改写的...
  5.   #--------------------------------------------------------------------------
  6.   def zhengtai_test
  7.     a = Sprite.new
  8.     a.bitmap = Bitmap.new(544, 416)
  9.     color = Color.new(0, 0, 255)
  10.     b = Array.new.fill(416, 0..256)
  11.     10000.times do
  12.       x = zhengtai_rand(0, 256)
  13.       b[x] -= 1
  14.       a.bitmap.set_pixel(x, b[x], color)
  15.     end
  16.     p "生成完毕..."
  17.   end  
复制代码

哇咔咔,这不是吾验证rand函数服从均匀分布写的脚本改编的吗?
这个技术吾用到逝3里正好~随机属性的福星~聪仔大赞~
作者: 弗雷德    时间: 2015-1-23 16:30
小聪进步超级大呀,
俺离开学校十几年,上面的符号基本都看不懂了
作者: taroxd    时间: 2015-1-23 17:29
本帖最后由 taroxd 于 2015-1-23 17:55 编辑

正态敢不敢翻译成 normal - -

不要因为昵称是正太就写拼音啊= =


其实大可不必这么麻烦。得到每个数的概率之后,参考 VA 脚本中 Game_Unit#random_target 的方法就可以取到随机数了。
9L
作者: RyanBern    时间: 2015-1-23 17:51
本帖最后由 RyanBern 于 2015-1-23 19:54 编辑

最近模拟正态分布的脚本非常多呢……
试试利用中心极限定理去模拟正态分布吧
函数的名称别用zhengtai了,用normal吧。
模拟正态分布的话应该用Box-Muller法比较好。简单说一下,这个方法是通过模拟二维正态分布,然后利用它的边缘分布来求得服从正态分布的随机变量的。
具体来说,需要一点初等概率论知识,不清楚为什么也没关系。
以下内容引用自本人学习随机模拟时的讲义。


其中,ξ1和ξ2是独立同分布的随机变量,均服从[0,1]内的均匀分布。
而计算机产生均匀分布的方法比较容易,使用rand()函数即可。
这样,造出的x和y均服从标准正态分布了。

简单说一下原理:
这是利用二维标准正态分布来构造一维标准正态分布的方法。
先承认一个事实(证明略):
如果随机变量X的分布函数为F(x) := P(X <= x),随机变量Y服从U(0,1),即[0,1]上的均匀分布,则:
Z=F-1(Y)也是一个随机变量,且和随机变量X同分布。即随机变量Z的分布函数也为F(z)=P(Z <= z)。
这里F-1(x)为F(x)的分位数函数(可以理解为广义逆),当X为连续型随机变量时,F(x)严格单调递增,此时F-1(x)为F(x)的严格反函数。
基于以上事实,我们可以从均匀分布出发,构造出任何非均匀分布的随机变量,前提条件是我们要知道对应的分布函数F(x)的广义逆F-1(x)。

遗憾的是,对于正态分布(Normal Distribution),其分布函数F(x)不能写成初等函数的形式,自然就无法得知F-1(x)究竟长成什么样子。而Box-Muller方法则巧妙利用二维正态分布的一些比较好的性质,从而绕开了求F-1(x)的困难来获得正态分布的模拟。

设二维随机向量(X, Y)~N((0, 0), Σ),其中Σ=[1 0;0 1]为协方差矩阵。通过求边缘密度以及协方差矩阵的形式,随机变量X和Y独立同分布,且均服从标准正态分布。
这样,只需要通过某种方法表示出X或者Y,就可以获得服从标准正态分布的随机变量。
第一个图上已经说明,(X, Y)的密度p(x, y)的形式,利用极坐标变换:
X=Rcosθ
Y=Rsinθ
注意:R和θ都是随机变量
这样,我们得到(R, θ)这个随机向量的密度为p(r, θ)有图一的形式。
可以看到,密度函数中r和θ是变量分离的,这说明随机变量R和θ是相互独立的。
令M=R^2/2,则M与θ相互独立(由于M只和R有关),则可以通过求边缘分布得到以下事实:
M~Exp(1)
θ~U(0, 2π)

通过这种方法,我们就可以把一个服从正态分布的随机变量分解成另外两个随机变量的函数。
X=(√2M)cosθ
Y=(√2M)sinθ
这样,我们只需要模拟出M和θ的分布,就可以模拟出服从正态分布的随机变量。

刚才说过,M服从一个指数分布,θ服从一个均匀分布。
利用最开始提到的那个事实,对于指数分布,完全可以通过寻求逆函数的方式来模拟。
设M~Exp(1),G(x)为M的分布函数,g(x)=exp(-x)为M的密度。
则G(x)=∫exp(-t)dt=1-exp(-x)       ←在这里积分下限为0,积分上限为x
G-1(x)=-ln(1-x)
所以,可以通过产生一个均匀分布ξ1~U(0, 1),则M=-ln(ξ1),此时M~Exp(1)。
注意:如果ξ~U(0, 1),则1-ξ和ξ同分布,所以取M=-ln(1-ξ1)也可以。

θ是个均匀分布,只需生成与ξ1相互独立的随机变量ξ2~U(0, 1),然后作拉伸变换即可。

作者: taroxd    时间: 2015-1-23 17:55
本帖最后由 taroxd 于 2015-1-23 17:57 编辑
RyanBern 发表于 2015-1-23 17:51
最近模拟正态分布的脚本非常多呢……
试试利用中心极限定理去模拟正态分布吧
模拟正态分布的话应该 ...


代码附上。转自:http://stackoverflow.com/questio ... dom-numbers-in-ruby

@mean

RUBY 代码复制
  1. class RandomGaussian
  2.   def initialize(mean = 0.0, sd = 1.0, rng = lambda { Kernel.rand })
  3.     @mean, @sd, @rng = mean, sd, rng
  4.     @compute_next_pair = false
  5.   end
  6.  
  7.   def rand
  8.     if (@compute_next_pair = !@compute_next_pair)
  9.       # Compute a pair of random values with normal distribution.
  10.       # See [url]http://en.wikipedia.org/wiki/Box-Muller_transform[/url]
  11.       theta = 2 * Math::PI * @rng.call
  12.       scale = @sd * Math.sqrt(-2 * Math.log(1 - @rng.call))
  13.       @g1 = @mean + scale * Math.sin(theta)
  14.       @g0 = @mean + scale * Math.cos(theta)
  15.     else
  16.       @g1
  17.     end
  18.   end
  19. end
  
作者: 路路    时间: 2015-1-24 14:10
提示: 作者被禁止或删除 内容自动屏蔽




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1