Spaces:
Sleeping
Sleeping
| # AI-assisted (Claude Code, claude.ai) -- https://claude.ai | |
| """Extract ResNet50 image embeddings for articles in the subset.""" | |
| import torch | |
| import numpy as np | |
| import pandas as pd | |
| from torchvision import models, transforms | |
| from PIL import Image | |
| from pathlib import Path | |
| def extract_features( | |
| data_dir: str = "data", | |
| batch_size: int = 32, | |
| full_catalog: bool = True, | |
| ): | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| if device.type == "cuda": | |
| torch.cuda.set_per_process_memory_fraction(0.5, 0) | |
| print(f"GPU memory capped at 50%") | |
| print(f"Using device: {device}") | |
| # Load pretrained ResNet50, remove classification head | |
| model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT) | |
| model = torch.nn.Sequential(*list(model.children())[:-1]) # -> 2048-dim | |
| model = model.to(device) | |
| model.eval() | |
| preprocess = transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.CenterCrop(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize( | |
| mean=[0.485, 0.456, 0.406], | |
| std=[0.229, 0.224, 0.225], | |
| ), | |
| ]) | |
| data_dir = Path(data_dir) | |
| if full_catalog: | |
| articles = pd.read_csv(data_dir / "raw" / "articles.csv", dtype={"article_id": str}) | |
| print(f"Extracting for FULL catalog: {len(articles):,} articles") | |
| else: | |
| articles = pd.read_csv(data_dir / "processed" / "articles_subset.csv", dtype={"article_id": str}) | |
| print(f"Extracting for subset: {len(articles):,} articles") | |
| image_dir = data_dir / "raw" / "images" | |
| article_ids = articles["article_id"].values | |
| embeddings = np.zeros((len(article_ids), 2048), dtype=np.float32) | |
| missing = [] | |
| # Process in batches | |
| for start in range(0, len(article_ids), batch_size): | |
| end = min(start + batch_size, len(article_ids)) | |
| batch_ids = article_ids[start:end] | |
| batch_tensors = [] | |
| batch_indices = [] | |
| for i, aid in enumerate(batch_ids): | |
| img_path = image_dir / aid[:3] / f"{aid}.jpg" | |
| if not img_path.exists(): | |
| missing.append(aid) | |
| continue | |
| try: | |
| img = Image.open(img_path).convert("RGB") | |
| batch_tensors.append(preprocess(img)) | |
| batch_indices.append(start + i) | |
| except Exception as e: | |
| print(f" Error loading {aid}: {e}") | |
| missing.append(aid) | |
| if batch_tensors: | |
| batch = torch.stack(batch_tensors).to(device) | |
| with torch.no_grad(): | |
| features = model(batch).squeeze(-1).squeeze(-1).cpu().numpy() | |
| for j, idx in enumerate(batch_indices): | |
| embeddings[idx] = features[j] | |
| if (start // batch_size) % 10 == 0: | |
| print(f" Processed {end}/{len(article_ids)} articles...") | |
| # Save | |
| out_dir = data_dir / "processed" | |
| np.save(out_dir / "image_embeddings.npy", embeddings) | |
| # Save article_id -> index mapping | |
| id_map = pd.DataFrame({"article_id": article_ids, "embedding_idx": range(len(article_ids))}) | |
| id_map.to_csv(out_dir / "article_embedding_map.csv", index=False) | |
| print(f"\nDone! Saved {len(article_ids)} embeddings to {out_dir / 'image_embeddings.npy'}") | |
| print(f"Shape: {embeddings.shape}") | |
| if missing: | |
| print(f"Missing images: {len(missing)} articles") | |
| return embeddings, article_ids | |
| if __name__ == "__main__": | |
| import sys | |
| full = "--subset-only" not in sys.argv | |
| extract_features(full_catalog=full) | |