אינטגרציית מונטה קרלו

[ממוזער|281x281 פיקסלים|אנימציה של אינטגרציית מונטה קרלו של הפולינום <math>x^3 - 6x^2 + 9x + 1</math> בתחום <math>[0, 4]</math>. הקווים הנוספים מסמלים את כיסוי התנהגות הפונקציה ע״י הוספת נקודות דגימה נוספות. בכותרת מוצגת השגיאה היחסית שקטנה ככל שמספר נקודות הדגימה עולה.](https://he.wikipedia.org/wiki/%D7%A7%D7%95%D7%91%D7%A5:Monte-carlo-integration-animation-of-polynomial.gif)
**אינטגרציית מונטה קרלו** היא שיטת אינטגרציה נומרית מבוססת דגימה אקראית של נקודות בתחום האינטגרל, והיא מקרה פרטי של שיטת מונטה קרלו. השיטה שימושית במיוחד לחישוב אינטגרלים רב ממדיים מאחר שאינה מחלקת את תחום האינטגרל לרשת בדידה שגדלה בצורה מעריכית כתלות בממד.

שיטה זאת זוכה לשימושים נרחבים במתמטיקה פיננסית, פיזיקה סטטיסטית, גרפיקה ממוחשבת ובעוד תחומים רבים.

## רקע
נניח כי ברצוננו לחשב את האינטגרל המסוים I של פונקציה חד או רב ממדית g(\mathbf{x}) על התחום \Omega \sub \mathbb{R}^n כאשר \Omega היא קבוצה סופית.

I = \int_{\Omega} g( \mathbf{x}) d \mathbf{x}נניח כי \Omega בעלת נפח סופי V = \int_\Omega d \mathbf{x}.

נגדיר משתנה מקרי \vec X \sim \text{Unif}(\Omega) שמתפלג בצורה אחידה על \Omega ונשים לב כי פונקציית הצפיפות f של \vec X תוגדר להיותf(x) = \begin{cases}
p = 1/V & x \in \Omega \\
0 &\text{otherwise}
\end{cases}

נשים לב כי, התוחלת של \vec X כאשר עליו מופעלת הפונקציה g היא

\mathbb{E}[g(\vec X)] = \int_{\bar \Omega} g(\mathbf{x}) f(\mathbf{x}) d\mathbf{x} = \int_{\bar \Omega} g(\mathbf{x}) p \ d\mathbf{x} = p I = I/V

מאידך, עבור N דגימות מהמ״מ \vec X, \mathbf{x}_1, \mathbf{x}_2, \dots, \mathbf{x}_N \in \bar \Omega נשערך את \mathbb{E}[g(\vec X)] ע״י

\mathbb{E}[g(\vec X)] \approx {1 \over N} \sum_{i=1}^N g(\mathbf{x}_i)ולבסוף נקבל

I = V \cdot \mathbb{E}[g(\vec X)] \approx Q_N \equiv {V \over N} \sum_{i=1}^N g(\mathbf{x}_i)


מחוק המספרים הגדולים נקבל

\lim_{N \to \infty} Q_N = V \cdot \mathbb{E}[g(\vec X)] = I

### הערכת השגיאה
ראשית, נשים לב כי Q_N משערך חסר הטיה 

\mathbb{E}[Q_N] =
\mathbb{E} \Big[{V \over N } \sum_{i=1}^N g(\mathbf{x}_i) \Big]
= {V \over N} \sum_{i=1}^N \mathbb{E} [g(\mathbf{x}_i)]
= V \cdot \mathbb{E}[g(\vec X)] = I

נחשב את המשערך חסר ההטייה \hat \sigma^2_N של השונות \mathbb{V}[g(\vec X)]

\hat \sigma^2_N \equiv {1 \over N-1} \sum_{i=1}^N (g(\mathbf{x}_i) - I)^2 ממוזער|259x259px|שגיאה יחסית של אינטגרציית מונטה קרלו כתלות במספר הדגימות <math>N</math> שדועכת בקצב התכנסות <math>1/\sqrt{N}</math>והשונות של המשערך Q_N

\mathbb{V} [Q_N] =
\mathbb{V} \Big[{V \over N} \sum_{i=1}^N g(\mathbf{x}_i) \Big] =
{V^2 \over N^2} \sum_{i=1}^N \mathbb{V} [g(\mathbf{x}_i)]
= {V^2 \over N} \cdot \mathbb{V}[g(\vec X)]
\approx V^2 {\hat \sigma^2_N \over N}

ולבסוף נקבל, כי שורש השגיאה הריבועית ממוצעת, ע״פ משפט 6.9 של והיעדר ההטייה של Q_N הוא

\text{RMSE}(Q_N) = \sqrt{\mathbb{V} [Q_N] + \text{bias}(Q_N)} = \sqrt{\mathbb{V} [Q_N]} \approx V {\sigma^2_N \over \sqrt{N}}

## דוגמה: שערוך הערך <math>\pi</math> ע״י חישוב שטח מעגל היחידה
ממוזער|אנימציה של שערוך הערך <chem>\pi</chem> ע״י דגימת נקודות
הדגמה נפוצה של השיטה היא שערוך הערך <math>\pi</math> ע״י חישוב שטח מעגל היחידה \mathbb{S}^1. על אף שזו אינה דרך יעילה לחשיוב הערך של <math>\pi</math>, היא מדגימה את השיטה.

בשביל לחשב את \pi ניתן לחשב אינטגרל על התחום [-1, 1] \times [-1, 1] של פונקציית האינדיקטור של מעגל היחידה \mathbf{1}_{S^1} המוגדרת להיות

\mathbf{1}_{S^1} (x, y) = \begin{cases}
1 & \text{if }x^{2}+y^{2}\leq1\\
0 & \text{else}
\end{cases}ומכך מקבלים:

\pi = \text{area}( S^1) = \int_{S^1} d \mathbf{x} = \int_{[-1, 1]^2} \mathbf{1}_{S^1} d \mathbf{x}

והמשערך יהיה

Q_N(\pi) = {V \over N} \sum_{i=1}^N g(\mathbf{x}_i) = {\text{vol} ([-1, 1]^2) \over N} \sum_{i=1}^N \mathbf{1}_{S^1} = 4 \cdot { \# \{ \text{samples in } S^1 \} \over \# \{ \text{total samples} \}}


כלומר למעשה, החישוב נעשה ע״י דגימת נקודות בריבוע היחידה [-1, 1]^2, ספירה של הנקודות שנופלות בתוך מעגל היחידה, חלוקה במספר נקודות הדגימה הכולל (תחולת האינטגרנד) והכפלה בנפח ריבוע היחידה.

מימוש ההדגמה בשפת פייתון:
```python3
import numpy as np

### parameters
# number of samples
N = 1e6

# generate N uniform random points inside the unit cube [-1, 1] x [-1, 1]
X = np.random.uniform(low=-1, high=1, size=(int(N), 2))
domain_volume = 4  # unit cube area.

# mask for points inside the unit circle - equivalent to applying the indicator function of the unit circle.
distance = np.sqrt(X[:, 0] ** 2 + X[:, 1] ** 2)
inside_mask = distance <= 1  # array of booleans.

# compute the mean of the indicator function over the domain and multiply by the domain volume to get the integral value.
indicator_mean = np.sum(inside_mask) / N
integral_value = indicator_mean * domain_volume
```
ניתן כמובן לשערך את הערך \pi בעזרת חישוב שטחם של מעגלים בעלי רדיוס שונה מ-1

## יתרונות וחסרונות

**יתרונות**
* **פשטות - להבנה ומימוש** - השיטה אינטואיטיבית ודי פשוטה למימוש כפי שניתן לראות בקוד לעיל.
* **היעדר תלות בממד** - שיטות אינטגרציה נומרית אחרות (תרבוע גאוסיאני ושיטת סימפסון לדוגמה) מחלקות את תחום האינטגרציה לרשת בדידה בעלת N נקודות, בהן הפונקציה מופעלת. כאשר ממד האינטגרל גדול מאחד d \ge 1 אנו מקבלים גידול אקספוננציאלי במספר הנקודות מאחר שעלינו לחלק את התחום לרשת בדידה בכל ממד כלומר - N^d נקודות. שיטת מונטה לעומת זאת, דוגמת N נקודות במרחב הרב ממדי כולו ולכן אינה סובלת מ״קללת הממד״
**חסרונות**
* **התכנסות איטית** - כאמור, השגיאה הריבועית ממוצעת דועכת בקצב O({1 \over \sqrt{N}}) כתלות במספר הדגימות N.
* **אינה דטרמיניסטית - בעלת מרכיב אקראי (דגימת הנקודות) -** בשונה משיטות אינטגרציה נומרית רשתיות כגון שיטת הטרפז שמחלקות את תחום האינטגרציה לרשת בדידה דטרמיניסטית, שיטת מונטה קרלו דוגמת אקראית נקודות בתחום האינטגרציה. צורת הדגימה של הנקודות חשובה מאוד. לדוגמה, לא נרצה לקבל מעיין ״גוש״ נקודות קרובות אחת לשנייה (איור 1), אלא דגימה אחידה במרחב ע״מ לקבל כיסוי מספיק של התנהגות הפונקציה בתחום. נוסף על כך, אקראיות השיטה תאלץ אותנו לחזור על חישובנו מספר רב של פעמים על מנת ליצור סטטיסטיקה מספקת על התוצאות ולהשתמש ברווח בר-סמך ו/או מבחנים סטטיסטים.

## וריאציות
### דגימת שכבות רקורסיבית

הכללה של אינטגרציה מסתגלת לשיטת מונטה קרלו. כאשר בכל חזרור, אם הדיוק המתקבל אינו עומד בדיוק הרצוי, תחום האינטגרציה מחולק ל-2 תתי תחומים, אשר עליהם נעשית האינטגרציה, בצורה איטרטיבית. החלוקה תעשה לתחומים בהם שונות הפונקציה היא הגדולה ביותר על מנת להביא לכיסוי טוב יותר של התחום באזורים חשובים אלה.

כאשר האינטגרל הוא רב ממדי, חלוקה של כל ממד ל-2 תגדיל את מספר הנקודות בצורה מעריכית, לכן החלוקה תעשה בממד בו חלוקה נוספת תביא להקטנת השגיאה המשמעותית ביותר.

ניתן להרחיב על דגימת שכבות רקורסיבית ואלגוריתם MISER המממש אותה.

## דגימת חשיבות
[ממוזער|שגיאה יחסית של חישוב ערך האינטגרל של [התפלגות נורמלית|פעמון גאוסיאני **סטנדרטי** בתחום האינטגרציה [-50, 50] כתלות במספר הדגימות N. מדגיש את עליונות אלגוריתם לאס וגאס אשר דוגם פחות נקודות בקצוות התחום (מאחר שחשיבותן נמוכה מאוד) לעומת דגימה אחידה על התחום האינטגרציה.]]
בדומה לאלגוריתמים דטרמיניסטים כגון שיטת סימפסון, נקודות בעלות ערך שונה בהרבה מסביבתן, אשר משפיעות על הערך הסופי של האינטגרל, עלולות ״להתפספס״ כנקודות דגימה ולהשפיע לרעה על התוצאה הסופית. לכן ישנה חשיבות רבה על נקודות הדגימה עצמן, הדרך בהן הן נבחרות.

השיטה ״הנאבית״ פשוטה להבנה ומימוש, הנקודות נבחרות בצורה שרירותית ע״י דגימה אחידה על תחום האינטגרציה, ללא התייחסות לחשיבותן בתוצאה הסופית. וריאציות של השיטה מסוג "דגימת חשיבות" מציעות צורת דגימה אחרת.

דוגמה טובה לכך היא חישוב האינטגרל המסוים של פעמון גאוסיאני **סטנדרטי** בעל תוחלת \mu = 0 וסטיית תקן \sigma = 1 על התחום [-50, 50]. כאשר נשתמש בשיטה הנאיבית, מאחר שהדגימה אחידה על התחום, יתקבלו נקודות דגימה רחוקות ממרכז הפעמון (0) למרות היותן בעלות חשיבות נמוכה עד זניחה עבור חישוב האינטגרל שהרי עבור |x| \ge 3 (3 סטיות תקן) ערך הפונקציה קטן מ-
5 \cdot 10^{-3}.
אי לכך, רצוי להתמקד בדגימה באזורים בהם הנקודות בעלות חשיבות גבוהה (סביב מרכז הפעמון בדוגמה לעיל).
רעיון זה ממומש, לדוגמה, באלגוריתם לאס וגאס

.

## מימוש בקוד
קוד פייתון כללי כאשר הפונקציה ותחום האינטגרציה ידועים, והתחום הוא מלבן רב ממדי:
```python
import numpy as np
from typing import Callable


def monte_carlo_integration(
    domain: np.ndarray, N: int, f: Callable[[np.ndarray], float]
) -> float:
    """monte_carlo_integration

    Args:
        domain (np.ndarray): d-dimensional domain - [a_i, b_i] x ... x [a_d, b_d] - represented as a 2 x d numpy array.
        N (int): number of random points to generate.
        f (Callable[[np.ndarray], float]): function to integrate that receives a d-dimensional point and returns a float.

    Returns:
        float: integral value of f over the domain.
    """

    # generate N uniform random points - a random matrix of size N x d with each row representing a random point.
    # first column of the domain holds the lower bounds and the second column holds the upper bounds.
    X = np.random.uniform(
        low=domain[:, 0], high=domain[:, 1], size=(N, domain.shape[1])
    )

    # evaluate the function at the random points
    f_values = np.apply_along_axis(func1d=f, axis=1, arr=X)

    # compute the domain volume and final integral
    domain_volume = np.prod(domain[:, 1] - domain[:, 0])
    integral_value = np.mean(f_values) * domain_volume

    return integral_value


# Example usage with unit circle
domain = np.array([[-1, 1], [-1, 1]])
N = 1e6
f = lambda arr: np.where(np.linalg.norm(arr) <= 1, 1, 0)
integral_value = monte_carlo_integration(domain, N, f)
```


בנוסף, קיימות דוגמאות וספריות  פייתון שממשות גרסאות שונות של האלגוריתם.

## קישורים חיצוניים
* 

## הערות שוליים

קטגוריה:אנליזה נומרית
קטגוריה:אלגוריתמים
קטגוריה:אקראיות