segunda-feira, 31 de janeiro de 2022

Automatizando código para salvar arquivos

Quando estamos realizando alguma tarefa, normalmente iterativa, às vezes queremos salvar nosso progresso atual antes de toda a tarefa terminar. Isso evita que interrupções no software façam com que nossos resultados sejam perdidos. Por exemplo, você está realizando um Gibbs sampling e não quer perder suas estimativas caso algo aconteça antes do algoritmo terminar. Sabemos que modelos complexos em Inferência Bayesiana necessitam de mais tempo para realizar inferência por amostragem.
Suponha que você colocou o algoritmo para rodar 100 mil iterações. Mas, a cada 10 mil, quer salvar seu progresso, para evitar perda de resultados. Como fazemos isso?

Primeiro, vamos aprender a salvar um arquivo. Aqui, vou ensinar a usar a função saveRDS(). Essa função salva um objeto do R em um arquivo com extensão .rds e nome especificado pelo usuário. Vejamos um exemplo:
dados = c(1,20,12,90,5);
saveRDS(dados,"teste01.rds");

dados recebe um vetor com os números acima. Em seguida, salvamos o que tem na variável dados no arquivo "teste01.rds". Dessa forma, no diretório atual (que no caso, é o diretório do meu projeto) o arquivo é criado. É importante não esquecer de colocar a extensão do arquivo.

Agora, vamos ler o arquivo e ver o que tem nele.

dadosr = readRDS("teste01.rds")

> dadosr
[1]  1 20 12 90  5

Como esperado, dadosr recebe o que tem no arquivo "teste01.rds", que é o vetor de dados que salvamos inicialmente. Mas, apenas vetores podem ser salvos dessa forma? A resposta é não! Qualquer objeto pode ser gravado dessa maneira. Vejamos um exemplo com lista:

dados_aluno = list(nome = "João", disciplina = "Matematica", nota = 8.7)
saveRDS(dados_aluno,"teste02.rds");

Pronto, a lista criada que armazena o nome, disciplina e nota agora está salva no arquivo "teste02.rds". É claro que há maneiras mais eficientes para armazenar esse tipo de dado, mas aqui é apenas uma ilustração. Se lemos esse arquivo, obtemos:

dados_aluno_r = readRDS("teste02.rds")

> dados_aluno_r
$nome
[1] "João"

$disciplina
[1] "Matematica"

$nota
[1] 8.7

Agora que você sabe salvar um arquivo, é trivial automatizar gravação de resultados. Normalmente o fazemos em algoritmos iterativos e adicionamos o comando de gravação dentro da estrutura de repetição. Obviamente, temos que ter um controle de quanto em quanto tempo queremos gravar os resultados, pois, se gravamos a cada iteração, o algoritmo pode ficar extremamente lento. 

No exemplo mencionado, podemos optar por salvar um arquivo dos resultados a cada 10 mil iterações, até que as 100 mil sejam finalizadas. Ou podemos optar por gravar a cada 1 mil iterações. Esse "tempo" depende do problema em mãos. Se é algo que demora bastante (modelos muito complexos), salvar a cada poucas iterações é interessante. Agora, se o algoritmo é relativamente rápido, podemos dar um salto maior para a gravação. É claro que, se o algoritmo é rápido (algo factível sem o risco de perda de resultados), não precisamos salvar resultados preliminares.

Nesse post, faremos um exemplo banal para ilustrar a importância da automatização de código para gravar arquivos. Vamos gerar duas variáveis aleatórias e o resultado será gravado a cada mil iterações de um total de 10 mil. Vejamos o código abaixo:

result = matrix(ncol=2);
result = result[-1,];

for(i in 1:10000){
  x1 = rnorm(1); # gera uma observação da normal padrão
  x2 = runif(1); # gera uma observação da Uniforme(0,1)
  
  result = rbind(result,cbind(x1,x2));
  
  if(i%%1000 == 0) saveRDS(result,paste0("save_files/result_temp",i,".rds"));
}
saveRDS(result,"result.rds");

Primeiro, criamos a variável result, que armazenará uma matriz. Como ela é iniciada com uma linha contendo NA's, excluímos essa linha. Em seguida, iniciamos uma iteração de tamanho 10 mil. Para cada iteração, geramos um valor de uma distribuição normal padrão e um valor de uma uniforme(0,1) e esse resultado é adicionado a uma linha na matriz. Agora a parte interessante:

if(i%%1000 == 0) saveRDS(result,paste0("save_files/result_temp",i,".rds"));

O que essa linha de código está fazendo? Verificamos se passaram-se mil iterações. Caso positivo, salvamos nosso resultado prévio. Note que estou salvando o arquivo na pasta "save_files", com um nome que depende do valor de i. Ou seja, a cada mil iterações, vamos salvar nosso resultado com o nome "result_temp1000.rds", "result_temp2000.rds" e assim por diante. Se executarmos o código acima, veremos no nosso diretório os seguintes arquivos:

Esses arquivos foram salvos para você visualizar que o processo está funcionando. Se modificarmos o código para salvar o arquivo com o mesmo nome, a cada gravação o arquivo será sobrescrito. Isso é útil, já que queremos sempre o arquivo mais atualizado. Então, modificamos o código para:

if(i%%1000 == 0) saveRDS(result,"save_files/result_temp.rds");

Agora, os resultados prévios serão sempre salvos no arquivo "result_temp.rds". Eu particularmente gosto de colocar a palavra "temp" no nome para indicar que é um arquivo temporário ou de resultados prévios. Após a execução de todo o algoritmo, salvamos o resultado no arquivo "result.rds", que contém o resultado completo.

Note que, após finalizar o for, o arquivo "result_temp.rds" também armazenará o resultado completo. Normalmente isso depende se a última iteração do algoritmo vai salvar ou não os resultados prévios e depende da estrutura do nosso algoritmo. Varia de problema para problema. De qualquer modo, analise o seu problema e veja se é necessário salvar um arquivo após a execução da estrutura de repetição. No nosso caso, não é necessário e seria mal uso de memória física e de tempo, principalmente se o arquivo for muito grande. Sempre pense nesses detalhes!

Espero que tenha gostado da aula.

Até a próxima aula!

Nenhum comentário:

Postar um comentário