Spaces:
Sleeping
Sleeping
Commit ·
0ace5b0
1
Parent(s): 422d4ca
Fix HF model loading for meta tensor
Browse files- app.py +7 -6
- src/evaluator.py +46 -23
app.py
CHANGED
|
@@ -303,11 +303,11 @@ def merchant_product_scores(metric: str) -> List[List[Any]]:
|
|
| 303 |
for product in PRODUCTS:
|
| 304 |
try:
|
| 305 |
result = _predict_product(product["name"])
|
| 306 |
-
except Exception:
|
| 307 |
-
|
| 308 |
d = result.get("overall", {}) if metric == "Overall" else result.get("aspect_details", {}).get(metric, {})
|
| 309 |
rows.append([product["name"], product["category"], metric, d.get("label", "Unknown"), round(_safe_float(d.get("confidence")), 4), product["price"], product["average_rating"]])
|
| 310 |
-
return rows
|
| 311 |
|
| 312 |
|
| 313 |
def _metadata_risks(features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, str]:
|
|
@@ -453,7 +453,7 @@ def build_app() -> gr.Blocks:
|
|
| 453 |
with gr.Column(scale=6):
|
| 454 |
gr.Markdown("### Product Score Monitor")
|
| 455 |
merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Choose overall or aspect")
|
| 456 |
-
merchant_scores = gr.Dataframe(headers=["Product", "Category", "Metric", "Prediction", "Confidence", "Price", "Rating"], datatype=["str", "str", "str", "str", "number", "number", "number"], interactive=False)
|
| 457 |
refresh_scores = gr.Button("Refresh Product Scores", variant="primary")
|
| 458 |
gr.Markdown("### New Product Metadata Risk Screening")
|
| 459 |
with gr.Row():
|
|
@@ -466,7 +466,7 @@ def build_app() -> gr.Blocks:
|
|
| 466 |
new_focus = gr.Dropdown(["All"] + ASPECTS, value="All", label="Focus aspect")
|
| 467 |
screen_btn = gr.Button("Predict Metadata Risk", variant="primary")
|
| 468 |
risk_summary = gr.HTML()
|
| 469 |
-
risk_table = gr.Dataframe(headers=["Aspect", "Risk Level", "Model Signal", "Confidence", "Reason"], datatype=["str", "str", "str", "number", "str"], interactive=False)
|
| 470 |
gr.Markdown("### External Review Prediction")
|
| 471 |
external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=4)
|
| 472 |
with gr.Row():
|
|
@@ -480,7 +480,7 @@ def build_app() -> gr.Blocks:
|
|
| 480 |
external_btn = gr.Button("Analyze External Review", variant="primary")
|
| 481 |
ext_overall = gr.HTML()
|
| 482 |
ext_aspects = gr.HTML()
|
| 483 |
-
ext_table = gr.Dataframe(headers=["Aspect", "Prediction", "Confidence", "Key review evidence"], datatype=["str", "str", "number", "str"], interactive=False)
|
| 484 |
refresh_scores.click(merchant_product_scores, merchant_metric, merchant_scores)
|
| 485 |
merchant_metric.change(merchant_product_scores, merchant_metric, merchant_scores)
|
| 486 |
screen_btn.click(screen_new_product, [new_features, new_categories, new_price, new_rating, new_count, new_focus], [risk_summary, risk_table])
|
|
@@ -507,3 +507,4 @@ demo = build_app()
|
|
| 507 |
|
| 508 |
if __name__ == "__main__":
|
| 509 |
demo.launch()
|
|
|
|
|
|
| 303 |
for product in PRODUCTS:
|
| 304 |
try:
|
| 305 |
result = _predict_product(product["name"])
|
| 306 |
+
except Exception as exc:
|
| 307 |
+
return [["ERROR", "Model loading failed", metric, type(exc).__name__, 0.0, 0.0, 0.0]]
|
| 308 |
d = result.get("overall", {}) if metric == "Overall" else result.get("aspect_details", {}).get(metric, {})
|
| 309 |
rows.append([product["name"], product["category"], metric, d.get("label", "Unknown"), round(_safe_float(d.get("confidence")), 4), product["price"], product["average_rating"]])
|
| 310 |
+
return rows or [["No rows", "Try Refresh", metric, "-", 0.0, 0.0, 0.0]]
|
| 311 |
|
| 312 |
|
| 313 |
def _metadata_risks(features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, str]:
|
|
|
|
| 453 |
with gr.Column(scale=6):
|
| 454 |
gr.Markdown("### Product Score Monitor")
|
| 455 |
merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Choose overall or aspect")
|
| 456 |
+
merchant_scores = gr.Dataframe(headers=["Product", "Category", "Metric", "Prediction", "Confidence", "Price", "Rating"], value=[["Click Refresh Product Scores", "", "", "", 0.0, 0.0, 0.0]], datatype=["str", "str", "str", "str", "number", "number", "number"], interactive=False)
|
| 457 |
refresh_scores = gr.Button("Refresh Product Scores", variant="primary")
|
| 458 |
gr.Markdown("### New Product Metadata Risk Screening")
|
| 459 |
with gr.Row():
|
|
|
|
| 466 |
new_focus = gr.Dropdown(["All"] + ASPECTS, value="All", label="Focus aspect")
|
| 467 |
screen_btn = gr.Button("Predict Metadata Risk", variant="primary")
|
| 468 |
risk_summary = gr.HTML()
|
| 469 |
+
risk_table = gr.Dataframe(headers=["Aspect", "Risk Level", "Model Signal", "Confidence", "Reason"], value=[["Click Predict Metadata Risk", "", "", 0.0, ""]], datatype=["str", "str", "str", "number", "str"], interactive=False)
|
| 470 |
gr.Markdown("### External Review Prediction")
|
| 471 |
external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=4)
|
| 472 |
with gr.Row():
|
|
|
|
| 480 |
external_btn = gr.Button("Analyze External Review", variant="primary")
|
| 481 |
ext_overall = gr.HTML()
|
| 482 |
ext_aspects = gr.HTML()
|
| 483 |
+
ext_table = gr.Dataframe(headers=["Aspect", "Prediction", "Confidence", "Key review evidence"], value=[["Click Analyze External Review", "", 0.0, ""]], datatype=["str", "str", "number", "str"], interactive=False)
|
| 484 |
refresh_scores.click(merchant_product_scores, merchant_metric, merchant_scores)
|
| 485 |
merchant_metric.change(merchant_product_scores, merchant_metric, merchant_scores)
|
| 486 |
screen_btn.click(screen_new_product, [new_features, new_categories, new_price, new_rating, new_count, new_focus], [risk_summary, risk_table])
|
|
|
|
| 507 |
|
| 508 |
if __name__ == "__main__":
|
| 509 |
demo.launch()
|
| 510 |
+
|
src/evaluator.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Evaluation: per-aspect metrics, aggregated overall comparison.
|
| 2 |
|
| 3 |
Two label encodings live in this project (keep them straight!):
|
| 4 |
Per-aspect (ACSA): 0=Not_Mentioned, 1=Positive, 2=Negative
|
|
@@ -41,13 +41,31 @@ OVERALL_POSITIVE = 2
|
|
| 41 |
|
| 42 |
|
| 43 |
# ---------------------------------------------------------------------------
|
| 44 |
-
# Loaders (all use weights_only=False
|
| 45 |
# ---------------------------------------------------------------------------
|
| 46 |
|
| 47 |
def _load_ckpt(path: Path, device):
|
| 48 |
return torch.load(path, map_location=device, weights_only=False)
|
| 49 |
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def load_meta_acsa(checkpoint_dir: Path = None, meta_encoder: Optional[MetaEncoder] = None,
|
| 52 |
device=None):
|
| 53 |
if checkpoint_dir is None:
|
|
@@ -71,10 +89,11 @@ def load_meta_acsa(checkpoint_dir: Path = None, meta_encoder: Optional[MetaEncod
|
|
| 71 |
model = GatedAspectSemanticMetaFusionACSAModel(
|
| 72 |
bert_name=bert_name,
|
| 73 |
meta_in_dim=meta_in_dim,
|
| 74 |
-
)
|
| 75 |
else:
|
| 76 |
-
model = BertMetaFusionACSAModel(bert_name=bert_name, meta_in_dim=meta_in_dim)
|
| 77 |
-
model
|
|
|
|
| 78 |
model.eval()
|
| 79 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 80 |
return model, tokenizer, meta_encoder, device
|
|
@@ -88,8 +107,9 @@ def load_acsa(checkpoint_dir: Path = None, device=None):
|
|
| 88 |
device = get_device()
|
| 89 |
ckpt = _load_ckpt(checkpoint_dir / "best.pt", device)
|
| 90 |
bert_name = ckpt.get("config", {}).get("bert_name", cfg.BERT_MODEL_NAME)
|
| 91 |
-
model = BertACSAModel(bert_name=bert_name)
|
| 92 |
-
model
|
|
|
|
| 93 |
model.eval()
|
| 94 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 95 |
return model, tokenizer, device
|
|
@@ -103,8 +123,9 @@ def load_bert_overall(checkpoint_dir: Path = None, device=None):
|
|
| 103 |
device = get_device()
|
| 104 |
ckpt = _load_ckpt(checkpoint_dir / "best.pt", device)
|
| 105 |
bert_name = ckpt.get("config", {}).get("bert_name", cfg.BERT_MODEL_NAME)
|
| 106 |
-
model = BertOverallModel(bert_name=bert_name)
|
| 107 |
-
model
|
|
|
|
| 108 |
model.eval()
|
| 109 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 110 |
return model, tokenizer, device
|
|
@@ -228,17 +249,17 @@ def overall_metrics(y_true, y_pred):
|
|
| 228 |
# ---------------------------------------------------------------------------
|
| 229 |
|
| 230 |
def aspect_to_overall_sentiment(aspect_preds) -> np.ndarray:
|
| 231 |
-
"""Improved voting rule for aggregating per-aspect
|
| 232 |
|
| 233 |
Rules (applied per review):
|
| 234 |
1. Count n_pos and n_neg among MENTIONED aspects (skip Not_Mentioned).
|
| 235 |
-
2. If no aspect is mentioned at all
|
| 236 |
-
3. If n_neg
|
| 237 |
-
4. If n_neg == 1 and n_pos == 0
|
| 238 |
-
5. If n_pos
|
| 239 |
-
6. If n_pos > n_neg (but not all positive)
|
| 240 |
-
7. If n_neg > n_pos
|
| 241 |
-
8. Otherwise (tied, or only 1 pos and 0 neg)
|
| 242 |
|
| 243 |
Input (per-aspect): 0=NM, 1=Pos, 2=Neg
|
| 244 |
Output (overall): 0=Neg, 1=Neu, 2=Pos
|
|
@@ -253,16 +274,16 @@ def aspect_to_overall_sentiment(aspect_preds) -> np.ndarray:
|
|
| 253 |
|
| 254 |
out = np.full(preds.shape[0], OVERALL_NEUTRAL, dtype=np.int64)
|
| 255 |
|
| 256 |
-
# No aspects mentioned
|
| 257 |
-
# n_neg
|
| 258 |
out[n_neg >= 2] = OVERALL_NEGATIVE
|
| 259 |
-
# n_neg == 1, n_pos == 0
|
| 260 |
out[(n_neg == 1) & (n_pos == 0)] = OVERALL_NEGATIVE
|
| 261 |
-
# n_pos
|
| 262 |
out[(n_pos >= 2) & (n_neg == 0)] = OVERALL_POSITIVE
|
| 263 |
-
# n_pos > n_neg and n_pos
|
| 264 |
out[(n_pos > n_neg) & (n_pos >= 2)] = OVERALL_POSITIVE
|
| 265 |
-
# n_neg > n_pos
|
| 266 |
out[n_neg > n_pos] = OVERALL_NEGATIVE
|
| 267 |
|
| 268 |
return out
|
|
@@ -324,3 +345,5 @@ def format_aspect_summary(per_aspect_pred: np.ndarray) -> Dict[str, str]:
|
|
| 324 |
for i, aspect in enumerate(cfg.ASPECTS)}
|
| 325 |
|
| 326 |
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluation: per-aspect metrics, aggregated overall comparison.
|
| 2 |
|
| 3 |
Two label encodings live in this project (keep them straight!):
|
| 4 |
Per-aspect (ACSA): 0=Not_Mentioned, 1=Positive, 2=Negative
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
# ---------------------------------------------------------------------------
|
| 44 |
+
# Loaders (all use weights_only=False 閳?these are our own checkpoints)
|
| 45 |
# ---------------------------------------------------------------------------
|
| 46 |
|
| 47 |
def _load_ckpt(path: Path, device):
|
| 48 |
return torch.load(path, map_location=device, weights_only=False)
|
| 49 |
|
| 50 |
|
| 51 |
+
def _move_model_to_device(model, device):
|
| 52 |
+
"""Move safely when Transformers creates meta tensors in Spaces."""
|
| 53 |
+
try:
|
| 54 |
+
return model.to(device)
|
| 55 |
+
except NotImplementedError as exc:
|
| 56 |
+
if "meta tensor" not in str(exc):
|
| 57 |
+
raise
|
| 58 |
+
logger.warning("Model contains meta tensors; using to_empty() before loading checkpoint weights.")
|
| 59 |
+
return model.to_empty(device=device)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _load_model_state(model, state_dict, strict=True):
|
| 63 |
+
try:
|
| 64 |
+
return model.load_state_dict(state_dict, strict=strict, assign=True)
|
| 65 |
+
except TypeError:
|
| 66 |
+
return model.load_state_dict(state_dict, strict=strict)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
def load_meta_acsa(checkpoint_dir: Path = None, meta_encoder: Optional[MetaEncoder] = None,
|
| 70 |
device=None):
|
| 71 |
if checkpoint_dir is None:
|
|
|
|
| 89 |
model = GatedAspectSemanticMetaFusionACSAModel(
|
| 90 |
bert_name=bert_name,
|
| 91 |
meta_in_dim=meta_in_dim,
|
| 92 |
+
)
|
| 93 |
else:
|
| 94 |
+
model = BertMetaFusionACSAModel(bert_name=bert_name, meta_in_dim=meta_in_dim)
|
| 95 |
+
model = _move_model_to_device(model, device)
|
| 96 |
+
_load_model_state(model, ckpt["model_state_dict"], strict=False)
|
| 97 |
model.eval()
|
| 98 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 99 |
return model, tokenizer, meta_encoder, device
|
|
|
|
| 107 |
device = get_device()
|
| 108 |
ckpt = _load_ckpt(checkpoint_dir / "best.pt", device)
|
| 109 |
bert_name = ckpt.get("config", {}).get("bert_name", cfg.BERT_MODEL_NAME)
|
| 110 |
+
model = BertACSAModel(bert_name=bert_name)
|
| 111 |
+
model = _move_model_to_device(model, device)
|
| 112 |
+
_load_model_state(model, ckpt["model_state_dict"])
|
| 113 |
model.eval()
|
| 114 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 115 |
return model, tokenizer, device
|
|
|
|
| 123 |
device = get_device()
|
| 124 |
ckpt = _load_ckpt(checkpoint_dir / "best.pt", device)
|
| 125 |
bert_name = ckpt.get("config", {}).get("bert_name", cfg.BERT_MODEL_NAME)
|
| 126 |
+
model = BertOverallModel(bert_name=bert_name)
|
| 127 |
+
model = _move_model_to_device(model, device)
|
| 128 |
+
_load_model_state(model, ckpt["model_state_dict"])
|
| 129 |
model.eval()
|
| 130 |
tokenizer = AutoTokenizer.from_pretrained(checkpoint_dir / "tokenizer")
|
| 131 |
return model, tokenizer, device
|
|
|
|
| 249 |
# ---------------------------------------------------------------------------
|
| 250 |
|
| 251 |
def aspect_to_overall_sentiment(aspect_preds) -> np.ndarray:
|
| 252 |
+
"""Improved voting rule for aggregating per-aspect 閳?overall.
|
| 253 |
|
| 254 |
Rules (applied per review):
|
| 255 |
1. Count n_pos and n_neg among MENTIONED aspects (skip Not_Mentioned).
|
| 256 |
+
2. If no aspect is mentioned at all 閳?NEUTRAL (conservative default).
|
| 257 |
+
3. If n_neg 閳?2 閳?NEGATIVE (multiple negative aspects = clearly unhappy).
|
| 258 |
+
4. If n_neg == 1 and n_pos == 0 閳?NEGATIVE.
|
| 259 |
+
5. If n_pos 閳?2 and n_neg == 0 閳?POSITIVE.
|
| 260 |
+
6. If n_pos > n_neg (but not all positive) 閳?POSITIVE.
|
| 261 |
+
7. If n_neg > n_pos 閳?NEGATIVE.
|
| 262 |
+
8. Otherwise (tied, or only 1 pos and 0 neg) 閳?NEUTRAL.
|
| 263 |
|
| 264 |
Input (per-aspect): 0=NM, 1=Pos, 2=Neg
|
| 265 |
Output (overall): 0=Neg, 1=Neu, 2=Pos
|
|
|
|
| 274 |
|
| 275 |
out = np.full(preds.shape[0], OVERALL_NEUTRAL, dtype=np.int64)
|
| 276 |
|
| 277 |
+
# No aspects mentioned 閳?Neutral
|
| 278 |
+
# n_neg 閳?2 閳?Negative (strong signal)
|
| 279 |
out[n_neg >= 2] = OVERALL_NEGATIVE
|
| 280 |
+
# n_neg == 1, n_pos == 0 閳?Negative
|
| 281 |
out[(n_neg == 1) & (n_pos == 0)] = OVERALL_NEGATIVE
|
| 282 |
+
# n_pos 閳?2, n_neg == 0 閳?Positive (strong signal)
|
| 283 |
out[(n_pos >= 2) & (n_neg == 0)] = OVERALL_POSITIVE
|
| 284 |
+
# n_pos > n_neg and n_pos 閳?2 閳?Positive
|
| 285 |
out[(n_pos > n_neg) & (n_pos >= 2)] = OVERALL_POSITIVE
|
| 286 |
+
# n_neg > n_pos 閳?Negative (override the 閳? positive if more negatives)
|
| 287 |
out[n_neg > n_pos] = OVERALL_NEGATIVE
|
| 288 |
|
| 289 |
return out
|
|
|
|
| 345 |
for i, aspect in enumerate(cfg.ASPECTS)}
|
| 346 |
|
| 347 |
|
| 348 |
+
|
| 349 |
+
|