Esse texto faz parte de uma série de 3 partes sobre Continuous Machine Learning. Você pode checar a parte II aqui e a parte III aqui.
O que é isso?
Continuous Machine Learning (CML) segue o mesmo conceito de Continuous Integration and Continuous Delivery (CI/CD), famosos concenitos da Software Engineering / DevOps, mas aplicado a projetos de Aprendizado de Máquina e Ciência de Dados.
O que vou aprender com esse post?
Eu irei abordar aqui um conjunto de ferramentas que podem fazer sua vida como Cientista de Dados muito mais interessante. Nós utilizaremos o MIIC, um algoritmo de inferência de redes causais, para inferir a rede de um famoso dataset (alarm do pacote bnlearn). A ideia é utilizar (1) o git para versionar nosso código, (2) o DVC para versionar nosso dataset, arquivos de saíde e até o pipeline! (3) Utilizaremos o GitHub como git remote e (4) o Google Drive como DVC remote. Pense nos remotes como tendo uma cópia do seu repositório remotamente, em um outro computador. Eu também escrevi um tutorial sobre gerenciamento de projetos de ciência de dados com o DVC e se você estiver interessado, clique aqui para abrir o tutorial em uma nova aba para você lê-lo depois.
Uma coias que me incomoda um pouco é frequentemente ter que abrir a página do GitHub e utilizar a interface deles. É muito mais prático poder fazer tudo pelo terminar e aqui vou te apresentar o gh, a aplicação de linha de comando oficiial do GitHub. Também iremos utilizar o CML, uma biblioteca open source para implementar continuous integration & delivery (CI/CD) em projetos de aprendizagem de máquina. O CML irá conectar o git, DVC e GitHub Actions (Sistema de CI/CD built-in do GitHub). A ideia é criar um gatilho que será disparado toda vez que você fizer uma determinada ação no seu repositório, como um push. Essa ação pode ser processamento, qualquer tipo de computação, e irá por padrão acontecer em um GitHub Runner (Esse é o nome que o GitHub da a uma máquina virtual na infraestrutura deles. Veja mais sobre isso aqui). Um exemplo seria utilizar branches do git como experimentos no seu projeto de ciência de dados. Imagine executar seu código uma vez, alterar algum parâmetro do algoritmo, executá-lo novamente, mais uma alteração qui e outra ali e assim por diante até você ter várias branches e experimentos. Toda vez que você der um git push, por exemplo, um GitHub Runner poderia ser acionado para comparar as métricas desse experimento com os outros e imprimir na tela (como um comentário na página do Pull Request) um relatório. Parece legal, né?
Hora de arregaçar as mangas
Vamos criar nosso repositório no GitHub e fazer uma cópia local dele. Faremos isso a partir da linha de comando! (Instruções aqui de como instalar o gh).
mkdir $HOME/dev cd dev gh repo create dvc-miic-cml -d 'GitHub repo to play with DVC, MIIC and CML' --public
Você será perguntado se deseja criar uma cópia local do repositório. Se você disser não, você terá que clonar o repositório mais tarde, portanto responda com Y e pressione enter. Após isso, entre no diretório.
cd dvc-miic-cml
Vamos criar um arquivo LEIAME, onde iremos colocar uma descrição do nosso repositório. No GitHub isso costuma ser colocado em um arquio chamado README.md, escrito com formatação Markdown.
echo '# DVC-MIIC-CML' > README.md echo 'This is a sample repository for testing DVC, MIIC and CML in GitHub.' >> README.md echo 'The analyses will be performed using [MIIC](https://github.com/miicTeam/miic_R_package) to infer the network from the [alarm dataset](https://rdrr.io/cran/bnlearn/man/alarm.html).' >> README.md
Também iremos fazer o download de um arquivo de licença (GNU GPLv3) a partir do site do projeto GNU e salvá-lo como um arquivo chamado LICENSE, como costuma ser feito em repositórios no GitHub.
wget -c https://www.gnu.org/licenses/gpl-3.0.txt -O LICENSE
Após isso, iremos adicionar esses dois novos arquivos para o índice (index) do nosso repositório git através do comando git add
e após isso fazer um commit do estado atual do repositório (uma espécie de snapshot, caso você não tenha o workflow / linguajar do git na ponta da língua). Com o commit realizado, iremos enviar o estado atual do repositório para o remote no GitHub através do comando git push
. Desse modo, qualquer pessoa que solicite uma cópia atual do repositório para o GitHub, receberá também as nossas modificações. Como essa é a primeira vez que estamos dando um git push
, precisamos informar ao git quem é a branch principal, padrão, do nosso repositório. Faremos isso através do parâmetro --set-upstream
. No futuro, quando quisermos dar um novo git push
, não precisaremos desse parâmetro. Apenas git push
será suficiente.
# The dot when provided to git add means everything in the current folder git add . git commit -m 'Initial commit' git push --set-upstream origin master
Você pode checar o seu repositório no GitHub agora. Ele deve estar atualizado! O meu está! O git não foi feito para versionar datasets e arquivos grandes, nem muito menos pipelines. É nessa lacuna que o DVC cai como uma luva. Vamos começar a utilizá-lo para versionar o dataset que iremos analisar. Você pode baixar o dataset alarm através do site oficial do MIIC clicando aqui. Na linha de comando, você deve digitar:
wget -c https://miic.curie.fr/datasets/alarm1000samples.txt -O alarm.tsv
Eu particularmente não gosto quando os repositórios git são u amontoado de arquivos jogados na base do diretório. Prefiro organizar em sub diretórios. Vamos fazer isso.
mkdir data mv alarm.tsv data/
O DVC entra em cena
Se você digitar git status
, você irá ver que a pasta está como untracked, não versionada, o que de certo modo é um pouco irritante porque (a) git não deveria estar preocupado com arquivos de dados e (b) ainda que algumas pessoas forcem a barra e façam-no versionar datasets, você não quer isso. Afinal, temos o DVC, certo? Uma das coisas que o DVC faz automaticamente, após você informá-lo que você quer que ele versione alguns arquivos, é dizer para o git ignorar esses arquivos já que o DVC irá tomar conta deles. Assim como antes de começar a dizer o git o que fazer, com o DVC é o mesmo: Você precisa inicializar o DVC no repositório. Com o git, digitamos git init
, e com o DVC não será diferente. Instruções sobre como instalar o DVC podem ser encontradas aqui.
dvc init dvc add data/alarm.tsv
Após ser inicializado, o DVC irá criar arquivos arquivos. Esses meta-dados são arquivos que precisam ser versionados pelo git. Acho útil pensar no DVC como algo que complementa o git, portanto o git segue sendo o ator principal já que ele versiona os arquivos do DVC. Vamos adicionar esses arquivos ao índice do git, index, e realizar um commit. Nosso dataset não será adicionado nem comitado poqrue o DVC já o adicionou ao .gitignore, um arquivo oculto que o git uso para esse propósito: saber que arquivos deve ignorar.
git add . git commit -m 'Initiates DVC and asks it to track alarm dataset'
Dependendo do quanto você está familiarizado com o git, isso pode ser uma surpresa ou não mas você não precisa dar git push
(enviar a versão mais atual do repositório para um git remote, como o GitHub). Nós não iremos fazer isso agora, por exemplo (ainda que pudéssemos). E do mesmo modo que o GittHub é um remote do git, você tambám pode ter um remote do DVC. O Dropbox, um bucket do Amazon S3, Google Drive ou até uma pasta no seu computador ou em um disco externo, qualquer uma dessas opções pode ser um DVC remote. Por questões de simplicidade, iremos aqui utilizar o Google Drive.
O primeiro passo é ir ao site do Google Drive, autenticar-se na sua conta, e criar uma pasta. Eu criei uma com o nome dvc-miic-cml example. A URL na barra de endereço do meu navegador ficou assim: https://drive.google.com/drive/u/1/folders/188CmpQIYqKOgvcgaLZOxz1GqlwTasv8c
O próximo passo é extrair a última parte dessa URL, após o folders/, ou seja, 188CmpQIYqKOgvcgaLZOxz1GqlwTasv8c. Vamos agora informar o DVC que queremos que essa pasta do Google Drive seja nosso DVC remote.
dvc remote add -d myremote gdrive://188CmpQIYqKOgvcgaLZOxz1GqlwTasv8c
O parâmetro -d é importante pois ele diz ao DVC que esse é nosso remote padrão. Assim como é possível ter mais de um remote no git, também é possível ter mais de um no DVC. Sem informar qual o remote padrão, toda vez que déssemos um git push
, o DVC perguntaria qual o remote desejado. O próximo passo é enviar as atualizações do nosso repositório local para o GitHub, nosso git remote, com o git push
. Consegue adivinhar qual o comando para enviarmos as atualizações do repositório local para o DVC remote? Tenho certeza que você adivinhou corretamente!
dvc push
Para ter certeza que está tudo claro pra você: Os códigos fonte e meta dados do DVC irão para o GitHub remote. Os dados, pipelines e saída de algoritmos irão para o DVC remote.
Se você for conferir a sua pasta no Google Drive agora, você verá que a pasta não está mais vazia. O nome das pastas e seus conteúdos lá não fazem muito sentido, mas acredite em mim: O DVC sabe como interpretar isso :P. Por força do hábito, você digita git status
e percebe que algo mudou no seu repositório. Como assim?! Ao adicionar um remote, um arquivo de configuração do DVC foi alterado. Você poderia executar um git add
para adicionar o arquivo ao índice e um git commit
para salvar um novo snapshot do seu repositório, mas por uma questão didática, eu irei fazer algo diferente: Eu irei emendar o último commit, e ao fazer isso atualizar a mensagem de commit. Amending, ou emendamento, é útil quando você realiza um commit e pouco tempo depois percebe que esqueceu de algo, ou decide melhorar a mensagem do seu commit. A ideia é alterar o seu último commit, em vez de fazer um novo.
git add . git commit --amend -m 'Initiates DVC, sets the default remote and asks it to track alarm dataset'
Ok, vamos dar um git push
agora.
git push
A página do GitHub do seu repositório deve estar parecida com essa aqui. Talvez você esteja se perguntando por que existem dois arquivos na sua pasta data/
do GitHub se eu te disse que o git não irá versionar datasets. Um desses arquivos é o .gitignore
, responsável por informar o git para ignorar arquivos nesse diretório. O arquivo com a extensão .dvc
é utilizado pelo DVC e contém uma hash construída a partir do conteúdo do dataset. É assim que o DVC sabe quando o dataset mudou, porque a hash irá mudar também.
Nota sobre o DVC
Se alguém estiver interessado neste repositório (talvez você esteja), a ideia é fazer como faria com qualquer outro repositório do GitHub. É só cloná-lo!
git clone https://github.com/mribeirodantas/dvc-miic-cml.git cd dvc-miic-cml
Ao listar os arquivos dentro do repositório com o comando ls data
, você perceberá que o dataset não está lá. Bem, claro que ele não está, certo? Você apenas clonou o repositório git, com código. Vamos executar o comando dvc pull
para baixar os dados versionados pelo DVC.
dvc pull
Agora sim 🙂 . Hora de começar a escrever nosso script de inferência de rede. Utilizaremos o MIIC (Multivariate information-based inductive causation) para isso. Crie um arquivo chamado infer_network.R
com o conteúdo abaixo:
library(miic) alarm_dataset <- read.table('data/alarm.tsv', header = TRUE) res <- miic(input_data = alarm_dataset) total_edges <- nrow(res$all.edges.summary) retained_edges <- nrow(res$all.edges.summary[res$all.edges.summary$type == 'P', ]) ratio_edges <- paste0('Ratio of retained edges: ', retained_edges/total_edges) write.table(ratio_edges, file = 'metrics.txt', col.names = FALSE, row.names = FALSE)
Esse código carrega o pacote do R miic
, lê o dataset, trazendo-o para dentro do ambiente R, chama o algoritmo miic
para inferir a rede a partir desse dataset e calcula a razão de arestas mantidas com relação ao total possível. Após isso, essa razão é salva em um arquivo chamado metrics.txt
.
GitHub Actions
Agora é hora de começar a brincar com o GitHub Actions e deixar o CML fazer sua mágica. Toda vez que fizermos um novo commit para esse repositório e fazemos um push, o modelo será construído novamente e a métrica será recalculada.
Para utilizar o GitHub Actions, precisamos criar um arquivo especial em uma pasta especial. O caminho dentro de nosso repositório git é: .github/workflows
Dentro da pasta, você precisar criar o arquivo que o GitHub Action irá buscar por instruções. Você pode escolher qualquer nome, mas precisa estar no formato YAML format. Vamos começar criando o caminho para nosso arquivo do GitHub Action.
mkdir -p .github/workflows cd .github/workflows
Após isso, criaremos um arquivo chamado cml.yaml
e escreveremos o código abaixo. Essas instruções basicamente pedem por uma máquina rodando a última versão do Ubuntu, com o R devidamente instalado, clona o nosso repositório git, instala o MIIC, DVC, as suas dependências, executa o comando dvc pull
para baixar nosso dataset, e chama o script infer_network.R
que irá inferir a rede e salvar a métrica para um arquivo que, no final de tudo, será impresso na tela.
name: dvc-cml-miic on: [push] jobs: run: runs-on: [ubuntu-latest] steps: - uses: r-lib/actions/setup-r@master with: version: '3.6.1' - uses: actions/checkout@v2 - name: cml_run env: repo_token: ${{ secrets.GITHUB_TOKEN }} GDRIVE_CREDENTIALS_DATA: ${{ secrets.GDRIVE_CREDENTIALS_DATA }} run: | # Install miic and dependencies wget -c https://github.com/miicTeam/miic_R_package/archive/v1.4.2.tar.gz tar -xvzf v1.4.2.tar.gz cd miic_R_package-1.4.2 R --silent -e "install.packages(c(\"igraph\", \"ppcor\", \"scales\", \"Rcpp\"))" R CMD INSTALL . --preclean cd .. # Install Python packages pip install --upgrade pip pip install wheel pip install PyDrive2==1.6.0 --use-feature=2020-resolver # Install DVC wget -c https://github.com/iterative/dvc/releases/download/1.4.0/dvc_1.4.0_amd64.deb sudo apt install ./dvc_1.4.0_amd64.deb # Run DVC dvc pull Rscript infer_network.R # Write your CML report echo "MODEL METRICS" cat metrics.txt
Em vez de fazer um commit com essas novas alterações na branch principal,m a master, nós iremos criar uma banch de experimento. É assim que você deve usar o git com o DVC! Nós iremos analisar a versão raw, bruta, do datase alarm, sem pré-processamento, portanto eu irei chamar essa branch de raw_alarm_dataset
.
Você já usou o dvc pull
, portanto sua máquina já está autentiada com o Google Drive. Crie um GitHub secret com o conteúdo do arquivo .dvc/tmp/gdrive-user-credentials.json
e nomeie esse GitHub secret como GDRIVE_CREDENTIALS_DATA.
git checkout -b raw_alarm_dataset # infer_network.R is not in this folder, therefore `git add .` wouldn't # add it to the index of your git repository. -A adds everything. git add -A git commit -m 'Infers alarm network with MIIC and default parameters' git push origin raw_alarm_dataset gh pr create --title 'Network inference of alarm dataset'
Agora, vá para o GitHub e veja o que está acontecendo. Se tudo ocorrer de acordo com o plano, você irá ver algo parecido com a imagem abaixo quando a checagem estiver concluída.
Ok… Você imprimiu sua métrica no log. Massa mas vamos lá, seria mais interessante ter algo ao menos um pouquinho mais interessante, né? `^^
Vamos adicionar mais algumas linhas no script infer_network.R
para fazê-lo plotar a rede inferida para uma imagem, e alterar a parte final para fazer uso de algumas funcionalidades do CML. O código do novo infer_network.R
deve estar assim:
library(miic) alarm_dataset <- read.table('data/alarm.tsv', header = TRUE) res <- miic(input_data = alarm_dataset) total_edges <- nrow(res$all.edges.summary) retained_edges <- nrow(res$all.edges.summary[res$all.edges.summary$type == 'P', ]) ratio_edges <- paste0('Ratio of retained edges: ', retained_edges/total_edges) write.table(ratio_edges, file = 'metrics.txt', col.names = FALSE, row.names = FALSE) # Plot network png(file='network_diagram.png') miic.plot(res) dev.off()
E o novo arquivo cml.yaml
deve estar como no código abaixo. A alteração é que agora iremos instalar o CML para poder utilizá-lo.
name: dvc-cml-miic on: [push] jobs: run: runs-on: [ubuntu-latest] steps: - uses: r-lib/actions/setup-r@master with: version: '3.6.1' - uses: actions/checkout@v2 - name: cml_run env: repo_token: ${{ secrets.GITHUB_TOKEN }} GDRIVE_CREDENTIALS_DATA: ${{ secrets.GDRIVE_CREDENTIALS_DATA }} run: | # Install miic and dependencies wget -c https://github.com/miicTeam/miic_R_package/archive/v1.4.2.tar.gz tar -xvzf v1.4.2.tar.gz cd miic_R_package-1.4.2 R --silent -e "install.packages(c(\"igraph\", \"ppcor\", \"scales\", \"Rcpp\"))" R CMD INSTALL . --preclean cd .. # Install Python packages pip install --upgrade pip pip install wheel pip install PyDrive2==1.6.0 --use-feature=2020-resolver # Install DVC wget -c https://github.com/iterative/dvc/releases/download/1.4.0/dvc_1.4.0_amd64.deb sudo apt install ./dvc_1.4.0_amd64.deb # Run DVC dvc pull Rscript infer_network.R # Install CML npm init --yes npm i @dvcorg/cml@latest # Write your CML report echo "## Model Metrics" > report.md cat metrics.txt >> report.md echo "## Data visualization" >> report.md npx cml-publish network_diagram.png --md >> report.md npx cml-send-comment report.md
Hora de fazer um commit.
git add .
git commit -m 'Uses CML to improve PR feedback'
git push origin raw_alarm_dataset
Ok, agora após as checagens serem realizadas, você deve ver um comentário automático no seu Pull Request com a figura da rede, como abaixo.
Digamos que eu ache que muitas arestas foram removidas e talvez a rede não seja consistente. Irei alterar o código do script infer_network.R
para fazer o MIIC buscar por uma rede consistente. A terceira linha deve ser agora como abaixo:
res <- miic(input_data = alarm_dataset, consistent='orientation')
Vamos salvar o estado atual do repositório local com um commit.
git add . git commit -m 'Makes network consistent' git push origin raw_alarm_dataset
Ok, agora está como eu queria e já posso aprovar o pull request 🙂 . Eu poderia fazer isso clicando no botão verde “Merge pull request” direto no site do GitHub ou eu poderia usar o gh
novamente.
gh pr merge 1
Ele irá te fazer duas perguntas. Eu escolhi criar um commit de merge (mesclagem) e não remover a branch, nem localmente nem no GitHub. Para retornar a branch principal, a master, execute o seguinte comando:
git checkout master
Usando containers do Docker
Você provavelmente reparou que o GitHub Runner demora um certo tempo para fazer as checagens e dependendo de quantos pacotes você quer instalar, essa checagem pode demorar bastante. Um modo de evitar essa demora toda vez que você fizer um push é utilizar um container do Docker que já tem tudo instalado, softwares, pacotes, dependências, tudo. O modo como temos feito até agora está pronto para você adicionar o seu container, já que eu estou instalando o CML manualmente. Se você não quiser usar um container próprio, mas não quer ter que baixar e instalar o CML em todo push, podemos utilizar o container oficial do projeto CML.
Se você se lembra bem, há pouco nós aceitamos o pull request, o que significa que mesclamos aquela branch com a principal, a master. Embora o remote do GitHub esteja atualizado, a nossa cópia local não. Vamos atualizá-la com um git pull
, e após isso criar uma nova branch.
git pull
git checkout -b cml_container
Altere o arquivo cml.yaml
para ficar como no código abaixo.
scripts
Vamos adicionar esse arquivo ao índice do git, realizar um commit, push e criar um Pull Request (PR).
git add . git commit -m 'Makes use of CML container' git push origin cml_container gh pr create --title 'Use CML container'
Após isso, tudo deve correr tranquilamente, como aconteceu aqui. Você pode mesclar o pull request e executar um git pull
novamente para atualizar seu repositório local.
gh pr merge 2 git pull
O que mais?
O DVC não se limita a versionar datasets. Nós também poderíamos criar pipelines (sequências de operações, do download de nossos dados, passando por pré-processamento, inferência, pós-processamento, geração de relatórios, etc) e versioná-los, assim como os arquivos de saíde desses scripts como as imagens que o script infer_network.R
gerou. Poderíamos ter vários scripts para etapas anterior e posterior a de inferência e tudo pode ser orquestrado pelo DVC através de um simples comando: dvc repro
. Etapas que não precisam ser executadas (apenas datasets que são utilizados em uma etapa maios tardia foram modificados) não são executados. Em vez de ter vários scripts sendo chamados no seu GitHub Action file, teria apenas um dvc repro
.
Além disso, poderíamos ter um container do Docker com tudo que não muda entre os commits já instalado. R, DVC, CML, MIIC, dependências do MIIC, entre outras coisas. Isso definitivamente nos pouparia de uma espera nada prazerosa, dependendo do que você for fazer. No nosso caso aqui, baixar, compilar e instalar o MIIC leva alguns minutos que poderiam ser poupados. Para o nosso simples exemplos, acho que não é o fim do mundo esperar alguns poucos minutos, mas se começar a ficar mais complexo, e com datasets maiores, o tempo pode simplesmente se tornar inviável, o que tornaria indispensável criar o seu container do Docker (Parte II dessa série) e utilizar sua própria infraestrutura computacional sincronizada com o GItHub Actions (Parte III).
Por hoje é só pessoal! 😉
Você provavelmente não estaria lendo esse texto se não fosse pela Elle O’Brien, que me mostrou tantas coisas sobre o CML, apresentações e exemplos, e o David Ortega que me ajudou a preparar o ambiente R dentro do container docker oficial do projeto CML.