malelolpiotr's picture
Upload 11 files
127d28d verified
Raw History Blame Contribute Delete
7.96 kB
#!/usr/bin/env python3
# utils.py - Common utilities for XSimGCL project
"""
Shared helper functions for data conversion, hardware setup,
configuration management, checkpoint handling.
FIXED: resolve_config_path now checks script directory FIRST before going up
ADDED: load_active_config() — czyta train_dataset.yaml i zwraca (config_path, dataset_type)
"""
import torch
import numpy as np
import scipy.sparse as sp
import yaml
import os
import sys
from typing import Dict, Optional, List, Tuple
# ============================================================================
# SECTION 1: Data Conversion
# ============================================================================
def scipy_to_torch_sparse(mat):
"""
Convert scipy sparse matrix to torch sparse tensor.
Używa formatu CSR (Compressed Sparse Row) jeśli PyTorch >= 1.11.
CSR jest cache-friendly dla torch.sparse.mm na CUDA — szybszy forward pass GCN.
Fallback do COO dla starszych wersji PyTorch.
Args:
mat: Scipy sparse matrix or dense array
Returns:
torch.sparse.Tensor (CSR preferred) or original input if not sparse
"""
if not sp.issparse(mat):
return mat
csr = mat.tocsr()
crow_indices = torch.from_numpy(csr.indptr.astype(np.int64))
col_indices = torch.from_numpy(csr.indices.astype(np.int64))
values = torch.from_numpy(csr.data.astype(np.float32))
try:
return torch.sparse_csr_tensor(
crow_indices, col_indices, values, torch.Size(csr.shape)
)
except Exception:
# Fallback: COO dla PyTorch < 1.11
coo = mat.tocoo()
indices = torch.from_numpy(np.vstack((coo.row, coo.col)).astype(np.int64))
values_coo = torch.from_numpy(coo.data.astype(np.float32))
return torch.sparse_coo_tensor(indices, values_coo, torch.Size(coo.shape)).coalesce()
# ============================================================================
# SECTION 2: Hardware Setup
# ============================================================================
def setup_device(device_arg: str) -> str:
"""
Auto-detect or select compute device.
Args:
device_arg: 'auto', 'cuda', or 'cpu'
Returns:
Selected device string ('cuda' or 'cpu')
"""
if device_arg == 'auto':
if torch.cuda.is_available():
print(f"[Device] CUDA: {torch.cuda.get_device_name(0)}")
return 'cuda'
print("[Device] Using CPU")
return 'cpu'
if device_arg == 'cuda' and not torch.cuda.is_available():
print("[Device] CUDA requested but not available → falling back to CPU")
return 'cpu'
return device_arg
# ============================================================================
# SECTION 3: Config Management
# ============================================================================
def load_yaml_config(path: str, required: bool = True) -> Optional[Dict]:
"""
Load YAML configuration file.
Args:
path: Path to YAML file
required: If True, exit on missing file
Returns:
Configuration dictionary or None
"""
if not os.path.exists(path):
if required:
print(f"[ERROR] Missing configuration file: {path}")
sys.exit(1)
return None
try:
with open(path, 'r') as f:
return yaml.safe_load(f)
except Exception as e:
print(f"[ERROR] Failed to load YAML: {e}")
if required:
sys.exit(1)
return None
def load_active_config(script_dir: Optional[str] = None) -> Tuple[str, str]:
"""
Czyta train_dataset.yaml i zwraca (config_path, dataset_type).
Pozwala wszystkim skryptom (train_model, png, test_recommend, grid_train)
automatycznie wiedzieć którego datasetu i której konfiguracji używać
bez ręcznego podawania argumentów.
train_dataset.yaml:
dataset: "f95" # lub "steam"
Returns:
(config_path, dataset_type) gdzie dataset_type in {'f95', 'steam'}
"""
if script_dir is None:
script_dir = get_script_directory()
td_path = os.path.join(script_dir, 'train_dataset.yaml')
if not os.path.exists(td_path):
# Fallback: szukaj w katalogu roboczym
td_path = os.path.join(os.getcwd(), 'train_dataset.yaml')
td = load_yaml_config(td_path, required=False)
if not td:
# Brak pliku → domyślnie f95
print(f" [load_active_config] train_dataset.yaml not found, defaulting to f95")
config_path = os.path.join(script_dir, 'config.yaml')
return config_path, 'f95'
dataset_type = str(td.get('dataset', 'f95')).lower().strip()
if dataset_type == 'steam':
config_path = os.path.join(script_dir, 'config_steam.yaml')
else:
dataset_type = 'f95'
config_path = os.path.join(script_dir, 'config.yaml')
print(f" [Dataset] train_dataset.yaml → dataset={dataset_type}, config={os.path.basename(config_path)}")
return config_path, dataset_type
def load_dataset_class(dataset_type: str):
"""
Zwraca klasę datasetu na podstawie dataset_type.
Args:
dataset_type: 'f95' lub 'steam'
Returns:
Klasa datasetu (F95Dataset lub SteamDataset)
"""
if dataset_type == 'steam':
from game_dataset_steam import SteamDataset
return SteamDataset
else:
from game_dataset import F95Dataset
return F95Dataset
# ============================================================================
# SECTION 4: Checkpoint Management
# ============================================================================
def find_checkpoint(base_dir: str, checkpoint_patterns: List[str]) -> Optional[str]:
"""
Search for checkpoint directory matching patterns.
Args:
base_dir: Base directory to search in
checkpoint_patterns: List of subdirectory names to try
Returns:
Path to checkpoint directory or None
"""
for pattern in checkpoint_patterns:
checkpoint_dir = os.path.join(base_dir, pattern)
if os.path.exists(os.path.join(checkpoint_dir, 'best_model.pt')):
return checkpoint_dir
return None
# ============================================================================
# SECTION 5: Path Utilities (FIXED)
# ============================================================================
def get_script_directory() -> str:
"""
Get directory where the calling script is located.
Returns:
Absolute path to script directory
"""
if hasattr(sys.modules['__main__'], '__file__'):
script_path = os.path.abspath(sys.modules['__main__'].__file__)
return os.path.dirname(script_path)
return os.path.abspath(os.getcwd())
def get_project_root() -> str:
"""
Get project root directory (where config.yaml is expected).
FIXED: Now searches from SCRIPT directory first, then goes up.
Returns:
Absolute path to project root
"""
current = get_script_directory()
if os.path.exists(os.path.join(current, 'config.yaml')):
return current
search_path = current
while search_path != '/':
if os.path.exists(os.path.join(search_path, 'config.yaml')):
return search_path
search_path = os.path.dirname(search_path)
return current
def resolve_config_path(custom_path: Optional[str] = None) -> str:
"""
Resolve configuration file path.
UWAGA: Ta funkcja zwraca config.yaml (F95) bez uwzględnienia train_dataset.yaml.
Używaj load_active_config() jeśli chcesz automatyczną selekcję datasetu.
Args:
custom_path: Custom path to config.yaml (optional)
Returns:
Resolved absolute path to config.yaml
"""
if custom_path:
return os.path.abspath(custom_path)
project_root = get_project_root()
return os.path.join(project_root, 'config.yaml')