File size: 7,110 Bytes
f801f42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
00a925e
f801f42
 
 
 
 
 
 
00a925e
f801f42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import gradio as gr
import pandas as pd
import numpy as np
import torch
import joblib
import json
import os
import traceback
from transformers import AutoTokenizer, AutoModel
from torch import nn

# --- 1. DEFINIÇÃO DA ARQUITETURA DO MODELO ---
# Colocamos a classe aqui para que a aplicação seja autocontida.
class RegressionTransformer(nn.Module):
    def __init__(self, model_name='neuralmind/bert-base-portuguese-cased'):
        super(RegressionTransformer, self).__init__()
        self.bert = AutoModel.from_pretrained(model_name)
        self.regressor = nn.Sequential(
            nn.Dropout(p=0.2),
            nn.Linear(self.bert.config.hidden_size, 128),
            nn.ReLU(),
            nn.Dropout(p=0.2),
            nn.Linear(128, 1)
        )
    def forward(self, input_ids, attention_mask):
        outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
        pooled_output = outputs.pooler_output
        return self.regressor(pooled_output)

# --- 2. CARREGAMENTO DOS ARTEFATOS GLOBAIS ---
# Esta seção é executada uma vez quando a aplicação inicia no Hugging Face Spaces.
print("Carregando artefatos do modelo...")
device = torch.device("cpu")
CACHE_DIR = "./huggingface_cache" 

try:
    # Carrega o tokenizador
    TOKENIZER_NAME = 'neuralmind/bert-base-portuguese-cased'
    tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_NAME, cache_dir=CACHE_DIR)
    print("Tokenizador carregado.")

    # Carrega o modelo treinado
    MODEL_PATH = './model/best_model_state.pth'
    model = RegressionTransformer(model_name=TOKENIZER_NAME)
    model.load_state_dict(torch.load(MODEL_PATH, map_location=device))
    model.to(device)
    model.eval()
    print("Modelo carregado.")

    # Carrega a lista de features que o modelo espera
    FEATURES_PATH = './model/model_features.json'
    with open(FEATURES_PATH, 'r') as f:
        features_dict = json.load(f)
        model_features = features_dict['numeric'] + features_dict['categorical']
    print("Features do modelo carregadas.")

except Exception as e:
    print("!!!!!! ERRO FATAL AO CARREGAR ARTEFATOS !!!!!!")
    traceback.print_exc()
    raise e

# --- 3. FUNÇÃO DE LÓGICA / PREVISÃO ---
# Esta função contém a "inteligência" do back-end.
def predict_price(make, model_name, year, odometer, trim, body, transmission, color, interior):
    try:
        print(f"Recebida nova predição: {make}, {model_name}, {year}, {odometer}km")
        
        # Converte os inputs da interface em um DataFrame de uma linha
        input_data_dict = {
            'make': make, 'model': model_name, 'year': int(year), 'odometer': float(odometer),
            'trim': trim, 'body': body, 'transmission': transmission, 
            'color': color, 'interior': interior
        }
        input_df = pd.DataFrame([input_data_dict])
        
        # Aplica a mesma engenharia de features do treinamento
        ano_referencia = 2024
        input_df['age'] = ano_referencia - input_df['year']
        input_df['sale_month'] = 0 # Placeholder
        input_df['sale_dayofweek'] = 0 # Placeholder
        input_df['sale_dayofyear'] = 0 
        input_df['make_popularity'] = 0
        input_df['model_popularity'] = 0
        input_df['km_per_year'] = 0
        
        # Cria a "frase" textual
        def criar_representacao_textual(row):
            partes = [f"{coluna}[{str(row[coluna])}]" for coluna in model_features if coluna in row]
            return " | ".join(partes)
        text_input = input_df.apply(criar_representacao_textual, axis=1).iloc[0]
        
        # Tokeniza e prepara os tensores
        encoded_text = tokenizer.encode_plus(
            text_input, max_length=128, return_tensors='pt',
            padding='max_length', truncation=True
        )
        input_ids = encoded_text['input_ids'].to(device)
        attention_mask = encoded_text['attention_mask'].to(device)
        
        # Faz a inferência
        with torch.no_grad():
            prediction_log = model(input_ids, attention_mask)
        
        # Pós-processa o resultado
        predicted_price = np.expm1(prediction_log.cpu().numpy()[0][0])
        
        # Formata a string de saída para o usuário
        return f"R$ {predicted_price:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")

    except Exception as e:
        print("!!!!!! ERRO DURANTE A PREVISÃO !!!!!!")
        traceback.print_exc()
        return "Ocorreu um erro. Verifique os logs."

# --- 4. DEFINIÇÃO DA INTERFACE COM GRADIO ---
# Esta seção define a aparência do "front-end".
with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}") as demo:
    gr.Markdown("# 🚗 FipeFinder AI: Previsão de Preços de Carros Usados")
    gr.Markdown("Preencha as características do veículo para receber uma estimativa de preço de mercado baseada em nosso modelo de IA. Este projeto demonstra um pipeline completo de Machine Learning, desde a análise de dados e treinamento de um modelo Transformer até o deploy de uma aplicação interativa.")
    
    with gr.Row():
        with gr.Column(scale=1):
            make_input = gr.Dropdown(label="Marca", choices=["Ford", "Chevrolet", "Honda", "Toyota", "Nissan", "Hyundai", "Kia", "BMW", "Mercedes-Benz", "Volkswagen"])
            model_input = gr.Textbox(label="Modelo", placeholder="Ex: Ka, Onix, Civic, Corolla...")
            year_input = gr.Slider(label="Ano do Modelo", minimum=2000, maximum=2024, step=1, value=2015)
            
        with gr.Column(scale=1):
            odo_input = gr.Number(label="Quilometragem (km)", value=80000)
            trim_input = gr.Textbox(label="Versão", placeholder="Ex: SE 1.0, LTZ, EXL...")
            body_input = gr.Dropdown(label="Carroceria", choices=["Sedan", "SUV", "Hatchback", "Pickup", "Minivan", "Coupe", "Wagon"])
            
        with gr.Column(scale=1):
            trans_input = gr.Radio(label="Transmissão", choices=["automatic", "manual"], value="automatic")
            color_input = gr.Textbox(label="Cor Externa", placeholder="Ex: preto, branco, prata...")
            interior_input = gr.Textbox(label="Cor Interior", placeholder="Ex: preto, cinza, bege...")
            
    predict_btn = gr.Button("Estimar Preço", variant="primary")
    output_price = gr.Label(label="Preço Estimado")

    predict_btn.click(
        fn=predict_price,
        inputs=[make_input, model_input, year_input, odo_input, trim_input, body_input, trans_input, color_input, interior_input],
        outputs=output_price
    )
    
    gr.Examples(
        examples=[
            ["Ford", "Focus", 2013, 75000.0, "SE 2.0", "Hatchback", "automatic", "prata", "preto"],
            ["Chevrolet", "Onix", 2018, 90000.0, "LT 1.0", "Hatchback", "manual", "branco", "cinza"],
            ["Toyota", "Corolla", 2020, 40000.0, "XEi", "Sedan", "automatic", "preto", "preto"],
        ],
        inputs=[make_input, model_input, year_input, odo_input, trim_input, body_input, trans_input, color_input, interior_input]
    )

# --- Lançamento da Interface ---
if __name__ == "__main__":
    demo.launch()