【问题标题】:random numpy array whose values are between -1 and 1 and sum to 1随机 numpy 数组,其值介于 -1 和 1 之间,总和为 1
【发布时间】:2021-01-01 14:53:37
【问题描述】:

创建一个给定 size 的 NumPy 数组 x 的最佳方法是什么,其值随机(且均匀?)分布在 -11 之间,并且总和为 1

我根据here 的讨论尝试了2*np.random.rand(size)-1np.random.uniform(-1,1,size),但是如果我采用一种转换方法,通过之后通过它们的总和x/=np.sum(x) 重新缩放这两种方法,这可以确保元素总和为 1 , 但是:数组中的某些元素突然大于或小于 1 (>1, -1),这是不想要的。

【问题讨论】:

  • 任何围绕零对称的分布都会有一个总和,要么为零,要么非常接近它并且很小。除以零无效。将数字除以小分数会使它们更大。您能否提供更多关于您实际尝试实现的目标以及为什么需要这样做的背景信息?
  • @PaulH 它的 mean 接近于零。我不明白为什么它的 sum 必须接近于零。
  • 生成数组x,其元素都在-1和1之间,元素和为1
  • 你所要求的没有数学意义。 “在-1和1之间随机均匀分布”完全决定了分布;您不能在此之上附加其他条件。
  • @PaulH 我明白了,但是以np.random.uniform(-1, 1, 1000).sum() 为例,您应该不会对看到大约 50 左右的值感到惊讶。

标签: python arrays numpy random


【解决方案1】:

你编写了一个代数矛盾。您引用的问题的假设是随机样本将大约填充范围 [-1, 1]。如果您以线性方式重新缩放,则在代数上不可能维持该范围,除非在缩放之前总和为 1,以便缩放没有发生变化。

你有两个直接的选择:

  1. 放弃范围的想法。进行简单更改以确保总和为至少 1,并在缩放后接受更小的范围。你可以用任何你喜欢的方式来做这件事,让选择偏向积极的一面。
  2. 更改您原来的“随机”选择算法,使其总和趋于保持接近 1,然后添加一个最终元素,使其恰好返回 1.0。这样您就不必重新缩放。

考虑基本区间代数。如果您从[-1,1] 的区间(范围)开始并乘以a(对您来说就是1/sum(x)),那么得到的区间就是[-a,a]。如果a > 1,如您的情况,则生成的间隔更大。如果是a < 0,则交换区间的两端。


从您的 cmets 来看,我推断您的概念问题更加微妙。您正试图强制一个期望值为0 的分布产生总和为 1。除非您同意以某种方式在没有特定界限的情况下倾斜该分布,否则这是不现实的。到目前为止,您拒绝了我的建议,但没有提供您接受的任何内容。在您确定之前,我无法合理地为您推荐解决方案。

【讨论】:

  • 总和为 1 的要求必须是准确的,并且在最后一个元素上添加太乱了
  • 优先考虑输出(重新缩放后),而不是输入(重新缩放前),满足两个要求。不必满足输入的要求,只需满足输出的要求,如果这样更容易的话
  • 这并不容易;线性变换可以在初始生成之后应用,或者简单地合并到原始过程中。
  • 让我知道我是否应该删除转换步骤并将问题编辑为更直接地“如何生成值介于 -1 和 1 之间且总和为 1 的随机数组”
  • 好吧,我会的。我完全理解您的建议并且已经完成了
【解决方案2】:

在这种情况下,让我们让均匀分布开始该过程,但调整值以使总和为 1。为了说明起见,我将使用初始步骤 [-1, -0.75, 0, 0.25, 1] 这给我们一个总和 - 0.5,但我们需要 1.0

第 1 步:计算所需的总更改量:1.0 - (-0.5) = 1.5

现在,我们将分配元素之间的变化分配是某种适当的方式。我使用的一种简单方法是最大限度地移动中间元素,同时保持端点稳定。

STEP 2:计算每个元素与最近端点的差异。为了你的好范围,这是1 - abs(x)

第 3 步:总结这些差异。划分为所需的更改。这给出了调整每个元素的数量。

把这么多放到图表中:

  x    diff  adjust
-1.0   0.00  0.0
-0.75  0.25  0.1875
 0.0   1.0   0.75
 0.25  0.75  0.5625
 1.0   0.0   0.0

现在,只需添加 xadjust 列即可获取新值:

 x    adjust  new
-1.0  0.0     -1.0
-0.75 0.1875  -0.5625
 0    0.75     0.75
 0.25 0.5625   0.8125
 1.0  0.0      1.0

有您调整后的数据集:总和为 1.0,端点完好无损。


简单的python代码:

x = [-1, -0.75, 0, 0.25, 1.0]
total = sum(x)
diff = [1 - abs(q) for q in x]
total_diff = sum(diff)
needed = 1.0 - sum(x)

adjust = [q * needed / total_diff for q in diff]
new = [x[i] + adjust[i] for i in range(len(x))]
for i in range(len(x)):
    print(f'{x[i]:8} {diff[i]:8} {adjust[i]:8} {new[i]:8}')
print (new, sum(new))

输出:

      -1        0      0.0     -1.0
   -0.75     0.25   0.1875  -0.5625
       0        1     0.75     0.75
    0.25     0.75   0.5625   0.8125
     1.0      0.0      0.0      1.0
[-1.0, -0.5625, 0.75, 0.8125, 1.0] 1.0

我会让你在 NumPy 中对其进行矢量化处理。

【讨论】:

  • name 'needed' is not defined。应该是needed = total_diff - total?然后,当我使用 needed 的定义将 [-1, -0.75, 0, 0.25, 1] 发送到您的函数时,校正后的数组的总和为 2。
  • 我找到了一种使用转换后的 Dirichlet 分布的方法,但需要帮助 stackoverflow.com/questions/63910689/…
  • 我看到其他人在“明显”分析方面击败了我。
【解决方案3】:

您可以为正值和负值创建两个不同的数组。确保正极加起来为 1,负极加起来为 0。

import numpy as np
size = 10
x_pos = np.random.uniform(0, 1, int(np.floor(size/2)))
x_pos = x_pos/x_pos.sum() 
x_neg = np.random.uniform(0, 1, int(np.ceil(size/2)))
x_neg = x_neg - x_neg.mean()

x = np.concatenate([x_pos, x_neg])
np.random.shuffle(x)

print(x.sum(), x.max(), x.min())
>>> 0.9999999999999998 0.4928358768227867 -0.3265210342316333

print(x)
>>>[ 0.49283588  0.33974127 -0.26079784  0.28127281  0.23749531 -0.32652103
  0.12651658  0.01497403 -0.03823131  0.13271431]

【讨论】:

  • x_neg 中没有负数。 x_neg 的代码 = x_pos
  • 在连接时我正在服用-x_neg。
  • size = 5 或更小时不起作用。违反了 1 和 -1 的最大值和最小值。
  • 再想一想,放大到 2 将确保正侧具有可能超过 1 的大值。因此有两个数组,一个具有正值加起来为 1,另一个居中围绕它的平均值,所以它加起来是 0
  • 我找到了一种使用转换后的 Dirichlet 分布的方法,但需要帮助 stackoverflow.com/questions/63910689/…
【解决方案4】:

拒绝抽样

您可以使用rejection sampling。下面的方法通过在比原始空间小 1 维的空间中进行采样来实现这一点。

  • 第 1 步:通过从均匀分布中抽样每个 x(i) 来随机抽样 x(1)、x(2)、...、x(n-1)
  • 步骤 2:如果总和 S = x(1) + x(2) + ... + x(n-1) 小于 0 或大于 2,则拒绝并在步骤 1 中重新开始。
  • 第 3 步:计算第 n 个变量为 x(n) = 1-S

直觉

您可以在笛卡尔坐标为 ±1, ±1 的 n 维立方体内部查看向量 x(1), x(2), ..., x(n-1), x(n) ,...,±1。这样您就可以遵循约束 -1

坐标总和必须等于 1 的附加约束将坐标约束到比超立方体更小的空间,并且将是维度为 n-1 的hyperplane

如果您进行常规拒绝采样,从均匀分布中采样所有坐标,那么您将永远不会命中约束。采样点永远不会在超平面中。因此,您考虑 n-1 个坐标的子空间。现在您可以使用拒绝抽样。

视觉上

假设您有维度 4,那么您可以从 4 中绘制坐标中的 3。该图(均匀地)填充了一个多面体。下面通过在切片中绘制多面体来说明这一点。每个切片对应不同的总和 S = x(1) + x(2) + ... + x(n-1) 和不同的 x(n) 值。

图像:3 个坐标的域。每个彩色表面与第 4 个坐标的不同值相关。

边际分布

对于大维度,拒绝抽样将变得不那么有效,因为拒绝的比例随着维度的数量而增加。

“解决”这个问题的一种方法是从边缘分布中抽样。但是,计算这些边际分布有点乏味。比较:对于从狄利克雷分布生成样本,存在similar algorithm,但在这种情况下,边缘分布相对容易。 (然而,推导出这些分布并非不可能,见下文“与 Irwin Hall 分布的关系”)

在上面的示例中,x(4) 坐标的边缘分布对应于切口的表面积。因此,对于 4 维,您可能能够根据该图计算出计算(您需要计算那些不规则多边形的面积),但对于更大的维度,它开始变得更加复杂。

与 Irwin Hall 分布的关系

要获得边缘分布,您可以使用截断的Irwin Hall distributions。欧文霍尔分布是均匀分布变量之和的分布,将遵循一些分段多项式形状。下面以一个示例进行演示。

代码

由于我的 python 生锈了,我将主要添加 R 代码。该算法非常基本,因此我想任何 Python 编码器都可以轻松地将其改编成 Python 代码。在我看来,这个问题的难点在于算法而不是如何在 Python 中编码(虽然我不是 Python 编码器,所以我把它留给其他人)。

图像:采样输出。 4 条黑色曲线是四个坐标的边缘分布。红色曲线是基于 Irwin Hall 分布的计算。这可以通过直接计算而不是拒绝抽样来扩展到抽样方法。

python中的拒绝采样

import numpy as np

def sampler(size):
   reject = 1
   while reject:
      x = np.random.rand(size - 1) # step 1
      S = np.sum(x)
      reject = (S<0) or (S>2)      # step 2
   x = np.append(x,1-S)            # step 3
   return[x]

y = sampler(5) 
print(y, np.sum(y))

R 中的更多代码,包括与 Irwin Hall 分布的比较。此分布可用于计算边际分布,并可用于设计一种比拒绝抽样更有效的算法。

### function to do rejection sample
samp <- function(n) {
  S <- -1
  ## a while loop that performs step 1 (sample) and 2 (compare sum)
  while((S<0) || (S>2) ) { 
    x <- runif(n-1,-1,1)
    S <- sum(x)
  }
  x <- c(x,1-S) ## step 3 (generate n-th coordinate)
  x
}

### compute 10^5 samples
y <- replicate(10^5,samp(4))

### plot histograms
h1 <- hist(y[1,], breaks = seq(-1,1,0.05))
h2 <- hist(y[2,], breaks = seq(-1,1,0.05))
h3 <- hist(y[3,], breaks = seq(-1,1,0.05))
h4 <- hist(y[4,], breaks = seq(-1,1,0.05))

### histograms together in a line plot
plot(h1$mids,h1$density, type = 'l', ylim = c(0,1),
     xlab = "x[i]", ylab = "frequency", main = "marginal distributions")
lines(h2$mids,h2$density)
lines(h3$mids,h3$density)
lines(h4$mids,h4$density)

### add distribution based on Irwin Hall distribution

### Irwin Hall PDF
dih <- function(x,n=3) {
  k <- 0:(floor(x))   
  terms <- (-1)^k * choose(n,k) *(x-k)^(n-1)
  sum(terms)/prod(1:(n-1))
}
dih <- Vectorize(dih)

### Irwin Hall CDF
pih <- function(x,n=3) {
  k <- 0:(floor(x))   
  terms <- (-1)^k * choose(n,k) *(x-k)^n
  sum(terms)/prod(1:(n))
}
pih <- Vectorize(pih)


### adding the line 
### (note we need to scale the variable for the Erwin Hall distribution)
xn <- seq(-1,1,0.001)

range <- c(-1,1)
cum <- pih(1.5+(1-range)/2,3)
scale <- 0.5/(cum[1]-cum[2]) ### renormalize
                           ### (the factor 0.5 is due to the scale difference)
lines(xn,scale*dih(1.5+(1-xn)/2,3),col = 2)

【讨论】:

  • 旁注:uniform 定义不明确,但我假设超平面上的概率密度为常数,以每个欧几里得体积元素 dx(1)*dx(2)*...*dx (n)。这有点难以可视化,因为您不是在超立方体的体积上积分,而是在超平面上积分。例如。想象一下嵌入在二维空间中的一维直线上的均匀密度的简单情况。
  • 这不再是统计堆栈,那么如何为 $[-1,1]$ 和 sum $1$ 请求编码拒绝采样
  • 也许这个问题在统计堆栈上会更热门。这是一个非常基本的算法,但我在 python 中不太擅长。我将添加我的 R 代码。把它变成python应该很简单。
  • 好的,但是这个问题也被问到了没有结果的统计数据上。这是它的编码对应物,由问题本身的编码尝试表明
  • stats stackchange 的问题是不同的。该问题没有具体说明分布必须是均匀的。该添加使提供答案变得更加容易,因为没有该规范,就有无限多的可能性。 (其实还是有无限多的可能,因为“统一”这个词有歧义,但这是最简单的方法)
猜你喜欢
  • 1970-01-01
  • 2012-11-03
  • 1970-01-01
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
  • 2022-11-29
  • 1970-01-01
  • 2012-04-10
相关资源
最近更新 更多