교체 없는 확률적 랜덤 샘플링 알고리즘

기인

얼마 전에 저는 급우들이 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
pvaluestatistic

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가 이산 분포 테스트에 적합하지 않다는 것을 알았습니다.
구체적인 이유는 여기를 참조하십시오.

추천

출처blog.csdn.net/qq_45256489/article/details/122086971