import numpy as np class DynamicalSOM: def __init__(self, input_dim, map_dim): self.input_dim = input_dim self.map_dim = map_dim self.weights = np.random.rand(map_dim[0], map_dim[1], input_dim) self.cluster_labels = np.zeros(map_dim) self.cluster_centers = np.zeros((0, input_dim)) self.num_clusters = 0 def find_best_matching_unit(self, input_vector): distances = np.linalg.norm(self.weights - input_vector, axis=-1) bmu_index = np.unravel_index(np.argmin(distances), distances.shape) return bmu_index def update_weights(self, input_vector, bmu_index, learning_rate): for i in range(self.map_dim[0]): for j in range(self.map_dim[1]): distance_to_bmu = np.linalg.norm(np.array([i, j]) - np.array(bmu_index)) neighborhood_function = np.exp(-(distance_to_bmu**2) / (2 * learning_rate**2)) self.weights[i, j] += neighborhood_function * learning_rate * (input_vector - self.weights[i, j]) def train(self, input_data, num_epochs, initial_learning_rate): for epoch in range(num_epochs): learning_rate = initial_learning_rate * np.exp(-epoch / num_epochs) np.random.shuffle(input_data) for input_vector in input_data: bmu_index = self.find_best_matching_unit(input_vector) self.update_weights(input_vector, bmu_index, learning_rate) def cluster_neurons(self): # Initialize cluster centers with the first neuron's weight vector self.cluster_centers = np.expand_dims(self.weights[0, 0], axis=0) # Assign all neurons to the first cluster self.cluster_labels[:] = 0 for i in range(self.map_dim[0]): for j in range(self.map_dim[1]): # Calculate the distance between the current neuron and each cluster center distances_to_clusters = np.linalg.norm(self.weights[i, j] - self.cluster_centers, axis=-1) # Find the closest cluster center closest_cluster_index = np.argmin(distances_to_clusters) # Assign the neuron to the closest cluster self.cluster_labels[i, j] = closest_cluster_index # Update cluster centers based on the mean of neurons in each cluster for cluster_index in range(self.num_clusters): cluster_neurons_indices = np.where(self.cluster_labels == cluster_index) cluster_neurons = self.weights[cluster_neurons_indices] cluster_center = np.mean(cluster_neurons, axis=0) self.cluster_centers[cluster_index] = cluster_center def introduce_fox_neuron(self): if self.num_clusters > 1: # Calculate distances between cluster centers distances = np.linalg.norm(self.cluster_centers[:, np.newaxis, :] - self.cluster_centers, axis=-1) # Find the indices of the two closest clusters closest_clusters_indices = np.unravel_index(np.argpartition(distances, 1, axis=None)[:2], distances.shape) # Average the two cluster centers to get the fox neuron fox_neuron = np.mean(self.cluster_centers[closest_clusters_indices], axis=0) # Add the fox neuron to the map self.weights = np.append(self.weights, np.expand_dims(fox_neuron, axis=(0, 1)), axis=0) self.num_clusters += 1 # Update cluster labels with the new neuron self.cluster_labels = np.append(self.cluster_labels, np.full((1, self.map_dim[1]), self.num_clusters - 1), axis=0) def summary(self): print("Dynamical Self-Organizing Map Summary:") print("Input Dimension:", self.input_dim) print("Map Dimension:", self.map_dim) print("Number of Neurons:", self.map_dim[0] * self.map_dim[1]) print("Number of Clusters:", self.num_clusters) # Example usage: input_data = np.random.rand(1000, 100) map_dimensions = (10, 10) som = DynamicalSOM(input_dim=100, map_dim=map_dimensions) num_epochs = 100 initial_learning_rate = 0.1 som.train(input_data, num_epochs, initial_learning_rate) som.cluster_neurons() som.introduce_fox_neuron() som.summary()