Views
No views yet
| Split | Accuracy | F1-Score (Smoking) |
|---|---|---|
| Validation | 94.44% | - |
| Test | 89.73% | 89.96% |
1pip install torch torchvision pillow
2Load Model
3pythonimport torch
4import torch.nn as nn
5from torchvision import models
6from torchvision.models import ResNet34_Weights
7from PIL import Image
8import torchvision.transforms as transforms
9
10# Define LoRA Layer
11class LoRALayer(nn.Module):
12 def __init__(self, original_layer, rank=8):
13 super().__init__()
14 self.original_layer = original_layer
15 self.rank = rank
16
17 out_channels = original_layer.out_channels
18 in_channels = original_layer.in_channels
19 kernel_size = original_layer.kernel_size
20
21 self.lora_A = nn.Parameter(
22 torch.randn(rank, in_channels, *kernel_size) * 0.01
23 )
24 self.lora_B = nn.Parameter(
25 torch.zeros(out_channels, rank, 1, 1)
26 )
27
28 self.original_layer.weight.requires_grad = False
29 if self.original_layer.bias is not None:
30 self.original_layer.bias.requires_grad = False
31
32 def forward(self, x):
33 original_output = self.original_layer(x)
34 lora_output = nn.functional.conv2d(
35 x, self.lora_A,
36 stride=self.original_layer.stride,
37 padding=self.original_layer.padding
38 )
39 lora_output = nn.functional.conv2d(lora_output, self.lora_B)
40 return original_output + lora_output
41
42def apply_lora_to_model(model, rank=8):
43 for param in model.parameters():
44 param.requires_grad = False
45
46 for param in model.fc.parameters():
47 param.requires_grad = True
48
49 for block in model.layer3:
50 if hasattr(block, 'conv1'):
51 block.conv1 = LoRALayer(block.conv1, rank=rank)
52 if hasattr(block, 'conv2'):
53 block.conv2 = LoRALayer(block.conv2, rank=rank)
54
55 for block in model.layer4:
56 if hasattr(block, 'conv1'):
57 block.conv1 = LoRALayer(block.conv1, rank=rank)
58 if hasattr(block, 'conv2'):
59 block.conv2 = LoRALayer(block.conv2, rank=rank)
60
61 return model
62
63# Load model
64model = models.resnet34(weights=ResNet34_Weights.IMAGENET1K_V1)
65model.fc = nn.Linear(model.fc.in_features, 2)
66model = apply_lora_to_model(model, rank=8)
67
68# Load trained weights
69model.load_state_dict(torch.load('best_model.pth', map_location='cpu'))
70model.eval()
71
72# Preprocessing
73transform = transforms.Compose([
74 transforms.Resize((224, 224)),
75 transforms.ToTensor(),
76 transforms.Normalize(
77 mean=[0.485, 0.456, 0.406],
78 std=[0.229, 0.224, 0.225]
79 )
80])
81
82# Inference
83def predict(image_path):
84 image = Image.open(image_path).convert('RGB')
85 image_tensor = transform(image).unsqueeze(0)
86
87 with torch.no_grad():
88 outputs = model(image_tensor)
89 probs = torch.softmax(outputs, dim=1)
90 confidence, predicted = torch.max(probs, 1)
91
92 classes = ['Non-Smoker', 'Smoker']
93 return classes[predicted.item()], confidence.item() * 100
94
95# Example
96prediction, confidence = predict('image.jpg')
97print(f"{prediction} ({confidence:.1f}% confidence)")
98Training Details
99Dataset: 1,120 images from Kaggle Smoking Detection Dataset
100
101Training: 716 images (64%)
102Validation: 180 images (16%)
103Test: 224 images (20%)
104
105Hyperparameters:
106
107Learning Rate: 1e-4
108Optimizer: AdamW (weight decay: 1e-4)
109Batch Size: 32
110Epochs: 15
111LoRA Rank: 8
112
113Data Augmentation:
114
115Random horizontal flip (p=0.5)
116Random rotation (±10°)
117Color jitter (brightness, contrast, saturation)
118
119What is LoRA?
120LoRA (Low-Rank Adaptation) adds small trainable matrices to frozen pretrained weights:
121Output = W_frozen × input + (B × A) × input
122Where A and B are low-rank matrices (rank=8), adding only 2.14% trainable parameters while maintaining model capacity.
123Benefits:
124
125Prevents overfitting on small datasets
126Preserves pretrained ImageNet features
127Faster training and lower memory usage
128Easier deployment (smaller checkpoint files)
129
130Model Architecture
131ResNet34 (21.7M parameters)
132├── Frozen Layers (21.3M - 97.86%)
133│ ├── conv1, layer1, layer2
134│ └── Pretrained ImageNet weights
135└── Trainable Layers (465K - 2.14%)
136 ├── LoRA adapters on layer3 (6 blocks)
137 ├── LoRA adapters on layer4 (3 blocks)
138 └── Classification head fc (512 → 2)
139Limitations
140
141Trained on limited dataset (1,120 images)
142Low resolution images (250×250)
143May not generalize to all smoking scenarios
144Best for frontal/profile views with visible cigarettes
145
146Citation
147bibtex@misc{smoker-detection-lora,
148 author = {Noel Triguero},
149 title = {Smoker Detection with LoRA Fine-Tuning},
150 year = {2025},
151 publisher = {Hugging Face},
152 howpublished = {\url{https://huggingface.co/notrito/smoker-detection}}
153}
154References
155
156LoRA Paper - Hu et al., 2021
157Dataset - Sujay Kapadnis
158Training Notebook
159
160Contact
161
162Author: Noel Triguero
163Email: noel.triguero@gmail.com
164Kaggle: notrito