Implementação da arquitetura medalhão (Bronze → Silver → Gold) para pipelines de dados e treinamento de modelos de Machine Learning na plataforma Databricks.
Resumo: Este repositório materializa um pipeline completo de dados que transforma dados brutos em modelos preditivos, utilizando a arquitetura multi-hop (medalhão). Os dados evoluem por três camadas — Bronze (cru), Silver (limpo e normalizado em star schema) e Gold (features engenheiradas para ML) — alimentando modelos XGBoost com tracking via MLflow e registro no Unity Catalog.
- Arquitetura
- Datasets
- Camada Bronze → Silver
- Camada Silver → Gold
- Treinamento de Modelos
- Resultados
- Pré-requisitos
- Como Executar
- Deploy com Databricks Bundles
- Estrutura do Repositório
- Licença
flowchart LR
A[Bronze<br/>Dados Brutos] --> B[Silver<br/>Star Schema]
B --> C[Gold<br/>ML-Ready Features]
C --> D[MLflow<br/>Model Registry]
style A fill:#CD7F32,color:#fff
style B fill:#C0C0C0,color:#000
style C fill:#FFD700,color:#000
style D fill:#0194E2,color:#fff
O projeto implementa a arquitetura medalhão (multi-hop) em 4 camadas:
| Camada | Catálogo | Descrição |
|---|---|---|
| Bronze | <catalog>.1_bronze_schema |
Dados brutos como ingeridos (CSV/API) |
| Silver | <catalog>.2_silver_schema |
Dados limpos em star schema (dim_*, fact_*) |
| Gold | <catalog>.3_gold_schema |
Tabela denormalizada com features para ML (ml_features_*) |
| Model Registry | <catalog>.4_model_schema |
Modelos registrados no Unity Catalog |
O projeto trabalha com 3 datasets reais de domínios distintos:
- Fonte: Kaggle
- Descrição: Dados reais de e-commerce brasileiro com 9 tabelas relacionais (pedidos, pagamentos, reviews, clientes, vendedores, produtos, geolocalização)
- Problema de ML: Predição de satisfação do cliente (review_score 1–5 → binário: 0–3 insatisfeito, 4–5 satisfeito)
- Modelos:
olist_review_score_predictor_silver(baseline) /olist_review_score_predictor_gold(challenger)
erDiagram
olist_customers_dataset ||--o{ olist_orders_dataset : "faz"
olist_orders_dataset ||--|{ olist_order_items_dataset : "contém"
olist_orders_dataset ||--|{ olist_order_payments_dataset : "pago_via"
olist_orders_dataset ||--o{ olist_order_reviews_dataset : "recebe"
olist_products_dataset ||--o{ olist_order_items_dataset : "pertence_a"
olist_sellers_dataset ||--o{ olist_order_items_dataset : "vende"
erDiagram
dim_customers ||--o{ fact_orders : "realiza"
fact_orders ||--|{ fact_order_items : "possui"
fact_orders ||--|{ fact_order_payments : "pago_via"
fact_orders ||--o{ fact_order_reviews : "avaliado_em"
dim_products ||--o{ fact_order_items : "inclui"
dim_sellers ||--o{ fact_order_items : "vendido_por"
- Fonte: Kaggle
- Descrição: Dados reais de compras de supermercado com mais de 3 milhões de pedidos
- Problema de ML: Predição de recompra de produto (
reordered: 0 ou 1) - Modelos:
supermarket_reorder_predictor_silver/supermarket_reorder_predictor_gold
- Fonte: Google BigQuery (dataset público)
- Descrição: Dataset de e-commerce de moda com eventos de navegação, inventário e centros de distribuição
- Problema de ML: Predição de retorno de pedido (
returned: 0 ou 1) - Modelos:
thelook_return_predictor_silver/thelook_return_predictor_gold
1_bronze_to_silver/src/
Transformar dados brutos em um star schema limpo e normalizado.
- Padronização: Colunas convertidas para
snake_case - Tipos: Strings convertidas para timestamp, casting de tipos numéricos
- Nulos: Tratamento com
fillna("unknown")efillna(0) - Validação: Remoção de duplicatas (
dropDuplicates), datas futuras, valores negativos - Texto: Lowercase e remoção de espaços extras
# 1_bronze_to_silver/src/olist.py
from pyspark.sql.functions import col, lower, trim
df_bronze = spark.table("olist.1_bronze_schema.olist_customers_dataset")
df_silver = (
df_bronze
.select(
col("customer_id").alias("customer_id"),
lower(trim(col("customer_city"))).alias("city"),
)
.dropDuplicates()
)
df_silver.write.mode("overwrite").saveAsTable("olist.2_silver_schema.dim_customers")| Tabelas Silver (Star Schema) | |
|---|---|
Olist: dim_customers, dim_sellers, dim_products, dim_geolocation, fact_orders, fact_order_items, fact_order_payments, fact_order_reviews |
|
Supermarket: dim_users, dim_products, dim_departments, dim_aisles, fact_orders, fact_order_products |
|
TheLook: dim_distribution_centers, dim_products, dim_users, fact_orders, fact_order_items, fact_inventory_items, fact_events |
2_silver_to_gold/src/
Construir uma tabela denormalizada com features engenheiradas para ML.
| Dataset | Exemplos de Features |
|---|---|
| Olist | total_products_value, total_freight_value, n_items, payment_value, n_payment_types, delivery_delay, delay_ratio, delivery_vs_estimated_ratio, haversine_distance, is_same_state, is_credit_card, installment_ratio, product_volume, product_weight, n_photos, seller_n_orders, seller_n_customers |
| Supermarket | user_total_orders, user_total_products, user_avg_cart_size, user_order_hour, user_recency, prod_add_to_cart_order, prod_reordered_rate, prod_n_days_since_prior, user_product_position, user_product_reorder_rate |
| TheLook | event_count_7d, event_count_30d, product_view_7d, add_to_cart_7d, purchase_7d, conversion_rate_7d, category_bounce_rate, same_delivery_center, brand_category_interaction |
- Temporais:
datediff(estimated, actual), ratios de delay, buckets de hora/dia - Geográficas: Distância Haversine via UDF, flag
is_same_state - Histórico do Usuário: Agregações por cliente (total gasto, frequência, variedade)
- Interação Usuário-Produto: Recência, posição no carrinho, taxa de recompra
3_training_model/src/training/
Para cada dataset, dois modelos são treinados:
| Modelo | Fonte | Features | Complexidade |
|---|---|---|---|
| Baseline (Silver) | Star schema | Agregações simples em tempo de treino (~5–6 features) | Baixa |
| Challenger (Gold) | Tabela ml_features_* |
Features pré-engenheiradas (~22–35 features) | Alta |
# 1. Load data
features_df = read_gold_table("ml_features_review_prediction")
X_train, X_val, X_test, y_train, y_val, y_test = split_data(
features_df, target_col="review_score", test_size=0.2, val_size=0.16
)
# 2. Handle class imbalance
classes_weight = compute_class_weight(
class_weight="balanced", classes=sorted_y, y=y_train
)
sample_weights = np.array([classes_weight[i] for i in y_train])
# 3. Train XGBoost
model = XGBClassifier(
objective="binary:logistic",
n_estimators=1000,
learning_rate=0.05,
early_stopping_rounds=20,
eval_metric="logloss",
random_state=42,
)
model.fit(X_train, y_train, eval_set=[(X_val, y_val)], sample_weight=sample_weights)
# 4. Evaluate
metrics = {
"accuracy": accuracy_score(y_test, y_pred),
"f1_macro": f1_score(y_test, y_pred, average="macro"),
"precision_macro": precision_score(y_test, y_pred, average="macro"),
"roc_auc": roc_auc_score(y_test, y_proba),
"ks_statistic": ks_statistic(y_test, y_proba),
}
# 5. Log to MLflow
with mlflow.start_run(experiment_id=EXPERIMENT_ID) as run:
mlflow.log_metrics(metrics)
mlflow.log_figure(fig, "confusion_matrix.png")
mlflow.log_figure(fig, "roc_curve.png")
mlflow.log_figure(fig, "shap_summary_bar.png")
mlflow.xgboost.log_model(
xgb_model=bst,
artifact_path="model",
registered_model_name=f"{MODEL_NAME}",
)
set_registered_model_alias(name=MODEL_NAME, alias=ALIAS, version=version)Gráficos de importância de features são gerados automaticamente e armazenados no MLflow:
| Dataset | Silver (Baseline) | Gold (Challenger) |
|---|---|---|
| Olist | total_products_value, total_freight_value |
delivery_delay, delay_ratio |
| Supermarket | user_total_orders, user_recency |
user_product_reorder_rate, prod_reordered_rate |
| TheLook | event_count_30d, purchase_7d |
conversion_rate_7d, brand_category_interaction |
Veja os gráficos em docs/images/aggregations/.
Os resultados variam conforme o dataset e a qualidade das features. Em geral, o modelo Challenger (Gold) supera o Baseline (Silver) devido à riqueza das features engenheiradas. As métricas são armazenadas no MLflow e podem ser consultadas via:
runs = mlflow.search_runs(experiment_ids=[EXPERIMENT_ID])| Dataset | Modelo | Acurácia | ROC AUC | F1 (macro) |
|---|---|---|---|---|
| Olist | Baseline (Silver) | ~0.72 | ~0.76 | ~0.70 |
| Olist | Challenger (Gold) | ~0.80 | ~0.87 | ~0.79 |
| Supermarket | Baseline (Silver) | ~0.65 | ~0.70 | ~0.62 |
| Supermarket | Challenger (Gold) | ~0.74 | ~0.82 | ~0.72 |
| TheLook | Baseline (Silver) | ~0.70 | ~0.75 | ~0.68 |
| TheLook | Challenger (Gold) | ~0.78 | ~0.85 | ~0.77 |
Nota: Valores aproximados. Consulte o MLflow para métricas exatas de cada execução.
- Databricks Workspace (Unity Catalog habilitado)
- Databricks CLI configurado (
databricks auth login) - Python 3.10+
- Dependencies:
xgboost==1.7.6,mlflow==2.16.2,shap==0.48.0(ver3_training_model/src/requirements.txt) - Databricks Bundle CLI para deploy
# Clone o repositório
git clone <repo-url>
cd multihop_for_machine_learning
# Configure o Databricks CLI
databricks auth login --host https://<workspace>.databricks.com
# (Opcional) Linting com Ruff
pip install ruff pre-commit
pre-commit install
ruff check .# Olist
databricks bundle run -t dev olist_bronze_to_silver_job
# Supermarket
databricks bundle run -t dev supermarket_bronze_to_silver_job
# TheLook
databricks bundle run -t dev thelook_bronze_to_silver_jobManual: Execute o notebook correspondente no Databricks:
1_bronze_to_silver/src/olist.py1_bronze_to_silver/src/supermarket.py1_bronze_to_silver/src/thelook.py
databricks bundle run -t dev olist_silver_to_gold_jobManual: Execute o notebook:
2_silver_to_gold/src/olist.py(ousupermarket.py,thelook.py)
# Treina baseline (Silver) e challenger (Gold) sequencialmente
databricks bundle run -t dev olist_model_training_jobManual: Execute os notebooks na ordem:
3_training_model/src/training/olist_silver.py(baseline)3_training_model/src/training/olist_gold.py(challenger)
Cada camada possui seu próprio bundle para deploy independente:
# Deploy para dev
databricks bundle deploy -t dev
# Deploy para produção
databricks bundle deploy -t prod
# Executar um job
databricks bundle run -t dev <job_name>| Variável | Descrição | Default (dev) | Default (prod) |
|---|---|---|---|
sample_fraction |
Fração dos dados para treino | 0.25 | 0.25 |
n_estimators |
Nº máximo de árvores (XGBoost) | 1000 | 1000 |
early_stopping_rounds |
Parada precoce | 20 | 20 |
learning_rate |
Taxa de aprendizado | 0.05 | 0.05 |
shap_sample_fraction |
Fração para análise SHAP | 1.0 | 1.0 |
multihop_for_machine_learning/
│
├── 1_bronze_to_silver/ # Pipeline Bronze → Silver
│ ├── src/ # Notebooks PySpark
│ ├── resources/ # Definições de jobs Databricks
│ ├── databricks.yml # Bundle config
│ ├── targets.yml # Targets dev/prod
│ ├── OLIST.md # Documentação detalhada Olist
│ ├── SUPERMARKET.md # Documentação detalhada Supermarket
│ └── THELOOK.md # Documentação detalhada TheLook
│
├── 2_silver_to_gold/ # Pipeline Silver → Gold
│ ├── src/ # Notebooks PySpark
│ ├── resources/ # Definições de jobs
│ ├── databricks.yml # Bundle config
│ └── ...documentação...
│
├── 3_training_model/ # Treinamento de Modelos
│ ├── src/
│ │ ├── training/ # Notebooks de treino
│ │ │ ├── olist_silver.py # Baseline (Silver)
│ │ │ ├── olist_gold.py # Challenger (Gold)
│ │ │ ├── supermarket_silver.py
│ │ │ ├── supermarket_gold.py
│ │ │ ├── thelook_silver.py
│ │ │ └── thelook_gold.py
│ │ ├── utils/
│ │ │ ├── general_helpers.py # Split de dados
│ │ │ ├── ml_helpers.py # Pipeline ML + MLflow
│ │ │ └── visuals.py # Visualizações SHAP
│ │ └── requirements.txt
│ ├── resources/ # Definições de jobs
│ ├── databricks.yml
│ └── ...documentação...
│
├── docs/ # Documentação e diagramas
│ ├── images/
│ │ ├── diagram/ # Diagrama geral da arquitetura
│ │ ├── er/ # Diagramas ER (Bronze/Silver)
│ │ └── aggregations/ # SHAP summary bar charts
│ ├── mermaid/ # Diagramas Mermaid (ER)
│ └── excalidraw/ # Diagramas editáveis
│
├── AGENTS.md # Guidelines para assistentes de IA
└── README.md # Este arquivo
Este projeto está licenciado sob a MIT License — veja o arquivo LICENSE para detalhes.
Para dúvidas, sugestões ou feedback, abra uma issue no repositório.