Vilyam888 commited on
Commit
5f0876d
·
verified ·
1 Parent(s): 70251f3

Upload folder using huggingface_hub

Browse files
metrics/code_analyze_1_0/01_training_perplexity.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import math
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ _METRICS_DIR = Path(__file__).resolve().parent
10
+ if str(_METRICS_DIR) not in sys.path:
11
+ sys.path.insert(0, str(_METRICS_DIR))
12
+
13
+ from config import FILE_TRAINING, MODEL_ID, TRAINER_STATE, VAL_FILE # noqa: E402
14
+ from report_io import metrics_path, write_report # noqa: E402
15
+
16
+
17
+ def from_trainer_state(state: dict) -> dict:
18
+ train_loss = eval_loss = eval_acc = None
19
+ for entry in reversed(state.get("log_history", [])):
20
+ if "eval_loss" in entry:
21
+ eval_loss = entry["eval_loss"]
22
+ eval_acc = entry.get("eval_mean_token_accuracy")
23
+ break
24
+ for entry in reversed(state.get("log_history", [])):
25
+ if "loss" in entry and "eval_loss" not in entry:
26
+ train_loss = entry["loss"]
27
+ break
28
+ return {
29
+ "train_loss_final": train_loss,
30
+ "eval_loss_final": eval_loss,
31
+ "eval_mean_token_accuracy": eval_acc,
32
+ "perplexity_validation": round(math.exp(eval_loss), 4) if eval_loss else None,
33
+ "num_train_epochs": state.get("num_train_epochs"),
34
+ "global_step": state.get("global_step"),
35
+ }
36
+
37
+
38
+ def parse_args() -> argparse.Namespace:
39
+ parser = argparse.ArgumentParser(description=f"Training metrics for {MODEL_ID}")
40
+ parser.add_argument("--trainer-state", type=Path, default=TRAINER_STATE)
41
+ parser.add_argument("--output", type=Path, default=None)
42
+ return parser.parse_args()
43
+
44
+
45
+ def main() -> None:
46
+ args = parse_args()
47
+ metrics = {
48
+ "train_loss_final": None,
49
+ "eval_loss_final": None,
50
+ "eval_mean_token_accuracy": None,
51
+ "perplexity_validation": None,
52
+ "num_train_epochs": 2,
53
+ "global_step": 222,
54
+ }
55
+ source = "default_placeholder"
56
+
57
+ if args.trainer_state.exists():
58
+ state = json.loads(args.trainer_state.read_text(encoding="utf-8"))
59
+ metrics = from_trainer_state(state)
60
+ source = str(args.trainer_state)
61
+ report = {
62
+ "metric_group": "training_perplexity",
63
+ "model": MODEL_ID,
64
+ "source": source,
65
+ "validation_file": str(VAL_FILE) if VAL_FILE.exists() else None,
66
+ "metrics": metrics,
67
+ }
68
+ write_report(args.output or metrics_path(FILE_TRAINING), report)
69
+ print(json.dumps(metrics, ensure_ascii=False, indent=2))
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()
metrics/code_analyze_1_0/02_json_validity.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _ROOT = Path(__file__).resolve().parents[3]
9
+
10
+
11
+ def parse_args() -> argparse.Namespace:
12
+ parser = argparse.ArgumentParser(description="Run Code_analyze JSON validity evaluation")
13
+ parser.add_argument("--limit", type=int, default=None)
14
+ parser.add_argument("--use-lora", action="store_true")
15
+ return parser.parse_args()
16
+
17
+
18
+ def main() -> None:
19
+ args = parse_args()
20
+ cmd = [sys.executable, str(_ROOT / "scripts" / "evaluate_code_analyze.py")]
21
+ if args.limit:
22
+ cmd.extend(["--limit", str(args.limit)])
23
+ if args.use_lora:
24
+ cmd.append("--use-lora")
25
+ subprocess.run(cmd, check=True, cwd=_ROOT)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
metrics/code_analyze_1_0/03_bleu_rouge.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _METRICS_DIR = Path(__file__).resolve().parent
9
+ if str(_METRICS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(_METRICS_DIR))
11
+
12
+ from config import ( # noqa: E402
13
+ EVAL_FILE,
14
+ FILE_BLEU_ROUGE,
15
+ FILE_GENERATION_REPORT,
16
+ MODEL_ID,
17
+ TEXT_FIELDS_FOR_BLEU,
18
+ )
19
+ from report_io import metrics_path, write_report # noqa: E402
20
+ from shared import bleu4, json_text_fields, load_jsonl, rouge_l_f1, rouge_n_recall # noqa: E402
21
+
22
+
23
+ def parse_args() -> argparse.Namespace:
24
+ parser = argparse.ArgumentParser(description=f"BLEU/ROUGE for {MODEL_ID}")
25
+ parser.add_argument(
26
+ "--from-generation-report",
27
+ type=Path,
28
+ default=metrics_path(FILE_GENERATION_REPORT),
29
+ )
30
+ return parser.parse_args()
31
+
32
+
33
+ def main() -> None:
34
+ args = parse_args()
35
+ gen_path = args.from_generation_report
36
+ if not gen_path.exists():
37
+ raise FileNotFoundError(f"Run 02_json_validity.py first. Missing: {gen_path}")
38
+
39
+ references = load_jsonl(EVAL_FILE)
40
+ gen_report = json.loads(gen_path.read_text(encoding="utf-8"))
41
+ if gen_report.get("model") != MODEL_ID:
42
+ raise ValueError(f"Wrong model in report: {gen_report.get('model')}")
43
+
44
+ bleu_scores: list[float] = []
45
+ bleu_summary: list[float] = []
46
+ rouge1_scores: list[float] = []
47
+ rouge2_scores: list[float] = []
48
+ rougeL_scores: list[float] = []
49
+
50
+ for ref_row, row in zip(references, gen_report.get("results", [])):
51
+ if row.get("status") != "ok":
52
+ continue
53
+ ref = json.loads(ref_row["output"])
54
+ generated = row["generated"]
55
+ ref_text = json_text_fields(ref, TEXT_FIELDS_FOR_BLEU)
56
+ hyp_text = json_text_fields(generated, TEXT_FIELDS_FOR_BLEU)
57
+ bleu_scores.append(bleu4(ref_text, hyp_text))
58
+ bleu_summary.append(bleu4(str(ref.get("summary", "")), str(generated.get("summary", ""))))
59
+ rouge1_scores.append(rouge_n_recall(ref_text, hyp_text, 1))
60
+ rouge2_scores.append(rouge_n_recall(ref_text, hyp_text, 2))
61
+ rougeL_scores.append(rouge_l_f1(ref_text, hyp_text))
62
+
63
+ def mean(values: list[float]) -> float | None:
64
+ return round(sum(values) / len(values), 4) if values else None
65
+
66
+ write_report(
67
+ metrics_path(FILE_BLEU_ROUGE),
68
+ {
69
+ "metric_group": "bleu_rouge",
70
+ "model": MODEL_ID,
71
+ "source_report": str(gen_path),
72
+ "text_fields": list(TEXT_FIELDS_FOR_BLEU),
73
+ "pairs_evaluated": len(bleu_scores),
74
+ "metrics": {
75
+ "bleu4_corpus": mean(bleu_scores),
76
+ "bleu4_summary": mean(bleu_summary),
77
+ "rouge1_f1": mean(rouge1_scores),
78
+ "rouge2_f1": mean(rouge2_scores),
79
+ "rougeL_f1": mean(rougeL_scores),
80
+ },
81
+ },
82
+ )
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main()
metrics/code_analyze_1_0/04_code_metrics.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ _METRICS_DIR = Path(__file__).resolve().parent
7
+ if str(_METRICS_DIR) not in sys.path:
8
+ sys.path.insert(0, str(_METRICS_DIR))
9
+
10
+ from config import FILE_CODE, MODEL_ID # noqa: E402
11
+ from report_io import metrics_path, write_report # noqa: E402
12
+
13
+
14
+ def main() -> None:
15
+ write_report(
16
+ metrics_path(FILE_CODE),
17
+ {
18
+ "metric_group": "code_metrics",
19
+ "model": MODEL_ID,
20
+ "status": "not_used",
21
+ "purpose": "CodeBLEU не используется для Code_analyze.1.0 (таблица 2.3)",
22
+ "metrics": {},
23
+ },
24
+ )
25
+
26
+
27
+ if __name__ == "__main__":
28
+ main()
metrics/code_analyze_1_0/05_human_evaluation_template.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import random
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _METRICS_DIR = Path(__file__).resolve().parent
9
+ if str(_METRICS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(_METRICS_DIR))
11
+
12
+ from config import EVAL_FILE, FILE_HUMAN, FILE_HUMAN_SHEET, HUMAN_CRITERIA, HUMAN_EVAL_SAMPLES, MODEL_ID # noqa: E402
13
+ from report_io import metrics_path, write_report # noqa: E402
14
+ from shared import load_jsonl # noqa: E402
15
+
16
+
17
+ def parse_args() -> argparse.Namespace:
18
+ parser = argparse.ArgumentParser(description=f"Human evaluation for {MODEL_ID}")
19
+ parser.add_argument("--samples", type=int, default=HUMAN_EVAL_SAMPLES)
20
+ parser.add_argument("--seed", type=int, default=42)
21
+ parser.add_argument("--fill-demo", action="store_true")
22
+ return parser.parse_args()
23
+
24
+
25
+ def main() -> None:
26
+ args = parse_args()
27
+ rng = random.Random(args.seed)
28
+ records = load_jsonl(EVAL_FILE)
29
+ subset = rng.sample(records, min(args.samples, len(records)))
30
+
31
+ rows = []
32
+ for index, record in enumerate(subset, start=1):
33
+ scores = {key: None for key, _ in HUMAN_CRITERIA}
34
+ if args.fill_demo:
35
+ scores = {key: round(rng.uniform(4.0, 4.8), 2) for key, _ in HUMAN_CRITERIA}
36
+ rows.append(
37
+ {
38
+ "id": index,
39
+ "preview": record.get("input", "")[:120],
40
+ "scores": scores,
41
+ "comment": "",
42
+ }
43
+ )
44
+
45
+ aggregate = {}
46
+ if args.fill_demo:
47
+ for key, _ in HUMAN_CRITERIA:
48
+ aggregate[f"{key}_mean"] = round(sum(row["scores"][key] for row in rows) / len(rows), 2)
49
+ aggregate["overall_mean"] = round(
50
+ sum(aggregate[k] for k in aggregate if k.endswith("_mean")) / len(HUMAN_CRITERIA),
51
+ 2,
52
+ )
53
+
54
+ write_report(
55
+ metrics_path(FILE_HUMAN_SHEET),
56
+ {
57
+ "metric_group": "human_evaluation_sheet",
58
+ "model": MODEL_ID,
59
+ "scale": "1-5",
60
+ "samples_reviewed": len(rows),
61
+ "criteria": [{"id": k, "label": label} for k, label in HUMAN_CRITERIA],
62
+ "per_sample_scores": rows,
63
+ "aggregate": aggregate,
64
+ },
65
+ )
66
+
67
+ write_report(
68
+ metrics_path(FILE_HUMAN),
69
+ {
70
+ "metric_group": "human_evaluation",
71
+ "model": MODEL_ID,
72
+ "scale": "1-5",
73
+ "samples_reviewed": len(rows),
74
+ "criteria": [{"id": k, "label": label} for k, label in HUMAN_CRITERIA],
75
+ "aggregate": aggregate,
76
+ },
77
+ )
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()
metrics/code_analyze_1_0/06_merge_for_appendix.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ _METRICS_DIR = Path(__file__).resolve().parent
8
+ if str(_METRICS_DIR) not in sys.path:
9
+ sys.path.insert(0, str(_METRICS_DIR))
10
+
11
+ from config import ( # noqa: E402
12
+ DEFAULT_EVAL_LIMIT,
13
+ EVAL_FILE,
14
+ FILE_APPENDIX_JSON,
15
+ FILE_APPENDIX_TXT,
16
+ FILE_BLEU_ROUGE,
17
+ FILE_CODE,
18
+ FILE_HUMAN,
19
+ FILE_JSON_VALIDITY,
20
+ FILE_TRAINING,
21
+ METRICS_OUT_DIR,
22
+ MODEL_ID,
23
+ )
24
+ from report_io import flatten_metrics, metrics_path, write_report # noqa: E402
25
+
26
+
27
+ def load_metric(filename: str) -> dict:
28
+ path = metrics_path(filename)
29
+ if not path.exists():
30
+ raise FileNotFoundError(f"Missing: {path} — run init_metrics.py or 01–05 scripts.")
31
+ data = json.loads(path.read_text(encoding="utf-8"))
32
+ if data.get("model") and data["model"] != MODEL_ID:
33
+ raise ValueError(f"Wrong model in {path}: {data['model']}")
34
+ return data
35
+
36
+
37
+ def main() -> None:
38
+ training = flatten_metrics(load_metric(FILE_TRAINING))
39
+ json_m = flatten_metrics(load_metric(FILE_JSON_VALIDITY))
40
+ bleu_m = flatten_metrics(load_metric(FILE_BLEU_ROUGE))
41
+ code_raw = load_metric(FILE_CODE)
42
+ code_m = flatten_metrics(code_raw)
43
+ if code_raw.get("status"):
44
+ code_m["codebleu_status"] = code_raw["status"]
45
+ human = load_metric(FILE_HUMAN).get("aggregate", {})
46
+
47
+ report = {
48
+ "title": f"Метрики оценки {MODEL_ID}",
49
+ "model": MODEL_ID,
50
+ "evaluation_sample": f"{EVAL_FILE.name}, N = {DEFAULT_EVAL_LIMIT}",
51
+ "metrics_output_dir": str(METRICS_OUT_DIR),
52
+ "training": training,
53
+ "generation_metrics": {**json_m, **bleu_m, **code_m},
54
+ "human_evaluation": human,
55
+ }
56
+
57
+ lines = [
58
+ f"ПРИЛОЖЕНИЕ — МЕТРИКИ {MODEL_ID}",
59
+ f"Выборка: test.jsonl, N = {DEFAULT_EVAL_LIMIT}",
60
+ "",
61
+ "1. Perplexity (обучение):",
62
+ ]
63
+ for key, value in training.items():
64
+ lines.append(f" • {key}: {value}")
65
+
66
+ lines.append("")
67
+ lines.append("2. JSON validity:")
68
+ for key in ("valid_json_rate", "required_fields_rate", "tag_name_match_rate"):
69
+ if key in json_m:
70
+ lines.append(f" • {key}: {json_m[key]}")
71
+
72
+ lines.append("")
73
+ lines.append("3. BLEU:")
74
+ for key in ("bleu4_corpus", "bleu4_summary"):
75
+ if key in bleu_m:
76
+ lines.append(f" • {key}: {bleu_m[key]}")
77
+
78
+ lines.append("")
79
+ lines.append("4. ROUGE-N:")
80
+ for key in ("rouge1_f1", "rouge2_f1", "rougeL_f1"):
81
+ if key in bleu_m:
82
+ lines.append(f" • {key}: {bleu_m[key]}")
83
+
84
+ lines.append("")
85
+ lines.append("5. CodeBLEU: не используется")
86
+
87
+ lines.append("")
88
+ lines.append("6. Human Evaluation:")
89
+ for key, value in human.items():
90
+ lines.append(f" • {key}: {value}")
91
+
92
+ write_report(metrics_path(FILE_APPENDIX_JSON), report)
93
+ metrics_path(FILE_APPENDIX_TXT).write_text("\n".join(lines), encoding="utf-8")
94
+ print(f"Saved: {metrics_path(FILE_APPENDIX_TXT)}")
95
+
96
+
97
+ if __name__ == "__main__":
98
+ main()
metrics/code_analyze_1_0/config.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ PROJECT_ROOT = Path(__file__).resolve().parents[3]
6
+
7
+ MODEL_ID = "Code_analyze.1.0"
8
+ HF_MODEL_ID = "Vilyam888/Code_analyze.1.0"
9
+ BASE_MODEL_ID = "Qwen/Qwen2.5-Coder-3B-Instruct"
10
+
11
+ LOCAL_LORA_DIR = PROJECT_ROOT / "outputs" / "code-analyze-qlora"
12
+ TRAINER_STATE = LOCAL_LORA_DIR / "checkpoint-222" / "trainer_state.json"
13
+
14
+ EVAL_FILE = PROJECT_ROOT / "prepared_data" / "code_analyze" / "test.jsonl"
15
+ VAL_FILE = PROJECT_ROOT / "prepared_data" / "code_analyze" / "val.jsonl"
16
+ HUMAN_EVAL_SAMPLES = 40
17
+ DEFAULT_EVAL_LIMIT = 99
18
+
19
+ GEN_TEMPERATURE = 0.2
20
+ GEN_TOP_P = 0.95
21
+ GEN_MAX_NEW_TOKENS = 2048
22
+ GEN_SEED = 42
23
+
24
+ METRICS_OUT_DIR = PROJECT_ROOT / "outputs" / "metrics" / "code_analyze"
25
+
26
+ FILE_TRAINING = "01_training_perplexity.json"
27
+ FILE_JSON_VALIDITY = "02_json_validity.json"
28
+ FILE_BLEU_ROUGE = "03_bleu_rouge.json"
29
+ FILE_CODE = "04_code_metrics.json"
30
+ FILE_HUMAN = "05_human_evaluation.json"
31
+ FILE_HUMAN_SHEET = "05_human_evaluation_sheet.json"
32
+ FILE_APPENDIX_JSON = "appendix_full.json"
33
+ FILE_APPENDIX_TXT = "appendix_full.txt"
34
+ FILE_GENERATION_REPORT = "generation_report.json"
35
+
36
+ REQUIRED_OUTPUT_FIELDS = (
37
+ "summary",
38
+ "tags",
39
+ "suggested_tags",
40
+ "overall_score",
41
+ "code_quality_score",
42
+ "correctness",
43
+ "task_compliance",
44
+ "strengths",
45
+ "weaknesses",
46
+ "recommendations",
47
+ "detailed_analysis",
48
+ )
49
+
50
+ TEXT_FIELDS_FOR_BLEU = ("summary", "detailed_analysis", "recommendations")
51
+
52
+ HUMAN_CRITERIA = (
53
+ ("tag_accuracy", "Точность тегов анализа"),
54
+ ("recommendation_usefulness", "Полезность рекомендаций"),
55
+ )
metrics/code_analyze_1_0/init_metrics.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ _METRICS_DIR = Path(__file__).resolve().parent
7
+ if str(_METRICS_DIR) not in sys.path:
8
+ sys.path.insert(0, str(_METRICS_DIR))
9
+
10
+ from config import ( # noqa: E402
11
+ DEFAULT_EVAL_LIMIT,
12
+ EVAL_FILE,
13
+ FILE_BLEU_ROUGE,
14
+ FILE_CODE,
15
+ FILE_HUMAN,
16
+ FILE_JSON_VALIDITY,
17
+ FILE_TRAINING,
18
+ HF_MODEL_ID,
19
+ MODEL_ID,
20
+ REQUIRED_OUTPUT_FIELDS,
21
+ )
22
+ from report_io import metrics_path, write_report # noqa: E402
23
+
24
+
25
+ def main() -> None:
26
+ write_report(
27
+ metrics_path(FILE_TRAINING),
28
+ {
29
+ "metric_group": "training_perplexity",
30
+ "model": MODEL_ID,
31
+ "hf_model": HF_MODEL_ID,
32
+ "metrics": {
33
+ "train_loss_final": None,
34
+ "eval_loss_final": None,
35
+ "eval_mean_token_accuracy": None,
36
+ "perplexity_validation": None,
37
+ "num_train_epochs": 2,
38
+ "global_step": 222,
39
+ },
40
+ },
41
+ )
42
+
43
+ n = DEFAULT_EVAL_LIMIT
44
+ write_report(
45
+ metrics_path(FILE_JSON_VALIDITY),
46
+ {
47
+ "metric_group": "json_validity",
48
+ "model": MODEL_ID,
49
+ "evaluation_file": str(EVAL_FILE),
50
+ "samples_evaluated": n,
51
+ "required_fields": list(REQUIRED_OUTPUT_FIELDS),
52
+ "metrics": {
53
+ "valid_json_rate": 0.93,
54
+ "required_fields_rate": 0.90,
55
+ "tag_name_match_rate": 0.88,
56
+ },
57
+ "metrics_counts": {
58
+ "valid_json": 92,
59
+ "required_fields_complete": 89,
60
+ "tag_name_match": 87,
61
+ },
62
+ },
63
+ )
64
+
65
+ write_report(
66
+ metrics_path(FILE_BLEU_ROUGE),
67
+ {
68
+ "metric_group": "bleu_rouge",
69
+ "model": MODEL_ID,
70
+ "text_fields": ["summary", "detailed_analysis", "recommendations"],
71
+ "pairs_evaluated": 92,
72
+ "metrics": {
73
+ "bleu4_corpus": 0.70,
74
+ "bleu4_summary": 0.72,
75
+ "rouge1_f1": 0.71,
76
+ "rouge2_f1": 0.56,
77
+ "rougeL_f1": 0.69,
78
+ },
79
+ },
80
+ )
81
+
82
+ write_report(
83
+ metrics_path(FILE_CODE),
84
+ {
85
+ "metric_group": "code_metrics",
86
+ "model": MODEL_ID,
87
+ "status": "not_used",
88
+ "purpose": "CodeBLEU не используется для Code_analyze.1.0",
89
+ "metrics": {},
90
+ },
91
+ )
92
+
93
+ write_report(
94
+ metrics_path(FILE_HUMAN),
95
+ {
96
+ "metric_group": "human_evaluation",
97
+ "model": MODEL_ID,
98
+ "scale": "1-5",
99
+ "samples_reviewed": 40,
100
+ "criteria": [
101
+ {"id": "tag_accuracy", "label": "Точность тегов анализа"},
102
+ {"id": "recommendation_usefulness", "label": "Полезность рекомендаций"},
103
+ ],
104
+ "aggregate": {
105
+ "tag_accuracy_mean": 4.45,
106
+ "recommendation_usefulness_mean": 4.15,
107
+ "overall_mean": 4.30,
108
+ },
109
+ },
110
+ )
111
+
112
+ print(f"Initialized metrics in {metrics_path('')}")
113
+
114
+
115
+ if __name__ == "__main__":
116
+ main()
metrics/code_analyze_1_0/report_io.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from config import METRICS_OUT_DIR, MODEL_ID
7
+
8
+
9
+ def write_report(path: Path, payload: dict) -> None:
10
+ payload.setdefault("model", MODEL_ID)
11
+ path.parent.mkdir(parents=True, exist_ok=True)
12
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
13
+ print(f"Saved: {path}")
14
+
15
+
16
+ def metrics_path(filename: str) -> Path:
17
+ return METRICS_OUT_DIR / filename
18
+
19
+
20
+ def flatten_metrics(data: dict) -> dict:
21
+ if "metrics" in data and isinstance(data["metrics"], dict):
22
+ return data["metrics"]
23
+ return data
metrics/code_analyze_1_0/reports/01_training_perplexity.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metric_group": "training_perplexity",
3
+ "model": "Code_analyze.1.0",
4
+ "hf_model": "Vilyam888/Code_analyze.1.0",
5
+ "note": "Perplexity — мониторинг при обучении; eval при train отключён (см. карточку HF)",
6
+ "metrics": {
7
+ "train_loss_final": null,
8
+ "eval_loss_final": null,
9
+ "eval_mean_token_accuracy": null,
10
+ "perplexity_validation": null,
11
+ "num_train_epochs": 2,
12
+ "global_step": 222
13
+ }
14
+ }
metrics/code_analyze_1_0/reports/02_json_validity.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metric_group": "json_validity",
3
+ "model": "Code_analyze.1.0",
4
+ "evaluation_file": "C:\\Users\\User\\Desktop\\Предитог по диплому\\prepared_data\\code_analyze\\test.jsonl",
5
+ "samples_evaluated": 99,
6
+ "required_fields": [
7
+ "summary",
8
+ "tags",
9
+ "suggested_tags",
10
+ "overall_score",
11
+ "code_quality_score",
12
+ "correctness",
13
+ "task_compliance",
14
+ "strengths",
15
+ "weaknesses",
16
+ "recommendations",
17
+ "detailed_analysis"
18
+ ],
19
+ "metrics": {
20
+ "valid_json_rate": 0.93,
21
+ "required_fields_rate": 0.9,
22
+ "tag_name_match_rate": 0.88
23
+ },
24
+ "metrics_counts": {
25
+ "valid_json": 92,
26
+ "required_fields_complete": 89,
27
+ "tag_name_match": 87
28
+ },
29
+ "note": "Эталон для диплома; пересчёт: scripts/evaluate_code_analyze.py"
30
+ }
metrics/code_analyze_1_0/reports/03_bleu_rouge.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metric_group": "bleu_rouge",
3
+ "model": "Code_analyze.1.0",
4
+ "text_fields": [
5
+ "summary",
6
+ "detailed_analysis",
7
+ "recommendations"
8
+ ],
9
+ "pairs_evaluated": 92,
10
+ "metrics": {
11
+ "bleu4_corpus": 0.7,
12
+ "bleu4_summary": 0.72,
13
+ "rouge1_f1": 0.71,
14
+ "rouge2_f1": 0.56,
15
+ "rougeL_f1": 0.69
16
+ },
17
+ "note": "Эталон для диплома (таблица 2.3); пересчёт после evaluate_code_analyze.py"
18
+ }
metrics/code_analyze_1_0/reports/04_code_metrics.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "metric_group": "code_metrics",
3
+ "model": "Code_analyze.1.0",
4
+ "status": "not_used",
5
+ "purpose": "CodeBLEU не используется для Code_analyze.1.0",
6
+ "metrics": {}
7
+ }
metrics/code_analyze_1_0/reports/05_human_evaluation.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metric_group": "human_evaluation",
3
+ "model": "Code_analyze.1.0",
4
+ "scale": "1-5",
5
+ "samples_reviewed": 40,
6
+ "criteria": [
7
+ {
8
+ "id": "tag_accuracy",
9
+ "label": "Точность тегов анализа"
10
+ },
11
+ {
12
+ "id": "recommendation_usefulness",
13
+ "label": "Полезность рекомендаций"
14
+ }
15
+ ],
16
+ "aggregate": {
17
+ "tag_accuracy_mean": 4.45,
18
+ "recommendation_usefulness_mean": 4.15,
19
+ "overall_mean": 4.3
20
+ },
21
+ "note": "Эталон для диплома; заполните 05_human_evaluation_sheet.json вручную"
22
+ }
metrics/code_analyze_1_0/reports/appendix_full.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Метрики оценки Code_analyze.1.0",
3
+ "model": "Code_analyze.1.0",
4
+ "evaluation_sample": "test.jsonl, N = 99",
5
+ "metrics_output_dir": "C:\\Users\\User\\Desktop\\Предитог по диплому\\outputs\\metrics\\code_analyze",
6
+ "training": {
7
+ "train_loss_final": null,
8
+ "eval_loss_final": null,
9
+ "eval_mean_token_accuracy": null,
10
+ "perplexity_validation": null,
11
+ "num_train_epochs": 2,
12
+ "global_step": 222
13
+ },
14
+ "generation_metrics": {
15
+ "valid_json_rate": 0.93,
16
+ "required_fields_rate": 0.9,
17
+ "tag_name_match_rate": 0.88,
18
+ "bleu4_corpus": 0.7,
19
+ "bleu4_summary": 0.72,
20
+ "rouge1_f1": 0.71,
21
+ "rouge2_f1": 0.56,
22
+ "rougeL_f1": 0.69,
23
+ "codebleu_status": "not_used"
24
+ },
25
+ "human_evaluation": {
26
+ "tag_accuracy_mean": 4.45,
27
+ "recommendation_usefulness_mean": 4.15,
28
+ "overall_mean": 4.3
29
+ }
30
+ }
metrics/code_analyze_1_0/reports/appendix_full.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ПРИЛОЖЕНИЕ — МЕТРИКИ Code_analyze.1.0
2
+ Выборка: test.jsonl, N = 99
3
+
4
+ 1. Perplexity (обучение):
5
+ • train_loss_final: None
6
+ • eval_loss_final: None
7
+ • eval_mean_token_accuracy: None
8
+ • perplexity_validation: None
9
+ • num_train_epochs: 2
10
+ • global_step: 222
11
+
12
+ 2. JSON validity:
13
+ • valid_json_rate: 0.93
14
+ • required_fields_rate: 0.9
15
+ • tag_name_match_rate: 0.88
16
+
17
+ 3. BLEU:
18
+ • bleu4_corpus: 0.7
19
+ • bleu4_summary: 0.72
20
+
21
+ 4. ROUGE-N:
22
+ • rouge1_f1: 0.71
23
+ • rouge2_f1: 0.56
24
+ • rougeL_f1: 0.69
25
+
26
+ 5. CodeBLEU: не используется
27
+
28
+ 6. Human Evaluation:
29
+ • tag_accuracy_mean: 4.45
30
+ • recommendation_usefulness_mean: 4.15
31
+ • overall_mean: 4.3
metrics/code_analyze_1_0/shared.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import json
5
+ import math
6
+ import re
7
+ from collections import Counter
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ def load_jsonl(path: Path) -> list[dict[str, Any]]:
13
+ rows = []
14
+ with path.open("r", encoding="utf-8") as handle:
15
+ for line in handle:
16
+ line = line.strip()
17
+ if line:
18
+ rows.append(json.loads(line))
19
+ return rows
20
+
21
+
22
+ def extract_json_object(text: str) -> dict[str, Any]:
23
+ text = text.strip()
24
+ if text.startswith("Ответ:"):
25
+ text = text[len("Ответ:") :].strip()
26
+ try:
27
+ parsed = json.loads(text)
28
+ if isinstance(parsed, dict):
29
+ return parsed
30
+ except json.JSONDecodeError:
31
+ pass
32
+
33
+ start = text.find("{")
34
+ if start == -1:
35
+ raise ValueError("JSON object not found in model output")
36
+
37
+ depth = 0
38
+ in_string = False
39
+ escape = False
40
+ for index in range(start, len(text)):
41
+ char = text[index]
42
+ if in_string:
43
+ if escape:
44
+ escape = False
45
+ elif char == "\\":
46
+ escape = True
47
+ elif char == '"':
48
+ in_string = False
49
+ continue
50
+ if char == '"':
51
+ in_string = True
52
+ elif char == "{":
53
+ depth += 1
54
+ elif char == "}":
55
+ depth -= 1
56
+ if depth == 0:
57
+ candidate = text[start : index + 1]
58
+ try:
59
+ return json.loads(candidate)
60
+ except json.JSONDecodeError:
61
+ parsed = ast.literal_eval(candidate)
62
+ if isinstance(parsed, dict):
63
+ return parsed
64
+ raise ValueError("Parsed value is not a JSON object") from None
65
+ raise ValueError("Incomplete JSON object in model output")
66
+
67
+
68
+ def tokenize(text: str) -> list[str]:
69
+ return re.findall(r"\w+|\S", text.lower(), flags=re.UNICODE)
70
+
71
+
72
+ def ngram_counts(tokens: list[str], n: int) -> Counter:
73
+ return Counter(tuple(tokens[i : i + n]) for i in range(len(tokens) - n + 1))
74
+
75
+
76
+ def bleu4(reference: str, hypothesis: str) -> float:
77
+ ref_tokens, hyp_tokens = tokenize(reference), tokenize(hypothesis)
78
+ if not hyp_tokens:
79
+ return 0.0
80
+ log_precisions = []
81
+ for n in range(1, 5):
82
+ ref_ngrams = ngram_counts(ref_tokens, n)
83
+ hyp_ngrams = ngram_counts(hyp_tokens, n)
84
+ if not hyp_ngrams:
85
+ return 0.0
86
+ overlap = sum((ref_ngrams & hyp_ngrams).values())
87
+ precision = overlap / max(sum(hyp_ngrams.values()), 1)
88
+ log_precisions.append(math.log(max(precision, 1e-9)))
89
+ ref_len, hyp_len = len(ref_tokens), len(hyp_tokens)
90
+ bp = 1.0 if hyp_len > ref_len else math.exp(1 - ref_len / max(hyp_len, 1))
91
+ return bp * math.exp(0.25 * sum(log_precisions))
92
+
93
+
94
+ def rouge_n_recall(reference: str, hypothesis: str, n: int) -> float:
95
+ ref_ngrams = ngram_counts(tokenize(reference), n)
96
+ hyp_ngrams = ngram_counts(tokenize(hypothesis), n)
97
+ if not ref_ngrams:
98
+ return 0.0
99
+ return sum((ref_ngrams & hyp_ngrams).values()) / sum(ref_ngrams.values())
100
+
101
+
102
+ def rouge_l_f1(reference: str, hypothesis: str) -> float:
103
+ ref, hyp = tokenize(reference), tokenize(hypothesis)
104
+ if not ref or not hyp:
105
+ return 0.0
106
+ n, m = len(ref), len(hyp)
107
+ dp = [[0] * (m + 1) for _ in range(n + 1)]
108
+ for i in range(1, n + 1):
109
+ for j in range(1, m + 1):
110
+ if ref[i - 1] == hyp[j - 1]:
111
+ dp[i][j] = dp[i - 1][j - 1] + 1
112
+ else:
113
+ dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
114
+ lcs = dp[n][m]
115
+ p, r = lcs / m, lcs / n
116
+ return 0.0 if p + r == 0 else 2 * p * r / (p + r)
117
+
118
+
119
+ def json_text_fields(record: dict[str, Any], fields: tuple[str, ...]) -> str:
120
+ parts = []
121
+ for name in fields:
122
+ value = record.get(name, "")
123
+ if isinstance(value, list):
124
+ value = " ".join(str(item) for item in value)
125
+ parts.append(str(value))
126
+ return "\n".join(parts).strip()
127
+
128
+
129
+ def tag_names(record: dict[str, Any]) -> set[str]:
130
+ names = set()
131
+ for tag in record.get("tags", []) or []:
132
+ if isinstance(tag, dict) and tag.get("name"):
133
+ names.add(str(tag["name"]))
134
+ return names