diff --git a/common/utils.py b/common/utils.py index 04442d4..90dfde9 100644 --- a/common/utils.py +++ b/common/utils.py @@ -37,6 +37,7 @@ def plot_gaussian_contours_fading( - color: A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. - max_std: Max standard deviations to draw contours to. Default is 2. - sigma_steps: Step size in standard deviations for each contour. Default is 0.25. + - linewidth: width of boundary line """ from matplotlib.patches import Ellipse @@ -75,12 +76,13 @@ def plot_gaussian_contours_covariance( Parameters: - - ax: Matplotlib axis object. If None, creates a new figure and axis. + - ax: Matplotlib axis object. If None, creates a new figure and axis. - mean: A numpy array representing the mean (μ) of the distribution. - covariance: A 2x2 numpy array representing the covariance matrix of the distribution. - color: A 4D numpy array including RGB and alpha channels to specify the color and initial opacity. - max_std: Max standard deviations to draw contours to. Default is 2. - sigma_steps: Step size in standard deviations for each contour. Default is 0.25. + - linewidth: width of boundary line """ from matplotlib.patches import Ellipse @@ -109,3 +111,38 @@ def plot_gaussian_contours_covariance( ax.add_patch(ellipse) +def plot_weight_matrix_as_ellipse( + ax: Axes, + s: float, + W: np.ndarray, + mean: np.ndarray, + color: np.ndarray, + linewidth: int = 1 +): + """ + Plots the transformation of a unit circle by the weight matrix W as an ellipse. + + Parameters: + - ax: Matplotlib axis object. If None, creates a new figure and axis. + - mean: The center point (x, y) of the ellipse. + - s: Scalar to scale the weight matrix W. + - W: 2x2 weight matrix. + - color: Color of the ellipse. + - linewidth: width of boundary line + """ + # Compute the transformation matrix + transform_matrix = W[:2, :2] + + # Generate points on a unit circle + theta = np.linspace(0, 2 * np.pi, 100) + circle = np.array([np.cos(theta), np.sin(theta)]) # Unit circle + + # Apply the linear transformation to the circle to get an ellipse + ellipse = 0.25*s * s * (transform_matrix @ circle) + + # Shift the ellipse to the specified mean + ellipse[0, :] += mean[0] + ellipse[1, :] += mean[1] + + # Plotting + ax.plot(ellipse[0, :], ellipse[1, :], color=color, linewidth=linewidth) diff --git a/elementary/QuadraticNeuronART.py b/elementary/QuadraticNeuronART.py index ca485ea..1ddd346 100644 --- a/elementary/QuadraticNeuronART.py +++ b/elementary/QuadraticNeuronART.py @@ -9,9 +9,10 @@ """ import numpy as np -from typing import Optional +from typing import Optional, Iterable +from matplotlib.axes import Axes from common.BaseART import BaseART -from common.utils import normalize, l2norm2 +from common.utils import normalize, l2norm2, plot_weight_matrix_as_ellipse def prepare_data(data: np.ndarray) -> np.ndarray: normalized = normalize(data) @@ -78,3 +79,14 @@ def update(self, i: np.ndarray, w: np.ndarray, params, cache: Optional[dict] = N def new_weight(self, i: np.ndarray, params: dict) -> np.ndarray: w_new = np.identity(self.dim_) return np.concatenate([w_new.flatten(), i, [params["s_init"]]]) + + def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1): + # kinda works + from matplotlib.patches import Rectangle + for w, col in zip(self.W, colors): + dim2 = self.dim_ * self.dim_ + w_ = w[:dim2].reshape((self.dim_, self.dim_)) + b = w[dim2:-1] + s = w[-1] + plot_weight_matrix_as_ellipse(ax, s, w_, b, col) + diff --git a/examples/test_quadratic_neuron_art.py b/examples/test_quadratic_neuron_art.py index 59e4929..2cade32 100644 --- a/examples/test_quadratic_neuron_art.py +++ b/examples/test_quadratic_neuron_art.py @@ -23,7 +23,7 @@ def cluster_blobs(): params = { "rho": 0.9, - "s_init": 1.0, + "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1