Métodos Monte Carlo com o Dataproc e o Apache Spark

O Dataproc e o Apache Spark oferecem a infraestrutura e a capacidade para executar simulações Monte Carlo desenvolvidas em Java, Python ou Scala.

Os métodos Monte Carlo ajudam a responder uma grande variedade de perguntas relacionadas a negócios, engenharia, ciências, matemática e outros campos. Em uma simulação Monte Carlo, com o uso de amostragem aleatória repetida para criar uma distribuição de probabilidade para uma variável, você consegue respostas para perguntas que não conseguiria com outros métodos. Em finanças, por exemplo, a determinação do preço de uma opção de equidade requer a análise de milhares de possibilidades de mudança do preço da ação ao longo do tempo. Com os métodos Monte Carlo, você simula essas oscilações em uma ampla variedade de resultados possíveis e, ao mesmo tempo, mantém o controle sobre o domínio das entradas para o problema.

No passado, a execução de milhares de simulações poderia levar um longo tempo e ter um alto custo. Com o Dataproc, a capacidade é provisionada sob demanda e paga por minuto. Com o Apache Spark, você usa os clusters de dezenas, centenas ou milhares de servidores para executar simulações de maneira intuitiva, e faz o escalonamento de acordo com as suas necessidades. Isso significa que você consegue executar mais simulações com mais rapidez, o que ajuda a inovar no seu negócio com mais agilidade e possibilita um melhor gerenciamento dos riscos.

A segurança é sempre importante ao lidar com dados financeiros. O Dataproc é executado no Google Cloud, o que ajuda a manter os dados seguros, protegidos e particulares de várias maneiras. Por exemplo, todos os dados são criptografados durante a transmissão e quando em repouso, e o Google Cloud está em conformidade com ISO 27001, SOC3 e PCI.

Objetivos

  • Criar um cluster gerenciado do Dataproc com o Apache Spark pré-instalado.
  • Executar uma simulação Monte Carlo com o Python que faça a estimativa do crescimento de um portfólio de ações ao longo do tempo.
  • Executar uma simulação de Monte Carlo com o Scala que simule os lucros de um cassino.

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Como fazer a limpeza.

Antes de começar

Como criar um cluster do Dataproc

Siga as etapas para criar um cluster do Dataproc no Console do Google Cloud. As configurações de cluster padrão, que incluem dois nós de trabalho, são suficientes para este tutorial.

Como desativar a geração de registros de avisos

Por padrão, um registro detalhado é impresso na janela de console pelo Apache Spark. Para os fins deste tutorial, altere o nível da geração de registro para registrar somente os erros. Siga estas etapas:

Use ssh para se conectar ao nó principal do cluster do Dataproc

  1. No Console do Cloud, acesse a página Instâncias de VM.

    Acessar a página Instâncias de VM

  2. Na lista de instâncias de máquina virtual, clique em SSH na linha da instância à qual você quer se conectar.

    Botão

Uma janela de navegador é aberta no diretório principal do nó principal.

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

Alterar a configuração de geração de registros

  1. No diretório inicial do nó principal, edite /etc/spark/conf/log4j.properties.

    sudo nano /etc/spark/conf/log4j.properties
    
  2. Defina log4j.rootCategory igual a 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. Salve as alterações e saia do editor. Se você quiser reativar a geração de registros detalhados, reverta a alteração restaurando o valor de .rootCategory para o valor original (INFO).

Linguagens de programação do Spark

O Spark é compatível com Python, Scala e Java como linguagens de programação para aplicativos autônomos, e fornece interpretadores interativos para o Python e o Scala. A escolha da linguagem é uma questão de preferência pessoal. Neste tutorial, usamos os interpretadores interativos porque assim é possível fazer testes por meio da alteração do código, tentando diferentes valores de entrada e visualizando os resultados.

Como estimar o crescimento do portfólio

Em finanças, os métodos Monte Carlo são, às vezes, usados para executar simulações que tentam prever o desempenho de um investimento. Para responder a perguntas sobre o desempenho do portfólio na média ou nos cenários mais pessimistas, amostras aleatórias dos resultados em várias condições prováveis de mercado são produzidas por essas simulações.

Siga estas etapas para criar uma simulação que usa os métodos Monte Carlo para estimar o crescimento de um investimento financeiro com base em alguns fatores de mercado comuns.

  1. Inicie o interpretador de Python a partir do nó principal do Dataproc.

    pyspark
    

    Aguarde o prompt do Spark >>>.

  2. Insira o código a seguir. Mantenha o recuo na definição da função.

    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. Pressione return até ver novamente o prompt do Spark.

    No código acima, foi definida uma função que modela o que pode acontecer quando um investidor tem uma conta de aposentadoria existente que é investida no mercado de ações, onde eles depositam dinheiro anualmente. A função gera um retorno de investimento aleatório, como uma porcentagem, para cada ano que um termo especificado durar. Essa função gera um valor de semente como parâmetro que é usado para realimentar o gerador de números aleatórios. Isso garante que a função não receba a mesma lista de números cada vez que ela é executada. A função random.normalvariate garante que esses valores aleatórios ocorram em uma distribuição normal em relação à média e ao desvio padrão especificados. A função aumenta o valor do portfólio de acordo com o volume de crescimento, que pode ser positivo ou negativo, e adiciona uma soma anual que representa o investimento adicional.

    As constantes obrigatórias serão definidas em uma etapa posterior.

  4. Crie muitas sugestões para alimentar a função. No prompt do Spark, insira o código a seguir, que gera 10.000 sugestões:

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

    O resultado da operação parallelize é um conjunto de dados distribuídos resiliente (RDD, na sigla em inglês), que é uma coleção de elementos otimizados para processamento paralelo. Nesse caso, o RDD contém sementes que são baseadas no horário atual do sistema.

    Quando o RDD é criado, os dados são divididos no Spark com base no número de workers e núcleos disponíveis. Nesse caso, o Spark escolhe usar oito partes, uma parte para cada núcleo. Isso atende a essa simulação, que tem 10.000 itens de dados. Para simulações maiores, cada parte pode ser maior do que o limite padrão. Nesse caso, especificar um segundo parâmetro para parallelize pode aumentar o número de frações, o que pode ajudar a manter o tamanho de cada fração sob controle, enquanto o Spark ainda aproveita todos os oito núcleos.

  5. Alimente o RDD que contém as sementes para a função de crescimento.

    results = seeds.map(grow)
    

    O método map passa cada semente no RDD para a função grow e anexa cada resultado a um novo RDD, que é armazenado em results. Observe que essa operação de transformação não produz os resultados imediatamente. Isso não é feito no Spark antes dos resultados serem necessários. Devido a essa avaliação lenta, o código pode ser inserido sem ter as constantes definidas.

  6. Especifique alguns valores para a função.

    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. Chame reduce para agregar os valores no RDD. Insira o seguinte código para somar os resultados no RDD:

    sum = results.reduce(add)
    
  8. Estime e exiba o retorno médio:

    print sum / 10000.
    

    Verifique se o caractere ponto (.) foi incluído no final. Ele representa um valor aritmético de ponto flutuante.

  9. Agora altere uma pressuposição e veja como os resultados mudam. Por exemplo, insira um novo valor para o retorno médio do mercado:

    MKT_AVG_RETURN = 0.07
    
  10. Execute a simulação novamente.

    print sc.parallelize([time.time() + i for i in xrange(10000)]) \
            .map(grow).reduce(add)/10000.
    
  11. Quando terminar os testes, pressione CTRL+D para sair do interpretador de Python.

Como programar uma simulação Monte Carlo em Scala

A cidade de Monte Carlo é conhecida como um destino de apostas. Nesta seção, você usa o Scala para criar uma simulação que modela a vantagem matemática que um cassino tem em um jogo de azar. O "house edge" de um cassino real varia bastante entre os jogos. Ele pode ser maior do que 20% no keno, por exemplo. Neste tutorial, um jogo simples será criado, onde a casa tem somente uma vantagem de 1%. Veja como ele funciona:

  • O jogador faz uma aposta, que consiste de algumas fichas do fundo de apostas.
  • O jogador rola um dado de cem lados (que legal, não?).
  • Se o resultado do teste for um número de 1 a 49, o jogador ganha.
  • Caso o resultado seja entre 50 e 100, o jogador perde a aposta.

Veja que, nesse jogo, o jogador tem uma desvantagem de 1%: em 51 dos 100 resultados possíveis de cada rolamento, ele perde.

Siga estas etapas para criar e executar o jogo:

  1. Inicie o interpretador de Scala a partir do nó principal do Dataproc.

    spark-shell
    
  2. Copie e cole o código a seguir para criar o jogo Scala não tem os mesmos requisitos que Python quando se trata de recuo. Portanto, basta copiar e colar esse código no 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. Pressione return até ver o prompt scala>.

  4. Insira o código a seguir para jogar 25 vezes, que é o valor padrão de NUMBER_OF_GAMES.

    playSession()
    

    Seu fundo de apostas começou com o valor de 10 unidades. E agora, está mais alto ou mais baixo?

  5. Agora simule 10.000 jogadores apostando 100 fichas por jogo. Jogue 10.000 vezes em uma sessão. Nesta simulação de Monte Carlo, calculamos a probabilidade de perder todo seu dinheiro antes do final da sessão. Insira o código a seguir:

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

    Observe que a sintaxe .reduce(_+_) é uma abreviação em Scala para agregação usando uma função de soma. A funcionalidade dela é equivalente à sintaxe .reduce(add) que você viu no exemplo do Python.

    No código anterior, as seguintes etapas foram executadas:

    • criação de RDD com os resultados da sessão jogada;
    • substituição dos resultados dos jogadores falidos pelo número 1 e dos resultados diferentes de zero pelo número 0;
    • soma da contagem de jogadores falidos;
    • divisão da contagem pelo número de jogadores.

    Um resultado típico seria:

    0.998
    

    Isso representa uma garantia quase total de que você perderia todo o dinheiro, mesmo se o cassino tivesse somente uma vantagem de 1%.

Como fazer a limpeza

Para evitar que os recursos usados neste tutorial sejam cobrados na conta do Google Cloud Platform, faça o seguinte:

Excluir o projeto

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar a página "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir

  • Para saber mais sobre como enviar jobs do Spark para o Dataproc sem precisar usar o ssh para se conectar ao cluster, leia Dataproc: enviar um job.

  • Teste outros recursos do Google Cloud. Veja nossos tutoriais.