zustandssumme

início | arquivo | sobre

despliego y me voy

Inicialmente escrito como uma seção da página memória da máquina, entituluda “modelo como interface de programação de aplicativos”, esse texto é composto por reflexões e comentários sobre a questão “o que fazer com um modelo treinado?”

mlops

Por algum motivo – ou por muitos – praticamente todos os materiais de estudo contendo aprendizagem de máquina, aprendizagem estatística, inteligência artificial, redes neurais, ciência de dados, etc, no título, que eu estudei até hoje, não tratam de assuntos relacionados ao que fazer após a execução da última célula do notebook de modelagem.

Em um contexto acadêmico, por exemplo, um projeto de aprendizagem de máquina é composto por um conjunto de experimentos guiados pelo método científico. Após as conclusões os resultados são sumarizados, publicados e eventualmente apresentados em congressos. Porém, no contexto de mercado, em que projetos de aprendizagem de máquina entregam soluções para tomada de decisões de negócios, há um universo além dos ciclos de experimentação e divulgação de resultados: é necessário homologar, documentar, versionar, empacotar, implantar e monitorar o produto do projeto.

Diagramas de metodologias como a CRISP-DM sugerem que todas essas etapas que sucedem a aplicação de técnicas de aprendizagem de máquina compõem um vértice isolado – ou seja, um tipo de ponto final do projeto. Contudo, nem sempre esse é o caso: projetos são versionados e monitorados inclusive na expectativa da identificação de problemas a serem corrigidos e melhorias a serem incorporadas em versões futuras. Além disso, ao sintetizar tantas etapas em “deployment” o diagrama suprime o caráter interdisciplinar desse tipo de projeto – manifestado principalmente na integração entre ideias de ciência da computação, aprendizagem estatística e práticas de desenvolvimento de software.

Desse caráter fortemente cíclico e dessa essência dev de projetos de aprendizagem de máquina, ideias como MLOps vêm se popularizando. Apesar do conceito não estar completamente consolidado, MLOps sugere a incorporação entre machine learning (ML) e a já estabelecida cultura DevOps, que integra desenvolvimento de softwares (Dev) a operações em infraestrutura (Ops).

Na prática em MLOps todas as etapas de um projeto de aprendizagem de máquina em contexto de mercado estão integradas em um fluxo cíclico. É fácil de encontrar bons diagramas ilustrando a dinâmica da MLOps, mas para os propósitos desse texto vou usar a seguinte releitura pessoal:

Alguns elementos recorrentes desse tipo de diagrama estão omitidos ou renomeados – de fato, é uma releitura. Apesar disso, os principais artefatos estão presentes. Após a formulação do problema a ser solucionado pelo produto final do projeto, seu desenvolvimento flui em torno de três eixos:

Vale comentar que há um viés “modelocêntrico” na descrição acima, de modo que os mesmos eixos poderiam ser redefinidos a partir de um ponto de vista centrado em dados.

exemplo

Para materializar as ideias de MLOps, paradoxalmente vou substituir um modelo de aprendizagem de máquina por um algoritmo de busca em grafo. A grande vantagem dessa substituição é a simplificação dos testes de sanidade do resultado final. A adaptação da busca em árvore para a predição de modelos é trivial e será discutida brevemente na seção sobre a página web.
Embora a implementação do algoritmo de busca não seja explicada em detalhes nesse texto, o código pode ser melhor compreedindo através do notebook Kaggle Simple Tree Search.

Finalmente, o sistema considerado é o conjunto de (algumas) estações do metrô da região metropolitana de São Paulo e o problema resolvido é

dadas duas estações, qual é o caminho ótimo entre elas?

No notebook a ideia é que o resultado seja uma imagem do mapa contendo as estações e uma linha representando o caminho ótimo

Entretanto, para simplificar o exemplo desse texto, desconsiderei a construção desse gráfico final e usei apenas o resultado do algoritmo de busca.

experimento

Nesse problema especificamente, os experimentos associados a dados se concentraram no desenvolvimento de scripts para captura das informações relevantes – estações, latitude, longitude e conexões a outras estações – e transformação para estruturas que possibilitem a execução de algoritmos de busca em grafo. O produto final desse processo é a tabela metrosp_stations.csv do dataset Kaggle São Paulo Metro.

Os experimentos relacionados a algoritmos aqui foram focados na escolha da melhor estratégia entre as principais técnicas de busca para a representação do metrô de São Paulo em grafo. O algoritmo de busca escolhido é o depth-first search.

modelo

Após a seleção de um algoritmo a partir dos resultados dos experimentos, o modelo é documentado, versionado e empacotado para uso produtivo. Uma forma de minimizar esforços nas tarefas de documentação e empacotamento é adaptar o modelo ao framework da biblioteca Kedro.

kedro

Como a própria documentação da biblioteca sugere, Kedro pode ser entendida como um framework para projetos de ciência de dados. Na prática e a grosso modo, adotar esse framework significa obedecer algumas regras de onde tabelas, parâmetros e funções devem estar para que a biblioteca lide com documentação, empacotamento e publicação do projeto, por exemplo.

O custo dessas facilidades é uma curva de aprendizagem que eu considero levemente íngrime e por isso esse texto vai tratar a biblioteca como uma ferramenta, se esquivando o máximo possível da pretenção de explicar como usá-la. Apesar disso, é útil comentar desde já que a unidade básica do Kedro são os nodes e que pipelines essencialmente são nodes dispostos de forma sequencial.

pipelines e nodes

Projetos kedro podem ser iniciados com

kedro new        # cria o projeto
kedro build-reqs # cria src/requirements.in
kedro install    # instala as bibliotecas

e pipelines são construídos com

kedro pipeline create nome_do_pipeline

O comando kedro new constrói a estrutura de diretórios do projeto, representada abaixo – com a supressão de alguns que não serão relevantes nesse texto

.
├── conf
│   ├── base
│   │   ├── catalog.yml      <--- aqui os dados de entrada e saída são identificados
│   │   ...
│   │   └── parameters.yml   <--- aqui ficam os parâmetros
│   ...
├── data   <--- aqui ficam os dados
│   ...
├── docs   <--- aqui fica a documentação
│   ...
...
└── src
    ├── nome_do_projeto
    │   ...
    │   ├── pipeline_registry.py   <--- aqui os pipelines são declarados
    │   ├── pipelines
    │   │   ├── __init__.py
    │   │   └── nome_do_pipeline
    │   │       ├── __init__.py
    │   │       ├── nodes.py       <--- aqui as funções são definidas
    │   │       ├── pipeline.py    <--- aqui as funções são associadas
    │   │       ...
    │   ...
    ├── requirements.in   <--- aqui vão as bibliotecas
    ...


A adaptação de um notebook Kaggle para a estrutura de projetos Kedro pode ser feita (1) transferindo os dados para algum sub-diretório de data (2) os registrando em conf/base/catalog.yml de acordo com seus tipos

tabela_1:
  type: pandas.CSVDataSet
  filepath: data/01_raw/dataset_a.csv

tabela_2:
  type: pandas.CSVDataSet
  filepath: data/01_raw/dataset_b.csv

resultado:
  type: json.JSONDataSet
  filepath: data/07_model_output/resultado.json

(3) se necessário adicionando os parâmetros em conf/base/parameters.yml

param_1: "palavra"

param_2: 
    - 0.001
    - 0.01
    - 0.1
    - 1.0

(4) escrevendo as funções no arquivo src/nome_do_projeto/pipelines/nome_do_pipeline/nodes.py

def funcao_1(t:pd.DataFrame, s:str) -> float:
    ...
    return val


def funcao_2(t:pd.DataFrame, l:List, v:float) -> Dict:
    ...
    return dic

(5) replicando a lógica do notebook no arquivo src/nome_do_projeto/pipelines/nome_do_pipeline/pipeline.py, em forma de nodes agrupados em pipelines

def create_pipeline(**kwargs) -> Pipeline:
    pipeline_1 = Pipeline(
        [
            node(
                funcao_1,
                inputs = ["tabela_1", "params:param_1"],
                outputs = "valor_intermediario",
                name = "node_1",
            ),
            node(
                funcao_2,
                inputs = ["tabela_2", "params:param_2", "valor_intermediario"],
                outputs = "resultado",
                name = "node_2",
            )
        ]
    )

    return pipeline_1

e (6) declarando os pipelines no arquivo src/nome_do_projeto/pipeline_registry.py

from nome_do_projeto.pipelines import nome_do_pipeline

def register_pipelines() -> Dict[str, Pipeline]:
    pipeline_1 = nome_do_pipeline.create_pipeline()

    return {
        "pipeline_1" : pipeline_1,
        "__default__": pipeline_1
        }

git

O raciocínio descrito nesses seis passos foi usado na adaptação do notebook Simple Tree Search. O versionamento do projeto foi feito no repositório metro-sp-mdp – a partir daqui é relevante saber que a versão do projeto pode ser controlada através do parâmetro version da função setup do arquivo src/setup.py; o valor desse parâmetro é incorporado no layout da documentação e no nome do arquivo do pacote.

A estrutura do projeto pode ser visualizada com

kedro viz

A documentação pode ser construída em docs/build/ com

kedro build-docs

kedro package

src/dist/

pypi

~/.pypirc

[pypi]
  username = __token__
  password = API_TOKEN   # criada em https://pypi.org/manage/account/

src

python3 -m twine upload dist/* --verbose

produção

flask

import metro_sp_mdp.pipelines.mdp as sp_mdp
...

app = Flask(__name__, template_folder='templates')

@app.route('/', methods=['GET', 'POST'])
def form_input():
    if request.method == 'GET':
        return render_template('index.html')
    
    if request.method == 'POST':
        metro_from = request.form['metro_from'].lower()
        metro_to = request.form['metro_to'].lower()
        ...
        return render_template('index.html', result=solucao['estacoes'])

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)



<form class="" action="{{ url_for('form_input') }}" method="POST">
    <label for="metro_from">da estação </label> <input type="text" name="metro_from" id='metro_from'> 
    <label for="metro_to">para a estação </label> <input type="text" name="metro_to" id='metro_to'> 
    <button type="submit" name="button">resolver</button>
</form>
<br/>
<div class="resultado">
    {% if result %}
        {% for value in result %}
            {{ value }} - 
        {% endfor %}
    {% endif %}
</div>

ec2

 chmod 400 security_key.pem 
 ssh -i security_key.pem ec2-user@public_ipv4_address

docker

kedro docker init
kedro docker build


!data/01_raw/metrosp_stations.csv


ARG BASE_IMAGE=python:3.8-slim
FROM $BASE_IMAGE

RUN pip install --upgrade pip
RUN pip install metro-sp-mdp --upgrade

WORKDIR /app
ADD . /app

EXPOSE 80
ENTRYPOINT ["python3", "app.py"]


clone -b release/2.0.0 https://github.com/thiagodsd/metro-sp-mdp.git
cd metro-sp-mdp/
sudo service docker start
sudo docker build -t metrosp .
sudo docker run -p 80:80 metrosp .
tags: aprendizagem-estatistica, notas