Views
No views yet
1class EfficientNetSkinLesionClassifier(nn.Module, PyTorchModelHubMixin):
2 """
3 PytorchModelHubMixin adds push to Hugging Face Hub
4
5 See: https://huggingface.co/docs/hub/models-uploading#upload-a-pytorch-model-using-huggingfacehub
6 """
7 def __init__(self, num_classes=1):
8 super(EfficientNetSkinLesionClassifier, self).__init__()
9
10 # Step 1: Initialize backbone with the best available weights
11 base_weights = EfficientNet_V2_M_Weights.DEFAULT
12 self.backbone = efficientnet_v2_m(weights=base_weights)
13
14 # Freeze entire backbone
15 for param in self.backbone.parameters():
16 param.requires_grad = False
17
18 # Unfreeze ONLY the last block (low level feature detection is fine)
19 for param in self.backbone.features[-1].parameters():
20 param.requires_grad = True
21
22 # Step 3: Replace the original classification head with a new one
23 # The original classifier is called self.backbone.classifier
24 # See: https://docs.pytorch.org/vision/main/_modules/torchvision/models/efficientnet.html#EfficientNet_V2_M_Weights
25
26 feat_dim = self.backbone.classifier[1].in_features # Access the in_features of the final Linear layer
27
28 # Same classifier format as original
29 # Just switiching from ImageNet 1k classes to our binary classification
30 # Dropout is 0.3 in pytorch implementation
31 self.backbone.classifier = nn.Sequential(
32 nn.Dropout(p=0.3, inplace=True),
33 nn.Linear(feat_dim, num_classes)
34 )
35
36 def forward(self, x):
37 out = self.backbone(x)
38
39 return out
40
41from huggingface_hub import hf_hub_download
42
43weights_path = hf_hub_download(
44 repo_id="avanishd/efficient-net-v2-m-finetuned-skin-lesion-classification",
45 filename="model.safetensors"
46 )
47
48from safetensors.torch import load_model
49
50model = EfficientNetSkinLesionClassifier()
51load_model(model, filename=weights_path, strict=True)
52
53model.to(device) # Don't forget to put on GPU
54
55model.eval() # Set model to evaluation mode
56
57# Example with PH2 Dataset
58
59class PH2Dataset(Dataset):
60 """
61 Dataset for PH2 images, which are in png format.
62
63 PH2 contains skin lesions images classified as
64
65 - Common Nevus (benign)
66 - Atypical Nevus (benign)
67 - Melanoma (malignant)
68
69 No need for is real label here, since this is purely for testing
70 """
71
72 def __init__(self, dir_path, metadata, transform=None):
73 super(PH2Dataset, self).__init__()
74
75 self.dir_path = dir_path
76 self.transform = transform
77
78 self.image_files = [os.path.join(dir_path, f) for f in os.listdir(dir_path)
79 if f.lower().endswith(('.jpg', '.jpeg', '.png'))]
80
81 # Load metadata w/ polars (only 2 columns)
82 self.metadata = pl.read_csv(metadata)
83
84 self.diagnostic_mapping = {
85 "Common Nevus": 0,
86 "Atypical Nevus": 0,
87 "Melanoma": 1,
88 }
89
90 def __len__(self):
91 return len(self.image_files)
92
93 def __getitem__(self, idx):
94 # The image name in the metadata csv are like IMD003
95 image_id = self.image_files[idx].split('/')[-1].split('.')[0]
96
97 # Still need the entire path to open the image
98 image = Image.open(self.image_files[idx]).convert('RGB')
99
100 if self.transform: # Apply transform if it exists
101 image = self.transform(image)
102
103 diagnosis = self.metadata.filter(pl.col("image_name") == image_id).select("diagnosis").item()
104
105 label = torch.tensor(self.diagnostic_mapping[diagnosis], dtype=torch.int16)
106
107 return image, label
108
109
110transform = transforms.Compose([
111 transforms.ToTensor(),
112 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # Image net mean and std
113 transforms.Resize((224, 224)), # Dimensions for Efficient Net v2
114])
115
116ph_2_images = "/content/data/ph2_data/images"
117ph_2_metadata = "/content/data/ph2_data/ph_2_dataset.csv"
118
119ex_dataset = PH2Dataset(ph_2_images, ph_2_metadata, transform)
120
121ex_loader = DataLoader(ex_dataset, batch_size=64, shuffle=False)
122
123for (images, labels) in test_loader:
124 images = images.to(device)
125 labels = labels.to(device)
126 output = model(images)
127
128 y_pred_prob = torch.sigmoid(output).cpu().numpy().ravel()
129 y_pred = np.where(y_pred_prob < 0.5, 0, 1)
130
131 return y_pred
132| Training Loss | Epoch | Step |
|---|---|---|
| 0.4121 | 1 | 100 |
| 0.2303 | 1 | 200 |
| 0.7978 | 1 | 300 |
| 0.5703 | 1 | 400 |
| 0.3542 | 1 | 500 |
| 0.5727 | 1 | 600 |
| 0.4968 | 1 | 700 |
| 0.2870 | 1 | 800 |
| 0.5946 | 1 | 900 |
| 0.1645 | 1 | 1000 |
| 0.3951 | 1 | 1100 |
| 0.4131 | 1 | 1200 |
| 0.1167 | 1 | 1300 |
| 0.1780 | 1 | 1400 |
| 0.3001 | 1 | 1500 |
| 0.5137 | 1 | 1600 |
| 0.1442 | 1 | 1700 |
| 0.2861 | 1 | 1800 |
| 1.1042 | 1 | 1900 |
| 0.6788 | 1 | 2000 |
| 0.3203 | 1 | 2100 |
| 0.3083 | 1 | 2200 |
| 0.6786 | 1 | 2300 |
| 0.7330 | 1 | 2400 |
| 3.2796 | 1 | 2500 |
| 0.1256 | 1 | 2600 |
| 0.5112 | 1 | 2700 |
| 0.5217 | 1 | 2800 |
| 0.2267 | 1 | 2900 |
| 0.2648 | 1 | 3000 |
| 0.4319 | 1 | 3100 |
| 0.1849 | 1 | 3200 |
| 0.1746 | 1 | 3300 |
| 0.2552 | 1 | 3400 |
| 0.1613 | 1 | 3500 |
| 0.4685 | 1 | 3600 |
| 0.1714 | 1 | 3700 |
| 0.2739 | 1 | 3800 |
| 0.2142 | 1 | 3900 |
| 0.2366 | 1 | 4000 |
| 0.1659 | 1 | 4100 |
| 0.1367 | 1 | 4200 |
| 0.3813 | 1 | 4300 |
| 0.3124 | 1 | 4400 |
| 0.3023 | 1 | 4500 |
| 0.2140 | 1 | 4600 |
| 0.9778 | 1 | 4700 |
| 0.3501 | 1 | 4800 |
| 0.2174 | 1 | 4900 |
| 0.3568 | 1 | 5000 |
| 0.0863 | 1 | 5100 |
| 6.8996 | 1 | 5200 |