שיטות מונטה קרלו באמצעות Apache Spark

‫Managed Service for Apache Spark ו-Apache Spark מספקים תשתית וקיבולת שמאפשרות להריץ סימולציות מונטה קרלו שכתובות ב-Java, ב-Python או ב-Scala.

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

בעבר, הפעלת אלפי סימולציות הייתה יכולה לקחת הרבה זמן ולצבור עלויות גבוהות. בעזרת Managed Service for Apache Spark אפשר להקצות קיבולת לפי דרישה ולשלם עליה לפי דקה. ‫Apache Spark מאפשר להשתמש באשכולות של עשרות, מאות או אלפי שרתים כדי להריץ סימולציות בצורה אינטואיטיבית, ולהתאים את קנה המידה לצרכים שלכם. זה אומר שאתם יכולים להריץ יותר סימולציות במהירות, וכך לחדש מהר יותר ולנהל סיכונים בצורה טובה יותר.

כשעובדים עם נתונים פיננסיים, האבטחה תמיד חשובה. ‫Managed Service for Apache Spark פועל ב- Google Cloud, וכך עוזר לשמור על הנתונים שלכם בטוחים, מאובטחים ופרטיים בכמה דרכים. לדוגמה, כל הנתונים מוצפנים במהלך ההעברה וכשהם באחסון, ו Google Cloud הם תואמים לתקנים ISO 27001,‏ SOC3 ו-PCI.

מטרות

  • יצירת אשכול מנוהל של Managed Service for Apache Spark עם Apache Spark מותקן מראש.
  • הפעלת סימולציית מונטה קרלו באמצעות Python, שמעריכה את הצמיחה של תיק מניות לאורך זמן.
  • תריץ סימולציית מונטה קרלו באמצעות Scala כדי לדמות איך קזינו מרוויח כסף.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

כדי ליצור הערכת עלויות בהתאם לשימוש החזוי, אתם יכולים להשתמש במחשבון התמחור.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

כשמסיימים את המשימות שמתוארות במסמך הזה אפשר למחוק את המשאבים שיצרתם כדי להימנע מחיובים נוספים. מידע נוסף זמין בקטע הסרת המשאבים.

לפני שמתחילים

  • הגדרת פרויקט ב- Google Cloud
    1. נכנסים לחשבון Google Cloud . אנחנו ממליצים למשתמשים חדשים ב- Google Cloud ליצור חשבון כדי שיוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
    2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

      Roles required to select or create a project

      • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
      • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

      Go to project selector

    3. Verify that billing is enabled for your Google Cloud project.

    4. Enable the Dataproc and Compute Engine APIs, if any are not already enabled.

      Roles required to enable APIs

      To enable APIs, you need the serviceusage.services.enable permission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.

      Enable the APIs

    5. Install the Google Cloud CLI.

    6. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

    7. To initialize the gcloud CLI, run the following command:

      gcloud init
    8. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

      Roles required to select or create a project

      • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
      • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

      Go to project selector

    9. Verify that billing is enabled for your Google Cloud project.

    10. Enable the Dataproc and Compute Engine APIs, if any are not already enabled.

      Roles required to enable APIs

      To enable APIs, you need the serviceusage.services.enable permission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.

      Enable the APIs

    11. Install the Google Cloud CLI.

    12. If you're using an external identity provider (IdP), you must first sign in to the gcloud CLI with your federated identity.

    13. To initialize the gcloud CLI, run the following command:

      gcloud init

יצירת אשכול Managed Service for Apache Spark

פועלים לפי השלבים כדי ליצור אשכול של Managed Service for Apache Spark ממסוף Google Cloud . הגדרות ברירת המחדל של האשכול, שכוללות שני צומתי עובדים, מספיקות למדריך הזה.

השבתת הרישום ביומן עבור אזהרות

כברירת מחדל, Apache Spark מדפיס רישום מפורט ביומן בחלון המסוף. לצורך המדריך הזה, משנים את רמת הרישום ביומן כך שרק שגיאות יירשמו ביומן. איך לעשות את זה?

משתמשים ב-ssh כדי להתחבר לצומת הראשי של האשכול ב-Managed Service for Apache Spark.

לצומת הראשי של אשכול Managed Service for Apache Spark יש את הסיומת -m בשם ה-VM שלו.

  1. נכנסים לדף VM instances במסוף Google Cloud .

    כניסה לדף VM instances

  2. ברשימת המכונות הווירטואליות, לוחצים על SSH בשורה של המכונה שרוצים להתחבר אליה.

    הלחצן SSH ליד שם המכונה.

ייפתח חלון SSH שמחובר לצומת הראשי.

Connected, host fingerprint: ssh-rsa 2048 ...
...
user@clusterName-m:~$

שינוי הגדרת הרישום ביומן

  1. בספריית הבית של הצומת הראשי, עורכים את /etc/spark/conf/log4j.properties.

    sudo nano /etc/spark/conf/log4j.properties
    
  2. מגדירים את log4j.rootCategory להיות שווה ל-ERROR.

    # Set only errors to be logged to the console
    log4j.rootCategory=ERROR, console
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.target=System.err
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n
    
  3. שומרים את השינויים ויוצאים מכלי העריכה. אם רוצים להפעיל שוב רישום מפורט (verbose) ביומן, צריך לבטל את השינוי ולהחזיר את הערך של .rootCategory לערך המקורי (INFO).

שפות תכנות ב-Spark

ב-Spark אפשר להשתמש ב-Python, ב-Scala וב-Java כשפות תכנות לאפליקציות עצמאיות, ויש בו מפרשים אינטראקטיביים ל-Python ול-Scala. השפה שתבחרו היא עניין של העדפה אישית. במדריך הזה נעשה שימוש במפרשים אינטראקטיביים כי אפשר להתנסות בהם על ידי שינוי הקוד, ניסיון של ערכי קלט שונים ואז צפייה בתוצאות.

הערכת הצמיחה של תיק ההשקעות

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

כדי ליצור סימולציה שמשתמשת בשיטות מונטה קרלו כדי לנסות להעריך את הצמיחה של השקעה פיננסית על סמך כמה גורמים נפוצים בשוק:

  1. מפעילים את מפרש Python מהצומת הראשי של Managed Service for Apache Spark.

    pyspark
    

    מחכים להנחיה של Spark‏ >>>.

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

    import random
    import time
    from operator import add
    
    def grow(seed):
        random.seed(seed)
        portfolio_value = INVESTMENT_INIT
        for i in range(TERM):
            growth = random.normalvariate(MKT_AVG_RETURN, MKT_STD_DEV)
            portfolio_value += portfolio_value * growth + INVESTMENT_ANN
        return portfolio_value
    
  3. לוחצים על return עד שרואים שוב את ההנחיה של Spark.

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

    בשלב הבא תגדירו את הקבועים הנדרשים.

  4. יוצרים הרבה ערכי seed כדי להזין אותם לפונקציה. בפרומפט של Spark, מזינים את הקוד הבא, שיוצר 10,000 זרעים:

    seeds = sc.parallelize([time.time() + i for i in range(10000)])
    

    התוצאה של הפעולה parallelize היא מערך נתונים מבוזר ועמיד (RDD), שהוא אוסף של רכיבים שעברו אופטימיזציה לעיבוד מקביל. במקרה הזה, ה-RDD מכיל ערכי התחלה שמבוססים על השעה הנוכחית במערכת.

    כשיוצרים את ה-RDD, ‏ Spark מחלק את הנתונים על סמך מספר העובדים והליבות שזמינים. במקרה הזה, Spark בוחר להשתמש בשמונה פרוסות, פרוסה אחת לכל ליבה. זה בסדר לסימולציה הזו, שיש בה 10,000 פריטי נתונים. בסימולציות גדולות יותר, כל פרוסה עשויה להיות גדולה יותר מהמגבלה שמוגדרת כברירת מחדל. במקרה כזה, ציון פרמטר שני ל-parallelize יכול להגדיל את מספר הפרוסות, וכך לשמור על גודל פרוסה שניתן לניהול, בזמן ש-Spark עדיין מנצל את כל שמונת הליבות.

  5. מעבירים את ה-RDD שמכיל את ה-seeds לפונקציית הצמיחה.

    results = seeds.map(grow)
    

    ה-method‏ map מעבירה כל seed ב-RDD לפונקציה grow ומוסיפה כל תוצאה ל-RDD חדש, שמאוחסן ב-results. שימו לב שהפעולה הזו, שמבצעת טרנספורמציה, לא מפיקה את התוצאות שלה באופן מיידי. ‫Spark לא יבצע את העבודה הזו עד שיהיה צורך בתוצאות. ההערכה העצלה הזו היא הסיבה לכך שאפשר להזין קוד בלי להגדיר את הקבועים.

  6. מציינים כמה ערכים לפונקציה.

    INVESTMENT_INIT = 100000  # starting amount
    INVESTMENT_ANN = 10000  # yearly new investment
    TERM = 30  # number of years
    MKT_AVG_RETURN = 0.11 # percentage
    MKT_STD_DEV = 0.18  # standard deviation
    
  7. מפעילים את הפונקציה reduce כדי לצבור את הערכים ב-RDD. מזינים את הקוד הבא כדי לסכם את התוצאות ב-RDD:

    sum = results.reduce(add)
    
  8. הערכה והצגה של ההחזר הממוצע:

    print (sum / 10000.)
    

    חשוב לכלול את תו הנקודה (.) בסוף. הוא מציין פעולות אריתמטיות בשיטת נקודה צפה.

  9. עכשיו משנים את ההנחה ובודקים איך התוצאות משתנות. לדוגמה, אתם יכולים להזין ערך חדש להחזר הממוצע בשוק:

    MKT_AVG_RETURN = 0.07
    
  10. מריצים את הסימולציה שוב.

    print (sc.parallelize([time.time() + i for i in range(10000)]) \
            .map(grow).reduce(add)/10000.)
    
  11. כשמסיימים את הניסוי, מקישים על CTRL+D כדי לצאת מהמפרש של Python.

תכנות סימולציית מונטה קרלו ב-Scala

מונטה קרלו, כמובן, מפורסמת כיעד להימורים. בקטע הזה תשתמשו ב-Scala כדי ליצור סימולציה שמדמה את היתרון המתמטי שיש לקזינו במשחק מזל. היתרון של הקזינו בבתי קזינו אמיתיים משתנה מאוד ממשחק למשחק. לדוגמה, במשחק הלוטו הוא יכול להיות מעל 20%. במדריך הזה ניצור משחק פשוט שבו לבית יש יתרון של אחוז אחד בלבד. כך המשחק פועל:

  • השחקן מבצע הימור, שמורכב ממספר ז'יטונים מקרן ההימורים.
  • השחקן מטיל קובייה עם 100 צדדים (איזה מגניב זה יכול להיות?).
  • אם התוצאה היא מספר בין 1 ל-49, השחקן מנצח.
  • אם התוצאה היא בין 50 ל-100, השחקן מפסיד בהתערבות.

אפשר לראות שהמשחק הזה יוצר חיסרון של אחוז אחד לשחקן: ב-51 מתוך 100 התוצאות האפשריות של כל הטלה, השחקן מפסיד.

כדי ליצור ולהפעיל את המשחק:

  1. מפעילים את מפרש Scala מהצומת הראשי של Managed Service for Apache Spark.

    spark-shell
    
  2. מעתיקים ומדביקים את הקוד הבא כדי ליצור את המשחק. ל-Scala אין את אותן דרישות כמו ל-Python בכל הנוגע להזחה, ולכן אפשר פשוט להעתיק ולהדביק את הקוד הזה בהנחיה של scala>.

    val STARTING_FUND = 10
    val STAKE = 1   // the amount of the bet
    val NUMBER_OF_GAMES = 25
    
    def rollDie: Int = {
        val r = scala.util.Random
        r.nextInt(99) + 1
    }
    
    def playGame(stake: Int): (Int) = {
        val faceValue = rollDie
        if (faceValue < 50)
            (2*stake)
        else
            (0)
    }
    
    // Function to play the game multiple times
    // Returns the final fund amount
    def playSession(
       startingFund: Int = STARTING_FUND,
       stake: Int = STAKE,
       numberOfGames: Int = NUMBER_OF_GAMES):
       (Int) = {
    
        // Initialize values
        var (currentFund, currentStake, currentGame) = (startingFund, 0, 1)
    
        // Keep playing until number of games is reached or funds run out
        while (currentGame <= numberOfGames && currentFund > 0) {
    
            // Set the current bet and deduct it from the fund
            currentStake = math.min(stake, currentFund)
            currentFund -= currentStake
    
            // Play the game
            val (winnings) = playGame(currentStake)
    
            // Add any winnings
            currentFund += winnings
    
            // Increment the loop counter
            currentGame += 1
        }
        (currentFund)
    }
    
  3. מקישים על return עד שמופיעה ההנחיה scala>.

  4. מזינים את הקוד הבא כדי לשחק במשחק 25 פעמים, שזה ערך ברירת המחדל של NUMBER_OF_GAMES.

    playSession()
    

    ההון ההתחלתי שלכם היה 10 יחידות. האם הוא גבוה או נמוך יותר עכשיו?

  5. עכשיו נדמה 10,000 שחקנים שמבצעים הימור של 100 צ'יפים בכל משחק. לשחק ב-10,000 משחקים בהפעלה אחת. הסימולציה הזו של מונטה קרלו מחשבת את ההסתברות להפסד של כל הכסף לפני סוף הסשן. מזינים את הקוד הבא:

    (sc.parallelize(1 to 10000, 500)
      .map(i => playSession(100000, 100, 250000))
      .map(i => if (i == 0) 1 else 0)
      .reduce(_+_)/10000.0)
    

    שימו לב שהתחביר .reduce(_+_) הוא קיצור דרך ב-Scala לצבירה באמצעות פונקציית סיכום. היא שוות ערך מבחינה פונקציונלית לתחביר .reduce(add) שראיתם בדוגמה של Python.

    הקוד שלמעלה מבצע את השלבים הבאים:

    • יוצר RDD עם תוצאות הפעלת הסשן.
    • מחליף את התוצאות של שחקנים שהוכרזו כפושטים רגל במספר 1 ואת התוצאות שאינן אפס במספר 0.
    • הפונקציה מחשבת את סכום מספר השחקנים שפשטו רגל.
    • המדד מחושב על ידי חלוקת מספר הפעמים שהאירוע הופעל במספר השחקנים.

    תוצאה טיפוסית יכולה להיות:

    0.998
    

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

הסרת המשאבים

מחיקת הפרויקט

  1. במסוף Google Cloud , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

המאמרים הבאים