Skip to content

Commit e3bfb9a

Browse files
authored
Merge pull request #69 from Flippchen/dev
Dev
2 parents 823eb16 + 97598c5 commit e3bfb9a

File tree

5 files changed

+151
-25
lines changed

5 files changed

+151
-25
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,26 @@ To see the architecture of the Local App UI, click the arrow below.
6363
<img alt="Architecture of the Web UI" src="assets/architecture.png" height="400">
6464
</details>
6565

66+
### Experimental Ensemble Models 🧪
67+
To improve accuracy and prediction capabilities, I have experimented with two ensemble models. Ensemble models combine predictions from multiple models to give a final verdict, which often results in better prediction accuracy. These ensemble models are still in the experimental phase and can be found in the [ensemble](web_ui/main_ensemble.py).
68+
69+
1. Car Type Ensemble Model (weighted average)
70+
- Description: Trained two models on the same objective. Each with its on strengths and weaknesses. The results get weighted (correct weights through intensive testing). The models also differ in model architecture.
71+
- Achievements:
72+
- More balanced predictions
73+
- Better prediction accuracy
74+
2. Car Type hierarchy with car series (specific car type) (weighted average)
75+
- Description: Created a hierarchy of models. The first models predicts the car type. The second model predicts the car series. After this the results get aligned with the car type model.
76+
- Achievements:
77+
- much less outlier predictions
78+
- better prediction accuracy
79+
80+
They can be found at [PorscheInsight-Ensemble](https://classify.autos/classify-ensemble).
81+
6682
### ToDos
6783

6884
- [ ] Experiment with EfficientNet-Lite4
6985
- [ ] Retrain all models with better dataset
70-
- [ ] Build an ensemble model with car_type and car_series
7186
- [ ] Switch to Google Cloud function/use S3 bucket/compress image
7287
- [ ] Improve pre_filter model/Use Grounded SAM
7388
- [ ] Add Taycans to images/models
@@ -99,6 +114,7 @@ To see the architecture of the Local App UI, click the arrow below.
99114
- [x] Try autokeras
100115
- [x] Improve model predictions overall
101116
- [x] Evaluate feature engineering/ More data augmentation
117+
- [x] Build an ensemble model with car_type and car_series
102118

103119
</details>
104120

utilities/class_names.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,47 @@
1111
'Cayman_2013', 'Cayman_2014', 'Cayman_2015', 'Cayman_2016', 'Macan_2014', 'Macan_2015', 'Macan_2016', 'Macan_2017', 'Macan_2018', 'Macan_2019', 'Panamera_2009', 'Panamera_2010',
1212
'Panamera_2011', 'Panamera_2012', 'Panamera_2013', 'Panamera_2014', 'Panamera_2015', 'Panamera_2016', 'Panamera_2017', 'Panamera_2018', 'Panamera_2019']
1313

14-
MODEL_VARIANTS = ['911_930', '911_964', '911_991', '911_991_Facelift', '911_992', '911_996_Facelift', '911_997', '911_997_Facelift', '918_Spyderr_1_Generation', 'Boxster_981', 'Boxster_982',
15-
'Boxster_986', 'Boxster_987_Facelift', 'Boxter_986_Facelift', 'Boxter_987', 'Carrera_GT_980', 'Cayenne_955', 'Cayenne_955_Facelift', 'Cayenne_958', 'Cayenne_958_Facelift',
14+
MODEL_VARIANTS = ['911_930', '911_964', '911_991', '911_991_Facelift', '911_992', '911_996_Facelift', '911_997', '911_997_Facelift', '918_Spyder_1_Generation', 'Boxster_981', 'Boxster_982',
15+
'Boxster_986', 'Boxster_987_Facelift', 'Boxster_986_Facelift', 'Boxter_987', 'Carrera_GT_980', 'Cayenne_955', 'Cayenne_955_Facelift', 'Cayenne_958', 'Cayenne_958_Facelift',
1616
'Cayenne_9YA', 'Cayman_981C', 'Cayman_982C', 'Cayman_987C', 'Cayman_987_Facelift', 'Macan_95B', 'Macan_95B_Facelift', 'Panamera_970', 'Panamera_970_Facelift', 'Panamera_971']
1717

1818
PRE_FILTER = ['other', 'other_car_brand', 'porsche']
19+
HIERARCHY = {
20+
'911': [
21+
'911_930', '911_964', '911_991', '911_991_Facelift', '911_992',
22+
'911_996_Facelift', '911_997', '911_997_Facelift'
23+
],
24+
'918': [
25+
'918_Spyder_1_Generation'
26+
],
27+
'Boxster': [
28+
'Boxster_981', 'Boxster_982', 'Boxster_986', 'Boxster_987_Facelift',
29+
'Boxster_986_Facelift', 'Boxster_987'
30+
],
31+
'Carrera GT': [
32+
'Carrera_GT_980'
33+
],
34+
'Cayenne': [
35+
'Cayenne_955', 'Cayenne_955_Facelift', 'Cayenne_958',
36+
'Cayenne_958_Facelift', 'Cayenne_9YA'
37+
],
38+
'Cayman': [
39+
'Cayman_981C', 'Cayman_982C', 'Cayman_987C', 'Cayman_987_Facelift'
40+
],
41+
'Macan': [
42+
'Macan_95B', 'Macan_95B_Facelift'
43+
],
44+
'Panamera': [
45+
'Panamera_970', 'Panamera_970_Facelift', 'Panamera_971'
46+
],
47+
'718 Cayman': [
48+
'Cayman_981C', 'Cayman_982C', 'Cayman_987C', 'Cayman_987_Facelift'
49+
],
50+
'718 Boxster': [
51+
'Boxster_981', 'Boxster_982', 'Boxster_986', 'Boxster_987_Facelift',
52+
'Boxster_986_Facelift', 'Boxster_987'
53+
]
54+
}
1955

2056

2157
def get_classes_for_model(name: str) -> List[str]:

web_ui/build_requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
eel~=0.16.0
33
pooch==1.7.0
44
numpy==1.23.5
5-
Pillow==9.5.0
5+
Pillow==10.0.0
66
rembg~=2.0.32
7-
onnxruntime==1.14.1
7+
onnxruntime==1.15.1

web_ui/main.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,27 @@ def prepare_image(image_data: Image, target_size: Tuple, remove_background: bool
116116
return img_array, mask
117117

118118

119-
def get_top_3_predictions(prediction: np.ndarray, model_name: str) -> List[Tuple[str, float]]:
119+
def get_top_n_predictions(prediction: np.ndarray, model_name: str, n: int = 3) -> List[Tuple[str, float]]:
120120
"""
121-
Get top 3 predictions from the model output.
121+
Get top n predictions from the model output.
122122
123123
Args:
124124
prediction (np.ndarray): Output prediction from a model.
125125
model_name (str): Name of the model that produced the prediction.
126+
n (int, optional): Number of top predictions to retrieve. Defaults to 3.
126127
127128
Returns:
128-
List[Tuple[str, float]]: A list of top 3 predictions along with their respective scores.
129+
List[Tuple[str, float]]: A list of top n predictions along with their respective scores.
129130
"""
130131

131-
top_3 = prediction[0].argsort()[-3:][::-1]
132+
# Ensure that n does not exceed the total number of classes
133+
n = min(n, prediction[0].shape[0])
134+
135+
top_n_indices = prediction[0].argsort()[-n:][::-1]
132136
classes = get_classes_for_model(model_name)
133-
top_3 = [(classes[i], round(prediction[0][i] * 100, 2)) for i in top_3]
134-
return top_3
137+
top_n_predictions = [(classes[i], round(prediction[0][i] * 100, 2)) for i in top_n_indices]
138+
139+
return top_n_predictions
135140

136141

137142
def get_pre_filter_prediction(image_data: np.ndarray, model_name: str):
@@ -153,7 +158,7 @@ def get_pre_filter_prediction(image_data: np.ndarray, model_name: str):
153158
models[model_name] = load_model(model_name)
154159
input_name = models[model_name].get_inputs()[0].name
155160
prediction = models[model_name].run(None, {input_name: image_data})
156-
filter_names = get_top_3_predictions(prediction[0], "pre_filter")
161+
filter_names = get_top_n_predictions(prediction[0], "pre_filter")
157162
return filter_names
158163

159164

@@ -215,7 +220,7 @@ def classify_image(image_data: str, model_name: str, show_mask: bool = False) ->
215220
prediction = model.run(None, {input_name: filter_image})
216221

217222
# Retrieving the top 3 predictions
218-
top_3_predictions = get_top_3_predictions(prediction[0], model_name)
223+
top_3_predictions = get_top_n_predictions(prediction[0], model_name)
219224

220225
return (top_3_predictions, mask_base64) if show_mask else [top_3_predictions]
221226

web_ui/main_ensemble.py

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from io import BytesIO
1515
from PIL import Image
1616

17-
from utilities.class_names import get_classes_for_model
17+
from utilities.class_names import get_classes_for_model, HIERARCHY
1818
from utilities.prepare_images import replace_background, resize_and_pad_image, fix_image, convert_mask
1919
import pooch
2020
from rembg import new_session
@@ -25,7 +25,6 @@
2525
"all_specific_model_variants": None,
2626
"specific_model_variants": None,
2727
"pre_filter": None,
28-
"car_type_2": None,
2928
}
3029

3130
# Initiate session
@@ -142,22 +141,27 @@ def prepare_image(image_data: Image, target_size: Tuple, remove_background: bool
142141
return img_array, mask
143142

144143

145-
def get_top_3_predictions(prediction: np.ndarray, model_name: str) -> List[Tuple[str, float]]:
144+
def get_top_n_predictions(prediction: np.ndarray, model_name: str, n: int = 3) -> List[Tuple[str, float]]:
146145
"""
147-
Get top 3 predictions from the model output.
146+
Get top n predictions from the model output.
148147
149148
Args:
150149
prediction (np.ndarray): Output prediction from a model.
151150
model_name (str): Name of the model that produced the prediction.
151+
n (int, optional): Number of top predictions to retrieve. Defaults to 3.
152152
153153
Returns:
154-
List[Tuple[str, float]]: A list of top 3 predictions along with their respective scores.
154+
List[Tuple[str, float]]: A list of top n predictions along with their respective scores.
155155
"""
156156

157-
top_3 = prediction[0].argsort()[-3:][::-1]
157+
# Ensure that n does not exceed the total number of classes
158+
n = min(n, prediction[0].shape[0])
159+
160+
top_n_indices = prediction[0].argsort()[-n:][::-1]
158161
classes = get_classes_for_model(model_name)
159-
top_3 = [(classes[i], round(prediction[0][i] * 100, 2)) for i in top_3]
160-
return top_3
162+
top_n_predictions = [(classes[i], round(prediction[0][i] * 100, 2)) for i in top_n_indices]
163+
164+
return top_n_predictions
161165

162166

163167
def get_pre_filter_prediction(image_data: np.ndarray, model_name: str):
@@ -179,7 +183,7 @@ def get_pre_filter_prediction(image_data: np.ndarray, model_name: str):
179183
models[model_name] = load_model(model_name)
180184
input_name = models[model_name].get_inputs()[0].name
181185
prediction = models[model_name].run(None, {input_name: image_data})
182-
filter_names = get_top_3_predictions(prediction[0], "pre_filter")
186+
filter_names = get_top_n_predictions(prediction[0], "pre_filter")
183187
return filter_names
184188

185189

@@ -247,18 +251,83 @@ def classify_image(image_data: str, model_name: str, show_mask: bool = False) ->
247251
if pre_filter_predictions[0][0] != "porsche":
248252
return (pre_filter_predictions, mask_base64) if show_mask else [pre_filter_predictions]
249253

250-
if model_name != "car_type":
254+
if model_name != "car_type" and model_name != "specific_model_variants":
255+
input_name = model.get_inputs()[0].name
256+
prediction = model.run(None, {input_name: filter_image})
257+
elif model_name == "specific_model_variants":
258+
if models["car_type"] is None:
259+
models["car_type"] = load_model("car_type")
260+
261+
# Hierarchical prediction
262+
pre_prediction = ensemble_predictions_weighted(models["car_type"], filter_image)
263+
top_pre_prediction = get_top_n_predictions(pre_prediction[0], "car_type", 8)
264+
265+
# Run the specific model
251266
input_name = model.get_inputs()[0].name
252267
prediction = model.run(None, {input_name: filter_image})
268+
top_prediction = get_top_n_predictions(prediction[0], "specific_model_variants", 25)
269+
270+
# Adjust the top_prediction based on top_pre_prediction and hierarchy
271+
all_adjusted_predictions = []
272+
273+
for car_type, car_type_possibility in top_pre_prediction:
274+
# Initialize an empty list for each car_type to store its adjusted predictions
275+
car_type_adjusted_predictions = []
276+
277+
# Normalize the car_type_possibility
278+
normalized_car_type_possibility = car_type_possibility / 100.0
279+
280+
# Fetch the possible series based on hierarchy
281+
possible_series = HIERARCHY.get(car_type, [])
282+
283+
# Check if any of the series from the top_prediction belongs to possible_series
284+
for car_series, car_series_possibility in top_prediction:
285+
if car_series in possible_series:
286+
normalized_car_series_possibility = car_series_possibility / 100.0
287+
# Multiply the normalized possibilities
288+
adjusted_possibility_normalized = 0.6 * normalized_car_series_possibility + 0.4 * normalized_car_type_possibility
289+
# Convert back to the 1-100 scale
290+
adjusted_possibility = adjusted_possibility_normalized * 100
291+
car_type_adjusted_predictions.append((car_series, adjusted_possibility))
292+
293+
# Sort the predictions for this car_type
294+
car_type_adjusted_predictions = sorted(car_type_adjusted_predictions, key=lambda x: x[1], reverse=True)
295+
296+
# Append them to the overall list
297+
all_adjusted_predictions.extend(car_type_adjusted_predictions)
298+
299+
# Get top 3 predictions
300+
top_3_predictions = all_adjusted_predictions[:3]
301+
302+
return (top_3_predictions, mask_base64) if show_mask else [top_3_predictions]
253303
else:
254-
# prediction = ensemble_predictions_weighted(model, filter_image)
255304
prediction = ensemble_predictions_weighted(model, filter_image)
256305

257306
# Retrieving the top 3 predictions
258-
top_3_predictions = get_top_3_predictions(prediction[0], model_name)
307+
top_3_predictions = get_top_n_predictions(prediction[0], model_name)
259308

260309
return (top_3_predictions, mask_base64) if show_mask else [top_3_predictions]
261310

262311

263312
eel.init("web")
264313
eel.start("index.html", size=(1000, 800), mode="default")
314+
315+
316+
"""How the car_type/car_series ensemble works:
317+
318+
Incorporating Hierarchical Predictions for Porsche Images
319+
320+
Hierarchy Setup:
321+
322+
Create a mapping between 'car type' (e.g., Macan, 911) and 'car series' (e.g., 911_991, Macan_95B).
323+
Prediction Process:
324+
325+
Get the top predictions for 'car type' using Model 1.
326+
Predict 'car series' using Model 2.
327+
Adjustment:
328+
329+
For each 'car type' from Model 1:
330+
Filter 'car series' predictions of Model 2 that align with the hierarchy.
331+
Adjust the probabilities of these 'car series' predictions based on 'car type' probability.
332+
Sort and combine the adjusted predictions.
333+
"""

0 commit comments

Comments
 (0)