File size: 5,512 Bytes
2a18db6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# KAGGLE: {"gpu": false, "internet": false, "kernels": ["alekstop/03-embed", "alekstop/07-finetune", "alekstop/08-stack2", "alekstop/11-release"]}

# %% [markdown]
# # 14 — Сборка релиза в одну папку
#
# Артефакты решения разбросаны по четырём ядрам, и каждое из них отдаёт вместе
# с нужным файлом гигабайты ненужного (`03-embed` — 1.9 ГБ эмбеддингов ради
# одного `emb_meta.json`). Ядро собирает только то, что требуется
# `final_inference.ipynb`, в одну папку `release/`, которую остаётся скачать и
# залить на HuggingFace.
#
# | что | откуда | зачем |
# |---|---|---|
# | `l1_*.joblib`, `l2_*.joblib`, `release_config.json` | `11-release` | ансамбль и пороги |
# | `ft_fold{0..4}.pt`, `ft_meta.json` | `07-finetune` | дообученный DINOv3 |
# | `pte_ft.npy`, `submission.csv` | `07`, `08` | сверка воспроизводимости |
# | `emb_meta.json` | `03-embed` | какие бэкбоны и с каким разрешением считать |

# %%
import os, json, shutil, time
from pathlib import Path

T0 = time.time()
WORK = Path(os.environ.get('MQC_WORK', '/kaggle/working'))
IN = Path(os.environ.get('MQC_IN', '/kaggle/input'))
REL = WORK / 'release'
REL.mkdir(exist_ok=True)

find = lambda pat: next(p for p in IN.rglob(pat))

# %%
# --- копирование ---
src_rel = find('release_config.json').parent
n = 0
for p in sorted(src_rel.glob('*')):
    if p.is_file():
        shutil.copy2(p, REL / p.name)
        n += 1
print('из 11-release:', n, 'файлов')

FT_DIR = find('ft_meta.json').parent
for name in ['ft_meta.json', 'pte_ft.npy', 'oof_ft.npy']:
    shutil.copy2(FT_DIR / name, REL / name)
for p in sorted(FT_DIR.glob('ft_fold*.pt')):
    shutil.copy2(p, REL / p.name)
print('из 07-finetune:', len(list(REL.glob('ft_fold*.pt'))), 'весов')

shutil.copy2(find('emb_meta.json'), REL / 'emb_meta.json')

# submission из 08 — эталон для сверки в Colab
shutil.copy2(find('final_config.json').parent / 'submission.csv', REL / 'submission.csv')

# %%
# --- карточка репозитория ---
CFG = json.load(open(REL / 'release_config.json'))
EM = json.load(open(REL / 'emb_meta.json'))
FTM = json.load(open(REL / 'ft_meta.json'))

README = """---
license: mit
tags:
  - 3d
  - mesh-quality
  - multi-label-classification
---

# 3D Mesh Quality Control — артефакты решения

Предсказание 10 дефектов 3D-меша и метки пригодности `quality` по мешу и шести
рендерам карт нормалей. OOF-метрика соревнования: **f1_final = %.3f из 20**
(quality %.4f, artefacts %.4f).

## Состав ансамбля

| уровень | модель | OOF |
|---|---|---|
| 1 | дообученный %s @%d, multi-view | 14.236 |
| 1 | LightGBM (эмбеддинги 4 бэкбонов + 472 признака) | 13.826 |
| 1 | LightGBM (только признаки) | 13.048 |
| 1 | логрег по эмбеддингам | 12.692 |
| 2 | LightGBM по %d мета-признакам | 14.469 |
| бленд | %.2f · уровень 2 + %.2f · дообученный ViT | **%.3f** |

Замороженные бэкбоны: %s.

## Файлы

* `lib_features.py`, `lib_embed.py`, `lib_model.py` — препроцессинг и модель,
  **сгенерированы из обучающих ядер**, а не написаны заново;
* `l1_*.joblib`, `l2_*.joblib` — модели обоих уровней, по 5 фолдов;
* `ft_fold{0..4}.pt` — веса дообученного ViT (fp16);
* `release_config.json` — пороги пер-лейбл, состав ансамбля, порядок колонок;
* `submission.csv`, `pte_ft.npy` — эталоны для сверки воспроизводимости.

## Использование

Ноутбук `final_inference.ipynb` скачивает этот репозиторий целиком
(`snapshot_download`, токен не нужен), считает признаки и выдаёт
`submission.csv`, сверяя его с приложенным эталоном.
""" % (CFG['oof_final'], CFG.get('oof_quality', 0.7442), CFG.get('oof_artefacts', 0.7114),
       FTM['backbone'], FTM['img'], 4 * 11 + 4, 1 - CFG['blend_w'], CFG['blend_w'],
       CFG['oof_final'], ', '.join(CFG['emb_order']))
(REL / 'README.md').write_text(README, encoding='utf-8')

# %%
# --- проверка комплектности ---
need = (['release_config.json', 'emb_meta.json', 'ft_meta.json', 'pte_ft.npy',
         'submission.csv', 'README.md']
        + ['ft_fold%d.pt' % k for k in range(CFG['nfold'])]
        + ['l2_f%d_%s.joblib' % (k, t) for k in range(CFG['nfold'])
           for t in CFG['targets'] if t != 'intersection'])
miss = [f for f in need if not (REL / f).exists()]
print('НЕ ХВАТАЕТ:' if miss else 'комплект полный', miss[:10])

files = sorted(REL.glob('*'))
size = sum(p.stat().st_size for p in files)
print('релиз: %d файлов, %.1f МБ' % (len(files), size / 1e6))
for p in files:
    if p.stat().st_size > 5e6:
        print('  %-28s %7.1f МБ' % (p.name, p.stat().st_size / 1e6))
print('всего: %.1f мин' % ((time.time() - T0) / 60))