Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion src/models/svm_classifier.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from sklearn.svm import SVC
from src.models.base_classifier import BaseModel
from src.config import get_config
import numpy as np
import matplotlib.pyplot as plt

class SVMClassifier(BaseModel):
"""
Expand Down Expand Up @@ -30,4 +32,62 @@ def _build_model(self):
probability=self.params.get('probability', default_params.get('probability', True)),
class_weight=self.params.get('class_weight', default_params.get('class_weight', None)),
random_state=self.params.get('random_state', default_params.get('random_state', 42))
)
)

def plot_decision_boundary(self, X, y, title="SVM Decision Boundary"):

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title contains a spelling error: "boudaries" should be "boundaries".

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

"""
Visualizes the decision boundary, margins, and support vectors.

NOTE: This method strictly requires 2D feature data.
Comment on lines +39 to +41

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring is incomplete and missing important information. It should include an Args section describing the parameters (X, y, title), a Returns section (even if it returns None, this should be explicit), and potentially a Raises section documenting when the method returns early due to errors. Additionally, it should specify what type of data X and y should be (e.g., array-like).

Suggested change
Visualizes the decision boundary, margins, and support vectors.
NOTE: This method strictly requires 2D feature data.
Visualizes the decision boundary, margins, and support vectors for a trained SVM model.
Args:
X (array-like of shape (n_samples, 2)): Feature data. Must be 2-dimensional.
y (array-like of shape (n_samples,)): Target labels.
title (str, optional): Title for the plot. Defaults to "SVM Decision Boundary".
Returns:
None
Notes:
- This method strictly requires 2D feature data (i.e., X must have exactly 2 features).
- If the model is not trained or X is not 2D, the method prints an error message and returns early without plotting.

Copilot uses AI. Check for mistakes.
"""
if self.model is None:
raise RuntimeError("Model not trained. Call train() first.")


X = np.array(X)
y = np.array(y)

# Strict dimension check
if X.shape[1] != 2:
raise ValueError(
f"Cannot visualize SVM boundary with {X.shape[1]} dimensions. "
"Tip: Use PCA to reduce to 2D or select only 2 columns."
)

h = 0.02 # Step size

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 0.02 for the step size should be either configurable or defined as a named constant with a comment explaining its purpose. This would improve code maintainability and allow users to adjust the visualization resolution if needed.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))

plt.figure(figsize=(10, 6))

# Calculate distance to hyperplane (decision_function)
Z = self.model.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, levels=[-100, 0, 100], alpha=0.2, colors=['#FF9999', '#9999FF'])

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic numbers -100 and 100 used as contour levels appear arbitrary and may not provide meaningful boundaries for all datasets. Consider using data-driven values (e.g., based on the min/max of the decision function values) or making these configurable parameters to ensure appropriate visualization across different data scales.

Suggested change
plt.contourf(xx, yy, Z, levels=[-100, 0, 100], alpha=0.2, colors=['#FF9999', '#9999FF'])
# Use data-driven contour levels for background shading
z_min, z_max = Z.min(), Z.max()
# Ensure 0 is between z_min and z_max for proper boundary coloring
if z_min < 0 < z_max:
contour_levels = [z_min, 0, z_max]
else:
# If 0 is outside the range, just use min and max
contour_levels = [z_min, z_max]
plt.contourf(xx, yy, Z, levels=contour_levels, alpha=0.2, colors=['#FF9999', '#9999FF'])

Copilot uses AI. Check for mistakes.

# Plot key lines:
# Level -1 : Negative class margin (dashed)
# Level 0 : Decision Boundary (solid)
# Level 1 : Positive class margin (dashed)
contours = plt.contour(xx, yy, Z, levels=[-1, 0, 1],
linestyles=['--', '-', '--'],
colors='k',
linewidths=[1, 2, 1])

plt.clabel(contours, inline=True, fontsize=10, fmt='%1.0f')

plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.bwr, edgecolors='k', s=60)

sv = self.model.support_vectors_
plt.scatter(sv[:, 0], sv[:, 1], s=200,
linewidth=1.5, facecolors='none', edgecolors='k', label='Support Vectors')

plt.title(title)
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend(loc="upper right")
plt.grid(False)
plt.show()