Spaces:
Sleeping
Sleeping
Upload parking_slot_classifier.py
Browse files
ai_model/parking_slot_classifier.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smart Parking Slot Classifier
|
| 3 |
+
A basic classification model to find the optimal parking spot based on:
|
| 4 |
+
- Proximity to destination
|
| 5 |
+
- Slot popularity (historical usage)
|
| 6 |
+
- User feedback scores
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 11 |
+
from sklearn.preprocessing import StandardScaler
|
| 12 |
+
import pickle
|
| 13 |
+
import json
|
| 14 |
+
from typing import List, Dict, Tuple
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ParkingSlotClassifier:
|
| 18 |
+
"""
|
| 19 |
+
Basic ML model to classify and rank parking slots based on multiple factors
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self):
|
| 23 |
+
self.model = RandomForestClassifier(
|
| 24 |
+
n_estimators=100,
|
| 25 |
+
max_depth=10,
|
| 26 |
+
random_state=42
|
| 27 |
+
)
|
| 28 |
+
self.scaler = StandardScaler()
|
| 29 |
+
self.is_trained = False
|
| 30 |
+
|
| 31 |
+
def calculate_proximity_score(self, slot_coords: Tuple[float, float],
|
| 32 |
+
user_coords: Tuple[float, float]) -> float:
|
| 33 |
+
"""
|
| 34 |
+
Calculate distance-based proximity score using Haversine formula
|
| 35 |
+
Returns score between 0-1 (1 being closest)
|
| 36 |
+
"""
|
| 37 |
+
lat1, lon1 = slot_coords
|
| 38 |
+
lat2, lon2 = user_coords
|
| 39 |
+
|
| 40 |
+
# Haversine formula
|
| 41 |
+
R = 6371 # Earth's radius in kilometers
|
| 42 |
+
|
| 43 |
+
lat1_rad = np.radians(lat1)
|
| 44 |
+
lat2_rad = np.radians(lat2)
|
| 45 |
+
delta_lat = np.radians(lat2 - lat1)
|
| 46 |
+
delta_lon = np.radians(lon2 - lon1)
|
| 47 |
+
|
| 48 |
+
a = np.sin(delta_lat/2)**2 + np.cos(lat1_rad) * np.cos(lat2_rad) * np.sin(delta_lon/2)**2
|
| 49 |
+
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))
|
| 50 |
+
distance = R * c
|
| 51 |
+
|
| 52 |
+
# Convert to score (inverse relationship - closer is better)
|
| 53 |
+
# Max distance considered: 5km
|
| 54 |
+
proximity_score = max(0, 1 - (distance / 5.0))
|
| 55 |
+
return proximity_score
|
| 56 |
+
|
| 57 |
+
def prepare_features(self, slots_data: List[Dict]) -> np.ndarray:
|
| 58 |
+
"""
|
| 59 |
+
Prepare feature matrix from slot data
|
| 60 |
+
Features:
|
| 61 |
+
1. Proximity score (0-1)
|
| 62 |
+
2. Average feedback score (0-5)
|
| 63 |
+
3. Popularity score (normalized booking count)
|
| 64 |
+
4. Availability score (1 if available, 0 if not)
|
| 65 |
+
5. Price factor (normalized)
|
| 66 |
+
"""
|
| 67 |
+
features = []
|
| 68 |
+
|
| 69 |
+
for slot in slots_data:
|
| 70 |
+
feature_vector = [
|
| 71 |
+
slot.get('proximity_score', 0.5),
|
| 72 |
+
slot.get('avg_feedback', 3.0) / 5.0, # Normalize to 0-1
|
| 73 |
+
slot.get('popularity_score', 0.5),
|
| 74 |
+
1.0 if slot.get('is_available', True) else 0.0,
|
| 75 |
+
slot.get('price_factor', 0.5)
|
| 76 |
+
]
|
| 77 |
+
features.append(feature_vector)
|
| 78 |
+
|
| 79 |
+
return np.array(features)
|
| 80 |
+
|
| 81 |
+
def train(self, training_data: List[Dict], labels: List[int]):
|
| 82 |
+
"""
|
| 83 |
+
Train the model with historical data
|
| 84 |
+
labels: 1 if slot was chosen and user was satisfied, 0 otherwise
|
| 85 |
+
"""
|
| 86 |
+
X = self.prepare_features(training_data)
|
| 87 |
+
X_scaled = self.scaler.fit_transform(X)
|
| 88 |
+
|
| 89 |
+
self.model.fit(X_scaled, labels)
|
| 90 |
+
self.is_trained = True
|
| 91 |
+
print(f"Model trained with {len(training_data)} samples")
|
| 92 |
+
|
| 93 |
+
def predict_best_slots(self, slots_data: List[Dict],
|
| 94 |
+
user_location: Tuple[float, float],
|
| 95 |
+
top_k: int = 3) -> List[Dict]:
|
| 96 |
+
"""
|
| 97 |
+
Predict and return top K best parking slots
|
| 98 |
+
"""
|
| 99 |
+
# Calculate proximity scores for all slots
|
| 100 |
+
for slot in slots_data:
|
| 101 |
+
slot_coords = (slot['latitude'], slot['longitude'])
|
| 102 |
+
slot['proximity_score'] = self.calculate_proximity_score(
|
| 103 |
+
slot_coords, user_location
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# If model is trained, use it for prediction
|
| 107 |
+
if self.is_trained:
|
| 108 |
+
X = self.prepare_features(slots_data)
|
| 109 |
+
X_scaled = self.scaler.transform(X)
|
| 110 |
+
|
| 111 |
+
# Get probability scores for positive class
|
| 112 |
+
scores = self.model.predict_proba(X_scaled)[:, 1]
|
| 113 |
+
else:
|
| 114 |
+
# Fallback to weighted scoring if model not trained
|
| 115 |
+
scores = self._calculate_weighted_scores(slots_data)
|
| 116 |
+
|
| 117 |
+
# Add scores to slots
|
| 118 |
+
for i, slot in enumerate(slots_data):
|
| 119 |
+
slot['recommendation_score'] = float(scores[i])
|
| 120 |
+
|
| 121 |
+
# Sort by score and return top K
|
| 122 |
+
sorted_slots = sorted(slots_data,
|
| 123 |
+
key=lambda x: x['recommendation_score'],
|
| 124 |
+
reverse=True)
|
| 125 |
+
|
| 126 |
+
return sorted_slots[:top_k]
|
| 127 |
+
|
| 128 |
+
def _calculate_weighted_scores(self, slots_data: List[Dict]) -> np.ndarray:
|
| 129 |
+
"""
|
| 130 |
+
Fallback weighted scoring when model is not trained
|
| 131 |
+
"""
|
| 132 |
+
scores = []
|
| 133 |
+
for slot in slots_data:
|
| 134 |
+
# Weights for different factors
|
| 135 |
+
score = (
|
| 136 |
+
slot.get('proximity_score', 0.5) * 0.4 + # 40% weight to proximity
|
| 137 |
+
(slot.get('avg_feedback', 3.0) / 5.0) * 0.3 + # 30% to feedback
|
| 138 |
+
slot.get('popularity_score', 0.5) * 0.2 + # 20% to popularity
|
| 139 |
+
(1.0 if slot.get('is_available', True) else 0.0) * 0.1 # 10% availability
|
| 140 |
+
)
|
| 141 |
+
scores.append(score)
|
| 142 |
+
|
| 143 |
+
return np.array(scores)
|
| 144 |
+
|
| 145 |
+
def save_model(self, filepath: str):
|
| 146 |
+
"""Save trained model to disk"""
|
| 147 |
+
model_data = {
|
| 148 |
+
'model': self.model,
|
| 149 |
+
'scaler': self.scaler,
|
| 150 |
+
'is_trained': self.is_trained
|
| 151 |
+
}
|
| 152 |
+
with open(filepath, 'wb') as f:
|
| 153 |
+
pickle.dump(model_data, f)
|
| 154 |
+
print(f"Model saved to {filepath}")
|
| 155 |
+
|
| 156 |
+
def load_model(self, filepath: str):
|
| 157 |
+
"""Load trained model from disk"""
|
| 158 |
+
with open(filepath, 'rb') as f:
|
| 159 |
+
model_data = pickle.load(f)
|
| 160 |
+
|
| 161 |
+
self.model = model_data['model']
|
| 162 |
+
self.scaler = model_data['scaler']
|
| 163 |
+
self.is_trained = model_data['is_trained']
|
| 164 |
+
print(f"Model loaded from {filepath}")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def generate_training_data(num_samples: int = 100) -> Tuple[List[Dict], List[int]]:
|
| 168 |
+
"""
|
| 169 |
+
Generate synthetic training data for initial model training
|
| 170 |
+
In production, this would come from real user feedback
|
| 171 |
+
"""
|
| 172 |
+
slots_data = []
|
| 173 |
+
labels = []
|
| 174 |
+
|
| 175 |
+
for i in range(num_samples):
|
| 176 |
+
# Generate random slot data
|
| 177 |
+
slot = {
|
| 178 |
+
'slot_id': f'SLOT_{i}',
|
| 179 |
+
'latitude': 28.6139 + np.random.uniform(-0.1, 0.1),
|
| 180 |
+
'longitude': 77.2090 + np.random.uniform(-0.1, 0.1),
|
| 181 |
+
'proximity_score': np.random.uniform(0, 1),
|
| 182 |
+
'avg_feedback': np.random.uniform(2, 5),
|
| 183 |
+
'popularity_score': np.random.uniform(0, 1),
|
| 184 |
+
'is_available': np.random.choice([True, False], p=[0.7, 0.3]),
|
| 185 |
+
'price_factor': np.random.uniform(0.3, 1.0)
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
# Generate label based on features (higher quality slots more likely to be chosen)
|
| 189 |
+
quality_score = (
|
| 190 |
+
slot['proximity_score'] * 0.4 +
|
| 191 |
+
(slot['avg_feedback'] / 5.0) * 0.3 +
|
| 192 |
+
slot['popularity_score'] * 0.2 +
|
| 193 |
+
(1.0 if slot['is_available'] else 0.0) * 0.1
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
label = 1 if quality_score > 0.6 else 0
|
| 197 |
+
|
| 198 |
+
slots_data.append(slot)
|
| 199 |
+
labels.append(label)
|
| 200 |
+
|
| 201 |
+
return slots_data, labels
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
if __name__ == "__main__":
|
| 205 |
+
# Example usage
|
| 206 |
+
print("Initializing Parking Slot Classifier...")
|
| 207 |
+
classifier = ParkingSlotClassifier()
|
| 208 |
+
|
| 209 |
+
# Generate and train with synthetic data
|
| 210 |
+
print("\nGenerating training data...")
|
| 211 |
+
training_data, labels = generate_training_data(200)
|
| 212 |
+
|
| 213 |
+
print("\nTraining model...")
|
| 214 |
+
classifier.train(training_data, labels)
|
| 215 |
+
|
| 216 |
+
# Example prediction
|
| 217 |
+
print("\nTesting prediction...")
|
| 218 |
+
test_slots = [
|
| 219 |
+
{
|
| 220 |
+
'slot_id': 'A1',
|
| 221 |
+
'latitude': 28.6139,
|
| 222 |
+
'longitude': 77.2090,
|
| 223 |
+
'avg_feedback': 4.5,
|
| 224 |
+
'popularity_score': 0.8,
|
| 225 |
+
'is_available': True,
|
| 226 |
+
'price_factor': 0.7
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
'slot_id': 'B2',
|
| 230 |
+
'latitude': 28.6150,
|
| 231 |
+
'longitude': 77.2100,
|
| 232 |
+
'avg_feedback': 3.2,
|
| 233 |
+
'popularity_score': 0.4,
|
| 234 |
+
'is_available': True,
|
| 235 |
+
'price_factor': 0.5
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
'slot_id': 'C3',
|
| 239 |
+
'latitude': 28.6145,
|
| 240 |
+
'longitude': 77.2095,
|
| 241 |
+
'avg_feedback': 4.8,
|
| 242 |
+
'popularity_score': 0.9,
|
| 243 |
+
'is_available': True,
|
| 244 |
+
'price_factor': 0.9
|
| 245 |
+
}
|
| 246 |
+
]
|
| 247 |
+
|
| 248 |
+
user_location = (28.6139, 77.2090)
|
| 249 |
+
best_slots = classifier.predict_best_slots(test_slots, user_location, top_k=3)
|
| 250 |
+
|
| 251 |
+
print("\nTop 3 Recommended Slots:")
|
| 252 |
+
for i, slot in enumerate(best_slots, 1):
|
| 253 |
+
print(f"{i}. {slot['slot_id']} - Score: {slot['recommendation_score']:.3f}")
|
| 254 |
+
|
| 255 |
+
# Save model
|
| 256 |
+
classifier.save_model('parking_model.pkl')
|