기인
얼마 전에 저는 급우들이 ER
인터넷과 BA
인터넷에 대해 글을 쓰는 것을 도왔습니다.
그 중 BA
네트워크는 전체 그래프의 차수에 대한 차수의 비율을 확률로 취해야 합니다.
그래서 확률에 따라 무작위로 숫자를 뽑는 함수를 작성했습니다 pick
.
생각의 기차
아이디어는 간단했습니다. 숫자축을 여러 조각으로 나눈 뒤 무작위로 점을 그리는 것입니다.
예를 들어 배열은 간격에서 임의의 정수를 [1,2,3]
생성합니다 . 임의의 숫자가 인 경우 세 번째 숫자가 선택된 것으로 간주됩니다.[1,6]
5
질문
위의 방법은 숫자를 그리는 데 매우 효과적이지만 숫자를 그리는 것이라면 n
성능이 매우 심하게 떨어집니다. 더 많이 그릴수록 반복 가능성이 커집니다.
방금 셔플링 알고리즘을 사용하기 시작했고 i번째 숫자를 뽑을 때 i번째 위치의 숫자와 교환한 다음 확률에 따라 다음 숫자의 숫자를 가져옵니다. 이렇게 하면 반복 가져오기 문제가 해결되지만 효율성은 여전히 높지 않습니다. 접두사 합계가 매번 다시 계산되기 때문입니다.
나중에 저수지 알고리즘을 개선하기 위해 갔다 . 하지만 확률이 같지 않은 알고리즘은 없는 것 같습니다.
최종 알고리즘
마지막으로 확률을 0으로 설정하는 방법을 채택한다. 반복되는 가져오기를 방지할 뿐만 아니라 접두사 합계 업데이트를 용이하게 합니다.
접두사 합계는 트리 배열로 유지되며 쿼리 수정은 log(n) log(n)log ( n ) 은
이진 검색을 사용할 구간을 판단하며, 이진 검색의 원래 복잡도는 log (n) log(n)l o g ( n ) 내부에 또 다른 쿼리 접두사 sum이 있습니다. 총 복잡도( log (n ) ) 2 (log(n))^2( log ( n ) ) _ _2
점을 이해하기 위한 예를 들어보자
예를 들어 확률비는 이고 [1,2,3]
접두사 합은 [1,3,6]
두 번째 숫자가 처음 선택된 경우이다. 그러면 확률비는 [1,0,3]
, 접두어 합은 가 되고 [1,1,4]
, 접두어에 대한 이분법과 난수 보다 크거나 같은 첫 번째 위치 에 대한 각 검색은 반복 및 시간 복잡도를 보장할 수 없습니다 .
코드
from scipy.stats import ks_2samp
import numpy as np
import random
from typing import List
class TreeArray:
def __init__(self, arr: List[int]) -> None:
self.len = len(arr)+1
self.arr = [0]*self.len
for i in range(0, self.len-1):
self.add(i, arr[i])
def add(self, i: int, d: int) -> None:
'''i位置加上d'''
i += 1
while i < self.len:
self.arr[i] += d
i += (i & (-i))
def sum(self, i: int) -> int:
'''获取到前i个数的和'''
res = 0
while i > 0:
res += self.arr[i]
i -= (i & (-i))
return res
pass
def bsearch(arr: TreeArray, k: int) -> int:
'''返回大于等于k的下标,若多个符合返回最小的'''
l, r = -1, arr.len-1
while l+1 < r:
m = int((l+r)/2)
if arr.sum(m+1) < k:
l = m
else:
r = m
return r
def pick(ps: List[int], n: int) -> List[int]:
'''以特定概率比例ps随机选取n个数'''
section, res = TreeArray(ps), [None]*n
cur_sum = section.sum(len(ps))
for i in range(n):
# [1,cur_sum]
x = random.randint(1, cur_sum)
j = bsearch(section, x)
res[i], cur_sum = j, cur_sum-ps[j]
section.add(j, -ps[j])
return res
시험
from scipy.stats import ks_2samp
import numpy as np
n = 500
s = 5
p = np.array([5, 3, 2, 5, 2, 1, 8, 3, 2, 1])
p1 = p/sum(p)
ids = []
for i in range(len(p)):
ids.append(i)
cnt = [0]*len(p)
cnt1 = [0]*len(p)
for i in range(n):
ans = pick(p, s)
for i in range(s):
cnt[ans[i]] += 1
ans = np.random.choice(ids, size=s, replace=False, p=p1)
for i in range(s):
cnt1[ans[i]] += 1
print(ks_2samp(cnt1, cnt))
500
여기에서 라운드를 그립니다 . KS-test는 확률분포를 만족하는지 판단한다.
여기서 매치메이킹 기능은 numpy
커리의 np.random.choice
. 매우 높다고
볼 수 있습니다 . 소스 코드의 주석에 따르면 값이 높거나 낮을수록 두 분포 사이의 유사도가 높다는 것을 알 수 있습니다 .pvalue
pvalue
statistic
KS 통계량이 작거나 p-값이 높으면
두 표본의 분포가
같다는 가설을 기각할 수 없습니다.
삽화
이것이 끝이라고 생각하십니까? 실제로는 아닙니다.
데이터 크기를 늘리면 이상한 일이 발생했습니다.
데이터 양이 많을수록 두 분포가 더 유사하다는 것은 당연합니다. 하지만 제가 측정한 결과는 정반대였습니다. 그 추세는 n이 500일 때, n이 5000일 때, n이 50000일 때
단조롭지 않습니다.
KstestResult(statistic=0.2, pvalue=0.9944575548290717)
KstestResult(statistic=0.1, pvalue=1.0)
KstestResult(statistic=0.3, pvalue=0.7869297884777761)
5000을 복용했을 때 효과가 가장 좋은 것을 알 수 있습니다.
나중에 확인하고 정보를 써보니 ks가 이산 분포 테스트에 적합하지 않다는 것을 알았습니다.
구체적인 이유는 여기를 참조하십시오.