toderian commited on
Commit
b915bae
·
verified ·
1 Parent(s): 4e1d398

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. README.md +164 -0
  2. config.json +36 -0
  3. model.py +244 -0
  4. model.safetensors +3 -0
  5. pytorch_model.bin +3 -0
README.md ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - image-classification
5
+ - medical-imaging
6
+ - cervical-cancer
7
+ - pytorch
8
+ - safetensors
9
+ datasets:
10
+ - custom
11
+ metrics:
12
+ - accuracy
13
+ - f1
14
+ pipeline_tag: image-classification
15
+ library_name: pytorch
16
+ ---
17
+
18
+ # Cervical Type Classification Model
19
+
20
+ ## Model Description
21
+
22
+ This model classifies cervical images into 3 transformation zone types, which is important for colposcopy evaluation and cervical cancer screening.
23
+
24
+ | Label | Type | Description |
25
+ |-------|------|-------------|
26
+ | 0 | Type 1 | Transformation zone fully visible on ectocervix |
27
+ | 1 | Type 2 | Transformation zone partially visible (extends into endocervical canal) |
28
+ | 2 | Type 3 | Transformation zone not visible (entirely within endocervical canal) |
29
+
30
+ ## Model Architecture
31
+
32
+ Simple CNN with 4 convolutional layers:
33
+
34
+ ```
35
+ Input (256x256x3)
36
+
37
+ Conv2d(3→32) + BN + ReLU + MaxPool
38
+ Conv2d(32→64) + BN + ReLU + MaxPool
39
+ Conv2d(64→128) + BN + ReLU + MaxPool
40
+ Conv2d(128→256) + BN + ReLU + MaxPool
41
+
42
+ AdaptiveAvgPool2d(1)
43
+
44
+ FC(256→256) + ReLU + Dropout(0.4)
45
+ FC(256→128) + ReLU + Dropout(0.4)
46
+
47
+ FC(128→3) → Output
48
+ ```
49
+
50
+ **Parameters:** 488,451
51
+
52
+ ## Training
53
+
54
+ | Parameter | Value |
55
+ |-----------|-------|
56
+ | Learning Rate | 1e-4 |
57
+ | Batch Size | 32 |
58
+ | Dropout | 0.4 |
59
+ | Optimizer | Adam |
60
+ | Epochs | 50 |
61
+
62
+ ## Performance
63
+
64
+ | Metric | Value |
65
+ |--------|-------|
66
+ | **Validation Accuracy** | 61.69% |
67
+ | **Macro F1 Score** | 61.81% |
68
+
69
+ ### Per-Class F1 Scores
70
+
71
+ | Type | F1 Score |
72
+ |------|----------|
73
+ | Type 1 | 68.32% |
74
+ | Type 2 | 56.41% |
75
+ | Type 3 | 60.69% |
76
+
77
+ ## Usage
78
+
79
+ ### Installation
80
+
81
+ ```bash
82
+ pip install torch torchvision safetensors
83
+ ```
84
+
85
+ ### Quick Start
86
+
87
+ ```python
88
+ import torch
89
+ from PIL import Image
90
+ from torchvision import transforms
91
+
92
+ # Load model
93
+ from model import BaseCNN
94
+ model = BaseCNN.from_pretrained("./")
95
+ model.eval()
96
+
97
+ # Preprocess image
98
+ transform = transforms.Compose([
99
+ transforms.Resize((256, 256)),
100
+ transforms.ToTensor(),
101
+ ])
102
+
103
+ image = Image.open("cervical_image.jpg").convert("RGB")
104
+ input_tensor = transform(image).unsqueeze(0)
105
+
106
+ # Inference
107
+ with torch.no_grad():
108
+ output = model(input_tensor)
109
+ probabilities = torch.softmax(output, dim=1)
110
+ prediction = output.argmax(dim=1).item()
111
+
112
+ labels = ["Type 1", "Type 2", "Type 3"]
113
+ print(f"Prediction: {labels[prediction]}")
114
+ print(f"Confidence: {probabilities[0][prediction]:.2%}")
115
+ ```
116
+
117
+ ### Using with Hugging Face Hub
118
+
119
+ ```python
120
+ from huggingface_hub import hf_hub_download
121
+ import torch
122
+
123
+ # Download model files
124
+ model_path = hf_hub_download(repo_id="your-username/cervical-type-classifier", filename="model.safetensors")
125
+ config_path = hf_hub_download(repo_id="your-username/cervical-type-classifier", filename="config.json")
126
+
127
+ # Load using safetensors
128
+ from safetensors.torch import load_file
129
+ state_dict = load_file(model_path)
130
+
131
+ # Create model and load weights
132
+ from model import BaseCNN
133
+ import json
134
+
135
+ with open(config_path) as f:
136
+ config = json.load(f)
137
+
138
+ model = BaseCNN(**config['model_config'])
139
+ model.load_state_dict(state_dict)
140
+ model.eval()
141
+ ```
142
+
143
+ ## Limitations
144
+
145
+ - Model was trained on a specific dataset and may not generalize to all cervical imaging equipment
146
+ - Type 2 classification has lower accuracy (56.41% F1) as it represents an intermediate state
147
+ - Input images should be 256x256 RGB
148
+
149
+ ## Citation
150
+
151
+ If you use this model, please cite:
152
+
153
+ ```bibtex
154
+ @misc{cervical-type-classifier,
155
+ title={Cervical Type Classification Model},
156
+ author={Your Name},
157
+ year={2026},
158
+ howpublished={\url{https://huggingface.co/your-username/cervical-type-classifier}}
159
+ }
160
+ ```
161
+
162
+ ## License
163
+
164
+ MIT License
config.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "BaseCNN",
3
+ "model_config": {
4
+ "layers": [
5
+ 32,
6
+ 64,
7
+ 128,
8
+ 256
9
+ ],
10
+ "kernel": 3,
11
+ "padding": 1,
12
+ "stride": 1,
13
+ "batchnorm": true,
14
+ "bn_pre_activ": true,
15
+ "activation": "ReLU",
16
+ "dropout": 0.4,
17
+ "pool": true,
18
+ "fc_layers": [
19
+ 256,
20
+ 128
21
+ ],
22
+ "nr_classes": 3,
23
+ "in_channels": 3
24
+ },
25
+ "num_labels": 3,
26
+ "id2label": {
27
+ "0": "Type 1",
28
+ "1": "Type 2",
29
+ "2": "Type 3"
30
+ },
31
+ "label2id": {
32
+ "Type 1": 0,
33
+ "Type 2": 1,
34
+ "Type 3": 2
35
+ }
36
+ }
model.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Cervical Type Classification Model
3
+
4
+ This module contains the BaseCNN model for classifying cervical images
5
+ into 3 transformation zone types.
6
+
7
+ Usage:
8
+ from model import BaseCNN
9
+
10
+ # Load pretrained model
11
+ model = BaseCNN.from_pretrained("./")
12
+
13
+ # Or create from scratch
14
+ model = BaseCNN(
15
+ layers=[32, 64, 128, 256],
16
+ fc_layers=[256, 128],
17
+ nr_classes=3
18
+ )
19
+ """
20
+
21
+ import json
22
+ from pathlib import Path
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+
27
+ try:
28
+ from safetensors.torch import load_file, save_file
29
+ HAS_SAFETENSORS = True
30
+ except ImportError:
31
+ HAS_SAFETENSORS = False
32
+
33
+
34
+ class BaseCNN(nn.Module):
35
+ """
36
+ Simple CNN for cervical type classification.
37
+
38
+ Classifies cervical images into 3 transformation zone types:
39
+ - Type 1: Transformation zone fully visible on ectocervix
40
+ - Type 2: Transformation zone partially visible
41
+ - Type 3: Transformation zone not visible (within endocervical canal)
42
+
43
+ Args:
44
+ layers: List of output channels for each conv layer. Default: [32, 64, 128, 256]
45
+ kernel: Kernel size for conv layers. Default: 3
46
+ padding: Padding for conv layers. Default: 1
47
+ stride: Stride for conv layers. Default: 1
48
+ batchnorm: Whether to use batch normalization. Default: True
49
+ bn_pre_activ: Whether to apply BN before activation. Default: True
50
+ activation: Activation function name. Default: 'ReLU'
51
+ dropout: Dropout rate for FC layers. Default: 0.4
52
+ pool: Whether to use max pooling after each conv. Default: True
53
+ fc_layers: List of FC layer sizes. Default: [256, 128]
54
+ nr_classes: Number of output classes. Default: 3
55
+ in_channels: Number of input channels. Default: 3
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ layers: list = None,
61
+ kernel: int = 3,
62
+ padding: int = 1,
63
+ stride: int = 1,
64
+ batchnorm: bool = True,
65
+ bn_pre_activ: bool = True,
66
+ activation: str = 'ReLU',
67
+ dropout: float = 0.4,
68
+ pool: bool = True,
69
+ fc_layers: list = None,
70
+ nr_classes: int = 3,
71
+ in_channels: int = 3,
72
+ ):
73
+ super().__init__()
74
+
75
+ # Store config for serialization
76
+ self.config = {
77
+ 'layers': layers or [32, 64, 128, 256],
78
+ 'kernel': kernel,
79
+ 'padding': padding,
80
+ 'stride': stride,
81
+ 'batchnorm': batchnorm,
82
+ 'bn_pre_activ': bn_pre_activ,
83
+ 'activation': activation,
84
+ 'dropout': dropout,
85
+ 'pool': pool,
86
+ 'fc_layers': fc_layers or [256, 128],
87
+ 'nr_classes': nr_classes,
88
+ 'in_channels': in_channels,
89
+ }
90
+
91
+ layers = self.config['layers']
92
+ fc_layers = self.config['fc_layers']
93
+
94
+ # Activation function
95
+ activation_fn = getattr(nn, activation)
96
+
97
+ # Build convolutional layers (ModuleList to match original)
98
+ self.conv_layers = nn.ModuleList()
99
+ prev_channels = in_channels
100
+
101
+ for out_channels in layers:
102
+ self.conv_layers.append(
103
+ nn.Conv2d(prev_channels, out_channels, kernel, stride, padding)
104
+ )
105
+ if batchnorm and bn_pre_activ:
106
+ self.conv_layers.append(nn.BatchNorm2d(out_channels))
107
+ self.conv_layers.append(activation_fn())
108
+ if batchnorm and not bn_pre_activ:
109
+ self.conv_layers.append(nn.BatchNorm2d(out_channels))
110
+ if pool:
111
+ self.conv_layers.append(nn.MaxPool2d(2, 2))
112
+ prev_channels = out_channels
113
+
114
+ # Global average pooling
115
+ self.adaptive_pool = nn.AdaptiveAvgPool2d(1)
116
+
117
+ # Build fully connected layers (ModuleList to match original)
118
+ self.fc_layers = nn.ModuleList()
119
+ prev_features = layers[-1]
120
+
121
+ for fc_size in fc_layers:
122
+ self.fc_layers.append(nn.Linear(prev_features, fc_size))
123
+ self.fc_layers.append(activation_fn())
124
+ self.fc_layers.append(nn.Dropout(dropout))
125
+ prev_features = fc_size
126
+
127
+ # Final classifier (separate, to match original)
128
+ self.classifier = nn.Linear(prev_features, nr_classes)
129
+
130
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
131
+ """
132
+ Forward pass.
133
+
134
+ Args:
135
+ x: Input tensor of shape (batch_size, 3, 256, 256)
136
+
137
+ Returns:
138
+ Logits tensor of shape (batch_size, num_classes)
139
+ """
140
+ for layer in self.conv_layers:
141
+ x = layer(x)
142
+
143
+ x = self.adaptive_pool(x)
144
+ x = x.view(x.size(0), -1)
145
+
146
+ for layer in self.fc_layers:
147
+ x = layer(x)
148
+
149
+ x = self.classifier(x)
150
+ return x
151
+
152
+ @classmethod
153
+ def from_pretrained(cls, model_path: str, device: str = 'cpu') -> 'BaseCNN':
154
+ """
155
+ Load a pretrained model from a directory.
156
+
157
+ Args:
158
+ model_path: Path to directory containing model files
159
+ device: Device to load model on ('cpu' or 'cuda')
160
+
161
+ Returns:
162
+ Loaded model in eval mode
163
+ """
164
+ model_path = Path(model_path)
165
+
166
+ # Load config
167
+ config_path = model_path / 'config.json'
168
+ with open(config_path, 'r') as f:
169
+ config = json.load(f)
170
+
171
+ # Create model
172
+ model = cls(**config['model_config'])
173
+
174
+ # Load weights (prefer safetensors)
175
+ safetensors_path = model_path / 'model.safetensors'
176
+ pytorch_path = model_path / 'pytorch_model.bin'
177
+
178
+ if safetensors_path.exists() and HAS_SAFETENSORS:
179
+ state_dict = load_file(str(safetensors_path), device=device)
180
+ elif pytorch_path.exists():
181
+ state_dict = torch.load(pytorch_path, map_location=device, weights_only=True)
182
+ else:
183
+ raise FileNotFoundError(f"No model weights found in {model_path}")
184
+
185
+ model.load_state_dict(state_dict)
186
+ model.to(device)
187
+ model.eval()
188
+ return model
189
+
190
+ def save_pretrained(self, save_path: str) -> None:
191
+ """
192
+ Save model in Hugging Face compatible format.
193
+
194
+ Args:
195
+ save_path: Directory to save model files
196
+ """
197
+ save_path = Path(save_path)
198
+ save_path.mkdir(parents=True, exist_ok=True)
199
+
200
+ # Save config
201
+ config = {
202
+ 'model_type': 'BaseCNN',
203
+ 'model_config': self.config,
204
+ 'num_labels': self.config['nr_classes'],
205
+ 'id2label': {
206
+ '0': 'Type 1',
207
+ '1': 'Type 2',
208
+ '2': 'Type 3'
209
+ },
210
+ 'label2id': {
211
+ 'Type 1': 0,
212
+ 'Type 2': 1,
213
+ 'Type 3': 2
214
+ }
215
+ }
216
+ with open(save_path / 'config.json', 'w') as f:
217
+ json.dump(config, f, indent=2)
218
+
219
+ # Save weights
220
+ state_dict = {k: v.contiguous() for k, v in self.state_dict().items()}
221
+
222
+ # SafeTensors format (recommended)
223
+ if HAS_SAFETENSORS:
224
+ save_file(state_dict, str(save_path / 'model.safetensors'))
225
+
226
+ # PyTorch format (backup)
227
+ torch.save(state_dict, save_path / 'pytorch_model.bin')
228
+
229
+
230
+ # Label mappings
231
+ ID2LABEL = {0: 'Type 1', 1: 'Type 2', 2: 'Type 3'}
232
+ LABEL2ID = {'Type 1': 0, 'Type 2': 1, 'Type 3': 2}
233
+
234
+
235
+ if __name__ == '__main__':
236
+ # Quick test
237
+ model = BaseCNN()
238
+ print(f"Model parameters: {sum(p.numel() for p in model.parameters()):,}")
239
+
240
+ # Test forward pass
241
+ x = torch.randn(1, 3, 256, 256)
242
+ y = model(x)
243
+ print(f"Input shape: {x.shape}")
244
+ print(f"Output shape: {y.shape}")
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:beb3e17da6b94596232aa18078b9d22872f4711c7c1ef21a35f3277175d14063
3
+ size 1960588
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3d88b2345cde9dcdc7fc2b8ba76edb2c64abfbc274f320bd55ad9e12801c9b00
3
+ size 1969453