Metodi Monte Carlo basati su Dataproc e Apache Spark


Dataproc e Apache Spark forniscono l'infrastruttura e la capacità che puoi utilizzare per eseguire simulazioni Monte Carlo scritte in Java, Python o Scala.

I metodi Monte Carlo possono aiutare a rispondere a una vasta gamma di questioni in economia, ingegneria, scienze, matematica e altri campi. Utilizzando i campionamenti casuali ripetuti per creare una distribuzione di probabilità per una variabile, una simulazione Monte Carlo può fornire risposte a domande a cui altrimenti non sarebbe possibile rispondere. Nel settore finanziario, ad esempio, la determinazione del prezzo di un'opzione azionaria richiede l'analisi delle migliaia di modi in cui il prezzo dell'azione potrebbe variare nel tempo. I metodi Monte Carlo offrono un modo per simulare queste variazioni di prezzo delle azioni in un'ampia gamma di possibili risultati, mantenendo al contempo il controllo sul dominio dei possibili input per il problema.

In passato, l'esecuzione di migliaia di simulazioni richiedeva molto tempo e accumulava costi elevati. Dataproc consente di eseguire il provisioning di capacità on demand e di pagarla al minuto. Apache Spark consente di utilizzare cluster di decine, centinaia o migliaia di server per eseguire le simulazioni in modo intuitivo e scalabile in base alle tue esigenze. Ciò significa che puoi eseguire più simulazioni più rapidamente, il che può aiutare la tua azienda a innovare più rapidamente e gestire meglio i rischi.

La sicurezza è sempre importante quando si lavora con i dati finanziari. Dataproc viene eseguito su Google Cloud, il che contribuisce a mantenere i tuoi dati sicuri, protetti e privati in diversi modi. Ad esempio, tutti i dati sono criptati durante la trasmissione e quando sono at-rest e Google Cloud è conforme agli standard ISO 27001, SOC3 e PCI.

Obiettivi

  • Crea un cluster Dataproc gestito con Apache Spark preinstallato.
  • Eseguire una simulazione Monte Carlo utilizzando Python che stima la crescita di un portafoglio azionario nel tempo.
  • Esegui una simulazione Monte Carlo con Scala, che simula il modo in cui un casinò guadagna.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud possono essere idonei a una prova senza costi aggiuntivi.

Una volta completate le attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la pagina Pulizia.

Prima di iniziare

Creazione di un cluster Dataproc

Segui i passaggi per creare un cluster Dataproc dalla console Google Cloud. Per questo tutorial sono sufficienti le impostazioni predefinite del cluster, che includono nodi a due worker.

Disabilitazione del logging per gli avvisi

Per impostazione predefinita, Apache Spark stampa il logging dettagliato nella finestra della console. Ai fini di questo tutorial, modifica il livello di logging in modo da registrare solo gli errori. Segui questi passaggi:

Utilizza ssh per connetterti al nodo principale del cluster Dataproc

Il nodo principale del cluster Dataproc ha il suffisso -m sul nome della VM.

  1. Nella console Google Cloud, vai alla pagina Istanze VM.

    Vai a Istanze VM

  2. Nell'elenco delle istanze di macchine virtuali, fai clic su SSH nella riga dell'istanza a cui vuoi connetterti.

    Pulsante SSH accanto al nome dell'istanza.

Si apre una finestra SSH connessa al nodo primario.

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

Modifica l'impostazione di logging

  1. Nella home directory del nodo principale, modifica /etc/spark/conf/log4j.properties.

    sudo nano /etc/spark/conf/log4j.properties
    
  2. Imposta log4j.rootCategory su 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. Salva le modifiche ed esci dall'editor. Se vuoi riattivare il logging dettagliato, annulla la modifica ripristinando il valore originale di .rootCategory (INFO).

Linguaggi di programmazione Spark

Spark supporta Python, Scala e Java come linguaggi di programmazione per le applicazioni autonome e fornisce interpreti interattivi per Python e Scala. La lingua scelta dipende dalle preferenze personali. Questo tutorial utilizza gli interpreti interattivi perché puoi sperimentare modificando il codice, provando diversi valori di input e visualizzando i risultati.

Stimare la crescita del portafoglio

In finanza, i metodi Monte Carlo vengono a volte usati per eseguire simulazioni volte a prevedere il potenziale rendimento di un investimento. Producendo campioni casuali di risultati in una serie di probabili condizioni di mercato, una simulazione Monte Carlo può rispondere a domande sul rendimento medio di un portafoglio o in scenari peggiori.

Segui questi passaggi per creare una simulazione che utilizzi i metodi Monte Carlo per stimare la crescita di un investimento finanziario in base ad alcuni fattori di mercato comuni.

  1. Avvia l'interprete Python dal nodo principale di Dataproc.

    pyspark
    

    Attendi il prompt di Spark >>>.

  2. Inserisci il seguente codice. Assicurati di mantenere il rientro nella definizione della funzione.

    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. Premi return finché non viene visualizzato di nuovo il messaggio di Spark.

    Il codice precedente definisce una funzione che modella ciò che potrebbe accadere quando un investitore ha già un conto pensionistico investito nel mercato azionario, a cui aggiunge denaro ogni anno. La funzione genera un ritorno casuale sull'investimento, in percentuale, ogni anno per la durata di un periodo specificato. La funzione prende un valore seed come parametro. Questo valore viene utilizzato per eseguire il rendering del generatore di numeri casuali, in modo che la funzione non riceva lo stesso elenco di numeri casuali ogni volta che viene eseguita. La funzione random.normalvariate assicura che i valori casuali avvengano in una distribuzione normale per la media e la deviazione standard specificate. La funzione aumenta il valore del portafoglio in base all'importo della crescita, che potrebbe essere positivo o negativo, e aggiunge una somma annuale che rappresenta un ulteriore investimento.

    Puoi definire le costanti richieste in un passaggio successivo.

  4. Crea molti semi da fornire alla funzione. Nel messaggio Spark, inserisci il codice seguente, che genera 10.000 seed:

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

    Il risultato dell'operazione parallelize è un set di dati distribuito resiliente (RDD), ovvero una raccolta di elementi ottimizzati per l'elaborazione parallela. In questo caso, l'RDD contiene seed basati sull'ora attuale del sistema.

    Quando crea l'RDD, Spark suddivide i dati in base al numero di worker e core disponibili. In questo caso, Spark sceglie di utilizzare otto sezioni, una per ogni core. Non è un problema per questa simulazione, che ha 10.000 elementi di dati. Per simulazioni più grandi, ogni sezione potrebbe essere superiore al limite predefinito. In questo caso, specificare un secondo parametro per parallelize può aumentare il numero di sezioni, il che consente di mantenere gestibile la dimensione di ogni sezione, mentre Spark utilizza comunque tutti e otto i core.

  5. Fornisci all'RDD l'RDD che contiene i semi alla funzione di crescita.

    results = seeds.map(grow)
    

    Il metodo map passa ogni seed dell'RDD alla funzione grow e aggiunge ogni risultato a un nuovo RDD, che viene archiviato in results. Tieni presente che questa operazione, che esegue una trasformazione, non produce i risultati immediatamente. Spark non eseguirà queste operazioni finché non saranno necessari i risultati. Questa valutazione pigra è il motivo per cui puoi inserire il codice senza che le costanti vengano definite.

  6. Specifica alcuni valori per la funzione.

    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. Richiama reduce per aggregare i valori nell'RDD. Inserisci il seguente codice per sommare i risultati nell'RDD:

    sum = results.reduce(add)
    
  8. Stima e visualizza il ritorno medio:

    print (sum / 10000.)
    

    Assicurati di includere il punto (.) alla fine. Indica aritmetica in virgola mobile.

  9. Ora cambia un'ipotesi e guarda come cambiano i risultati. Ad esempio, puoi inserire un nuovo valore per il ritorno medio del mercato:

    MKT_AVG_RETURN = 0.07
    
  10. Esegui di nuovo la simulazione.

    print (sc.parallelize([time.time() + i for i in range(10000)]) \
            .map(grow).reduce(add)/10000.)
    
  11. Al termine dell'esperimento, premi CTRL+D per uscire dall'interprete Python.

Programmazione di una simulazione Monte Carlo in Scala

Monte Carlo, ovviamente, è una meta famosa per il gioco d'azzardo. In questa sezione, userai Scala per creare una simulazione che modella il vantaggio matematico che un casinò apprezza in un gioco di fortuna. Il "vantaggio della casa" in un vero casinò varia da gioco a gioco; ad esempio può essere superiore al 20% in keno. Questo tutorial crea un gioco semplice in cui la casa ha un vantaggio solo dell'1%. Ecco come funziona il gioco:

  • Il giocatore piazza una scommessa, costituita da una serie di fiches da un fondo bancario.
  • Il giocatore lancia un dado con 100 facce (non sarebbe fantastico?).
  • Se il risultato del tiro è un numero compreso tra 1 e 49, il giocatore vince.
  • Per i risultati da 50 a 100, il giocatore perde la scommessa.

Puoi notare che questo gioco crea uno svantaggio dell'1% per il giocatore: in 51 dei 100 risultati possibili per ogni tiro, il giocatore perde.

Per creare ed eseguire il gioco, procedi nel seguente modo:

  1. Avvia l'interprete Scala dal nodo principale di Dataproc.

    spark-shell
    
  2. Copia e incolla il codice seguente per creare il gioco. Scala non ha gli stessi requisiti di Python per quanto riguarda il rientro, quindi puoi semplicemente copiare e incollare questo codice al prompt 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. Premi return finché non viene visualizzato il messaggio scala>.

  4. Inserisci il codice seguente per giocare 25 volte. Questo è il valore predefinito per NUMBER_OF_GAMES.

    playSession()
    

    Il tuo bankroll ha iniziato con un valore di 10 unità. È più alto o più basso, ora?

  5. Ora simula 10.000 giocatori che scommettono 100 chip a partita. Gioca a 10.000 partite in una sessione. Questa simulazione Monte Carlo calcola la probabilità di perdere tutti i soldi prima della fine della sessione. Inserisci il seguente codice:

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

    Tieni presente che la sintassi .reduce(_+_) è un'abbreviazione in Scala per l'aggregazione mediante una funzione di somma. È equivalente alla sintassi .reduce(add) che hai visto nell'esempio Python.

    Il codice precedente esegue i seguenti passaggi:

    • Crea un RDD con i risultati della riproduzione della sessione.
    • Sostituisce i risultati dei giocatori in bancarotta con il numero 1 e i risultati diversi da zero con il numero 0.
    • Somma il numero dei giocatori in bancarotta.
    • Divide il numero per il numero di giocatori.

    Un risultato tipico potrebbe essere:

    0.998
    

    Il che rappresenta una garanzia quasi di perdere tutti i soldi, anche se il casinò aveva un vantaggio solo dell'1%.

Esegui la pulizia

Elimina il progetto

  1. Nella console Google Cloud, vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona il progetto che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID del progetto e fai clic su Chiudi per eliminare il progetto.

Passaggi successivi

  • Per saperne di più sull'invio di job Spark a Dataproc senza dover utilizzare ssh per la connessione al cluster, consulta Dataproc - Invio di un job