Add portable Stage 6 native-bbox recovery bundle
Browse files
training-code/stage6-native-v2/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Bina 0.2 Stage 6 portable recovery bundle
|
| 2 |
+
|
| 3 |
+
This bundle resumes the native-bbox corrective lane on an intact or replacement
|
| 4 |
+
eight-GPU cluster.
|
| 5 |
+
|
| 6 |
+
## Immutable inputs
|
| 7 |
+
|
| 8 |
+
- Hugging Face artifact:
|
| 9 |
+
`Reza2kn/Bina-0.2-RizehPizeh/training-data/native-replay-v1.tar`
|
| 10 |
+
- Archive SHA-256:
|
| 11 |
+
`2cb4b0507a1f6fc1fcf6619b679de5fd35a52e2ee8f55c57ce5ebf00c52ffc40`
|
| 12 |
+
- Accepted replay:
|
| 13 |
+
7,041 hash-matched pages and 206,917 samples
|
| 14 |
+
- Fixed compositor:
|
| 15 |
+
6,984 pages and 205,289 samples
|
| 16 |
+
- Archived compositor fallback:
|
| 17 |
+
57 pages and 1,628 samples
|
| 18 |
+
- Excluded:
|
| 19 |
+
485 pages that matched neither known production compositor
|
| 20 |
+
|
| 21 |
+
## Cluster prerequisites
|
| 22 |
+
|
| 23 |
+
The cluster root is `/workspace/bina02-ppocr` and must contain:
|
| 24 |
+
|
| 25 |
+
- `PaddleOCR/`
|
| 26 |
+
- `venv/`
|
| 27 |
+
- `bina02_stage5_hardforms.yml`
|
| 28 |
+
- `watch_bina02_stage4_eval.py`
|
| 29 |
+
- `run/train-stage4-clean2m-lr3e6/best_accuracy`
|
| 30 |
+
- `data/corpus-v4/balanced`
|
| 31 |
+
|
| 32 |
+
The existing RTL label-encoding patch must remain active in PaddleOCR.
|
| 33 |
+
|
| 34 |
+
## Recovery sequence
|
| 35 |
+
|
| 36 |
+
1. Copy the three executable files in this directory to
|
| 37 |
+
`/workspace/bina02-ppocr`.
|
| 38 |
+
2. Obtain a fresh final CDN URL for the Hugging Face replay archive.
|
| 39 |
+
3. Run `fetch_native_replay_v2.sh "$SIGNED_CDN_URL"` under supervisor.
|
| 40 |
+
4. Run `run_bina02_stage6_v2.sh` under supervisor.
|
| 41 |
+
5. Export the best accepted checkpoint.
|
| 42 |
+
6. Run the exact cached-Surya Persian OCR Triple Threat benchmark.
|
| 43 |
+
7. Publish only if printed S3 and handwriting S3 are both below 10 with zero
|
| 44 |
+
missing predictions.
|
| 45 |
+
|
| 46 |
+
The training mix is approximately 30 percent exact native forms, 35 percent
|
| 47 |
+
general handwriting replay, and 35 percent printed replay. It uses a 1e-6
|
| 48 |
+
learning rate, frozen batch normalization, trainable backbone blocks 4 and 5
|
| 49 |
+
plus CTC layers, aspect-ratio-preserving resize and padding, and no geometric
|
| 50 |
+
`RecAug`.
|
training-code/stage6-native-v2/build_bina02_stage6_native_mix_v2.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import copy
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import lmdb
|
| 10 |
+
import yaml
|
| 11 |
+
|
| 12 |
+
ROOT = Path("/workspace/bina02-ppocr")
|
| 13 |
+
TRANSFER = Path(
|
| 14 |
+
os.environ.get(
|
| 15 |
+
"BINA_NATIVE_REPLAY_TRANSFER",
|
| 16 |
+
ROOT / "data/corpus-v5-native-replay/replay-transfer-v2",
|
| 17 |
+
)
|
| 18 |
+
)
|
| 19 |
+
FIXED_ROOT = TRANSFER / "full-fixed-v2"
|
| 20 |
+
STALE_ROOT = TRANSFER / "full-stale-fallback-v1"
|
| 21 |
+
FIXED = FIXED_ROOT / "lmdb"
|
| 22 |
+
STALE = STALE_ROOT / "lmdb"
|
| 23 |
+
BALANCED = ROOT / "data/corpus-v4/balanced"
|
| 24 |
+
MIX = ROOT / "data/corpus-v5-native-replay/stage6-mix-v2"
|
| 25 |
+
CONFIG = ROOT / "bina02_stage6_nativeforms_v2.yml"
|
| 26 |
+
COMBINED_EVAL_CONFIG = ROOT / "bina02_stage6_combined_eval_stage4_v2.yml"
|
| 27 |
+
NATIVE_EVAL_CONFIG = ROOT / "bina02_stage6_native_eval_stage4_v2.yml"
|
| 28 |
+
RUN = ROOT / "run/train-stage6-nativeforms-v2-30-35-35-lr1e6"
|
| 29 |
+
STAGE4 = ROOT / "run/train-stage4-clean2m-lr3e6/best_accuracy"
|
| 30 |
+
|
| 31 |
+
EXPECTED_SUMMARIES = {
|
| 32 |
+
FIXED_ROOT: {
|
| 33 |
+
"pages": 6984,
|
| 34 |
+
"samples": 205289,
|
| 35 |
+
"train_samples": 198483,
|
| 36 |
+
"eval_samples": 6806,
|
| 37 |
+
"rejected": 0,
|
| 38 |
+
"mismatched": 542,
|
| 39 |
+
},
|
| 40 |
+
STALE_ROOT: {
|
| 41 |
+
"pages": 57,
|
| 42 |
+
"samples": 1628,
|
| 43 |
+
"train_samples": 1628,
|
| 44 |
+
"eval_samples": 0,
|
| 45 |
+
"rejected": 0,
|
| 46 |
+
"mismatched": 485,
|
| 47 |
+
},
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def verify_summary(root: Path, expected: dict[str, int]) -> None:
|
| 52 |
+
summary_path = root / "SUMMARY.json"
|
| 53 |
+
if not summary_path.exists():
|
| 54 |
+
raise SystemExit(f"missing replay summary: {summary_path}")
|
| 55 |
+
summary = json.loads(summary_path.read_text())
|
| 56 |
+
actual = {key: summary.get(key) for key in expected}
|
| 57 |
+
if actual != expected:
|
| 58 |
+
raise SystemExit(
|
| 59 |
+
f"replay summary mismatch for {root}: expected={expected}, actual={actual}"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def leaves(root: Path) -> list[Path]:
|
| 64 |
+
return sorted({path.parent for path in root.rglob("data.mdb")})
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def count(path: Path) -> int:
|
| 68 |
+
env = lmdb.open(
|
| 69 |
+
str(path),
|
| 70 |
+
readonly=True,
|
| 71 |
+
lock=False,
|
| 72 |
+
readahead=False,
|
| 73 |
+
max_readers=1,
|
| 74 |
+
)
|
| 75 |
+
with env.begin() as txn:
|
| 76 |
+
value = txn.get(b"num-samples")
|
| 77 |
+
result = int(value) if value else 0
|
| 78 |
+
env.close()
|
| 79 |
+
return result
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def stable(paths: list[Path], tag: str) -> list[Path]:
|
| 83 |
+
return sorted(
|
| 84 |
+
paths,
|
| 85 |
+
key=lambda path: hashlib.sha256((tag + str(path)).encode()).hexdigest(),
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def choose(paths: list[Path], target: int, tag: str) -> list[tuple[Path, int]]:
|
| 90 |
+
selected: list[tuple[Path, int]] = []
|
| 91 |
+
total = 0
|
| 92 |
+
for path in stable(paths, tag):
|
| 93 |
+
samples = count(path)
|
| 94 |
+
if samples <= 0:
|
| 95 |
+
continue
|
| 96 |
+
selected.append((path, samples))
|
| 97 |
+
total += samples
|
| 98 |
+
if total >= target:
|
| 99 |
+
break
|
| 100 |
+
if total < target:
|
| 101 |
+
raise SystemExit(f"{tag} has only {total} samples; target is {target}")
|
| 102 |
+
return selected
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def link_group(
|
| 106 |
+
group: str,
|
| 107 |
+
entries: list[tuple[Path, int]],
|
| 108 |
+
) -> list[dict[str, object]]:
|
| 109 |
+
base = MIX / group
|
| 110 |
+
base.mkdir(parents=True, exist_ok=False)
|
| 111 |
+
rows: list[dict[str, object]] = []
|
| 112 |
+
for index, (source, samples) in enumerate(entries):
|
| 113 |
+
destination = base / f"{index:04d}-{source.parent.name}-{source.name}"
|
| 114 |
+
destination.mkdir()
|
| 115 |
+
os.link(source / "data.mdb", destination / "data.mdb")
|
| 116 |
+
rows.append(
|
| 117 |
+
{
|
| 118 |
+
"source": str(source),
|
| 119 |
+
"linked": str(destination),
|
| 120 |
+
"samples": samples,
|
| 121 |
+
}
|
| 122 |
+
)
|
| 123 |
+
return rows
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if MIX.exists():
|
| 127 |
+
raise SystemExit(f"refusing to overwrite existing mix: {MIX}")
|
| 128 |
+
for replay_root, expected in EXPECTED_SUMMARIES.items():
|
| 129 |
+
verify_summary(replay_root, expected)
|
| 130 |
+
for required in (FIXED, STALE, BALANCED, STAGE4):
|
| 131 |
+
if not required.exists():
|
| 132 |
+
raise SystemExit(f"missing required input: {required}")
|
| 133 |
+
|
| 134 |
+
native_train = []
|
| 135 |
+
for path in leaves(FIXED / "train") + leaves(STALE / "train"):
|
| 136 |
+
samples = count(path)
|
| 137 |
+
if samples > 0:
|
| 138 |
+
native_train.append((path, samples))
|
| 139 |
+
|
| 140 |
+
native_train_total = sum(samples for _, samples in native_train)
|
| 141 |
+
if native_train_total != 200111:
|
| 142 |
+
raise SystemExit(
|
| 143 |
+
f"native train count mismatch: expected 200111, got {native_train_total}"
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
replay_target = (native_train_total * 35 + 29) // 30
|
| 147 |
+
handwriting_replay = choose(
|
| 148 |
+
leaves(BALANCED / "train/00_handwriting"),
|
| 149 |
+
replay_target,
|
| 150 |
+
"stage6-v2-handwriting",
|
| 151 |
+
)
|
| 152 |
+
printed_replay = choose(
|
| 153 |
+
leaves(BALANCED / "train/01_printed"),
|
| 154 |
+
replay_target,
|
| 155 |
+
"stage6-v2-printed",
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
train_rows = {
|
| 159 |
+
"native_forms": link_group("train/00_nativeforms", native_train),
|
| 160 |
+
"handwriting_replay": link_group(
|
| 161 |
+
"train/01_handwriting_replay",
|
| 162 |
+
handwriting_replay,
|
| 163 |
+
),
|
| 164 |
+
"printed_replay": link_group(
|
| 165 |
+
"train/02_printed_replay",
|
| 166 |
+
printed_replay,
|
| 167 |
+
),
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
native_eval = []
|
| 171 |
+
for path in leaves(FIXED / "eval") + leaves(STALE / "eval"):
|
| 172 |
+
samples = count(path)
|
| 173 |
+
if samples > 0:
|
| 174 |
+
native_eval.append((path, samples))
|
| 175 |
+
handwriting_eval = [
|
| 176 |
+
(path, count(path))
|
| 177 |
+
for path in leaves(BALANCED / "eval/00_handwriting")
|
| 178 |
+
if count(path) > 0
|
| 179 |
+
]
|
| 180 |
+
printed_eval = [
|
| 181 |
+
(path, count(path))
|
| 182 |
+
for path in leaves(BALANCED / "eval/01_printed")
|
| 183 |
+
if count(path) > 0
|
| 184 |
+
]
|
| 185 |
+
|
| 186 |
+
eval_rows = {
|
| 187 |
+
"native_forms": link_group("eval/00_nativeforms", native_eval),
|
| 188 |
+
"handwriting": link_group("eval/01_handwriting", handwriting_eval),
|
| 189 |
+
"printed": link_group("eval/02_printed", printed_eval),
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
train_counts = {
|
| 193 |
+
key: sum(row["samples"] for row in rows)
|
| 194 |
+
for key, rows in train_rows.items()
|
| 195 |
+
}
|
| 196 |
+
eval_counts = {
|
| 197 |
+
key: sum(row["samples"] for row in rows)
|
| 198 |
+
for key, rows in eval_rows.items()
|
| 199 |
+
}
|
| 200 |
+
if eval_counts["native_forms"] != 6806:
|
| 201 |
+
raise SystemExit(
|
| 202 |
+
f"native eval count mismatch: expected 6806, got {eval_counts['native_forms']}"
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
train_total = sum(train_counts.values())
|
| 206 |
+
handwriting_domain_rows = (
|
| 207 |
+
eval_counts["native_forms"] + eval_counts["handwriting"]
|
| 208 |
+
)
|
| 209 |
+
manifest = {
|
| 210 |
+
"schema": "bina02.stage6-native-mix.v2",
|
| 211 |
+
"replay_archive_sha256": (
|
| 212 |
+
"2cb4b0507a1f6fc1fcf6619b679de5fd35a52e2ee8f55c57ce5ebf00c52ffc40"
|
| 213 |
+
),
|
| 214 |
+
"train": train_rows,
|
| 215 |
+
"eval": eval_rows,
|
| 216 |
+
"train_counts": train_counts,
|
| 217 |
+
"eval_counts": eval_counts,
|
| 218 |
+
"train_total": train_total,
|
| 219 |
+
"train_proportions": {
|
| 220 |
+
key: value / train_total for key, value in train_counts.items()
|
| 221 |
+
},
|
| 222 |
+
"handwriting_domain_rows": handwriting_domain_rows,
|
| 223 |
+
"printed_domain_rows": eval_counts["printed"],
|
| 224 |
+
"preprocessing": (
|
| 225 |
+
"native bbox crops with context; aspect-ratio-preserving resize+padding; "
|
| 226 |
+
"RecAug disabled"
|
| 227 |
+
),
|
| 228 |
+
}
|
| 229 |
+
(MIX / "MIX_MANIFEST.json").write_text(
|
| 230 |
+
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n"
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
base = yaml.safe_load((ROOT / "bina02_stage5_hardforms.yml").read_text())
|
| 234 |
+
base["Global"]["epoch_num"] = 2
|
| 235 |
+
base["Global"]["save_model_dir"] = str(RUN)
|
| 236 |
+
base["Global"]["pretrained_model"] = str(STAGE4)
|
| 237 |
+
base["Global"]["checkpoints"] = None
|
| 238 |
+
base["Global"]["eval_batch_step"] = [0, 200]
|
| 239 |
+
base["Global"].pop("stage5_mix_manifest", None)
|
| 240 |
+
base["Global"]["stage6_mix_manifest"] = str(MIX / "MIX_MANIFEST.json")
|
| 241 |
+
base["Global"]["eval_handwriting_rows"] = handwriting_domain_rows
|
| 242 |
+
base["Global"]["eval_printed_rows"] = eval_counts["printed"]
|
| 243 |
+
base["Optimizer"]["lr"]["learning_rate"] = 1.0e-6
|
| 244 |
+
base["Metric"]["domain_split_index"] = handwriting_domain_rows
|
| 245 |
+
base["Train"]["dataset"]["data_dir"] = str(MIX / "train")
|
| 246 |
+
base["Train"]["dataset"]["transforms"] = [
|
| 247 |
+
transform
|
| 248 |
+
for transform in base["Train"]["dataset"]["transforms"]
|
| 249 |
+
if "RecAug" not in transform
|
| 250 |
+
]
|
| 251 |
+
base["Eval"]["dataset"]["data_dir"] = str(MIX / "eval")
|
| 252 |
+
CONFIG.write_text(yaml.safe_dump(base, sort_keys=False, allow_unicode=True))
|
| 253 |
+
|
| 254 |
+
combined_eval = copy.deepcopy(base)
|
| 255 |
+
combined_eval["Global"]["pretrained_model"] = None
|
| 256 |
+
combined_eval["Global"]["checkpoints"] = str(STAGE4)
|
| 257 |
+
COMBINED_EVAL_CONFIG.write_text(
|
| 258 |
+
yaml.safe_dump(combined_eval, sort_keys=False, allow_unicode=True)
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
native_eval_config = copy.deepcopy(combined_eval)
|
| 262 |
+
native_eval_config["Eval"]["dataset"]["data_dir"] = str(
|
| 263 |
+
MIX / "eval/00_nativeforms"
|
| 264 |
+
)
|
| 265 |
+
native_eval_config["Metric"] = {
|
| 266 |
+
"name": "RecMetric",
|
| 267 |
+
"main_indicator": "norm_edit_dis",
|
| 268 |
+
"ignore_space": False,
|
| 269 |
+
}
|
| 270 |
+
NATIVE_EVAL_CONFIG.write_text(
|
| 271 |
+
yaml.safe_dump(native_eval_config, sort_keys=False, allow_unicode=True)
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
print(
|
| 275 |
+
json.dumps(
|
| 276 |
+
{
|
| 277 |
+
"manifest": str(MIX / "MIX_MANIFEST.json"),
|
| 278 |
+
"config": str(CONFIG),
|
| 279 |
+
"combined_eval_config": str(COMBINED_EVAL_CONFIG),
|
| 280 |
+
"native_eval_config": str(NATIVE_EVAL_CONFIG),
|
| 281 |
+
"train_counts": train_counts,
|
| 282 |
+
"eval_counts": eval_counts,
|
| 283 |
+
"train_total": train_total,
|
| 284 |
+
"train_proportions": manifest["train_proportions"],
|
| 285 |
+
"handwriting_domain_rows": handwriting_domain_rows,
|
| 286 |
+
},
|
| 287 |
+
ensure_ascii=False,
|
| 288 |
+
indent=2,
|
| 289 |
+
)
|
| 290 |
+
)
|
training-code/stage6-native-v2/fetch_native_replay_v2.sh
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
ROOT=/workspace/bina02-ppocr/data/corpus-v5-native-replay
|
| 5 |
+
EXPECTED_SHA256=2cb4b0507a1f6fc1fcf6619b679de5fd35a52e2ee8f55c57ce5ebf00c52ffc40
|
| 6 |
+
URL=${1:?usage: fetch_native_replay_v2.sh SIGNED_CDN_URL}
|
| 7 |
+
PARTIAL="$ROOT/native-replay-v1.tar.part"
|
| 8 |
+
ARCHIVE="$ROOT/native-replay-v1.tar"
|
| 9 |
+
DEST="$ROOT/replay-transfer-v2"
|
| 10 |
+
|
| 11 |
+
if [ -e "$ARCHIVE" ] || [ -e "$DEST" ]; then
|
| 12 |
+
echo "final archive or extraction destination already exists; refusing to overwrite"
|
| 13 |
+
exit 3
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
mkdir -p "$ROOT"
|
| 17 |
+
resume=()
|
| 18 |
+
if [ -s "$PARTIAL" ]; then
|
| 19 |
+
resume=(-C -)
|
| 20 |
+
fi
|
| 21 |
+
|
| 22 |
+
curl -4 -fL \
|
| 23 |
+
--connect-timeout 15 \
|
| 24 |
+
--retry 30 \
|
| 25 |
+
--retry-delay 5 \
|
| 26 |
+
--retry-all-errors \
|
| 27 |
+
"${resume[@]}" \
|
| 28 |
+
-o "$PARTIAL" \
|
| 29 |
+
"$URL"
|
| 30 |
+
|
| 31 |
+
printf '%s %s\n' "$EXPECTED_SHA256" "$PARTIAL" | sha256sum -c -
|
| 32 |
+
mv "$PARTIAL" "$ARCHIVE"
|
| 33 |
+
mkdir "$DEST"
|
| 34 |
+
tar --sparse -xf "$ARCHIVE" -C "$DEST"
|
| 35 |
+
printf '{"schema":"bina02.native-replay-fetch.v2","sha256":"%s","completed_at":"%s"}\n' \
|
| 36 |
+
"$EXPECTED_SHA256" \
|
| 37 |
+
"$(date -Iseconds)" \
|
| 38 |
+
> "$DEST/FETCH_COMPLETE.json"
|
| 39 |
+
echo "$(date -Iseconds) FETCH_AND_EXTRACT_COMPLETE"
|
training-code/stage6-native-v2/run_bina02_stage6_v2.sh
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
ROOT=/workspace/bina02-ppocr
|
| 5 |
+
RUN="$ROOT/run/train-stage6-nativeforms-v2-30-35-35-lr1e6"
|
| 6 |
+
CONFIG="$ROOT/bina02_stage6_nativeforms_v2.yml"
|
| 7 |
+
COMBINED_EVAL_CONFIG="$ROOT/bina02_stage6_combined_eval_stage4_v2.yml"
|
| 8 |
+
NATIVE_EVAL_CONFIG="$ROOT/bina02_stage6_native_eval_stage4_v2.yml"
|
| 9 |
+
BUILDER="$ROOT/build_bina02_stage6_native_mix_v2.py"
|
| 10 |
+
VENV="$ROOT/venv"
|
| 11 |
+
|
| 12 |
+
if [ -e "$RUN/status.log" ]; then
|
| 13 |
+
echo "Stage 6 v2 status already exists; refusing to duplicate the run"
|
| 14 |
+
exit 3
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
mkdir -p "$RUN" "$RUN/distributed-logs"
|
| 18 |
+
"$VENV/bin/python" "$BUILDER" > "$RUN/mix-build.json"
|
| 19 |
+
|
| 20 |
+
cd "$ROOT/PaddleOCR"
|
| 21 |
+
export PYTHONPATH="$ROOT/PaddleOCR"
|
| 22 |
+
export FLAGS_allocator_strategy=auto_growth
|
| 23 |
+
export OMP_NUM_THREADS=4
|
| 24 |
+
|
| 25 |
+
CUDA_VISIBLE_DEVICES=0 "$VENV/bin/python" tools/eval.py \
|
| 26 |
+
-c "$COMBINED_EVAL_CONFIG" \
|
| 27 |
+
> "$RUN/stage4-combined-baseline.log" 2>&1 &
|
| 28 |
+
combined_eval_pid=$!
|
| 29 |
+
|
| 30 |
+
CUDA_VISIBLE_DEVICES=1 "$VENV/bin/python" tools/eval.py \
|
| 31 |
+
-c "$NATIVE_EVAL_CONFIG" \
|
| 32 |
+
> "$RUN/stage4-native-baseline.log" 2>&1 &
|
| 33 |
+
native_eval_pid=$!
|
| 34 |
+
|
| 35 |
+
wait "$combined_eval_pid"
|
| 36 |
+
wait "$native_eval_pid"
|
| 37 |
+
|
| 38 |
+
read -r baseline_handwriting baseline_printed < <(
|
| 39 |
+
"$VENV/bin/python" - "$RUN/stage4-combined-baseline.log" <<'PY'
|
| 40 |
+
import re
|
| 41 |
+
import sys
|
| 42 |
+
from pathlib import Path
|
| 43 |
+
|
| 44 |
+
text = Path(sys.argv[1]).read_text(errors="replace")
|
| 45 |
+
matches = re.findall(
|
| 46 |
+
r"cur metric,.*?handwriting_norm_edit_dis:\s*([0-9.eE+-]+)"
|
| 47 |
+
r".*?printed_norm_edit_dis:\s*([0-9.eE+-]+)",
|
| 48 |
+
text,
|
| 49 |
+
)
|
| 50 |
+
if not matches:
|
| 51 |
+
raise SystemExit("could not parse Stage 4 combined-domain baseline")
|
| 52 |
+
print(*matches[-1])
|
| 53 |
+
PY
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
printf 'BASELINE_HANDWRITING=%s\nBASELINE_PRINTED=%s\n' \
|
| 57 |
+
"$baseline_handwriting" \
|
| 58 |
+
"$baseline_printed" \
|
| 59 |
+
> "$RUN/baselines.env"
|
| 60 |
+
|
| 61 |
+
cp "$CONFIG" "$RUN/launch-config.yml"
|
| 62 |
+
printf '%s TRAIN_START\n' "$(date -Iseconds)" > "$RUN/status.log"
|
| 63 |
+
|
| 64 |
+
setsid "$VENV/bin/python" -m paddle.distributed.launch \
|
| 65 |
+
--gpus "0,1,2,3,4,5,6,7" \
|
| 66 |
+
--log_dir "$RUN/distributed-logs" \
|
| 67 |
+
tools/train.py \
|
| 68 |
+
-c "$RUN/launch-config.yml" \
|
| 69 |
+
> "$RUN/train-launch.log" 2>&1 &
|
| 70 |
+
trainer=$!
|
| 71 |
+
printf '%s TRAIN_PID=%s\n' "$(date -Iseconds)" "$trainer" >> "$RUN/status.log"
|
| 72 |
+
|
| 73 |
+
"$VENV/bin/python" "$ROOT/watch_bina02_stage4_eval.py" \
|
| 74 |
+
--log "$RUN/train-launch.log" \
|
| 75 |
+
--pgid "$trainer" \
|
| 76 |
+
--state "$RUN/eval-gate-state.json" \
|
| 77 |
+
--baseline-handwriting "$baseline_handwriting" \
|
| 78 |
+
--baseline-printed "$baseline_printed" \
|
| 79 |
+
--handwriting-margin 0.003 \
|
| 80 |
+
--printed-margin 0.002 \
|
| 81 |
+
--patience 2 \
|
| 82 |
+
--poll-seconds 5 \
|
| 83 |
+
> "$RUN/eval-gate.log" 2>&1 &
|
| 84 |
+
watcher=$!
|
| 85 |
+
|
| 86 |
+
set +e
|
| 87 |
+
wait "$trainer"
|
| 88 |
+
rc=$?
|
| 89 |
+
set -e
|
| 90 |
+
kill "$watcher" 2>/dev/null || true
|
| 91 |
+
wait "$watcher" 2>/dev/null || true
|
| 92 |
+
printf '%s TRAIN_EXIT_CODE=%s\n' "$(date -Iseconds)" "$rc" >> "$RUN/status.log"
|
| 93 |
+
exit "$rc"
|