몬테카를로 적분 근사 - importance sampling 질문 드립니다.

조회수 1856회
% matplotlib inline
from sympy import *
from scipy import random
import numpy as np
import matplotlib.pyplot as plt

a = 0
b = 1        
M = 100000   
xrand = np.zeros(M)
yrand = np.zeros(M)
zrand = np.zeros(M)

for i in range(len(xrand)):
    xrand[i] = random.uniform(a,b)
    yrand[i] = random.uniform(a,b)
    zrand[i] = random.uniform(a,b)

위에서처럼 코드를 짜서 xrand[i] = random value 형태로 i값에 따라 랜덤밸류가 달라지는 구조를 만들긴 했습니다만 그 이후가 생각보다 잘 되지 않습니다. 정확히 어떤 상황이냐면 다음과 같이.

import random

N = 900
def monte_carlo(the_list) :
    if len(the_list) > N :
        return the_list
    else :
        x = random.random()
        y = random.random()
        z = random.random()
        if p(y)/p(x) > z :
            the_list.append(y)    
            return monte_carlo(the_list)
        else :
            the_list.append(x)   
                # 재귀
            return monte_carlo(the_list)

new_list = []
print(monte_carlo(new_list)) 

위 코드는 예전에 제가 질문했을 때 어떤 고수분께서 올려주신 코드(나중에 알았지만 importance sampling 의 일종) 입니다.

이분은 재귀함수 형태로 뽑아주셨는데, 생각보다 오차가 크게 나와서 .. 방법을 좀 바꾸려고 합니다.

C로 코딩해서 성공한 연구실 동료가 조언해준 방법대로 i 값에 의존하는 x[i]를 만든 후 판정조건식 ( p(y[i])/p(x[i)] > z[i] ) 을 만족할 때는 그대로 y를 append 하고,

else의 경우에는 재귀를 취하지 않고, 그냥 i = i + 1 로 넘어가서 ( i + 1)에서 새로운 판정 p(y[i+1]) /p(x[i+1]) > z[i+1] 을 해나아가는.. 그런 구조를 만들어서, 판정을 만족시킨 N개의 random value들을 얻고 싶은데 잘 되지 않네요.. (참고로 이 경우에도 M개의 random value 값들이 N개의 random 값들을 추출하기 전에 소진될 가능성이 있습니다. 무한개의 random value 리스트를 만드는게 가능한지 몰라서 일단은 이 방법으로 해보려고 합니다.)

어떻게 해야 할까요?... 조언 부탁드립니다.

추가) 일단 제가 며칠전에 완성했던 허졉한 코드는 다음과 같습니다.. 부끄럽지만 조언 부탁드려여 ㅠ.ㅠ 코드의 중간 부분부터가 importance 샘플링입니다.

from sympy import *
x = Symbol('x')
y = Symbol('y')
z = Symbol('z')
a = Symbol('a')

def f(x):
    return 3 * x **2
def p(x):
    return 2 * x 
def g(x) :
    return f(x)/p(x)

Integral(f(x),x).doit()
pprint(Integral(f(x),x).doit()) 
pprint(Integral(f(x), (x,0,1)).doit()) # accurate integration value
pprint(Integral(p(x), (x,0,1)).doit()) # Normalization check

# 구분구적법으로 함수 정적분 풀이 시도

% matplotlib inline
from sympy import *
from scipy import random
import numpy as np
import matplotlib.pyplot as plt

a = 0
b = 1        # limit of integration 0부터 파이까지 함수 적분
M = 100000   # 쪼개는 수
xrand = np.zeros(M)

for i in range(len(xrand)):
    xrand[i] = random.uniform(a,b)

integral = 0.0
for i in range(M):
    integral += f(xrand[i])

answer = (b-a)/float(M)*integral
error = abs(1-answer)
print("The integral from 0 to 1 of f(x): ", answer)
print("error: ", error)


import random

# importance sampling 시도해본다.

'''
 엽토님께.

 기본적인 아이디어는 integral(f(x))dx(0부터 1까지) 라는 정적분값을 난수를 이용해 근사과정으로 구하는 것입니다.
 f(x) = (f(x)/p(x)) * p(x) = g(x) * p(x) 라 하고 이때 p(x)를 weight function 또는 확률분포함수(pdf)라고 합니다.

 이 때 특정한 판정조건(아래코드의 조건식)을 만족하는 x값들을 N개 추출하고 이를 통해 g(x)의 평균값을 구하면 
 " Mean value of g(x) = f(x)의 원래 정적분값 " 이 성립함이 알려져 있습니다. N값이 커질수록 두 값의 차이는 줄어듭니다.

 (참고로 이 문제에서는 f(x) = 3x^2, p(x) = 2x 라 가정하여 g(x) = 3x^2/2x로 되어 있다고 보시면 됩니다.)
 (따라서 최종적으로 가까워져야 할 값은 정적분값인 1입니다.)
''' 

# 비어 있는 리스트를 주면 특정 식을 만족할 경우 해당 랜덤값을 최대 N개까지 목록에 넣어서 돌려준다.
N = 900
def monte_carlo(the_list) :
    if len(the_list) > N :
        return the_list
    else :
        x = random.random()
        y = random.random()
        z = random.random()
        if p(y)/p(x) > z :        # p(X)는 이 경우 1차 다항함수이므로 y/x 해도 상관없음.
            the_list.append(y)    # 이 함수 자체를 다시 실행
            return monte_carlo(the_list)
        else :
            the_list.append(x)   # x를 리스트에 꼽고
                # 재귀
            return monte_carlo(the_list)

new_list = []
#print(monte_carlo(new_list)) 

I = 0
for i in range(N):
    I += g(monte_carlo(new_list)[i])/ N
print(I)
  • 몬테카를로가 뭔지도 모르면서 덮어놓고 작성한 제 답변 내용이 계속 쓰이고 있는것 같아서 굉장히 신경쓰이네요. 혹시 전체 로직 flow chart나 알고리즘 설명 한번 올려주실수 있을까요? 일단 그 큰그림을 장악해야 코딩을 해도 할것 같네요. 엽토군 2019.1.16 19:03
  • 엽토님 덕분에 일단 모양새는 갖췃습니다만, 주피터 노트북에서는 재귀형식으로 하면 N값을 크게 늘리지 못하네요.. 박형선 2019.1.16 19:23
  • 말씀하신 코드 전체 올렷습니다. 박형선 2019.1.16 19:23
  • 주피터 노트북에서 현재 작성중인데 주피터에서는 recursion depth exceed 라고해서 재귀형식으로 하면 N값을 크게 늘리기 어려워 검증이 잘 안되더군요. 그리고 오차도 5퍼센트 내외로 큰편이라... 부끄럽지만 다시 질문올렸습니다 박형선 2019.1.16 19:24
  • 음... 사실 저는 전체 코드라기보다는 전체 순서도를 여쭌 거긴 한데... 괜찮습니다. 엽토군 2019.1.16 22:53

1 답변

  • 좋아요

    2

    싫어요
    채택 취소하기

    설명 감사합니다. 문과라서 그런가 수학 공학 조금만 들어가도 머리에 쥐가 나네요. ㅠ
    위키백과 등등을 읽고 전체 소스를 다시 보면서 지금 드는 생각은... "조건식을 만족하는 x값들"을 미리 뽑아놓을 게 아니라 하나씩 뽑아서 하나씩 처리해야 할 것 같다는 느낌이네요. 어쩌면 쓰고 계신 컴퓨팅 장비가 못버티는 이유 중 하나가 메모리 누수일 수 있거든요. 수십~수백만 원소를 갖는 배열이 있고 거기서 또 랜덤을 세 번 돌려서 만든 결과값을 저장하는 동적 배열이 또 있고... 하다 보면 말이죠.

    그나저나 애초에 몬테카를로 적분을 소개하는 글을 읽어봐도, 이게 확률분포함수 같은 게 도입되어야 할 정도의 일인지는 잘 모르겠습니다. 아래끝 위끝 사이의 무작위 xf(x)에 대입하는 짓을 매우 많이 해서 그 대입값들의 평균을 내면, 그 평균값 곱하기 (위끝 - 아래끝)은 결국 원하는 적분값에 매우 근접한다는 건데... 이를테면

    import random
    
    left = 0
    right = 1
    aggregate = 0
    pseudo_infinity = 1000000
    
    for i in range(pseudo_infinity) :
        x = random.uniform(left, right) # 랜덤 실수를 무한루프 안에서 번번이 생성
        aggregate += 3 * pow(x, 2) # 3x^2를 그냥 풀어쓴 표현식에 대입
    
    print((aggregate / pseudo_infinity) * (right - left))
    

    이런 것일 거란 말이죠. 이게 생각보다 원활하게 잘 돕니다. 온라인 IDE로 돌리면 1초 안에 한 번이 끝나고, 대충 세 번 돌려보면

    0.999900652741
    0.999908913132
    1.00060100573
    

    말씀하시는 대로 1에 수렴을 합니다. 숙제 끝~~?? -_-;

    요약: 확률분포함수를 도입한다든가 importance sampling을 한다거나 하지 않더라도 어딘가에서 "몬테카를로 방식"이라고 칭하는 그 방식을 충분한 퍼포먼스로 구현할 수 있는 것처럼 보입니다. 이 이상 뭔가 더 고도화된 방식을 도입해야 한다면, 그때에도 역시, 미리 랜덤 리스트를 뽑아놓을 필요는 없고 계속 무한루프를 돌면서 수시로 평균값을 갱신하는 방식을 생각해볼 수 있지 않나 합니다.

    원하시는 답변은 아닐 것 같은데 도움이 되면 좋겠습니다.

    • 답변 감사합니다. ㅎㅎ. 프로그래밍 처음인데 정말 눈물나네요.. 올려주신 자료 참고해서 노력해봐야겠어여 박형선 2019.1.18 14:23
    • 감사합니다 박형선 2019.1.18 14:23

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)