coremlflow 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Willian Marchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: coremlflow
3
+ Version: 0.0.2
4
+ Summary: Coreflow - Abstração de Funções para MLFlow não oficial.
5
+ Author: Willian Marchi
6
+ Author-email: willian.m.marchi@gmail.com
7
+ License: MIT License
8
+ Keywords: coremlflow mlflow core
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENCE
11
+ Requires-Dist: mlflow
12
+ Provides-Extra: sklearn
13
+ Requires-Dist: scikit-learn; extra == "sklearn"
14
+ Provides-Extra: prophet
15
+ Requires-Dist: prophet; extra == "prophet"
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: keywords
21
+ Dynamic: license
22
+ Dynamic: license-file
23
+ Dynamic: provides-extra
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
26
+
27
+ # Guia de Desenvolvimento: Modelos e Inferência no coremlflow
28
+
29
+ Este guia explica como criar scripts de **Treinamento (`train.py`)** e **Predição/Inferência (`predict.py`)** utilizando o framework base da biblioteca `coremlflow`.
30
+
31
+ A arquitetura orientada a objetos obriga a implementação de métodos específicos para padronizar o ciclo de vida do modelo e a integração automática com o MLflow.
32
+
33
+ ---
34
+
35
+ ## 🚀 1. Criando um Modelo de Treinamento (`train.py`)
36
+
37
+ Com o `coremlflow`, você tem a opção de herdar da classe base genérica ou utilizar as **abstrações específicas** construídas para os principais frameworks de Data Science.
38
+
39
+ ### A. Utilizando as Classes Especializadas (Recomendado)
40
+
41
+ O pacote `coremlflow.models` provê classes preparadas que já implementam automaticamente a melhor forma de salvar o seu modelo (ex: usando `mlflow.sklearn` ou `mlflow.prophet`).
42
+
43
+ #### Exemplo: Random Forest
44
+ Para Random Forest ou Isolation Forest, herde das classes em `coremlflow.models.sklearn`:
45
+
46
+ ```python
47
+ import pandas as pd
48
+ from typing import Dict, Any
49
+ from sklearn.ensemble import RandomForestClassifier
50
+ from coremlflow.models.sklearn import RandomForestMLFlowModel
51
+
52
+ class MeuModeloRF(RandomForestMLFlowModel):
53
+ def __init__(self, experiment_name: str, data_path: str, tracking_uri: str = None):
54
+ super().__init__(experiment_name, tracking_uri)
55
+ self.data_path = data_path
56
+
57
+ def load_data(self) -> Dict[str, Any]:
58
+ # Exemplo carregando dados de Treino/Teste
59
+ return {"X_train": [...], "y_train": [...], "X_test": [...], "y_test": [...]}
60
+
61
+ def train(self, data: Dict[str, Any], params: Dict[str, Any]) -> Any:
62
+ model = RandomForestClassifier(**params)
63
+ model.fit(data["X_train"], data["y_train"])
64
+ return model
65
+
66
+ def evaluate(self, model: Any, data: Dict[str, Any]) -> Dict[str, float]:
67
+ # Calcule e retorne suas métricas
68
+ score = model.score(data["X_test"], data["y_test"])
69
+ return {"accuracy": score}
70
+
71
+ def get_signature_data(self, model: Any, data: Dict[str, Any]) -> tuple:
72
+ return (data["X_train"], data["y_train"])
73
+ ```
74
+ *Note que não foi necessário implementar o método `save_model()`, pois ele já foi abstraído pela classe mãe `RandomForestMLFlowModel`.*
75
+
76
+ #### Outros modelos suportados nativamente:
77
+ - `IsolationForestMLFlowModel` (em `coremlflow.models.sklearn`)
78
+ - `ProphetMLFlowModel` (em `coremlflow.models.prophet`)
79
+
80
+ ### B. Utilizando a Classe Base Genérica
81
+
82
+ Se você estiver treinando um modelo Keras, PyTorch, ou outro ainda não mapeado nas classes filhas nativas, basta herdar nativamente da `MLFlowModelBase` e definir seu `.save_model()`.
83
+
84
+ ```python
85
+ from coremlflow.base import MLFlowModelBase
86
+ import mlflow.keras
87
+
88
+ class MeuModeloGenerico(MLFlowModelBase):
89
+ # ... defina load(), train(), evaluate(), get_signature_data() ...
90
+
91
+ def save_model(self, model, signature) -> None:
92
+ # [Obrigatório na Base]
93
+ mlflow.keras.log_model(model, artifact_path="model", signature=signature)
94
+ ```
95
+
96
+ ### Executando o Treinamento
97
+
98
+ Para rodar, instancie a sua classe e chame o método genérico `run(params)`. Ele vai orquestrar todas as chamadas garantindo o log correto no MLflow.
99
+
100
+ ```python
101
+ if __name__ == "__main__":
102
+ predictor = MeuModeloRF(experiment_name="Projeto_Churn", data_path="dados.csv")
103
+ params = {"n_estimators": 100, "max_depth": 5}
104
+
105
+ modelo = predictor.run(params=params)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## 🔮 2. Consumindo um Modelo em Produção (`predict.py`)
111
+
112
+ Ao fazer a inferência, importe o `MLFlowPredictorBase`.
113
+
114
+ Essa classe cuida internamente do carregamento do modelo a partir do Registry do MLflow. Se o Tracking Server do MLflow estiver fora do ar, você ainda pode fornecer um `local_model_path` como *fallback*.
115
+
116
+ ### Estrutura da Classe de Predição
117
+
118
+ Opcionalmente, você pode sobrescrever o método `predict`, mas por base, a classe já traz o comportamento chamando recursivamente o modelo baixado no formato `pyfunc`.
119
+
120
+ ```python
121
+ from coremlflow.predictor import MLFlowPredictorBase
122
+
123
+ # Você pode usar a classe base diretamente:
124
+ servico_ia = MLFlowPredictorBase(
125
+ internal_alias="CHURN_PROD",
126
+ internal_alias_map_path="./mlflow_aliases.json",
127
+ local_model_path="./modelos_backup/rf_model_v1" # Fallback opcional offline
128
+ )
129
+
130
+ # Os resultados dependerão da natureza do modelo treinado.
131
+ resultados = servico_ia.predict(dados_de_entrada)
132
+ ```
133
+
134
+ Ou, caso você precise criar um fluxo complexo de tratamento de dados de entrada antes / depois de passar pelo `.predict()`:
135
+
136
+ ```python
137
+ class SeuPredictorService(MLFlowPredictorBase):
138
+ def predict(self, input_data):
139
+ # 1. Trata input_data (ex: limpeza, feature engineering)
140
+ dados_tratados = limpar(input_data)
141
+
142
+ # 2. Chama a predição da classe base
143
+ resultado = super().predict(dados_tratados)
144
+
145
+ # 3. Pós Processa
146
+ return formatar(resultado)
147
+ ```
148
+
149
+ Exemplo de arquivo `./mlflow_aliases.json` (apelido interno -> run_id):
150
+
151
+ ```json
152
+ {
153
+ "CHURN_PROD": "a1b2c3d4e5f6g7h8i9j0",
154
+ "FORECAST_PROD": { "run_id": "ffffeeee111122223333444455556666", "artifact_path": "model" }
155
+ }
156
+ ```
@@ -0,0 +1,130 @@
1
+ # Guia de Desenvolvimento: Modelos e Inferência no coremlflow
2
+
3
+ Este guia explica como criar scripts de **Treinamento (`train.py`)** e **Predição/Inferência (`predict.py`)** utilizando o framework base da biblioteca `coremlflow`.
4
+
5
+ A arquitetura orientada a objetos obriga a implementação de métodos específicos para padronizar o ciclo de vida do modelo e a integração automática com o MLflow.
6
+
7
+ ---
8
+
9
+ ## 🚀 1. Criando um Modelo de Treinamento (`train.py`)
10
+
11
+ Com o `coremlflow`, você tem a opção de herdar da classe base genérica ou utilizar as **abstrações específicas** construídas para os principais frameworks de Data Science.
12
+
13
+ ### A. Utilizando as Classes Especializadas (Recomendado)
14
+
15
+ O pacote `coremlflow.models` provê classes preparadas que já implementam automaticamente a melhor forma de salvar o seu modelo (ex: usando `mlflow.sklearn` ou `mlflow.prophet`).
16
+
17
+ #### Exemplo: Random Forest
18
+ Para Random Forest ou Isolation Forest, herde das classes em `coremlflow.models.sklearn`:
19
+
20
+ ```python
21
+ import pandas as pd
22
+ from typing import Dict, Any
23
+ from sklearn.ensemble import RandomForestClassifier
24
+ from coremlflow.models.sklearn import RandomForestMLFlowModel
25
+
26
+ class MeuModeloRF(RandomForestMLFlowModel):
27
+ def __init__(self, experiment_name: str, data_path: str, tracking_uri: str = None):
28
+ super().__init__(experiment_name, tracking_uri)
29
+ self.data_path = data_path
30
+
31
+ def load_data(self) -> Dict[str, Any]:
32
+ # Exemplo carregando dados de Treino/Teste
33
+ return {"X_train": [...], "y_train": [...], "X_test": [...], "y_test": [...]}
34
+
35
+ def train(self, data: Dict[str, Any], params: Dict[str, Any]) -> Any:
36
+ model = RandomForestClassifier(**params)
37
+ model.fit(data["X_train"], data["y_train"])
38
+ return model
39
+
40
+ def evaluate(self, model: Any, data: Dict[str, Any]) -> Dict[str, float]:
41
+ # Calcule e retorne suas métricas
42
+ score = model.score(data["X_test"], data["y_test"])
43
+ return {"accuracy": score}
44
+
45
+ def get_signature_data(self, model: Any, data: Dict[str, Any]) -> tuple:
46
+ return (data["X_train"], data["y_train"])
47
+ ```
48
+ *Note que não foi necessário implementar o método `save_model()`, pois ele já foi abstraído pela classe mãe `RandomForestMLFlowModel`.*
49
+
50
+ #### Outros modelos suportados nativamente:
51
+ - `IsolationForestMLFlowModel` (em `coremlflow.models.sklearn`)
52
+ - `ProphetMLFlowModel` (em `coremlflow.models.prophet`)
53
+
54
+ ### B. Utilizando a Classe Base Genérica
55
+
56
+ Se você estiver treinando um modelo Keras, PyTorch, ou outro ainda não mapeado nas classes filhas nativas, basta herdar nativamente da `MLFlowModelBase` e definir seu `.save_model()`.
57
+
58
+ ```python
59
+ from coremlflow.base import MLFlowModelBase
60
+ import mlflow.keras
61
+
62
+ class MeuModeloGenerico(MLFlowModelBase):
63
+ # ... defina load(), train(), evaluate(), get_signature_data() ...
64
+
65
+ def save_model(self, model, signature) -> None:
66
+ # [Obrigatório na Base]
67
+ mlflow.keras.log_model(model, artifact_path="model", signature=signature)
68
+ ```
69
+
70
+ ### Executando o Treinamento
71
+
72
+ Para rodar, instancie a sua classe e chame o método genérico `run(params)`. Ele vai orquestrar todas as chamadas garantindo o log correto no MLflow.
73
+
74
+ ```python
75
+ if __name__ == "__main__":
76
+ predictor = MeuModeloRF(experiment_name="Projeto_Churn", data_path="dados.csv")
77
+ params = {"n_estimators": 100, "max_depth": 5}
78
+
79
+ modelo = predictor.run(params=params)
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 🔮 2. Consumindo um Modelo em Produção (`predict.py`)
85
+
86
+ Ao fazer a inferência, importe o `MLFlowPredictorBase`.
87
+
88
+ Essa classe cuida internamente do carregamento do modelo a partir do Registry do MLflow. Se o Tracking Server do MLflow estiver fora do ar, você ainda pode fornecer um `local_model_path` como *fallback*.
89
+
90
+ ### Estrutura da Classe de Predição
91
+
92
+ Opcionalmente, você pode sobrescrever o método `predict`, mas por base, a classe já traz o comportamento chamando recursivamente o modelo baixado no formato `pyfunc`.
93
+
94
+ ```python
95
+ from coremlflow.predictor import MLFlowPredictorBase
96
+
97
+ # Você pode usar a classe base diretamente:
98
+ servico_ia = MLFlowPredictorBase(
99
+ internal_alias="CHURN_PROD",
100
+ internal_alias_map_path="./mlflow_aliases.json",
101
+ local_model_path="./modelos_backup/rf_model_v1" # Fallback opcional offline
102
+ )
103
+
104
+ # Os resultados dependerão da natureza do modelo treinado.
105
+ resultados = servico_ia.predict(dados_de_entrada)
106
+ ```
107
+
108
+ Ou, caso você precise criar um fluxo complexo de tratamento de dados de entrada antes / depois de passar pelo `.predict()`:
109
+
110
+ ```python
111
+ class SeuPredictorService(MLFlowPredictorBase):
112
+ def predict(self, input_data):
113
+ # 1. Trata input_data (ex: limpeza, feature engineering)
114
+ dados_tratados = limpar(input_data)
115
+
116
+ # 2. Chama a predição da classe base
117
+ resultado = super().predict(dados_tratados)
118
+
119
+ # 3. Pós Processa
120
+ return formatar(resultado)
121
+ ```
122
+
123
+ Exemplo de arquivo `./mlflow_aliases.json` (apelido interno -> run_id):
124
+
125
+ ```json
126
+ {
127
+ "CHURN_PROD": "a1b2c3d4e5f6g7h8i9j0",
128
+ "FORECAST_PROD": { "run_id": "ffffeeee111122223333444455556666", "artifact_path": "model" }
129
+ }
130
+ ```
@@ -0,0 +1,9 @@
1
+ from .base import MLFlowModelBase
2
+ from .predictor import MLFlowPredictorBase
3
+ from .decorators import mlflow_standard_run
4
+
5
+ __all__ = [
6
+ "MLFlowModelBase",
7
+ "MLFlowPredictorBase",
8
+ "mlflow_standard_run",
9
+ ]
@@ -0,0 +1,75 @@
1
+ from abc import ABC, abstractmethod
2
+ import mlflow
3
+ import os
4
+ import logging
5
+ from typing import Any, Dict, Optional
6
+ from datetime import datetime
7
+
8
+ class MLFlowModelBase(ABC):
9
+ def __init__(self, experiment_name: str, tracking_uri: Optional[str] = None):
10
+ self.experiment_name = experiment_name
11
+
12
+ # Usa env var se não for passado via parâmetro
13
+ uri = tracking_uri or os.getenv("MLFLOW_TRACKING_URI", "http://localhost:5000")
14
+ mlflow.set_tracking_uri(uri)
15
+ mlflow.set_experiment(experiment_name)
16
+
17
+ logging.basicConfig(level=logging.INFO)
18
+ self.logger = logging.getLogger(self.__class__.__name__)
19
+
20
+ @abstractmethod
21
+ def load_data(self) -> Any:
22
+ """Obrigatório: Lógica para carregar os dados."""
23
+ pass
24
+
25
+ @abstractmethod
26
+ def train(self, data: Any, params: Dict[str, Any]) -> Any:
27
+ """Obrigatório: Lógica de treinamento do modelo."""
28
+ pass
29
+
30
+ @abstractmethod
31
+ def evaluate(self, model: Any, data: Any) -> Dict[str, float]:
32
+ """Obrigatório: Retornar dicionário de métricas (ex: {'acc': 0.9})."""
33
+ pass
34
+
35
+ @abstractmethod
36
+ def get_signature_data(self, model: Any, data: Any) -> tuple[Any, Any]:
37
+ """Obrigatório: Retornar (sample_input, sample_output) para infer_signature.
38
+ Caso não queira gerar signature, retorne (None, None).
39
+ """
40
+ pass
41
+
42
+ @abstractmethod
43
+ def save_model(self, model: Any, signature: Any) -> None:
44
+ """Obrigatório: Salvar o modelo no MLflow.
45
+ Use mlflow.sklearn.log_model, mlflow.pytorch.log_model, etc.
46
+ """
47
+ pass
48
+
49
+ def run(self, params: dict):
50
+ with mlflow.start_run(run_name=f"Run_{self.__class__.__name__}_{datetime.now().strftime('%H%M%S')}") as run:
51
+ # 1. Ciclo de vida padrão
52
+ data = self.load_data()
53
+ model = self.train(data, params)
54
+ metrics = self.evaluate(model, data)
55
+
56
+ # 2. Log Automático de Parâmetros e Métricas
57
+ mlflow.log_params(params)
58
+ mlflow.log_metrics(metrics)
59
+
60
+ # 3. Inteligência de Registro
61
+ sample_input, sample_output = self.get_signature_data(model, data)
62
+ signature = None
63
+ if sample_input is not None and sample_output is not None:
64
+ from mlflow.models import infer_signature
65
+ try:
66
+ signature = infer_signature(sample_input, sample_output)
67
+ self.logger.info("Signature inferred successfully.")
68
+ except Exception as e:
69
+ self.logger.warning(f"Failed to infer signature: {e}")
70
+
71
+ # 4. Salvar o modelo confiando na implementação da classe filha
72
+ self.save_model(model, signature)
73
+
74
+ self.logger.info(f"Modelo registrado com sucesso no Run ID: {run.info.run_id}")
75
+ return model
@@ -0,0 +1,35 @@
1
+ import functools
2
+ import time
3
+ import mlflow
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ def mlflow_standard_run(func):
9
+ """
10
+ Decorator para registrar a duração de execução e status em uma Active Run do MLflow.
11
+ Presume-se que seja executado dentro de um context manager ou que tenha uma run ativa.
12
+ """
13
+ @functools.wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ start_time = time.time()
16
+ logger.info(f"--- Iniciando execução de ML: {func.__name__} ---")
17
+
18
+ try:
19
+ result = func(*args, **kwargs)
20
+
21
+ duration = time.time() - start_time
22
+ # Só loga métricas se houver uma run ativa para não criar runs soltas indesejadas
23
+ if mlflow.active_run():
24
+ mlflow.log_metric(f"{func.__name__}_duration_seconds", duration)
25
+ mlflow.set_tag(f"{func.__name__}_status", "success")
26
+
27
+ return result
28
+ except Exception as e:
29
+ if mlflow.active_run():
30
+ mlflow.set_tag(f"{func.__name__}_status", "failed")
31
+ mlflow.log_param(f"{func.__name__}_error_message", str(e))
32
+ logger.error(f"Erro na execução de {func.__name__}: {e}")
33
+ raise e
34
+
35
+ return wrapper
File without changes
@@ -0,0 +1,33 @@
1
+ from typing import Any, Dict
2
+ from abc import abstractmethod
3
+ import mlflow
4
+ import mlflow.prophet
5
+ from coremlflow.base import MLFlowModelBase
6
+
7
+ class ProphetMLFlowModel(MLFlowModelBase):
8
+ """
9
+ Classe base para modelos da biblioteca Prophet.
10
+ O Prophet tem um modo de treino e de salvar modelo específico.
11
+ """
12
+ def save_model(self, model: Any, signature: Any) -> None:
13
+ """Salva o modelo usando mlflow.prophet.log_model."""
14
+ self.logger.info("Salvando modelo Prophet...")
15
+ mlflow.prophet.log_model(
16
+ pr_model=model,
17
+ artifact_path="model",
18
+ signature=signature
19
+ )
20
+
21
+ @abstractmethod
22
+ def train(self, data: Any, params: Dict[str, Any]) -> Any:
23
+ # Exemplo:
24
+ # from prophet import Prophet
25
+ # model = Prophet(**params)
26
+ # dataframe precisa ter 'ds' (data) e 'y' (valor)
27
+ # return model.fit(data)
28
+ pass
29
+
30
+ @abstractmethod
31
+ def evaluate(self, model: Any, data: Any) -> Dict[str, float]:
32
+ # Avaliação de séries temporais: MAPE, MAE, RMSE...
33
+ pass
@@ -0,0 +1,64 @@
1
+ from typing import Any, Dict
2
+ from abc import abstractmethod
3
+ import mlflow
4
+ import mlflow.sklearn
5
+ from coremlflow.base import MLFlowModelBase
6
+
7
+ class MLFlowSklearnModel(MLFlowModelBase):
8
+ """
9
+ Classe base intermediária para qualquer modelo que use o flavor do scikit-learn.
10
+ Ela auto-implementa a forma correta de salvar o modelo.
11
+ """
12
+ def save_model(self, model: Any, signature: Any) -> None:
13
+ """Salva o modelo usando mlflow.sklearn.log_model."""
14
+ self.logger.info("Salvando modelo Scikit-Learn...")
15
+ # Usa infer_signature se a signature for passada, senão salva sem
16
+ mlflow.sklearn.log_model(
17
+ sk_model=model,
18
+ artifact_path="model",
19
+ signature=signature
20
+ )
21
+
22
+ class RandomForestMLFlowModel(MLFlowSklearnModel):
23
+ """
24
+ Abstração específica para Random Forest.
25
+ Sugerimos implementar o train() com RandomForestClassifier ou RandomForestRegressor.
26
+ """
27
+
28
+ @abstractmethod
29
+ def train(self, data: Any, params: Dict[str, Any]) -> Any:
30
+ # Exemplo:
31
+ # from sklearn.ensemble import RandomForestClassifier
32
+ # model = RandomForestClassifier(**params)
33
+ # return model.fit(data['X'], data['y'])
34
+ pass
35
+
36
+ @abstractmethod
37
+ def evaluate(self, model: Any, data: Any) -> Dict[str, float]:
38
+ # Exemplo:
39
+ # from sklearn.metrics import accuracy_score
40
+ # preds = model.predict(data['X_test'])
41
+ # return {"accuracy": accuracy_score(data['y_test'], preds)}
42
+ pass
43
+
44
+
45
+ class IsolationForestMLFlowModel(MLFlowSklearnModel):
46
+ """
47
+ Abstração específica para Isolation Forest (Detecção de Anomalias).
48
+ Sugerimos implementar o train() abstraindo o uso não-supervisionado do Isolation Forest.
49
+ """
50
+
51
+ @abstractmethod
52
+ def train(self, data: Any, params: Dict[str, Any]) -> Any:
53
+ # Exemplo:
54
+ # from sklearn.ensemble import IsolationForest
55
+ # model = IsolationForest(**params)
56
+ # return model.fit(data['X'])
57
+ pass
58
+
59
+ @abstractmethod
60
+ def evaluate(self, model: Any, data: Any) -> Dict[str, float]:
61
+ # O IsolationForest retorna -1 para anomalias e 1 para inliers.
62
+ # Exemplo: usar F1-score caso tenha os rótulos verdadeiros de anomalias (data['y_true'])
63
+ # ou outras métricas não supervisionadas (Silhouette Score se aplicável).
64
+ pass
@@ -0,0 +1,116 @@
1
+ from abc import ABC, abstractmethod
2
+ import mlflow
3
+ import os
4
+ import logging
5
+ from typing import Optional, Any
6
+ import json
7
+
8
+ class MLFlowPredictorBase(ABC):
9
+ def __init__(
10
+ self,
11
+ model_name: Optional[str] = None,
12
+ alias: str = "Production",
13
+ tracking_uri: Optional[str] = None,
14
+ local_model_path: Optional[str] = None,
15
+ *,
16
+ model_uri: Optional[str] = None,
17
+ internal_alias: Optional[str] = None,
18
+ internal_alias_map_path: Optional[str] = None,
19
+ artifact_path: str = "model",
20
+ ):
21
+ """
22
+ Estratégias suportadas para carregar modelo (ordem):
23
+ - model_uri: URI direta do MLflow (ex: runs:/<run_id>/model, models:/<name>@<alias>, file:/...).
24
+ - model_name + alias: via Model Registry (quando existir) usando models:/{model_name}@{alias}
25
+ - internal_alias + internal_alias_map_path: apelido interno resolvido para runs:/<run_id>/{artifact_path}
26
+ - local_model_path: fallback local
27
+ """
28
+ self.model_name = model_name
29
+ self.alias = alias
30
+ self.local_model_path = local_model_path
31
+ self.model_uri = model_uri
32
+ self.internal_alias = internal_alias
33
+ self.internal_alias_map_path = internal_alias_map_path or os.getenv("COREMLFLOW_ALIAS_MAP_PATH")
34
+ self.artifact_path = artifact_path
35
+
36
+ uri = tracking_uri or os.getenv("MLFLOW_TRACKING_URI", "http://localhost:5000")
37
+ mlflow.set_tracking_uri(uri)
38
+
39
+ logging.basicConfig(level=logging.INFO)
40
+ self.logger = logging.getLogger(self.__class__.__name__)
41
+ self.model = self._load_model()
42
+
43
+ def _resolve_internal_alias_to_model_uri(self) -> Optional[str]:
44
+ if not self.internal_alias:
45
+ return None
46
+ if not self.internal_alias_map_path:
47
+ raise ValueError(
48
+ "internal_alias foi informado, mas internal_alias_map_path não foi fornecido "
49
+ "e COREMLFLOW_ALIAS_MAP_PATH não está definido."
50
+ )
51
+ if not os.path.exists(self.internal_alias_map_path):
52
+ raise FileNotFoundError(f"Arquivo de aliases não encontrado: {self.internal_alias_map_path}")
53
+
54
+ with open(self.internal_alias_map_path, "r", encoding="utf-8") as f:
55
+ mapping = json.load(f)
56
+
57
+ entry = mapping.get(self.internal_alias)
58
+ if entry is None:
59
+ raise KeyError(f"Alias interno não encontrado no mapa: {self.internal_alias}")
60
+
61
+ # Aceita dois formatos:
62
+ # 1) "CHURN_PROD": "<run_id>"
63
+ # 2) "CHURN_PROD": {"run_id": "...", "artifact_path": "model"}
64
+ if isinstance(entry, str):
65
+ run_id = entry
66
+ artifact_path = self.artifact_path
67
+ elif isinstance(entry, dict):
68
+ run_id = entry.get("run_id")
69
+ artifact_path = entry.get("artifact_path") or self.artifact_path
70
+ else:
71
+ raise TypeError("Entrada de alias deve ser str (run_id) ou dict {run_id, artifact_path}.")
72
+
73
+ if not run_id:
74
+ raise ValueError(f"Alias interno '{self.internal_alias}' não possui run_id válido.")
75
+
76
+ return f"runs:/{run_id}/{artifact_path}"
77
+
78
+ def _load_model(self):
79
+ """Carrega o modelo do MLflow por URI, registry (se existir) ou alias interno."""
80
+ try:
81
+ resolved_uri = None
82
+
83
+ if self.model_uri:
84
+ resolved_uri = self.model_uri
85
+ self.logger.info(f"Carregando modelo via model_uri: {resolved_uri}")
86
+ elif self.model_name:
87
+ # Sintaxe moderna para aliases do MLflow: models:/{model_name}@{alias}
88
+ resolved_uri = f"models:/{self.model_name}@{self.alias}"
89
+ self.logger.info(f"Carregando modelo do registry: {resolved_uri}")
90
+ elif self.internal_alias:
91
+ resolved_uri = self._resolve_internal_alias_to_model_uri()
92
+ self.logger.info(f"Carregando modelo via alias interno '{self.internal_alias}': {resolved_uri}")
93
+ else:
94
+ raise ValueError("Informe model_uri, model_name, ou internal_alias para carregar o modelo.")
95
+
96
+ return mlflow.pyfunc.load_model(resolved_uri)
97
+ except Exception as e:
98
+ self.logger.warning(f"Erro ao carregar do tracking server: {e}")
99
+ if self.local_model_path and os.path.exists(self.local_model_path):
100
+ self.logger.info(f"Carregando modelo via fallback local: {self.local_model_path}")
101
+ return mlflow.pyfunc.load_model(self.local_model_path)
102
+ else:
103
+ self.logger.error("Falha no fallback local: Caminho não fornecido ou inexistente.")
104
+ raise e
105
+
106
+ def predict(self, data: Any) -> Any:
107
+ """
108
+ Prediz baseado nos dados.
109
+ Retornos variam por tipo de modelo (ex: Prophet retorna DataFrame com 'yhat', scikit-learn retorna array).
110
+ Pode ser sobrescrita para tratar pré/pós processamento se desejado.
111
+ """
112
+ if self.model is None:
113
+ raise ValueError("Modelo não foi carregado.")
114
+
115
+ self.logger.info("Realizando predição com base nos dados fornecidos.")
116
+ return self.model.predict(data)
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: coremlflow
3
+ Version: 0.0.2
4
+ Summary: Coreflow - Abstração de Funções para MLFlow não oficial.
5
+ Author: Willian Marchi
6
+ Author-email: willian.m.marchi@gmail.com
7
+ License: MIT License
8
+ Keywords: coremlflow mlflow core
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENCE
11
+ Requires-Dist: mlflow
12
+ Provides-Extra: sklearn
13
+ Requires-Dist: scikit-learn; extra == "sklearn"
14
+ Provides-Extra: prophet
15
+ Requires-Dist: prophet; extra == "prophet"
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: keywords
21
+ Dynamic: license
22
+ Dynamic: license-file
23
+ Dynamic: provides-extra
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
26
+
27
+ # Guia de Desenvolvimento: Modelos e Inferência no coremlflow
28
+
29
+ Este guia explica como criar scripts de **Treinamento (`train.py`)** e **Predição/Inferência (`predict.py`)** utilizando o framework base da biblioteca `coremlflow`.
30
+
31
+ A arquitetura orientada a objetos obriga a implementação de métodos específicos para padronizar o ciclo de vida do modelo e a integração automática com o MLflow.
32
+
33
+ ---
34
+
35
+ ## 🚀 1. Criando um Modelo de Treinamento (`train.py`)
36
+
37
+ Com o `coremlflow`, você tem a opção de herdar da classe base genérica ou utilizar as **abstrações específicas** construídas para os principais frameworks de Data Science.
38
+
39
+ ### A. Utilizando as Classes Especializadas (Recomendado)
40
+
41
+ O pacote `coremlflow.models` provê classes preparadas que já implementam automaticamente a melhor forma de salvar o seu modelo (ex: usando `mlflow.sklearn` ou `mlflow.prophet`).
42
+
43
+ #### Exemplo: Random Forest
44
+ Para Random Forest ou Isolation Forest, herde das classes em `coremlflow.models.sklearn`:
45
+
46
+ ```python
47
+ import pandas as pd
48
+ from typing import Dict, Any
49
+ from sklearn.ensemble import RandomForestClassifier
50
+ from coremlflow.models.sklearn import RandomForestMLFlowModel
51
+
52
+ class MeuModeloRF(RandomForestMLFlowModel):
53
+ def __init__(self, experiment_name: str, data_path: str, tracking_uri: str = None):
54
+ super().__init__(experiment_name, tracking_uri)
55
+ self.data_path = data_path
56
+
57
+ def load_data(self) -> Dict[str, Any]:
58
+ # Exemplo carregando dados de Treino/Teste
59
+ return {"X_train": [...], "y_train": [...], "X_test": [...], "y_test": [...]}
60
+
61
+ def train(self, data: Dict[str, Any], params: Dict[str, Any]) -> Any:
62
+ model = RandomForestClassifier(**params)
63
+ model.fit(data["X_train"], data["y_train"])
64
+ return model
65
+
66
+ def evaluate(self, model: Any, data: Dict[str, Any]) -> Dict[str, float]:
67
+ # Calcule e retorne suas métricas
68
+ score = model.score(data["X_test"], data["y_test"])
69
+ return {"accuracy": score}
70
+
71
+ def get_signature_data(self, model: Any, data: Dict[str, Any]) -> tuple:
72
+ return (data["X_train"], data["y_train"])
73
+ ```
74
+ *Note que não foi necessário implementar o método `save_model()`, pois ele já foi abstraído pela classe mãe `RandomForestMLFlowModel`.*
75
+
76
+ #### Outros modelos suportados nativamente:
77
+ - `IsolationForestMLFlowModel` (em `coremlflow.models.sklearn`)
78
+ - `ProphetMLFlowModel` (em `coremlflow.models.prophet`)
79
+
80
+ ### B. Utilizando a Classe Base Genérica
81
+
82
+ Se você estiver treinando um modelo Keras, PyTorch, ou outro ainda não mapeado nas classes filhas nativas, basta herdar nativamente da `MLFlowModelBase` e definir seu `.save_model()`.
83
+
84
+ ```python
85
+ from coremlflow.base import MLFlowModelBase
86
+ import mlflow.keras
87
+
88
+ class MeuModeloGenerico(MLFlowModelBase):
89
+ # ... defina load(), train(), evaluate(), get_signature_data() ...
90
+
91
+ def save_model(self, model, signature) -> None:
92
+ # [Obrigatório na Base]
93
+ mlflow.keras.log_model(model, artifact_path="model", signature=signature)
94
+ ```
95
+
96
+ ### Executando o Treinamento
97
+
98
+ Para rodar, instancie a sua classe e chame o método genérico `run(params)`. Ele vai orquestrar todas as chamadas garantindo o log correto no MLflow.
99
+
100
+ ```python
101
+ if __name__ == "__main__":
102
+ predictor = MeuModeloRF(experiment_name="Projeto_Churn", data_path="dados.csv")
103
+ params = {"n_estimators": 100, "max_depth": 5}
104
+
105
+ modelo = predictor.run(params=params)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## 🔮 2. Consumindo um Modelo em Produção (`predict.py`)
111
+
112
+ Ao fazer a inferência, importe o `MLFlowPredictorBase`.
113
+
114
+ Essa classe cuida internamente do carregamento do modelo a partir do Registry do MLflow. Se o Tracking Server do MLflow estiver fora do ar, você ainda pode fornecer um `local_model_path` como *fallback*.
115
+
116
+ ### Estrutura da Classe de Predição
117
+
118
+ Opcionalmente, você pode sobrescrever o método `predict`, mas por base, a classe já traz o comportamento chamando recursivamente o modelo baixado no formato `pyfunc`.
119
+
120
+ ```python
121
+ from coremlflow.predictor import MLFlowPredictorBase
122
+
123
+ # Você pode usar a classe base diretamente:
124
+ servico_ia = MLFlowPredictorBase(
125
+ internal_alias="CHURN_PROD",
126
+ internal_alias_map_path="./mlflow_aliases.json",
127
+ local_model_path="./modelos_backup/rf_model_v1" # Fallback opcional offline
128
+ )
129
+
130
+ # Os resultados dependerão da natureza do modelo treinado.
131
+ resultados = servico_ia.predict(dados_de_entrada)
132
+ ```
133
+
134
+ Ou, caso você precise criar um fluxo complexo de tratamento de dados de entrada antes / depois de passar pelo `.predict()`:
135
+
136
+ ```python
137
+ class SeuPredictorService(MLFlowPredictorBase):
138
+ def predict(self, input_data):
139
+ # 1. Trata input_data (ex: limpeza, feature engineering)
140
+ dados_tratados = limpar(input_data)
141
+
142
+ # 2. Chama a predição da classe base
143
+ resultado = super().predict(dados_tratados)
144
+
145
+ # 3. Pós Processa
146
+ return formatar(resultado)
147
+ ```
148
+
149
+ Exemplo de arquivo `./mlflow_aliases.json` (apelido interno -> run_id):
150
+
151
+ ```json
152
+ {
153
+ "CHURN_PROD": "a1b2c3d4e5f6g7h8i9j0",
154
+ "FORECAST_PROD": { "run_id": "ffffeeee111122223333444455556666", "artifact_path": "model" }
155
+ }
156
+ ```
@@ -0,0 +1,16 @@
1
+ LICENCE
2
+ README.md
3
+ setup.py
4
+ coremlflow/__init__.py
5
+ coremlflow/base.py
6
+ coremlflow/decorators.py
7
+ coremlflow/predictor.py
8
+ coremlflow.egg-info/PKG-INFO
9
+ coremlflow.egg-info/SOURCES.txt
10
+ coremlflow.egg-info/dependency_links.txt
11
+ coremlflow.egg-info/requires.txt
12
+ coremlflow.egg-info/top_level.txt
13
+ coremlflow/models/__init__.py
14
+ coremlflow/models/prophet.py
15
+ coremlflow/models/sklearn.py
16
+ tests/test_models.py
@@ -0,0 +1,7 @@
1
+ mlflow
2
+
3
+ [prophet]
4
+ prophet
5
+
6
+ [sklearn]
7
+ scikit-learn
@@ -0,0 +1 @@
1
+ coremlflow
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r") as arq:
4
+ readme = arq.read()
5
+
6
+ setup(name='coremlflow',
7
+ version='0.0.2',
8
+ license='MIT License',
9
+ author='Willian Marchi',
10
+ long_description=readme,
11
+ long_description_content_type="text/markdown",
12
+ author_email='willian.m.marchi@gmail.com',
13
+ keywords='coremlflow mlflow core',
14
+ description=u'Coreflow - Abstração de Funções para MLFlow não oficial.',
15
+ packages=find_packages(exclude=("tests",)),
16
+ install_requires=['mlflow'],
17
+ extras_require={
18
+ "sklearn": ["scikit-learn"],
19
+ "prophet": ["prophet"],
20
+ },
21
+ )
@@ -0,0 +1,59 @@
1
+ import unittest
2
+ from unittest.mock import patch, MagicMock
3
+ from coreflow.models.sklearn import RandomForestMLFlowModel, IsolationForestMLFlowModel
4
+ from coreflow.models.prophet import ProphetMLFlowModel
5
+
6
+ class DummyRFModel(RandomForestMLFlowModel):
7
+ def load_data(self):
8
+ return {"X": [[1, 2], [3, 4]], "y": [0, 1]}
9
+ def train(self, data, params):
10
+ return "mock_rf_model"
11
+ def evaluate(self, model, data):
12
+ return {"accuracy": 0.95}
13
+ def get_signature_data(self, model, data):
14
+ return (data["X"], data["y"])
15
+
16
+ class DummyProphetModel(ProphetMLFlowModel):
17
+ def load_data(self):
18
+ return {"ds": ["2020-01-01", "2020-01-02"], "y": [1.0, 2.0]}
19
+ def train(self, data, params):
20
+ return "mock_prophet_model"
21
+ def evaluate(self, model, data):
22
+ return {"mape": 0.05}
23
+ def get_signature_data(self, model, data):
24
+ return (data, [1.5, 2.5])
25
+
26
+ class TestModels(unittest.TestCase):
27
+
28
+ @patch("coreflow.base.mlflow")
29
+ @patch("coreflow.models.sklearn.mlflow.sklearn")
30
+ def test_random_forest_model_run(self, mock_mlflow_sklearn, mock_mlflow_base):
31
+ # Mocking signature Inference
32
+ with patch("mlflow.models.infer_signature", return_value="mock_signature"):
33
+ model_instance = DummyRFModel(experiment_name="test_rf")
34
+ result = model_instance.run(params={"n_estimators": 100})
35
+
36
+ # Verificar se salvou corretamente
37
+ mock_mlflow_sklearn.log_model.assert_called_once_with(
38
+ sk_model="mock_rf_model",
39
+ artifact_path="model",
40
+ signature="mock_signature"
41
+ )
42
+ self.assertEqual(result, "mock_rf_model")
43
+
44
+ @patch("coreflow.base.mlflow")
45
+ @patch("coreflow.models.prophet.mlflow.prophet")
46
+ def test_prophet_model_run(self, mock_mlflow_prophet, mock_mlflow_base):
47
+ with patch("mlflow.models.infer_signature", return_value="mock_signature"):
48
+ model_instance = DummyProphetModel(experiment_name="test_prophet")
49
+ result = model_instance.run(params={"changepoint_prior_scale": 0.05})
50
+
51
+ mock_mlflow_prophet.log_model.assert_called_once_with(
52
+ pr_model="mock_prophet_model",
53
+ artifact_path="model",
54
+ signature="mock_signature"
55
+ )
56
+ self.assertEqual(result, "mock_prophet_model")
57
+
58
+ if __name__ == "__main__":
59
+ unittest.main()