Spaces:
Sleeping
Sleeping
File size: 19,338 Bytes
fe302e5 2472dc0 576c1ee b4132b9 f5f4281 576c1ee 937cea9 1109ddb 2472dc0 f5f4281 2472dc0 f5f4281 2472dc0 f5f4281 2472dc0 f5f4281 2472dc0 625b916 2472dc0 625b916 576c1ee f5f4281 47b1983 54986f8 f5f4281 625b916 1109ddb 576c1ee 625b916 1109ddb e285299 2472dc0 b4132b9 bbb9e13 2472dc0 a445c12 625b916 4b06db0 625b916 a445c12 625b916 ae042fe b4132b9 f5f4281 b4132b9 1109ddb 576c1ee 8ad0a80 e18bf9d 8ad0a80 e18bf9d b4132b9 8ad0a80 e18bf9d 576c1ee 9f44e15 576c1ee f5f4281 |
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
import gradio as gr
import numpy as np
import joblib
import re
from pathlib import Path
from scipy.sparse import hstack, csr_matrix
try:
import torch
from transformers import AutoTokenizer, AutoModel
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
# ---------------- Classifier ----------------
class PortugueseClassifier:
def __init__(self):
self.model_path = Path("models")
self.labels = None
self.models_loaded = False
self.tfidf_vectorizer = None
self.meta_learner = None
self.mlb = None
self.optimal_thresholds = None
self.trained_base_models = None
if TORCH_AVAILABLE:
self.bert_tokenizer = None
self.bert_model = None
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.load_models()
def load_models(self):
try:
mlb_path = self.model_path / "int_stacking_mlb_encoder.joblib"
tfidf_path = self.model_path / "int_stacking_tfidf_vectorizer.joblib"
meta_path = self.model_path / "int_stacking_meta_learner.joblib"
thresh_path = self.model_path / "int_stacking_optimal_thresholds.npy"
base_path = self.model_path / "int_stacking_base_models.joblib"
self.mlb = joblib.load(mlb_path)
self.labels = self.mlb.classes_.tolist()
self.tfidf_vectorizer = joblib.load(tfidf_path)
self.meta_learner = joblib.load(meta_path)
self.optimal_thresholds = np.load(thresh_path)
self.trained_base_models = joblib.load(base_path)
if TORCH_AVAILABLE:
self.bert_tokenizer = AutoTokenizer.from_pretrained('neuralmind/bert-base-portuguese-cased')
self.bert_model = AutoModel.from_pretrained('neuralmind/bert-base-portuguese-cased')
self.bert_model.eval()
self.bert_model = self.bert_model.to(self.device)
self.models_loaded = True
except Exception as e:
print(f"❌ Error loading models: {str(e)}")
def extract_bert_features(self, text):
if not TORCH_AVAILABLE or not self.bert_model:
return np.zeros((1, 768))
try:
inputs = self.bert_tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=512)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = self.bert_model(**inputs)
return outputs.last_hidden_state[:, 0, :].cpu().numpy()
except Exception:
return np.zeros((1, 768))
def predict(self, text):
if not self.models_loaded:
return [{"label": "Error", "probability": 0.0, "confidence": "low"}]
text = re.sub(r'\s+', ' ', text.strip())
if not text:
return [{"label": "Empty text", "probability": 0.0, "confidence": "low"}]
tfidf_features = self.tfidf_vectorizer.transform([text])
bert_features = self.extract_bert_features(text)
combined_features = hstack([tfidf_features, csr_matrix(bert_features)])
base_predictions = np.zeros((1, len(self.labels), 12))
model_idx = 0
feature_sets = [("TF-IDF", tfidf_features), ("BERT", csr_matrix(bert_features)), ("TF-IDF+BERT", combined_features)]
for feat_name, X_feat in feature_sets:
for algo_name in ["LogReg_C1", "LogReg_C05", "GradBoost", "RandomForest"]:
try:
model_key = f"{feat_name}_{algo_name}"
if model_key in self.trained_base_models:
model = self.trained_base_models[model_key]
pred = model.predict_proba(X_feat)
base_predictions[0, :, model_idx] = pred[0]
else:
base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.3
except Exception:
base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.2
model_idx += 1
meta_features = base_predictions.reshape(1, -1)
meta_pred = self.meta_learner.predict_proba(meta_features)[0]
simple_ensemble = np.mean(base_predictions, axis=2)
final_pred = 0.7 * meta_pred + 0.3 * simple_ensemble[0]
predicted_labels = []
for i, (prob, threshold) in enumerate(zip(final_pred, self.optimal_thresholds)):
if prob > threshold:
confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
predicted_labels.append({"label": self.labels[i], "probability": float(prob), "confidence": confidence})
if not predicted_labels:
max_idx = np.argmax(final_pred)
prob = final_pred[max_idx]
confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
predicted_labels.append({"label": self.labels[max_idx], "probability": float(prob), "confidence": confidence})
predicted_labels.sort(key=lambda x: x["probability"], reverse=True)
return predicted_labels
# ---------------- Load Classifier ----------------
classifier = PortugueseClassifier()
# ---------------- Suggestions ----------------
suggestions = [
"Pelo Senhor Presidente foi presente a esta reunião a informação n.º º ****** da Secção de Urbanismo e Fiscalização -- Serviço de Obras Particulares. \nPonderado e analisado o assunto o Executivo Municipal deliberou por unanimidade aprovar o projeto de arquitetura relativo ao Processo de obras n.º ***** -- EDIFIC.",
"Pelo Senhor Presidente foram presentes a esta reunião os documentos relativos à assunção de compromissos plurianuais relativos à Loja do Cidadão, em Alandroal que se anexam à presente ata. \nPonderado e analisado o assunto o Executivo Municipal deliberou por maioria, com os votos a favor dos eleitos pelo PS e a abstenção da eleita pelo Nós, Cidadãos, aprovar a assunção de compromissos plurianuais. \nReferiu o Sr. Presidente que o FAM enviou então o parecer necessário para aprovação destes dois pontos e que refere que \"atendendo ao exposto propõe que a Direção Executiva do FAM emita parecer prévio positivo à proposta de modificação ao orçamento municipal de 2024 apresentado pela Câmara Municipal\". ",
"-BALANCETE:\n-Operações orçamentas respeitante ao dia vinte e seis de março do corrente ano, é de um milhão oitocentos e onze mil quinhentos e quinze euros e noventa cêntimos.",
"-APOIO SOLICITADO CEAN-CENTRO EDUCATIVO ALICE NABEIRO-VIAGEM DE FINALISTAS-ANO 2024:\n-Apreciação da informação (registo 5911) dos serviços financeiros, referente ao assunto em epígrafe, que a seguir se transcreve:-“ Na sequência da documentação apresentada pelo CEAN – Centro Educativo Alice Nabeiro no âmbito da realização de uma Viagem de Finalistas a Paris, e após análise dos referidos documentos da mencionada entidade e visto a atividade preponderante ser de índole educativo e social, o valor que sugiro quantifica-se em 500,00 (quinhentos euros) o qual considero razoável para o tipo de atividade referida. O valor sugerido tem cobertura orçamental através da Grande Opção do Plano, 2 232 2011/5044 – Transferências Correntes para Instituições Culturais, cuja dotação global prevista nos Documentos Previsionais para o Exercício 2023 é de 150.000,00 €. Pelo exposto e para efeitos de apreciação por parte de V. Exas., é tudo o que me cabe informar, acerca do assunto mencionado em epígrafe”.\n-A CÂMARA DELIBEROU, POR UNANIMIDADE, ATRIBUIR UM APOIO NO VALOR DE 500,00€ (QUINHENTOS EUROS) AO CEAN-CENTRO EDUCATIVO ALICE NABEIRO, TENDO EM VISTA AJUDAR A CUSTEAR A VIAGEM DE FINALISTA 2024."
"-APOIO SOLICITADO PELA ASSOCIAÇÃO CULTURAL AXPRESS-ARTE - FESTIVAL INTERNACIONAL DE DANÇA:\n-Apreciação da informação (registo 5835) dos serviços financeiros, referente ao assunto em epígrafe, que a seguir se transcreve:-“ Na sequência da documentação apresentada pela Axpress-Arte – Associação Cultural para o exercício 2024, e em face da documentação apresentada pela referida instituição no âmbito da candidatura referida em epigrafe, e após análise dos referidos documentos da mencionada entidade e visto a atividade preponderante ser de índole cultural, o valor que sugiro quantifica-se em 3.000,00 (três mil euros) anuais o qual considero razoável para este tipo de atividade. O valor sugerido tem cobertura orçamental através da Grande Opção do Plano, 2 251 2011/5075 – Transferências Correntes para Instituições Culturais, cuja dotação global prevista nos Documentos Previsionais para o Exercício 2024 é de 90.000,00 €. Sendo o Histórico dos últimos anos de atribuição de apoios para a referida instituição o seguinte:\n\n————————————Quadro de Apoios 2019 – 2023————————————\nAno,Valor em Euros\n2019,0,00\n2020,0,00\n2021,0,00\n2022,3.000,00\n2023,3.000,00\n————————————————————————————————————————————————————— \n-Pelo exposto e para efeitos de apreciação por parte de V. Exas., é tudo o que me cabe informar, acerca do assunto mencionado em epígrafe”.\n-A CÂMARA DELIBEROU, POR UNANIMIDADE, ATRIBUIR UM APOIO NO VALOR DE 3.000,00€ (TRÊS MIL EUROS) À ASSOCIAÇÃO CULTURAL AXPRESS-ARTE, TENDO EM VISTA A REALIZAÇÃO DO FESTIVAL INTERNACIONAL DE DANÇA.",
"Presente declaração, emitida pelo Senhor Presidente da Câmara em 07.junho.2024, para ratificação, constante da plataforma de gestão documental SigmaDoc Web/NIPG: 19890/24_Pendente: 100707.\n\nDocumentos que se dão como inteiramente reproduzidos na presente ata e ficam, para todos os efeitos legais, arquivados em pasta própria existente para o efeito.\n\nA Câmara deliberou, ao abrigo da n.º 3, do artigo 35.º do RJAL, aprovado pela Lei n.º 75/2013, de 12 de setembro, na sua redação atual, ratificar a Declaração emitida pelo Senhor Presidente da Camara em 07.junho.2024, em que o Município da Covilhã pretende ceder apoio logístico, institucional e/ou monetário para o evento \"18.ª Santa Bebiana\", atividade esta levada a cabo pela Casa do Povo do Paul, no âmbito da candidatura ao “Cultura ao Centro 2024, da CCDRC”.",
"3.2. – ICOVI, Infraestruturas e Concessões da Covilhã, EM\n\nFoi presente à Câmara informação constante da plataforma de gestão documental SigmaDoc Web/NIPG: 15508/24_Pendente: 101576, relativa ao ofício da ICOVI – Infraestruturas e Concessões da Covilhã, EM, com a referência 11/23, datado de 02.abril.2024, no qual informa o Resultado Antes de Impostos de 2023 negativo no valor de - 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos) e solicita a cobertura desse resultado.\n\nDocumento que se dá como inteiramente reproduzido na presente ata e fica, para todos os efeitos legais, arquivado em pasta própria existente para o efeito.\n\nO Senhor Vereador Ricardo Miguel Correia Leitão Ferreira da Silva criticou o facto de a empresa somente enviar à Câmara um pedido do valor sem pelo menos enviar o relatório e contas.\n\nNão participou na discussão e votação do presente assunto o Senhor Vereador José Miguel Ribeiro Oliveira, nos termos previstos no n.º 6 do artigo 55.º do Anexo I da Lei nº. 75/2013, de 12 de setembro que aprova o RJAL, conjugado com o artigo 69.º do CPA – Código do Procedimento Administrativo, aprovado pelo Decreto-lei n.º 4/2015, de 7 de janeiro, na nova redação, por exercer as funções de Presidente do Conselho da Administração.\n\nA Câmara deliberou, com o voto contra dos Senhores Vereadores Ricardo Miguel Correia Leitão Ferreira da Silva e Jorge Humberto Martins Simões, tendo em conta que a Empresa Municipal ICOVI – Infraestruturas e Concessões da Covilhã, EM, apresenta um Resultado Antes de Impostos de 2023 negativo no valor de - 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos), nos termos da legislação aplicável, aprovar e efetuar uma transferência financeira no valor de 176.266,35€ (cento e setenta e seis mil, duzentos e sessenta e seis euros e trinta e cinco cêntimos)",
"Presente à Câmara informação, constante da distribuição no sistema informático de gestão documental com a referência EDOC/2022/27808, propondo a ratificação do Aditamento ao Contrato de Comparticipação entre o Instituto da Habitação e da Reabilitação Urbana, I.P. e o Município da Covilhã, destinado à Construção de edifício multifamiliar para 12 Alojamentos de Emergência (BNAUT).\n\nDocumentos que se dão como inteiramente reproduzidos na presente ata e ficam, para todos os efeitos legais, arquivados em pasta própria existente para o efeito.\n\nA Câmara deliberou ratificar o Aditamento ao Contrato de Comparticipação entre o Instituto da Habitação e da Reabilitação Urbana, I.P. e o Município da Covilhã – BNAUT, em que as Partes acordam em proceder à alteração do n.º 1 da Cláusula Terceira, do n.º 1 da Cláusula Quarta e da alínea a) do nº 1 da Cláusula Sexta do Contrato, e que tem por objeto a concessão de um apoio financeiro não reembolsável destinado a financiar a realização do projeto designado por “Construção de edifício multifamiliar para alojamento de emergência na Rua Comendador Gomes Correia n.º 39 a 65”, enquadrado no Aviso n.º 02/CO2-i02/2021, em que a Entidade Beneficiária é a Beneficiária Final, entidade globalmente responsável pela execução do Projeto de investimento ora contratualizado.",
"O Presidente da Câmara apresentou declaração de inexistência de conflitos de interesse relativamente aos procedimentos da ordem do dia da presente reunião, constantes dos pontos 1 a 7 e 9 a 18, que se dá aqui por reproduzida e fica arquivada em pasta anexa ao livro de atas.",
"7. TRÂNSITO – FREGUESIA DE PRAZINS SANTO TIRSO E CORVITE – ALTERAÇÃO DE TRÂNSITO NA TRAVESSA DO CAMPO NOVO - Presente a seguinte proposta: “Por forma a incrementar as condições de segurança e acessibilidade local dos moradores, a Junta de Freguesia submeteu proposta de alteração de trânsito na Travessa do Campo Novo, Freguesia de Prazins Santo Tirso e Corvite, no tramo compreendido entre a Travessa Nova do Campo Novo e a Rua 24 de Junho, aprovada pela Assembleia de Freguesia. Considerando os constrangimentos associados ao reduzido perfil transversal da Travessa do Campo Novo, a alteração potenciará o incremento da segurança rodoviária local, bem como a mitigação da prática de estacionamento em contravenção, submete-se à apreciação Camarária conforme postura anexa.” A postura e as atas dos órgãos executivo e deliberativo da Freguesia dão-se aqui por reproduzidos e ficam arquivados em pasta anexa ao livro de atas. DELIBERADO, POR UNANIMIDADE, SUBMETER À APROVAÇÃO DA ASSEMBLEIA MUNICIPAL.",
"1. Ata da reunião pública do Executivo Municipal de 11 de novembro de 2024.\nAprovada, por unanimidade, pelos presentes na reunião pública do Executivo Municipal de 11 de novembro de 2024.",
]
example_idx = 0
def next_example():
global example_idx
example_idx = (example_idx + 1) % len(suggestions)
return suggestions[example_idx]
def prev_example():
global example_idx
example_idx = (example_idx - 1 + len(suggestions)) % len(suggestions)
return suggestions[example_idx]
def use_suggestion(suggestion):
return suggestion
def classify_display(text):
preds = classifier.predict(text)
if not preds:
return "<div style='color:#777;text-align:center'>No topics detected.</div>"
chips = ""
for p in preds[:10]:
label = p["label"]
prob = p["probability"]
conf = p["confidence"]
color = {"high": "#00ff88", "medium": "#ffd966", "low": "#ff6666"}[conf]
chips += f"<span class='output-chip' style='border-color:{color}80;color:{color}'>{label} ({prob:.0%})</span>"
return f"<div style='display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-top:10px'>{chips}</div>"
# ---------------- CSS ----------------
custom_css = """
body { background-color: #0c0c0c; color: #f1f1f1; font-family: 'Inter', sans-serif; }
.gradio-container { background-color: #0c0c0c; color: #f1f1f1; }
h2, h3 { text-align: center; color: #00b4ff; font-weight: 600; }
textarea { background-color: #181818 !important; color: #fff !important; border-radius: 10px !important; border: 1px solid #333 !important; }
button { background-color: #007aff !important; color: white !important; font-weight: 600 !important; border-radius: 8px !important; border: none !important; }
button:hover { background-color: #00aaff !important; }
.output-chip { background-color: #1a1a1a; padding: 5px 12px; border-radius: 8px; font-weight: 500; border: 1px solid #007aff33; }
.suggestion-box { background-color: #112f50; border-radius: 10px; border: 1px solid #1f3c5a; padding: 10px; display: flex; align-items: center; justify-content: center; color: #eee; margin-top: 25px; position: relative; overflow: scroll; } .arrow-btn { width: 25px; height: 25px; font-size: 12px; padding: 0; background: none; border: none; color: #e0f0ff; cursor: pointer; font-weight: bold; }
.arrow-btn:hover { color: #ffffff; transform: scale(1.3); }
.use-btn { background-color:#66b3ff !important; color:#000 !important; font-weight:600 !important; border-radius:6px !important; padding:3px 8px !important; margin-left:5px;}
.use-btn:hover { background-color:#99ccff !important; }
.suggestion-box .prev-btn { position: absolute; top: 5px; left: 5px; }
.suggestion-box .next-btn { position: absolute; top: 5px; right: 5px; }
.suggestion-box .suggestion-text { width: 100%; text-align: center; border:none; background:none; color:#eee; font-weight:500; padding-top:8px; overflow-y: scroll;}
"""
# ---------------- Gradio UI ----------------
with gr.Blocks(css=custom_css, theme="gradio/soft") as demo:
gr.Markdown("## 🏛️ **Council Topics Classifier – PT**")
gr.Markdown("### Insert your text (in portuguese):")
input_text = gr.Textbox(label="", placeholder="Write your text (in portuguese)...", lines=6)
classify_btn = gr.Button("Classify")
output = gr.HTML()
classify_btn.click(fn=classify_display, inputs=input_text, outputs=output)
# Sugestões
prev_btn = gr.Button("⟨", elem_classes="prev-btn arrow-btn")
suggestion_display = gr.Textbox(value=suggestions[0], interactive=False, elem_classes="suggestion-text")
next_btn = gr.Button("⟩", elem_classes="next-btn arrow-btn")
use_btn = gr.Button("Use", elem_classes="use-btn")
prev_btn.click(fn=prev_example, outputs=suggestion_display)
next_btn.click(fn=next_example, outputs=suggestion_display)
use_btn.click(fn=use_suggestion, inputs=suggestion_display, outputs=input_text)
gr.Row([prev_btn, suggestion_display, next_btn, use_btn], elem_id="suggestion-box")
# ---------------- Launch ----------------
if __name__ == "__main__":
demo.launch() |