openTARS / app /murph /treinar_murph.py
ProjectOpenTARS4's picture
Mirror of openTARS 5.2 Ranger (revision 2026110101)
2695555 verified
Raw History Blame Contribute Delete
4.41 kB
"""Treina a Murph ajudante e exporta pra murph.json.
python3 murph/treinar_murph.py # treina com tudo e salva
python3 murph/treinar_murph.py --cv # só validação cruzada (escolha do C)
"""
import json
import math
import os
import sys
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold
AQUI = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, AQUI)
from dados import treino, TAREFAS # noqa: E402
sys.path.insert(0, os.path.dirname(AQUI))
import tars_murph as mm # noqa: E402
def amostras():
ger = [(f, c) for f, c, _ in json.load(open(os.path.join(AQUI, "gerado_limpo.json"), encoding="utf-8"))]
return treino() + ger
def treinar(dados, C):
textos = [f for f, _ in dados]
y = np.array([TAREFAS.index(c) for _, c in dados])
cont = [mm.contagens(t) for t in textos]
n = len(cont)
df = {}
for f in cont:
for k in f:
df[k] = df.get(k, 0) + 1
idf = {k: math.log((1 + n) / (1 + d)) + 1.0 for k, d in df.items()}
vetores = [mm.vetor(t, idf) for t in textos]
dv = DictVectorizer()
X = dv.fit_transform(vetores)
clf = LogisticRegression(C=C, max_iter=5000)
clf.fit(X, y)
nomes = dv.get_feature_names_out()
W = clf.coef_.T # (n_feats, n_tarefas)
pesos = {}
for k, linha in zip(nomes, W):
if np.abs(linha).max() >= 1e-3:
pesos[k] = [round(float(x), 4) for x in linha]
idf = {k: round(v, 4) for k, v in idf.items() if k in pesos}
return mm.Murph({"tarefas": TAREFAS, "idf": idf, "vies": [float(b) for b in clf.intercept_], "pesos": pesos})
def fora_da_dobra(dados, C, dobras=5):
"""(folga p1-p2, acertou, cobertura) de cada frase, prevista por um
modelo que não a viu."""
y = np.array([c for _, c in dados])
res = []
for tr, te in StratifiedKFold(dobras, shuffle=True, random_state=0).split(dados, y):
m = treinar([dados[i] for i in tr], C)
for i in te:
p = m.probabilidades(dados[i][0])
res.append((p[0][0] - p[1][0], p[0][1] == dados[i][1], mm.cobertura(dados[i][0], m.idf)))
return res
def calibrar(res, alvo=0.98):
"""Menor folga com que a Murph ainda acerta >= alvo. Confiante, ela decide
SOZINHA (pula embeddings e ajudante): por isso a régua é alta (98%)."""
res = sorted(res, reverse=True)
escolhida, acertos = 1.0, 0
for n, (folga, ok) in enumerate(res, 1):
acertos += ok
if acertos / n >= alvo:
escolhida = folga
return max(0.05, escolhida), sum(ok for _, ok in res) / len(res)
def cv(dados, C, dobras=5):
y = np.array([c for _, c in dados])
acc = []
for tr, te in StratifiedKFold(dobras, shuffle=True, random_state=0).split(dados, y):
m = treinar([dados[i] for i in tr], C)
acc.append(np.mean([m.classificar(dados[i][0])[0] == dados[i][1] for i in te]))
return np.mean(acc), np.std(acc)
if __name__ == "__main__":
dados = amostras()
if "--cv" in sys.argv:
for C in (5, 10, 20, 50):
m, s = cv(dados, C)
print(f"C={C:<3} CV {100 * m:.1f}% ± {100 * s:.1f}", flush=True)
else:
C = float(os.environ.get("MURPH_C", 20))
res = fora_da_dobra(dados, C)
limite, precisao = calibrar([(f, ok) for f, ok, _ in res])
# 98% das frases que ela nunca viu têm pelo menos esta cobertura;
# abaixo disso o pedido não se parece com nada do treino.
cobertura_minima = float(np.percentile([cob for _, _, cob in res], 2))
print(f"validação cruzada: {100 * precisao:.1f}%, folga 'confiante' >= {limite:.3f}, "
f"cobertura mínima {cobertura_minima:.2f}")
m = treinar(dados, C)
saida = mm.CAMINHO_PADRAO
with open(saida, "w", encoding="utf-8") as f:
json.dump({"formato": mm.VERSAO_FORMATO, "versao": mm.VERSAO, "tarefas": m.tarefas, "idf": m.idf, "vies": m.vies,
"pesos": m.pesos,
"limite": round(limite, 4), "cobertura_minima": round(cobertura_minima, 4), "precisao_cv": round(precisao, 4), "frases": len(dados)}, f, ensure_ascii=False, separators=(",", ":"))
print(f"{len(dados)} frases, {len(m.pesos)} características, {os.path.getsize(saida) / 1e6:.1f} MB -> {saida}")