Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release 0.1.2 #93

Merged
merged 74 commits into from
Oct 3, 2024
Merged
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
0a75fe6
SeqART
NiklasMelton Aug 20, 2024
7073514
Add initial implementation of cvi Art Modules.
Aug 22, 2024
0c036d0
sequence ART
NiklasMelton Aug 25, 2024
59c0adc
implement original match reset
NiklasMelton Aug 25, 2024
a031037
add argument for modified match reset
NiklasMelton Aug 26, 2024
151204b
add argument for modified match reset
NiklasMelton Aug 26, 2024
9859568
add argument for modified match reset
NiklasMelton Aug 26, 2024
487b27d
fix fusion ART
NiklasMelton Aug 26, 2024
acdf996
fix topo art
NiklasMelton Aug 26, 2024
3458bb3
Merge pull request #71 from NiklasMelton/artmap-fixes
NiklasMelton Aug 26, 2024
2e9c926
add artmap regression
NiklasMelton Aug 26, 2024
8858765
Merge pull request #72 from NiklasMelton/regression
NiklasMelton Aug 26, 2024
47e6e52
FusionART regression
NiklasMelton Aug 26, 2024
72ea572
Merge pull request #73 from NiklasMelton/regression
NiklasMelton Aug 26, 2024
9e43468
Merge pull request #70 from DustinTanksley/develop
NiklasMelton Aug 26, 2024
59efb7a
cammel case to snake case
NiklasMelton Aug 26, 2024
d715637
clean cvi ART and add examples
NiklasMelton Aug 26, 2024
4f9e21c
Merge pull request #74 from NiklasMelton/add-cvi-examples
NiklasMelton Aug 26, 2024
d1e9134
generalize CVI ART
NiklasMelton Aug 26, 2024
8055f18
Merge pull request #75 from NiklasMelton/generalize-cvi-art
NiklasMelton Aug 26, 2024
153ae67
fix match-tracking for fusionART
NiklasMelton Aug 26, 2024
a2a0b16
fix match-tracking for fusionART
NiklasMelton Aug 26, 2024
e2fe2d6
Merge pull request #76 from NiklasMelton/fix-fusion-art
NiklasMelton Aug 26, 2024
03ef3e0
Add and test FALCON
NiklasMelton Aug 28, 2024
5cdd270
Merge pull request #77 from NiklasMelton/FALCON
NiklasMelton Aug 28, 2024
3720fd9
convex-hull-art
NiklasMelton Aug 29, 2024
37a38e7
convex-hull-art
NiklasMelton Aug 29, 2024
3a8d1ea
Merge pull request #78 from NiklasMelton/experimental-convex-hull-art
NiklasMelton Aug 29, 2024
fb29a52
update activation for points and lines
NiklasMelton Aug 29, 2024
cf026be
Merge pull request #79 from NiklasMelton/update-convex-hull-art
NiklasMelton Aug 29, 2024
53b361b
Merge branch 'develop' into seq-art
NiklasMelton Aug 29, 2024
b30fb03
move to experimental
NiklasMelton Aug 29, 2024
08e9a55
move to experimental
NiklasMelton Aug 29, 2024
be59a58
Merge pull request #69 from NiklasMelton/seq-art
NiklasMelton Aug 29, 2024
df0165c
bump version
NiklasMelton Aug 29, 2024
49b0a87
Create pypi-publish.yml
NiklasMelton Aug 29, 2024
b7c1ef9
update-readme
NiklasMelton Sep 1, 2024
b9a06e9
update-readme
NiklasMelton Sep 1, 2024
58a8a6b
Merge pull request #80 from NiklasMelton/update-readme
NiklasMelton Sep 1, 2024
1b4e5a5
bug fixes
NiklasMelton Sep 6, 2024
16d4015
Merge pull request #81 from NiklasMelton/bug-fixes
NiklasMelton Sep 6, 2024
c4dea3e
add td-falcon
NiklasMelton Sep 6, 2024
fa14978
add td-falcon
NiklasMelton Sep 6, 2024
4571773
add td-falcon
NiklasMelton Sep 6, 2024
d9b6136
Merge pull request #82 from NiklasMelton/td-falcon
NiklasMelton Sep 6, 2024
1210888
update prepare_data method
NiklasMelton Sep 6, 2024
ed2ef34
add restore data method
NiklasMelton Sep 7, 2024
7c07182
add restore data method
NiklasMelton Sep 7, 2024
a90a091
Merge pull request #83 from NiklasMelton/prepare-data-changes
NiklasMelton Sep 7, 2024
7b439b3
add VAT
NiklasMelton Sep 9, 2024
f453aa5
add VAT citation
NiklasMelton Sep 9, 2024
71acd02
Merge pull request #85 from NiklasMelton/VAT
NiklasMelton Sep 9, 2024
115fa99
add VAT citation
NiklasMelton Sep 9, 2024
282e6a7
Merge pull request #86 from NiklasMelton/VAT
NiklasMelton Sep 9, 2024
4dbb726
update VAT to be a bit faster
NiklasMelton Sep 9, 2024
44868ac
update VAT to be a bit faster
NiklasMelton Sep 9, 2024
42554e5
Merge pull request #87 from NiklasMelton/update-VAT
NiklasMelton Sep 9, 2024
9ab0f8c
column-wise normalization
NiklasMelton Sep 9, 2024
fb7a824
fix artmap bug
NiklasMelton Sep 10, 2024
27cec4e
Merge pull request #88 from NiklasMelton/artmap-bug-fix
NiklasMelton Sep 10, 2024
ef472de
match tracking versions added
NiklasMelton Sep 10, 2024
a9b6af2
match tracking versions added
NiklasMelton Sep 10, 2024
0c91e9e
match tracking versions added
NiklasMelton Sep 10, 2024
0b43c1c
match tracking versions added
NiklasMelton Sep 10, 2024
dd7392f
Merge pull request #89 from NiklasMelton/match-tracking-versions
NiklasMelton Sep 10, 2024
567cf6e
optimize MT~
NiklasMelton Sep 14, 2024
3644656
Merge branch 'develop' into column-wise-normalization
NiklasMelton Sep 16, 2024
9e800a2
Merge pull request #90 from NiklasMelton/column-wise-normalization
NiklasMelton Sep 16, 2024
86d0264
Merge branch 'develop' into artmap-reset-update
NiklasMelton Sep 16, 2024
69a2f54
Merge pull request #91 from NiklasMelton/artmap-reset-update
NiklasMelton Sep 16, 2024
c88bd92
improve match-tracking
NiklasMelton Sep 18, 2024
97a5121
improve gaussian art
NiklasMelton Sep 26, 2024
59d674f
Merge pull request #92 from NiklasMelton/match-tracking-update
NiklasMelton Sep 26, 2024
4c69a9e
Merge branch 'main' into develop
NiklasMelton Oct 3, 2024
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
Prev Previous commit
Next Next commit
convex-hull-art
NiklasMelton committed Aug 29, 2024
commit 3720fd9a7aa1fbe752a53f036ccc91ca19ed9479
17 changes: 15 additions & 2 deletions artlib/common/BaseART.py
Original file line number Diff line number Diff line change
@@ -199,11 +199,12 @@ def match_criterion_bin(self, i: np.ndarray, w: np.ndarray, params: dict, cache:

"""
M, cache = self.match_criterion(i, w, params=params, cache=cache)
M_bin = M > params["rho"]
if cache is None:
cache = dict()
cache["match_criterion"] = M
cache["match_criterion_bin"] = M > params["rho"]
return M > params["rho"], cache
cache["match_criterion_bin"] = M_bin
return M_bin, cache

def update(self, i: np.ndarray, w: np.ndarray, params: dict, cache: Optional[dict] = None) -> np.ndarray:
"""
@@ -408,6 +409,17 @@ def post_step_fit(self, X: np.ndarray):
# this is where pruning steps can go
pass

def post_fit(self, X: np.ndarray):
"""
undefined function called after fit. Useful for cluster pruning

Parameters:
- X: data set

"""
# this is where pruning steps can go
pass


def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: Optional[Callable] = None, max_iter=1, match_reset_method: Literal["original", "modified"] = "original"):
"""
@@ -435,6 +447,7 @@ def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, match_reset_func: O
c = self.step_fit(x, match_reset_func=match_reset_func, match_reset_method=match_reset_method)
self.labels_[i] = c
self.post_step_fit(X)
self.post_fit(X)
return self


326 changes: 326 additions & 0 deletions artlib/experimental/ConvexHullART.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
import numpy as np
from matplotlib.axes import Axes
from copy import deepcopy
from typing import Optional, Iterable, List, Union
from scipy.spatial import ConvexHull

from artlib.common.BaseART import BaseART
from artlib.experimental.merging import merge_objects


def plot_convex_polygon(vertices: np.ndarray, ax: Axes, line_color: str = 'b', line_width: float = 1.0):
"""
Plots a convex polygon given its vertices using Matplotlib.

Parameters:
- vertices: A list of vertices representing a convex polygon.
- ax: A matplotlib Axes object to plot on. If None, creates a new figure and axes.
- line_color: The color of the polygon lines.
- line_width: The width of the polygon lines.
"""
vertices = np.array(vertices)
# Close the polygon by appending the first vertex at the end
vertices = np.vstack([vertices, vertices[0]])

ax.plot(vertices[:, 0], vertices[:, 1], linestyle='-', color=line_color, linewidth=line_width)


def volume_of_simplex(vertices):
"""
Calculates the n-dimensional volume of a simplex defined by its vertices.

Parameters:
- vertices: An (n+1) x n array representing the coordinates of the simplex vertices.

Returns:
- Volume of the simplex.
"""
vertices = np.asarray(vertices)
# Subtract the first vertex from all vertices to form a matrix
matrix = vertices[1:] - vertices[0]
# Calculate the absolute value of the determinant divided by factorial(n) for volume
return np.abs(np.linalg.det(matrix)) / np.math.factorial(len(vertices) - 1)


def minimum_distance(a1, a2):
def point_to_point_distance(P, Q):
"""Calculate the Euclidean distance between two points P and Q."""
return np.linalg.norm(P - Q)

def point_to_line_segment_distance(P, Q1, Q2):
"""Calculate the minimum distance between point P and line segment Q1-Q2."""
line_vec = Q2 - Q1
point_vec = P - Q1
line_len = np.dot(line_vec, line_vec)

if line_len == 0: # Q1 and Q2 are the same point
return np.linalg.norm(P - Q1)

t = max(0, min(1, np.dot(point_vec, line_vec) / line_len))
projection = Q1 + t * line_vec
return np.linalg.norm(P - projection)

def line_segment_to_line_segment_distance(A1, A2, B1, B2):
"""Calculate the minimum distance between two line segments A1-A2 and B1-B2."""
distances = [
point_to_line_segment_distance(A1, B1, B2),
point_to_line_segment_distance(A2, B1, B2),
point_to_line_segment_distance(B1, A1, A2),
point_to_line_segment_distance(B2, A1, A2),
]
return min(distances)

# Determine the cases and compute the distance
if a1.shape[0] == 1 and a2.shape[0] == 1:
# Case 1: Point to point
return point_to_point_distance(a1[0], a2[0])

elif a1.shape[0] == 1 and a2.shape[0] == 2:
# Case 2: Point to line segment
return point_to_line_segment_distance(a1[0], a2[0], a2[1])

elif a1.shape[0] == 2 and a2.shape[0] == 1:
# Case 3: Line segment to point
return point_to_line_segment_distance(a2[0], a1[0], a1[1])

elif a1.shape[0] == 2 and a2.shape[0] == 2:
# Case 4: Line segment to line segment
return line_segment_to_line_segment_distance(a1[0], a1[1], a2[0], a2[1])
else:
raise RuntimeError("INVALID DISTANCE CALCULATION")



class PseudoConvexHull:
def __init__(self, points: np.ndarray):
self.points = points

@property
def vertices(self):
return np.array([i for i in range(self.points.shape[0])])

def add_points(self, points):
self.points = np.vstack([self.points, points])


HullTypes = Union[ConvexHull, PseudoConvexHull]


def centroid_of_convex_hull(hull: HullTypes):
"""
Finds the centroid of the volume of a convex hull in n-dimensional space.

Parameters:
- vertices: An array of shape (m, n), where m is the number of vertices and n is the dimension.

Returns:
- Centroid coordinates as a numpy array of length n.
"""
hull_vertices = hull.points[hull.vertices]

centroid = np.zeros(hull_vertices.shape[1])
total_volume = 0

if isinstance(hull, PseudoConvexHull):
return np.mean(hull.points, axis=0)

for simplex in hull.simplices:
simplex_vertices = hull_vertices[simplex]
simplex_centroid = np.mean(simplex_vertices, axis=0)
simplex_volume = volume_of_simplex(simplex_vertices)

centroid += simplex_centroid * simplex_volume
total_volume += simplex_volume

centroid /= total_volume
return centroid


class ConvexHullART(BaseART):
def __init__(self, rho: float):
"""
Parameters:
- rho: vigilance parameter

"""
params = {
"rho": rho,
}
super().__init__(params)

@staticmethod
def validate_params(params: dict):
"""
validate clustering parameters

Parameters:
- params: dict containing parameters for the algorithm

"""
assert "rho" in params
assert params["rho"] >= 0.
assert isinstance(params["rho"], float)

def category_choice(self, i: np.ndarray, w: HullTypes, params: dict) -> tuple[float, Optional[dict]]:
"""
get the activation of the cluster

Parameters:
- i: data sample
- w: cluster weight / info
- params: dict containing parameters for the algorithm

Returns:
cluster activation, cache used for later processing

"""
if isinstance(w, PseudoConvexHull):
d = minimum_distance(i.reshape((1,-1)), w.points)
activation = 1. - d
if w.points.shape[0] == 1:
new_w = deepcopy(w)
new_w.add_points(i.reshape((1,-1)))
else:
new_points = np.vstack([w.points[w.vertices,:], i.reshape((1,-1))])
new_w = ConvexHull(new_points, incremental=True)
else:
new_w = ConvexHull(w.points[w.vertices,:], incremental=True)
new_w.add_points(i.reshape((1,-1)))
activation = w.area / new_w.area

cache = {"new_w": new_w, "activation": activation}
return activation, cache

def match_criterion(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> tuple[float, dict]:
"""
get the match criterion of the cluster

Parameters:
- i: data sample
- w: cluster weight / info
- params: dict containing parameters for the algorithm
- cache: dict containing values cached from previous calculations

Returns:
cluster match criterion, cache used for later processing

"""
return cache["activation"], cache


def update(self, i: np.ndarray, w: HullTypes, params: dict, cache: Optional[dict] = None) -> HullTypes:
"""
get the updated cluster weight

Parameters:
- i: data sample
- w: cluster weight / info
- params: dict containing parameters for the algorithm
- cache: dict containing values cached from previous calculations

Returns:
updated cluster weight, cache used for later processing

"""
return cache["new_w"]

def new_weight(self, i: np.ndarray, params: dict) -> HullTypes:
"""
generate a new cluster weight

Parameters:
- i: data sample
- w: cluster weight / info
- params: dict containing parameters for the algorithm

Returns:
updated cluster weight

"""
new_w = PseudoConvexHull(i.reshape((1,-1)))
return new_w

def merge_clusters(self):
def can_merge(w1, w2):
combined_points = np.vstack([w1.points[w1.vertices,:], w2.points[w2.vertices,:]])
new_w = ConvexHull(combined_points)

if isinstance(w1, PseudoConvexHull) and isinstance(w2, PseudoConvexHull):
d = minimum_distance(w1.points, w2.points)
activation = 1.0 - d
else:
if isinstance(w1, ConvexHull):
a1 = w1.area / new_w.area
else:
a1 = 0
if isinstance(w2, ConvexHull):
a2 = w2.area / new_w.area
else:
a2 = 0
activation = max(a1, a2)

if activation > self.params["rho"]:
return True
else:
return False

merges = merge_objects(self.W, can_merge)

new_W = []
new_sample_counter = []
new_labels = np.copy(self.labels_)
for i in range(len(merges)):
new_labels[np.isin(self.labels_, merges[i])] = i
new_sample_counter.append(sum(self.weight_sample_counter_[j] for j in merges[i]))
if len(merges[i]) > 1:
new_points = np.vstack([self.W[j].points for j in merges[i]])
if new_points.shape[0] > 2:
new_W.append(ConvexHull(new_points, incremental=True))
else:
new_W.append(PseudoConvexHull(new_points))
else:
new_W.append(self.W[merges[i][0]])
self.W = new_W
self.weight_sample_counter_ = new_sample_counter
self.labels_ = new_labels

def post_fit(self, X: np.ndarray):
"""
function called after fit. Useful for cluster pruning

Parameters:
- X: data set

"""
self.merge_clusters()




def get_cluster_centers(self) -> List[np.ndarray]:
"""
function for getting centers of each cluster. Used for regression
Returns:
cluster centroid
"""
centers = []
for w in self.W:
centers.append(centroid_of_convex_hull(w))
return centers

def plot_cluster_bounds(self, ax: Axes, colors: Iterable, linewidth: int = 1):
"""
undefined function for visualizing the bounds of each cluster

Parameters:
- ax: figure axes
- colors: colors to use for each cluster
- linewidth: width of boundary line

"""
for c, w in zip(colors, self.W):
if isinstance(w, ConvexHull):
vertices = w.points[w.vertices,:2]
else:
vertices = w.points[:, :2]
plot_convex_polygon(vertices, ax, line_width=linewidth, line_color=c)
Loading