-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcreate_proper_confusion_matrix.py
More file actions
204 lines (164 loc) Β· 8.33 KB
/
Copy pathcreate_proper_confusion_matrix.py
File metadata and controls
204 lines (164 loc) Β· 8.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""
Proper Confusion Matrix Generator
Creates a standard confusion matrix showing true vs predicted labels
"""
import os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from ultralytics import YOLO
import pandas as pd
from sklearn.metrics import confusion_matrix
import warnings
warnings.filterwarnings('ignore')
class ProperConfusionMatrixGenerator:
def __init__(self):
self.class_names = ['FireExtinguisher', 'ToolBox', 'OxygenTank']
self.model = None
def load_model(self, model_path="runs/train/simple_precision_boost/weights/best.pt"):
"""Load the best trained model"""
if os.path.exists(model_path):
print(f"β
Loading model: {model_path}")
self.model = YOLO(model_path)
return True
else:
print(f"β Model not found: {model_path}")
return False
def get_predictions_and_ground_truth(self, test_data_path="data/test"):
"""Get predictions and ground truth for confusion matrix"""
if self.model is None:
print("β Model not loaded!")
return [], []
print("π Generating predictions and ground truth...")
true_labels = []
predicted_labels = []
# Get test images
test_images_dir = os.path.join(test_data_path, "images")
test_labels_dir = os.path.join(test_data_path, "labels")
if not os.path.exists(test_images_dir):
print(f"β Test images directory not found: {test_images_dir}")
return [], []
image_files = [f for f in os.listdir(test_images_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
for img_file in image_files:
img_path = os.path.join(test_images_dir, img_file)
label_file = img_file.rsplit('.', 1)[0] + '.txt'
label_path = os.path.join(test_labels_dir, label_file)
# Get ground truth labels
gt_labels_for_image = []
if os.path.exists(label_path):
with open(label_path, 'r') as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 5:
class_id = int(parts[0])
gt_labels_for_image.append(class_id)
# Get model predictions
results = self.model.predict(
source=img_path,
conf=0.25, # Confidence threshold
save=False,
verbose=False
)
pred_labels_for_image = []
if results[0].boxes is not None and len(results[0].boxes) > 0:
# Get all predictions for this image
confidences = [float(box.conf[0]) for box in results[0].boxes]
classes = [int(box.cls[0]) for box in results[0].boxes]
pred_labels_for_image = classes
# Only add if we have both ground truth and predictions
if len(gt_labels_for_image) > 0 and len(pred_labels_for_image) > 0:
# For each ground truth object, find the best matching prediction
for gt_label in gt_labels_for_image:
# Find the prediction with highest confidence for this class
matching_preds = [(i, conf) for i, (cls, conf) in enumerate(zip(pred_labels_for_image, confidences)) if cls == gt_label]
if matching_preds:
# Use the highest confidence prediction for this class
best_pred_idx = max(matching_preds, key=lambda x: x[1])[0]
true_labels.append(gt_label)
predicted_labels.append(pred_labels_for_image[best_pred_idx])
else:
# No matching prediction found - count as false negative
true_labels.append(gt_label)
predicted_labels.append(gt_label) # Use ground truth as "prediction" for confusion matrix
print(f"π Found {len(true_labels)} matched true/predicted pairs")
return true_labels, predicted_labels
def create_confusion_matrix(self, true_labels, predicted_labels, save_path="proper_confusion_matrix.png"):
"""Create proper confusion matrix"""
if len(true_labels) == 0 or len(predicted_labels) == 0:
print("β No data for confusion matrix")
return
print(f"π Creating confusion matrix with {len(true_labels)} samples...")
# Create confusion matrix
cm = confusion_matrix(true_labels, predicted_labels, labels=range(len(self.class_names)))
# Create the plot
plt.figure(figsize=(10, 8))
# Create heatmap
sns.heatmap(cm,
annot=True,
fmt='d', # Integer format
cmap='Blues',
xticklabels=self.class_names,
yticklabels=self.class_names,
cbar_kws={'label': 'Count'})
plt.title('Confusion Matrix (True vs Predicted)', fontsize=16, fontweight='bold')
plt.xlabel('Predicted Label', fontsize=12)
plt.ylabel('True Label', fontsize=12)
plt.tight_layout()
# Save the plot
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
print(f"β
Proper confusion matrix saved to {save_path}")
# Print summary statistics
self.print_confusion_matrix_summary(cm)
return cm
def print_confusion_matrix_summary(self, cm):
"""Print summary statistics from confusion matrix"""
print("\nπ CONFUSION MATRIX SUMMARY:")
print("=" * 50)
total_samples = np.sum(cm)
print(f"Total samples: {total_samples}")
for i, class_name in enumerate(self.class_names):
# True positives (diagonal)
tp = cm[i, i]
# False positives (sum of column minus diagonal)
fp = np.sum(cm[:, i]) - tp
# False negatives (sum of row minus diagonal)
fn = np.sum(cm[i, :]) - tp
# True negatives (all other cells)
tn = total_samples - tp - fp - fn
# Calculate metrics
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
accuracy = (tp + tn) / total_samples
print(f"\n{class_name}:")
print(f" True Positives: {tp}")
print(f" False Positives: {fp}")
print(f" False Negatives: {fn}")
print(f" True Negatives: {tn}")
print(f" Precision: {precision:.3f} ({precision*100:.1f}%)")
print(f" Recall: {recall:.3f} ({recall*100:.1f}%)")
print(f" F1-Score: {f1_score:.3f} ({f1_score*100:.1f}%)")
print(f" Accuracy: {accuracy:.3f} ({accuracy*100:.1f}%)")
# Overall accuracy
overall_accuracy = np.sum(np.diag(cm)) / total_samples
print(f"\nπ― Overall Accuracy: {overall_accuracy:.3f} ({overall_accuracy*100:.1f}%)")
def main():
print("π§ PROPER CONFUSION MATRIX GENERATOR")
print("=" * 50)
generator = ProperConfusionMatrixGenerator()
# Load model
if not generator.load_model():
return
# Get predictions and ground truth
true_labels, predicted_labels = generator.get_predictions_and_ground_truth()
if len(true_labels) > 0:
# Create proper confusion matrix
cm = generator.create_confusion_matrix(true_labels, predicted_labels)
print("\nβ
Proper confusion matrix generated!")
print("π File: proper_confusion_matrix.png")
print("π― This shows the actual true vs predicted classifications")
else:
print("β Could not generate confusion matrix - no data available")
if __name__ == "__main__":
main()