Image-Text-to-Text
PEFT
Safetensors
qwen3-vl
vision-language
portrait-aesthetics
aesthetics-evaluation
lora
llama-factory
Instructions to use Artoria0429/code_portrait_track_1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use Artoria0429/code_portrait_track_1 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
| import argparse | |
| import copy | |
| import json | |
| import os | |
| import re | |
| from collections import Counter | |
| from glob import glob | |
| from typing import Any, Dict, List, Optional, Tuple | |
| DEFAULT_TEMPLATE_PATH = "outputs/predictions/track_1_test.json" | |
| DEFAULT_PRED_PATH = "outputs/predictions/predict_ckpt2660/generated_predictions.jsonl" | |
| DEFAULT_OUTPUT_PATH = "outputs/submissions/answers/track_1_test.json" | |
| def load_json(path: str) -> Any: | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| def load_jsonl(path: str) -> List[Dict[str, Any]]: | |
| rows: List[Dict[str, Any]] = [] | |
| with open(path, "r", encoding="utf-8") as f: | |
| for line_no, line in enumerate(f, start=1): | |
| raw = line.strip() | |
| if not raw: | |
| continue | |
| try: | |
| rows.append(json.loads(raw)) | |
| except Exception: | |
| print(f"[WARN] skip invalid jsonl line: {line_no}") | |
| return rows | |
| def normalize_level(level: Any) -> Optional[str]: | |
| if level is None: | |
| return None | |
| s = str(level).strip() | |
| if not s: | |
| return None | |
| m = { | |
| "poor": "Poor", | |
| "medium": "Medium", | |
| "good": "Good", | |
| "a": "A", | |
| "b": "B", | |
| "c": "C", | |
| } | |
| return m.get(s.lower(), s) | |
| def score_to_submission_level(score: float, low_threshold: float = 5.0, high_threshold: float = 7.0) -> str: | |
| # 比赛映射:A=Poor(0~5), B=Medium(5~7), C=Good(7~10) | |
| if score < low_threshold: | |
| return "A" | |
| if score < high_threshold: | |
| return "B" | |
| return "C" | |
| def to_submission_level(level: Any) -> Optional[str]: | |
| """ | |
| Submission mapping required by Track-1 template: | |
| A -> Poor, B -> Medium, C -> Good | |
| """ | |
| norm = normalize_level(level) | |
| if norm is None: | |
| return None | |
| m = { | |
| "Poor": "A", | |
| "Medium": "B", | |
| "Good": "C", | |
| "A": "A", | |
| "B": "B", | |
| "C": "C", | |
| } | |
| return m.get(norm) | |
| def normalize_prediction_text(pred_row: Dict[str, Any]) -> str: | |
| # 兼容不同推理后端字段名。 | |
| for k in ("predict", "response", "output", "text", "generation"): | |
| if k in pred_row: | |
| return str(pred_row.get(k, "")) | |
| return str(pred_row) | |
| def extract_level_from_text(pred_text: str, crit_key: str) -> Optional[str]: | |
| pattern = rf'"{re.escape(crit_key)}"\s*:\s*\{{.*?"level"\s*:\s*"([^"]+)"' | |
| m = re.search(pattern, pred_text, flags=re.IGNORECASE | re.DOTALL) | |
| if not m: | |
| return None | |
| return to_submission_level(m.group(1)) | |
| def try_parse_predict_json(text: str) -> Optional[Dict[str, Any]]: | |
| text = str(text or "").strip() | |
| if not text: | |
| return None | |
| # 1) 直接解析 | |
| try: | |
| obj = json.loads(text) | |
| if isinstance(obj, dict): | |
| return obj | |
| except Exception: | |
| pass | |
| # 2) 尝试提取从首个 { 到最后一个 } 的片段 | |
| left = text.find("{") | |
| right = text.rfind("}") | |
| if left != -1 and right != -1 and left < right: | |
| snippet = text[left:right + 1] | |
| try: | |
| obj = json.loads(snippet) | |
| if isinstance(obj, dict): | |
| return obj | |
| except Exception: | |
| pass | |
| return None | |
| def extract_answer(pred_obj: Optional[Dict[str, Any]], pred_text: str) -> Optional[str]: | |
| if pred_obj is not None: | |
| answer = str(pred_obj.get("answer", "")).strip().upper() | |
| if answer in {"A", "B", "C", "D"}: | |
| return answer | |
| m = re.search(r'"answer"\s*:\s*"([A-D])"', pred_text, re.IGNORECASE) | |
| if m: | |
| return m.group(1).upper() | |
| # 兼容 QA-only 推理:模型可能只输出单个字母(如 "C")。 | |
| raw = str(pred_text or "").strip().upper() | |
| if raw: | |
| # 情况1:整行仅有一个候选字母(允许尾随标点)。 | |
| m = re.match(r"^\s*([A-D])(?:[\.\)\]::]|\s)*$", raw) | |
| if m: | |
| return m.group(1) | |
| # 情况2:短文本中只出现唯一一个 A/B/C/D,且不包含常见 JSON/选项结构。 | |
| if len(raw) <= 12 and ("{" not in raw) and ("\"" not in raw): | |
| hits = re.findall(r"[A-D]", raw) | |
| if len(hits) == 1: | |
| return hits[0] | |
| return None | |
| def extract_total_score(pred_obj: Optional[Dict[str, Any]], pred_text: str) -> Optional[float]: | |
| val: Optional[float] = None | |
| if pred_obj is not None and "total_score" in pred_obj: | |
| try: | |
| val = float(pred_obj["total_score"]) | |
| except Exception: | |
| val = None | |
| if val is None: | |
| m = re.search(r'"total_score"\s*:\s*([0-9]+(?:\.[0-9]+)?)', pred_text) | |
| if m: | |
| val = float(m.group(1)) | |
| if val is None: | |
| return None | |
| return max(0.0, min(100.0, float(val))) | |
| def merge_total_scores(total_votes: List[float], mode: str) -> Optional[int]: | |
| if not total_votes: | |
| return None | |
| vals = [float(v) for v in total_votes] | |
| if mode == "trim_mean" and len(vals) >= 3: | |
| vals = sorted(vals)[1:-1] | |
| merged = sum(vals) / len(vals) | |
| return max(0, min(100, int(round(merged)))) | |
| def parse_score_from_text(pred_text: str, crit_key: str) -> Optional[float]: | |
| pattern = rf'"{re.escape(crit_key)}"\s*:\s*\{{.*?"score"\s*:\s*([0-9]+(?:\.[0-9]+)?)' | |
| m = re.search(pattern, pred_text, flags=re.IGNORECASE | re.DOTALL) | |
| if not m: | |
| return None | |
| try: | |
| return float(m.group(1)) | |
| except Exception: | |
| return None | |
| def extract_one_criteria_level_and_score( | |
| crit_key: str, | |
| pred_obj: Optional[Dict[str, Any]], | |
| pred_text: str, | |
| ) -> Tuple[Optional[str], Optional[float]]: | |
| score: Optional[float] = None | |
| level: Optional[str] = None | |
| if isinstance(pred_obj, dict): | |
| src = pred_obj.get("criteria", {}) | |
| if isinstance(src, dict) and crit_key in src: | |
| value = src.get(crit_key) | |
| if isinstance(value, dict): | |
| if "score" in value: | |
| try: | |
| score = float(value["score"]) | |
| except Exception: | |
| score = None | |
| level = to_submission_level(value.get("level")) | |
| else: | |
| level = to_submission_level(value) | |
| if score is None: | |
| score = parse_score_from_text(pred_text, crit_key) | |
| if level is None: | |
| level = extract_level_from_text(pred_text, crit_key) | |
| if level not in {"A", "B", "C"}: | |
| level = None | |
| return level, score | |
| def choose_majority_level( | |
| levels: List[str], | |
| scores: List[float], | |
| fallback_level: Optional[str], | |
| low_threshold: float, | |
| high_threshold: float, | |
| ) -> str: | |
| if levels: | |
| cnt = Counter(levels) | |
| top_n = max(cnt.values()) | |
| top_levels = sorted([k for k, v in cnt.items() if v == top_n]) | |
| if len(top_levels) == 1: | |
| return top_levels[0] | |
| # 平票时,用多次预测的均值 score 判定等级。 | |
| if scores: | |
| mean_score = sum(scores) / len(scores) | |
| return score_to_submission_level(mean_score, low_threshold=low_threshold, high_threshold=high_threshold) | |
| if fallback_level in {"A", "B", "C"}: | |
| return fallback_level | |
| return "B" | |
| def parse_weights(raw_weights: Optional[List[float]], n_models: int) -> List[float]: | |
| # 默认等权;若传入权重则要求和预测文件数一致。 | |
| if raw_weights is None: | |
| return [1.0] * n_models | |
| if len(raw_weights) != n_models: | |
| raise ValueError( | |
| f"--weights length ({len(raw_weights)}) must equal number of prediction files ({n_models})." | |
| ) | |
| for w in raw_weights: | |
| if w < 0: | |
| raise ValueError("weights must be non-negative.") | |
| # 全零没有意义,回退等权。 | |
| if sum(raw_weights) == 0: | |
| return [1.0] * n_models | |
| return raw_weights | |
| def load_thresholds( | |
| thresholds_json: str, | |
| ) -> Tuple[Dict[str, Dict[str, float]], Dict[str, float]]: | |
| """ | |
| Accepts JSON in either format: | |
| 1) {"criteria": {"Color Harmony": {"low": 4.9, "high": 7.1}}, "default": {"low":5,"high":7}} | |
| 2) {"Color Harmony": {"low": 4.9, "high": 7.1}, ...} | |
| """ | |
| default = {"low": 5.0, "high": 7.0} | |
| per_criteria: Dict[str, Dict[str, float]] = {} | |
| if not thresholds_json: | |
| return per_criteria, default | |
| if not os.path.exists(thresholds_json): | |
| print(f"[WARN] thresholds file not found: {thresholds_json}, fallback to default 5/7") | |
| return per_criteria, default | |
| obj = load_json(thresholds_json) | |
| if not isinstance(obj, dict): | |
| print(f"[WARN] invalid thresholds json format: {thresholds_json}, fallback to default 5/7") | |
| return per_criteria, default | |
| if "default" in obj and isinstance(obj.get("default"), dict): | |
| d = obj["default"] | |
| low = d.get("low", 5.0) | |
| high = d.get("high", 7.0) | |
| try: | |
| low_f = float(low) | |
| high_f = float(high) | |
| if low_f < high_f: | |
| default = {"low": low_f, "high": high_f} | |
| except Exception: | |
| pass | |
| src = obj.get("criteria") if isinstance(obj.get("criteria"), dict) else obj | |
| if isinstance(src, dict): | |
| for k, v in src.items(): | |
| if not isinstance(v, dict): | |
| continue | |
| if "low" not in v or "high" not in v: | |
| continue | |
| try: | |
| low = float(v["low"]) | |
| high = float(v["high"]) | |
| except Exception: | |
| continue | |
| if low < high: | |
| per_criteria[str(k)] = {"low": low, "high": high} | |
| return per_criteria, default | |
| def resolve_best_index(best_index: int, weights: List[float]) -> int: | |
| # best_index=-1 表示自动选择权重最高的模型作为平票时的优先模型。 | |
| if best_index >= 0: | |
| if best_index >= len(weights): | |
| raise ValueError(f"--best_index out of range: {best_index}, num_models={len(weights)}") | |
| return best_index | |
| return max(range(len(weights)), key=lambda i: weights[i]) | |
| def extract_criteria_voting( | |
| template_item: Dict[str, Any], | |
| pred_objs: List[Optional[Dict[str, Any]]], | |
| pred_texts: List[str], | |
| per_criteria_thresholds: Dict[str, Dict[str, float]], | |
| default_thresholds: Dict[str, float], | |
| ) -> Dict[str, Dict[str, str]]: | |
| out: Dict[str, Dict[str, str]] = {} | |
| for crit_key, crit_val in template_item.get("criteria", {}).items(): | |
| thresholds = per_criteria_thresholds.get(crit_key, default_thresholds) | |
| low = float(thresholds.get("low", 5.0)) | |
| high = float(thresholds.get("high", 7.0)) | |
| if not (low < high): | |
| low, high = 5.0, 7.0 | |
| level_votes: List[str] = [] | |
| score_votes: List[float] = [] | |
| for obj, text in zip(pred_objs, pred_texts): | |
| level, score = extract_one_criteria_level_and_score(crit_key, obj, text) | |
| if level is not None: | |
| level_votes.append(level) | |
| if score is not None: | |
| score_votes.append(score) | |
| prev = str(crit_val.get("level", "")).strip() if isinstance(crit_val, dict) else "" | |
| final_level = choose_majority_level( | |
| level_votes, | |
| score_votes, | |
| prev, | |
| low_threshold=low, | |
| high_threshold=high, | |
| ) | |
| out[crit_key] = {"level": final_level} | |
| return out | |
| def pick_default_predictions_path() -> str: | |
| candidates = [ | |
| "outputs/predictions/predict_ckpt2660/generated_predictions.jsonl", | |
| "outputs/predictions/generated_predictions.jsonl", | |
| ] | |
| # 自动兜底:在预测目录里找最近一次 generated_predictions.jsonl。 | |
| dynamic = sorted( | |
| glob("outputs/predictions/**/generated_predictions.jsonl", recursive=True), | |
| key=lambda x: os.path.getmtime(x), | |
| reverse=True, | |
| ) | |
| candidates = dynamic + candidates | |
| for p in candidates: | |
| if os.path.exists(p): | |
| return p | |
| return DEFAULT_PRED_PATH | |
| def choose_weighted_answer( | |
| votes_by_model: List[Optional[str]], | |
| weights: List[float], | |
| best_index: int, | |
| tie_break_answer: Optional[str], | |
| ) -> str: | |
| """ | |
| 更合理的答案融合策略: | |
| 1) 加权投票(按各变体可靠性权重) | |
| 2) 若平票且提供 tie-break 结果,则优先用 tie-break | |
| 3) 若仍平票,采用最佳变体(best_index)在平票选项中的答案 | |
| 4) 最后才做稳定兜底(字母序) | |
| """ | |
| label_scores = {"A": 0.0, "B": 0.0, "C": 0.0, "D": 0.0} | |
| for i, ans in enumerate(votes_by_model): | |
| if ans in label_scores: | |
| label_scores[ans] += weights[i] | |
| max_score = max(label_scores.values()) | |
| if max_score <= 0: | |
| return "A" | |
| tied = sorted([k for k, v in label_scores.items() if v == max_score]) | |
| if len(tied) == 1: | |
| return tied[0] | |
| if tie_break_answer in tied: | |
| return tie_break_answer # 用专门 tie-break 结果判平票 | |
| best_vote = votes_by_model[best_index] | |
| if best_vote in tied: | |
| return str(best_vote) | |
| return tied[0] | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--template_json", type=str, default=DEFAULT_TEMPLATE_PATH) | |
| parser.add_argument( | |
| "--predictions_jsonl", | |
| type=str, | |
| nargs="+", | |
| default=None, | |
| help="One or more generated_predictions.jsonl paths for voting.", | |
| ) | |
| parser.add_argument( | |
| "--weights", | |
| type=float, | |
| nargs="+", | |
| default=None, | |
| help="Optional weights for prediction files (same length as --predictions_jsonl).", | |
| ) | |
| parser.add_argument( | |
| "--best_index", | |
| type=int, | |
| default=-1, | |
| help="Best model index for tie fallback. -1 means auto argmax(weights).", | |
| ) | |
| parser.add_argument( | |
| "--tie_break_jsonl", | |
| type=str, | |
| default="", | |
| help="Optional tie-break predictions file used only when weighted vote ties.", | |
| ) | |
| parser.add_argument( | |
| "--thresholds_json", | |
| type=str, | |
| default="", | |
| help="Optional per-criterion thresholds json for score->A/B/C mapping.", | |
| ) | |
| parser.add_argument( | |
| "--total_score_fusion", | |
| type=str, | |
| default="mean", | |
| choices=["mean", "trim_mean"], | |
| help="Fusion mode for total_score across multi-prompt predictions.", | |
| ) | |
| parser.add_argument("--output_json", type=str, default=DEFAULT_OUTPUT_PATH) | |
| args = parser.parse_args() | |
| pred_paths = args.predictions_jsonl if args.predictions_jsonl else [pick_default_predictions_path()] | |
| pred_paths = [p for p in pred_paths if str(p).strip()] | |
| if not pred_paths: | |
| raise ValueError("No predictions_jsonl provided or discovered.") | |
| template_data = load_json(args.template_json) | |
| pred_sets = [load_jsonl(p) for p in pred_paths] | |
| weights = parse_weights(args.weights, len(pred_sets)) | |
| best_index = resolve_best_index(args.best_index, weights) | |
| per_criteria_thresholds, default_thresholds = load_thresholds(args.thresholds_json) | |
| tie_break_rows: List[Dict[str, Any]] = [] | |
| if args.tie_break_jsonl: | |
| tie_break_rows = load_jsonl(args.tie_break_jsonl) | |
| if not isinstance(template_data, list): | |
| raise ValueError("template_json must be a list.") | |
| print(f"[INFO] template items: {len(template_data)}") | |
| for p, rows in zip(pred_paths, pred_sets): | |
| print(f"[INFO] prediction rows: {len(rows)} ({p})") | |
| print(f"[INFO] answer weights: {weights}") | |
| print(f"[INFO] answer best_index: {best_index}") | |
| print(f"[INFO] criteria thresholds default: low={default_thresholds['low']}, high={default_thresholds['high']}") | |
| if per_criteria_thresholds: | |
| print(f"[INFO] criteria thresholds loaded: {len(per_criteria_thresholds)}") | |
| if args.tie_break_jsonl: | |
| print(f"[INFO] tie_break rows: {len(tie_break_rows)} ({args.tie_break_jsonl})") | |
| final_submission: List[Dict[str, Any]] = [] | |
| parsed_ok = 0 | |
| filled = 0 | |
| for i, item in enumerate(template_data): | |
| new_item = copy.deepcopy(item) | |
| # 收集每次预测在第 i 条样本上的结果。 | |
| row_texts: List[str] = [] | |
| row_objs: List[Optional[Dict[str, Any]]] = [] | |
| answer_votes_by_model: List[Optional[str]] = [] | |
| total_votes: List[int] = [] | |
| for rows in pred_sets: | |
| if i >= len(rows): | |
| answer_votes_by_model.append(None) | |
| continue | |
| pred_text = normalize_prediction_text(rows[i]) | |
| pred_obj = try_parse_predict_json(pred_text) | |
| if pred_obj is not None: | |
| parsed_ok += 1 | |
| row_texts.append(pred_text) | |
| row_objs.append(pred_obj) | |
| ans = extract_answer(pred_obj, pred_text) | |
| answer_votes_by_model.append(ans) | |
| ts = extract_total_score(pred_obj, pred_text) | |
| if ts is not None: | |
| total_votes.append(ts) | |
| if not row_texts: | |
| final_submission.append(new_item) | |
| continue | |
| new_item["criteria"] = extract_criteria_voting( | |
| new_item, | |
| row_objs, | |
| row_texts, | |
| per_criteria_thresholds=per_criteria_thresholds, | |
| default_thresholds=default_thresholds, | |
| ) | |
| # total_score 用多次预测均值,减少单次抖动。 | |
| merged_total = merge_total_scores(total_votes, args.total_score_fusion) | |
| if merged_total is not None: | |
| new_item["total_score"] = max(0, min(100, merged_total)) | |
| tie_break_answer: Optional[str] = None | |
| if i < len(tie_break_rows): | |
| tb_text = normalize_prediction_text(tie_break_rows[i]) | |
| tb_obj = try_parse_predict_json(tb_text) | |
| tie_break_answer = extract_answer(tb_obj, tb_text) | |
| new_item["answer"] = choose_weighted_answer( | |
| votes_by_model=answer_votes_by_model, | |
| weights=weights, | |
| best_index=best_index, | |
| tie_break_answer=tie_break_answer, | |
| ) | |
| final_submission.append(new_item) | |
| filled += 1 | |
| os.makedirs(os.path.dirname(args.output_json), exist_ok=True) | |
| with open(args.output_json, "w", encoding="utf-8") as f: | |
| json.dump(final_submission, f, ensure_ascii=False, indent=2) | |
| parsed_total = filled * len(pred_paths) | |
| print(f"[INFO] parsed predict json ok: {parsed_ok}/{parsed_total}") | |
| print(f"[INFO] saved submission: {args.output_json}") | |
| if __name__ == "__main__": | |
| main() | |