| |
| """ |
| Convert trained .pth files to .safetensors format and place them in the |
| correct directory structure. |
| |
| The source .pth files are read from the ZX2 external drive (read-only). |
| Output .safetensors files are written to the HuggingFace repo structure. |
| |
| Usage: |
| python3 convert_weights.py |
| |
| Requires: |
| pip install safetensors torch |
| """ |
|
|
| import os |
| import sys |
| import json |
| import torch |
| from safetensors.torch import save_file |
|
|
|
|
| |
| ZX2_BASE = '/Volumes/ZX2 1TB/se-alexnet/ClassifiedWithCondition' |
| REPO_ROOT = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
| |
| |
| WEIGHT_MAPPINGS = [ |
| |
| ('alexnet/facebased', |
| f'{ZX2_BASE}/RawAlexNet/FaceBased/AlexNet.pth'), |
| ('alexnet/objectbased', |
| f'{ZX2_BASE}/RawAlexNet/ObjectBased/AlexNet.pth'), |
|
|
| |
| ('vgg16/facebased', |
| f'{ZX2_BASE}/VGG16/FaceBased/vgg16Net.pth'), |
| ('vgg16/objectbased', |
| f'{ZX2_BASE}/VGG16/ObjectBased/vgg16Net.pth'), |
| ] |
|
|
| |
| for pretrain in ['FaceBased', 'ObjectBased']: |
| dest_pretrain = pretrain.lower() |
| for r in [2, 4, 8, 16, 32]: |
| dest = f'se-location1/{dest_pretrain}/squeeze-{r}' |
| src = f'{ZX2_BASE}/SELocate-1/{pretrain}/squeeze-{r}/AlexNet.pth' |
| WEIGHT_MAPPINGS.append((dest, src)) |
|
|
| |
| for pretrain in ['FaceBased', 'ObjectBased']: |
| dest_pretrain = pretrain.lower() |
| for r in [2, 4, 8, 16, 32]: |
| dest = f'se-location2/{dest_pretrain}/squeeze-{r}' |
| src = f'{ZX2_BASE}/SELocate-2/{pretrain}/squeeze-{r}/AlexNet.pth' |
| WEIGHT_MAPPINGS.append((dest, src)) |
|
|
| |
| for pretrain in ['FaceBased', 'ObjectBased']: |
| dest_pretrain = pretrain.lower() |
| for r in [2, 4, 8, 16, 32]: |
| dest = f'se-location3/{dest_pretrain}/squeeze-{r}' |
| src = f'{ZX2_BASE}/SELocate-3-best/{pretrain}/squeeze-{r}/AlexNet.pth' |
| WEIGHT_MAPPINGS.append((dest, src)) |
|
|
|
|
| def convert_single(src_path, dest_dir, dest_name='model.safetensors'): |
| """Convert a single .pth file to .safetensors.""" |
| dest_path = os.path.join(REPO_ROOT, dest_dir, dest_name) |
|
|
| if os.path.exists(dest_path): |
| print(f' β {dest_dir}/model.safetensors already exists, skipping') |
| return |
|
|
| os.makedirs(os.path.join(REPO_ROOT, dest_dir), exist_ok=True) |
|
|
| print(f' Loading: {os.path.basename(src_path)} ...', end=' ', flush=True) |
| try: |
| state_dict = torch.load(src_path, map_location='cpu', weights_only=True) |
| except Exception as e: |
| print(f'ERROR: {e}') |
| return |
|
|
| |
| if 'model' in state_dict: |
| state_dict = state_dict['model'] |
|
|
| |
| config_path = os.path.join(REPO_ROOT, dest_dir, 'config.json') |
| if os.path.exists(config_path): |
| with open(config_path) as f: |
| config = json.load(f) |
| |
| last_linear_keys = [k for k in state_dict if 'weight' in k |
| and len(state_dict[k].shape) == 2] |
| if last_linear_keys: |
| last_w = state_dict[last_linear_keys[-1]] |
| expected = config.get('num_classes') |
| if expected and last_w.shape[0] != expected: |
| print(f'\n β οΈ num_classes mismatch: state_dict={last_w.shape[0]}, ' |
| f'config={expected}') |
|
|
| print(f'saving .safetensors ({len(state_dict)} keys) ...', end=' ', flush=True) |
| save_file(state_dict, dest_path) |
| size_mb = os.path.getsize(dest_path) / (1024 * 1024) |
| print(f'β ({size_mb:.0f} MB)') |
|
|
|
|
| def main(): |
| print('=' * 60) |
| print('SE-AlexNet: .pth β .safetensors Conversion') |
| print('=' * 60) |
| print(f'Source: {ZX2_BASE}') |
| print(f'Target: {REPO_ROOT}') |
| print(f'Total: {len(WEIGHT_MAPPINGS)} models to convert') |
| print() |
|
|
| success = 0 |
| skipped = 0 |
| failed = 0 |
|
|
| for dest, src in WEIGHT_MAPPINGS: |
| if not os.path.exists(src): |
| print(f' β MISSING: {src}') |
| failed += 1 |
| continue |
| try: |
| convert_single(src, dest) |
| success += 1 |
| except Exception as e: |
| print(f' β FAILED: {e}') |
| failed += 1 |
|
|
| print() |
| print(f'Done. {success} converted, {skipped} skipped, {failed} failed.') |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|