Métodos de Monte Carlo con Dataproc y Apache Spark


Dataproc y Apache Spark proporcionan la infraestructura y la capacidad que se pueden usar para ejecutar simulaciones escritas en Java, Python o Scala.

Los métodos de Monte Carlo pueden ayudar a resolver un amplio rango de cuestiones sobre comercio, ingeniería, ciencia y matemáticas, entre otros campos. A través de muestras aleatorias repetidas que crean una probabilidad de distribución para una variable, una simulación de Monte Carlo puede brindar respuestas a preguntas que, de otro modo, serían imposibles de resolver. Por ejemplo, en finanzas, poner un precio a las opciones sobre acciones implica analizar las miles de formas en las que el precio de las acciones puede cambiar en el futuro. Los métodos de Monte Carlo permiten simular los cambios de precio de las acciones con un amplio rango de resultados posibles sin perder el control del dominio de las posibles causas del problema.

Antes, la ejecución de miles de simulaciones podía tardar mucho tiempo y generar grandes costos. Dataproc permite aprovisionar capacidad a pedido y pagar por minuto. Con Apache Spark, puedes usar clústeres de decenas, cientos o miles de servidores para ejecutar simulaciones de manera intuitiva y escalable con el fin de cumplir con tus necesidades. Esto significa que puedes ejecutar más simulaciones con mayor rapidez, lo que ayuda a que tu negocio innove y administre mejor los riesgos.

La seguridad siempre es importante cuando trabajas con datos financieros. Dataproc se ejecuta en Google Cloud, lo que ayuda a mantener tus datos seguros y privados de varias maneras. Por ejemplo, todos los datos se encriptan durante la transmisión y cuando están en reposo, y Google Cloud cumple con las normas ISO 27001, SOC3 y PCI.

Objetivos

  • Crear un clúster de Cloud Dataproc administrado con Apache Spark preinstalado
  • Ejecutar una simulación de Monte Carlo con Python que haga un cálculo aproximado del crecimiento de una cartera de valores en el tiempo
  • Ejecutar una simulación de Monte Carlo con Scala que simule la forma en que un casino gana dinero

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices las tareas que se describen en este documento, puedes borrar los recursos que creaste para evitar que continúe la facturación. Para obtener más información, consulta Cómo realizar una limpieza.

Antes de comenzar

Cómo crear un clúster de Dataproc

Sigue los pasos para crear un clúster de Dataproc desde la consola de Google Cloud. La configuración del clúster predeterminada, que incluye nodos de dos trabajadores, es suficiente para este instructivo.

Inhabilita el registro de advertencias

De forma predeterminada, Apache Spark muestra el registro detallado en la ventana de la consola. A los fines de este instructivo, cambia el nivel de registro para registrar solo los errores. Sigue estos pasos:

Usa ssh para conectarte al nodo principal del clúster de Dataproc

El nodo principal del clúster de Dataproc tiene el sufijo -m en el nombre de la VM.

  1. En la consola de Google Cloud, ve a la página Instancias de VM.

    Ir a Instancias de VM

  2. En la lista de instancias de máquinas virtuales, haz clic en SSH en la fila de la instancia a la que deseas conectarte.

    Botón SSH junto al nombre de la instancia.

Se abrirá una ventana SSH conectada al nodo principal.

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

Cambia la configuración de registro

  1. En el directorio principal del nodo principal, edita /etc/spark/conf/log4j.properties.

    sudo nano /etc/spark/conf/log4j.properties
    
  2. Configura log4j.rootCategory como 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. Guarda los cambios y cierra el editor. Si deseas volver a habilitar el registro detallado, restablece el valor de .rootCategory a su valor original (INFO) para anular el cambio.

Lenguajes de programación de Spark

Spark admite Python, Scala y Java como lenguajes de programación de aplicaciones independientes y proporciona intérpretes interactivos para Python y Scala. El lenguaje que elijas depende de tu preferencia. En este instructivo, se usan los intérpretes interactivos, ya que puedes experimentar si cambias el código, pruebas diferentes valores de entrada y visualizas los resultados.

Estima el crecimiento de la cartera

En finanzas, los métodos de Monte Carlo a veces se usan para ejecutar simulaciones que intentan predecir el rendimiento de una inversión. Un método de Monte Carlo produce muestras aleatorias de resultados en un rango de condiciones posibles de mercado para resolver preguntas sobre el rendimiento de una cartera en promedio o en los peores casos.

Sigue estos pasos para crear una simulación que use los métodos de Monte Carlo a fin de intentar calcular el crecimiento de una inversión financiera según los factores de mercado comunes.

  1. Inicia el intérprete de Python desde el nodo principal de Dataproc.

    pyspark
    

    Espera el mensaje de Spark >>>.

  2. Ingresa el siguiente código. Asegúrate de mantener la sangría en la definición de la función.

    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. Presiona return hasta que aparezca el mensaje de Spark.

    En el código anterior, se define una función que modela lo que puede ocurrir cuando un inversionista tiene una cuenta de retiro existente que se invirtió en el mercado de valores y a la que se le agrega dinero todos los años. La función genera un retorno de la inversión aleatorio, como porcentaje, cada año por un período específico y toma un valor inicial como parámetro. Este valor se usa para reiniciar el generador de números aleatorios a fin de garantizar que la función no obtenga la misma lista de números cada vez que se ejecuta. La función random.normalvariate garantiza que se produzcan valores aleatorios en una distribución normal para la media y la desviación estándar especificadas. La función aumenta el valor de la cartera según el nivel de crecimiento que puede ser positivo o negativo y agrega una suma anual que representa las inversiones adicionales.

    Definirás las constantes requeridas en otro paso.

  4. Crea muchos valores iniciales para la función. En el indicador de Spark, ingresa este código que genera 10,000 valores iniciales:

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

    El resultado de la operación parallelize es un conjunto de datos resilientes y distribuidos (RDD), que es una colección de elementos optimizados para el procesamiento paralelo. En este caso, el RDD contiene valores iniciales que se basan en la hora actual del sistema.

    Cuando Spark crea el RDD, divide los datos según el número de trabajadores y núcleos disponibles. En este caso, Spark usa ocho divisiones, una por cada núcleo. Es lo adecuado para esta simulación que tiene datos de 10,000 elementos. Sin embargo, en simulaciones de mayor tamaño, cada división puede superar el límite predeterminado. En ese caso, especificar un segundo parámetro en parallelize puede aumentar la cantidad de fragmentos, lo que puede ayudar a mantener el tamaño de cada fragmento administrable, mientras Spark aún aprovecha los ocho núcleos.

  5. Provee valores al RDD que contiene los valores iniciales de la función de crecimiento.

    results = seeds.map(grow)
    

    El método map pasa cada valor inicial en el RDD a la función grow y agrega cada resultado a un nuevo RDD, que se almacena en results. Ten en cuenta que esta operación, que realiza una transformación, no produce los resultados de inmediato. Spark no hará este trabajo hasta que se necesiten los resultados. Esta evaluación diferida es la razón por la que puedes ingresar el código sin definir las constantes.

  6. Especifica algunos valores para la función.

    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. Llama a reduce para agregar los valores en el RDD. Ingresa el siguiente código para sumar los resultados en el RDD:

    sum = results.reduce(add)
    
  8. Calcula y muestra el retorno promedio:

    print (sum / 10000.)
    

    Asegúrate de incluir el carácter del punto (.) al final. Significa la aritmética de coma flotante.

  9. Ahora modifica la suposición y verás cómo cambian los resultados. Por ejemplo, puedes ingresar un valor nuevo para el resultado promedio del mercado:

    MKT_AVG_RETURN = 0.07
    
  10. Vuelve a ejecutar la simulación.

    print (sc.parallelize([time.time() + i for i in range(10000)]) \
            .map(grow).reduce(add)/10000.)
    
  11. Cuando termines de experimentar, presiona CTRL+D para salir del intérprete de Python.

Carga una simulación de Monte Carlo en Scala

Montecarlo es un destino de juego famoso. En esta sección, usarás Scala para crear una simulación que modele la ventaja matemática que podría usar un casino en un juego de azar. La “ventaja de la casa” en un casino real varía mucho según el juego; en keno, por ejemplo, puede ser de alrededor del 20%. En este instructivo, se crea un juego sencillo en el que la ventaja de la casa es solo del uno por ciento. Así es cómo funciona el juego:

  • El jugador hace una apuesta que consiste de un número de fichas del fondo de dinero.
  • Luego, lanza un dado de 100 lados (¿no sería estupendo?).
  • Si el resultado es un número del 1 al 49, el jugador gana.
  • Si está entre el 50 y el 100, pierde la apuesta.

Como se ve, en este juego se crea una desventaja del uno por ciento para el jugador: en 51 de los 100 posibles resultados pierde.

Sigue estos pasos para crear y ejecutar el juego:

  1. Inicia el intérprete de Scala desde el nodo principal de Dataproc.

    spark-shell
    
  2. Copia y pega este código para crear el juego. Scala no tiene los mismos requisitos que Python en lo que respecta a las sangrías, por lo que puedes solo copiar y pegar el código en el mensaje de 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. Presiona return hasta que aparezca el mensaje scala>.

  4. Ingresa el siguiente código para jugar 25 veces al juego, que es el número predeterminado de NUMBER_OF_GAMES.

    playSession()
    

    Tus fondos comienzan con un valor de 10 unidades. ¿Ahora, es mayor o menor?

  5. Simula 10,000 jugadores que apuestan 100 fichas por juego. Juega 10,000 juegos en una sesión. Esta simulación de Monte Carlo calcula la probabilidad de perder todo tu dinero antes de que finalice la sesión. Ingresa el siguiente código:

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

    Ten en cuenta que la sintaxis .reduce(_+_) en Scala es la abreviación para agregar con una función de suma. Su función equivale a la sintaxis .reduce(add) que viste en el ejemplo de Python.

    El código anterior realiza las siguientes acciones:

    • Crea un RDD con los resultados de juego de una sesión.
    • Reemplaza los resultados de los jugadores en quiebra con el número 1 y los resultados que no son cero con el número 0.
    • Suma la cantidad de jugadores en quiebra.
    • La divide por el número de jugadores.

    Un resultado típico puede ser:

    0.998
    

    Este representa casi una garantía de que perderás todo tu dinero, incluso si la ventaja del casino solo era del uno por ciento.

Limpia

Borra el proyecto

  1. En la consola de Google Cloud, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

¿Qué sigue?

  • A fin de obtener más información sobre cómo enviar trabajos de Spark a Dataproc sin tener que usar ssh para conectarse al clúster, lee Dataproc: envía un trabajo.