lebiraja commited on
Commit
a66cac6
·
verified ·
1 Parent(s): f95f207

Upload DEVELOPER.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. DEVELOPER.md +523 -0
DEVELOPER.md ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Developer Guide
2
+
3
+ Complete guide for developers who want to modify, fine-tune, or extend the model.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Environment Setup](#environment-setup)
10
+ 2. [Project Structure](#project-structure)
11
+ 3. [Loading & Inspecting Model](#loading--inspecting-model)
12
+ 4. [Fine-Tuning on Custom Data](#fine-tuning-on-custom-data)
13
+ 5. [Model Modifications](#model-modifications)
14
+ 6. [Contributing](#contributing)
15
+
16
+ ---
17
+
18
+ ## Environment Setup
19
+
20
+ ### Clone Repository
21
+
22
+ ```bash
23
+ # From Hugging Face
24
+ git clone https://huggingface.co/lebiraja/retinal-disease-classifier
25
+ cd retinal-disease-classifier
26
+
27
+ # Or from your fork
28
+ git clone https://github.com/your-username/retinal-disease-classifier.git
29
+ cd retinal-disease-classifier
30
+ ```
31
+
32
+ ### Create Virtual Environment
33
+
34
+ ```bash
35
+ python3.10 -m venv venv
36
+ source venv/bin/activate # On Windows: venv\Scripts\activate
37
+
38
+ # Upgrade pip
39
+ pip install --upgrade pip setuptools wheel
40
+ ```
41
+
42
+ ### Install Dependencies
43
+
44
+ ```bash
45
+ # PyTorch with CUDA 12.1
46
+ pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
47
+
48
+ # Other dependencies
49
+ pip install -r requirements.txt
50
+ ```
51
+
52
+ Or manually:
53
+ ```bash
54
+ pip install \
55
+ albumentations==1.3.0 \
56
+ numpy==1.24.0 \
57
+ pandas==2.0.0 \
58
+ pillow==10.0.0 \
59
+ scikit-learn==1.3.0 \
60
+ matplotlib==3.7.0 \
61
+ tqdm==4.65.0
62
+ ```
63
+
64
+ ### Verify Setup
65
+
66
+ ```bash
67
+ python3 << 'EOF'
68
+ import torch
69
+ import albumentations
70
+ import sklearn
71
+ print(f"PyTorch: {torch.__version__}")
72
+ print(f"CUDA: {torch.cuda.is_available()}")
73
+ print(f"GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'None'}")
74
+ print("✅ Setup OK")
75
+ EOF
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Project Structure
81
+
82
+ ```
83
+ retinal-disease-classifier/
84
+ ├── config.py # Configuration (paths, hyperparameters)
85
+ ├── model.py # EfficientNet-B4 architecture
86
+ ├── dataset.py # Data loading & preprocessing
87
+ ├── train.py # Training script
88
+ ├── inference.py # Inference script
89
+
90
+ ├── pytorch_model.bin # Trained weights (~75 MB)
91
+ ├── config.json # Model config for HF
92
+
93
+ ├── USER_GUIDE.md # User documentation
94
+ ├── BACKEND.md # Backend integration guide
95
+ ├── DEVELOPER.md # This file
96
+
97
+ ├── outputs/
98
+ │ ├── checkpoints/ # Model checkpoints
99
+ │ ├── logs/ # training_log.csv
100
+ │ └── plots/ # loss curves
101
+
102
+ └── docs/ # Additional documentation
103
+ ├── SETUP.md
104
+ ├── ARCHITECTURE.md
105
+ ├── TRAINING.md
106
+ ├── INFERENCE.md
107
+ ├── API_REFERENCE.md
108
+ └── TROUBLESHOOTING.md
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Loading & Inspecting Model
114
+
115
+ ### Load Pretrained Model
116
+
117
+ ```python
118
+ import torch
119
+ from model import build_model
120
+
121
+ # Build model
122
+ model = build_model(num_classes=45)
123
+
124
+ # Load weights
125
+ checkpoint = torch.load("pytorch_model.bin", map_location="cpu")
126
+ model.load_state_dict(checkpoint["model_state_dict"])
127
+
128
+ # For inference
129
+ model.eval()
130
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
131
+ model = model.to(device)
132
+ ```
133
+
134
+ ### Inspect Architecture
135
+
136
+ ```python
137
+ # Print model summary
138
+ print(model)
139
+
140
+ # Get parameter counts
141
+ total_params = sum(p.numel() for p in model.parameters())
142
+ trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
143
+
144
+ print(f"Total parameters: {total_params / 1e6:.2f}M")
145
+ print(f"Trainable parameters: {trainable_params / 1e6:.2f}M")
146
+
147
+ # Get layer names
148
+ for name, module in model.named_modules():
149
+ print(f"{name}: {module}")
150
+ ```
151
+
152
+ ### Extract Features
153
+
154
+ ```python
155
+ # Get intermediate features (before classifier head)
156
+ class FeatureExtractor(torch.nn.Module):
157
+ def __init__(self, model):
158
+ super().__init__()
159
+ self.features = model.features # EfficientNet backbone
160
+
161
+ def forward(self, x):
162
+ return self.features(x)
163
+
164
+ extractor = FeatureExtractor(model)
165
+ with torch.no_grad():
166
+ features = extractor(tensor) # (1, 1792, 1, 1) for 384×384 input
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Fine-Tuning on Custom Data
172
+
173
+ ### Prepare Custom Dataset
174
+
175
+ ```python
176
+ # custom_dataset.py
177
+ import pandas as pd
178
+ import numpy as np
179
+ from PIL import Image
180
+ import torch
181
+ from torch.utils.data import Dataset
182
+ import albumentations as A
183
+ from albumentations.pytorch import ToTensorV2
184
+
185
+ class CustomRetinalDataset(Dataset):
186
+ def __init__(self, csv_path, image_dir, disease_columns, transform=None):
187
+ self.df = pd.read_csv(csv_path)
188
+ self.image_dir = image_dir
189
+ self.disease_columns = disease_columns
190
+ self.transform = transform
191
+
192
+ def __len__(self):
193
+ return len(self.df)
194
+
195
+ def __getitem__(self, idx):
196
+ # Load image
197
+ img_path = os.path.join(
198
+ self.image_dir,
199
+ self.df.iloc[idx]["filename"]
200
+ )
201
+ image = np.array(Image.open(img_path).convert("RGB"))
202
+
203
+ # Apply transforms
204
+ if self.transform:
205
+ image = self.transform(image=image)["image"]
206
+
207
+ # Get labels
208
+ labels = torch.tensor(
209
+ self.df.iloc[idx][self.disease_columns].values,
210
+ dtype=torch.float32
211
+ )
212
+
213
+ return image, labels
214
+ ```
215
+
216
+ ### Fine-Tune Script
217
+
218
+ ```python
219
+ # fine_tune.py
220
+ import torch
221
+ import torch.nn as nn
222
+ from torch.optim import AdamW
223
+ from torch.optim.lr_scheduler import CosineAnnealingLR
224
+ from torch.utils.data import DataLoader
225
+ from tqdm import tqdm
226
+
227
+ from model import build_model, get_param_groups
228
+ from custom_dataset import CustomRetinalDataset
229
+ from dataset import get_pos_weights
230
+ import albumentations as A
231
+ from albumentations.pytorch import ToTensorV2
232
+
233
+ # Hyperparameters
234
+ BATCH_SIZE = 8
235
+ EPOCHS = 20
236
+ LR = 5e-5 # Lower LR for fine-tuning
237
+ IMG_SIZE = 384
238
+
239
+ # Augmentations
240
+ transform = A.Compose([
241
+ A.Resize(IMG_SIZE, IMG_SIZE),
242
+ A.HorizontalFlip(p=0.5),
243
+ A.VerticalFlip(p=0.3),
244
+ A.RandomBrightnessContrast(p=0.3),
245
+ A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
246
+ ToTensorV2(),
247
+ ])
248
+
249
+ # Load dataset
250
+ train_ds = CustomRetinalDataset(
251
+ csv_path="custom_data/train_labels.csv",
252
+ image_dir="custom_data/train_images",
253
+ disease_columns=[f"disease_{i}" for i in range(45)],
254
+ transform=transform
255
+ )
256
+
257
+ train_loader = DataLoader(
258
+ train_ds,
259
+ batch_size=BATCH_SIZE,
260
+ shuffle=True,
261
+ num_workers=4
262
+ )
263
+
264
+ # Load pretrained model
265
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
266
+ model = build_model().to(device)
267
+ checkpoint = torch.load("pytorch_model.bin", map_location=device)
268
+ model.load_state_dict(checkpoint["model_state_dict"])
269
+
270
+ # Setup training
271
+ pos_weights = get_pos_weights(train_ds).to(device)
272
+ criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weights)
273
+ optimizer = AdamW(
274
+ get_param_groups(model, LR),
275
+ weight_decay=1e-2
276
+ )
277
+ scheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS)
278
+
279
+ # Training loop
280
+ for epoch in range(EPOCHS):
281
+ model.train()
282
+ total_loss = 0.0
283
+
284
+ pbar = tqdm(train_loader, desc=f"Epoch {epoch+1}/{EPOCHS}")
285
+ for images, labels in pbar:
286
+ images, labels = images.to(device), labels.to(device)
287
+
288
+ # Forward
289
+ optimizer.zero_grad()
290
+ logits = model(images)
291
+ loss = criterion(logits, labels)
292
+
293
+ # Backward
294
+ loss.backward()
295
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
296
+ optimizer.step()
297
+
298
+ total_loss += loss.item()
299
+ pbar.set_postfix({"loss": f"{total_loss / (pbar.n + 1):.4f}"})
300
+
301
+ scheduler.step()
302
+
303
+ # Save checkpoint
304
+ torch.save({
305
+ "epoch": epoch,
306
+ "model_state_dict": model.state_dict(),
307
+ "optimizer_state_dict": optimizer.state_dict(),
308
+ }, f"fine_tuned_epoch_{epoch}.pt")
309
+
310
+ print("✅ Fine-tuning complete")
311
+ ```
312
+
313
+ ### Run Fine-Tuning
314
+
315
+ ```bash
316
+ python fine_tune.py
317
+ ```
318
+
319
+ ### Use Fine-Tuned Model
320
+
321
+ ```python
322
+ import torch
323
+ from model import build_model
324
+
325
+ # Load fine-tuned weights
326
+ model = build_model()
327
+ checkpoint = torch.load("fine_tuned_epoch_19.pt")
328
+ model.load_state_dict(checkpoint["model_state_dict"])
329
+ model.eval()
330
+
331
+ # Use for inference
332
+ # ... same as normal inference
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Model Modifications
338
+
339
+ ### Change Number of Output Classes
340
+
341
+ ```python
342
+ # model.py
343
+ def build_model(num_classes=50): # Change from 45 to 50
344
+ model = models.efficientnet_b4(weights=...)
345
+ in_features = model.classifier[1].in_features
346
+ model.classifier = nn.Sequential(
347
+ nn.Dropout(p=0.4),
348
+ nn.Linear(in_features, num_classes), # 50 outputs
349
+ )
350
+ return model
351
+ ```
352
+
353
+ ### Add Dropout Regularization
354
+
355
+ ```python
356
+ # Increase dropout
357
+ nn.Dropout(p=0.6) # from 0.4
358
+
359
+ # Or add dropout to backbone
360
+ for module in model.features.modules():
361
+ if isinstance(module, nn.Dropout):
362
+ module.p = 0.3
363
+ ```
364
+
365
+ ### Freeze Backbone for Transfer Learning
366
+
367
+ ```python
368
+ # Freeze all backbone parameters
369
+ for param in model.features.parameters():
370
+ param.requires_grad = False
371
+
372
+ # Only head is trainable
373
+ for param in model.classifier.parameters():
374
+ param.requires_grad = True
375
+ ```
376
+
377
+ ### Use Different Backbone
378
+
379
+ ```python
380
+ # Try EfficientNet-B3 (smaller)
381
+ import torchvision.models as models
382
+
383
+ model = models.efficientnet_b3(weights=models.EfficientNet_B3_Weights.IMAGENET1K_V1)
384
+ in_features = model.classifier[1].in_features # 1536 for B3
385
+ model.classifier = nn.Sequential(
386
+ nn.Dropout(p=0.4),
387
+ nn.Linear(in_features, 45),
388
+ )
389
+ ```
390
+
391
+ ### Add Custom Layers
392
+
393
+ ```python
394
+ class CustomModel(nn.Module):
395
+ def __init__(self):
396
+ super().__init__()
397
+ self.backbone = models.efficientnet_b4(weights=...)
398
+ self.features_dim = 1792
399
+
400
+ # Add custom layers
401
+ self.classifier = nn.Sequential(
402
+ nn.Linear(self.features_dim, 512),
403
+ nn.ReLU(),
404
+ nn.BatchNorm1d(512),
405
+ nn.Dropout(p=0.4),
406
+
407
+ nn.Linear(512, 256),
408
+ nn.ReLU(),
409
+ nn.BatchNorm1d(256),
410
+ nn.Dropout(p=0.4),
411
+
412
+ nn.Linear(256, 45),
413
+ )
414
+
415
+ def forward(self, x):
416
+ x = self.backbone.features(x)
417
+ x = nn.functional.adaptive_avg_pool2d(x, 1)
418
+ x = x.flatten(1)
419
+ x = self.classifier(x)
420
+ return x
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Contributing
426
+
427
+ ### Code Style
428
+
429
+ ```python
430
+ # Follow PEP 8
431
+ # - Use 4 spaces for indentation
432
+ # - Max line length: 88 characters
433
+ # - Use type hints
434
+
435
+ def load_model(checkpoint_path: str) -> torch.nn.Module:
436
+ """Load model from checkpoint."""
437
+ pass
438
+ ```
439
+
440
+ ### Create Pull Request
441
+
442
+ 1. Fork repository
443
+ 2. Create feature branch: `git checkout -b feature/my-feature`
444
+ 3. Make changes and test
445
+ 4. Commit: `git commit -m "Add feature description"`
446
+ 5. Push: `git push origin feature/my-feature`
447
+ 6. Create PR on GitHub
448
+
449
+ ### Testing
450
+
451
+ ```python
452
+ # test_model.py
453
+ import torch
454
+ from model import build_model
455
+
456
+ def test_model_output_shape():
457
+ model = build_model()
458
+ model.eval()
459
+
460
+ # Test input
461
+ x = torch.randn(1, 3, 384, 384)
462
+
463
+ with torch.no_grad():
464
+ output = model(x)
465
+
466
+ assert output.shape == (1, 45), f"Expected (1, 45), got {output.shape}"
467
+ print("✅ Test passed")
468
+
469
+ if __name__ == "__main__":
470
+ test_model_output_shape()
471
+ ```
472
+
473
+ Run tests:
474
+ ```bash
475
+ python -m pytest test_model.py
476
+ ```
477
+
478
+ ---
479
+
480
+ ## Troubleshooting
481
+
482
+ ### Model weights mismatch
483
+
484
+ ```
485
+ RuntimeError: Error(s) in loading state_dict...
486
+ ```
487
+
488
+ **Solution:** Ensure model architecture matches checkpoint:
489
+ ```python
490
+ model = build_model(num_classes=45) # Must match saved checkpoint
491
+ ```
492
+
493
+ ### Out of memory during fine-tuning
494
+
495
+ **Solution:** Reduce batch size or image size:
496
+ ```python
497
+ BATCH_SIZE = 4 # or lower
498
+ IMG_SIZE = 256 # instead of 384
499
+ ```
500
+
501
+ ### Loss not decreasing
502
+
503
+ **Solution:** Check learning rate and data:
504
+ ```python
505
+ LR = 1e-4 # Increase if too low
506
+ # Verify data loading works
507
+ for img, label in train_loader:
508
+ print(img.shape, label.shape)
509
+ break
510
+ ```
511
+
512
+ ---
513
+
514
+ ## Resources
515
+
516
+ - **PyTorch Docs:** https://pytorch.org/docs
517
+ - **EfficientNet Paper:** https://arxiv.org/abs/1905.11946
518
+ - **Transfer Learning:** https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html
519
+
520
+ ---
521
+
522
+ **Last Updated:** February 22, 2026
523
+ **Status:** Production Ready ✅