|
|
""" |
|
|
Group Theory-based Algebraic Neural Network |
|
|
|
|
|
This example demonstrates neural networks that use group theory operations |
|
|
for data transformations, particularly focusing on symmetry groups. |
|
|
""" |
|
|
|
|
|
import numpy as np |
|
|
from typing import List, Tuple, Dict |
|
|
import math |
|
|
|
|
|
|
|
|
class GroupTheoryNetwork: |
|
|
""" |
|
|
Neural network based on group theory operations and symmetries. |
|
|
""" |
|
|
|
|
|
def __init__(self, input_dim: int, group_types: List[str], output_dim: int): |
|
|
self.input_dim = input_dim |
|
|
self.group_types = group_types |
|
|
self.output_dim = output_dim |
|
|
|
|
|
|
|
|
self.groups = self._initialize_groups() |
|
|
|
|
|
def _initialize_groups(self) -> Dict[str, List[np.ndarray]]: |
|
|
"""Initialize various group operations.""" |
|
|
groups = {} |
|
|
|
|
|
for group_type in self.group_types: |
|
|
if group_type.startswith("cyclic_"): |
|
|
n = int(group_type.split("_")[1]) |
|
|
groups[group_type] = self._generate_cyclic_group(n) |
|
|
elif group_type.startswith("dihedral_"): |
|
|
n = int(group_type.split("_")[1]) |
|
|
groups[group_type] = self._generate_dihedral_group(n) |
|
|
elif group_type == "symmetric_3": |
|
|
groups[group_type] = self._generate_symmetric_group_3() |
|
|
elif group_type == "reflection": |
|
|
groups[group_type] = self._generate_reflection_group() |
|
|
|
|
|
return groups |
|
|
|
|
|
def _generate_cyclic_group(self, n: int) -> List[np.ndarray]: |
|
|
"""Generate cyclic group Cn as rotation matrices.""" |
|
|
group_elements = [] |
|
|
|
|
|
for k in range(n): |
|
|
angle = 2 * math.pi * k / n |
|
|
|
|
|
if self.input_dim == 2: |
|
|
|
|
|
rotation = np.array([ |
|
|
[math.cos(angle), -math.sin(angle)], |
|
|
[math.sin(angle), math.cos(angle)] |
|
|
]) |
|
|
elif self.input_dim >= 3: |
|
|
|
|
|
rotation = np.eye(self.input_dim) |
|
|
if self.input_dim >= 2: |
|
|
rotation[0, 0] = math.cos(angle) |
|
|
rotation[0, 1] = -math.sin(angle) |
|
|
rotation[1, 0] = math.sin(angle) |
|
|
rotation[1, 1] = math.cos(angle) |
|
|
else: |
|
|
|
|
|
rotation = np.array([[(-1) ** k]]) |
|
|
|
|
|
group_elements.append(rotation) |
|
|
|
|
|
return group_elements |
|
|
|
|
|
def _generate_dihedral_group(self, n: int) -> List[np.ndarray]: |
|
|
"""Generate dihedral group Dn (rotations + reflections).""" |
|
|
group_elements = [] |
|
|
|
|
|
|
|
|
rotations = self._generate_cyclic_group(n) |
|
|
group_elements.extend(rotations) |
|
|
|
|
|
|
|
|
for k in range(n): |
|
|
angle = 2 * math.pi * k / n |
|
|
|
|
|
if self.input_dim == 2: |
|
|
|
|
|
reflection_angle = angle / 2 |
|
|
cos_2theta = math.cos(2 * reflection_angle) |
|
|
sin_2theta = math.sin(2 * reflection_angle) |
|
|
|
|
|
reflection = np.array([ |
|
|
[cos_2theta, sin_2theta], |
|
|
[sin_2theta, -cos_2theta] |
|
|
]) |
|
|
else: |
|
|
|
|
|
reflection = np.eye(self.input_dim) |
|
|
reflection[0, 0] = -1 |
|
|
|
|
|
group_elements.append(reflection) |
|
|
|
|
|
return group_elements |
|
|
|
|
|
def _generate_symmetric_group_3(self) -> List[np.ndarray]: |
|
|
"""Generate symmetric group S3 (permutations of 3 elements).""" |
|
|
if self.input_dim < 3: |
|
|
|
|
|
return self._generate_cyclic_group(3) |
|
|
|
|
|
|
|
|
permutations = [ |
|
|
[0, 1, 2], |
|
|
[1, 2, 0], |
|
|
[2, 0, 1], |
|
|
[1, 0, 2], |
|
|
[0, 2, 1], |
|
|
[2, 1, 0], |
|
|
] |
|
|
|
|
|
group_elements = [] |
|
|
for perm in permutations: |
|
|
matrix = np.eye(self.input_dim) |
|
|
for i in range(3): |
|
|
if i < self.input_dim and perm[i] < self.input_dim: |
|
|
matrix[i, i] = 0 |
|
|
matrix[i, perm[i]] = 1 |
|
|
group_elements.append(matrix) |
|
|
|
|
|
return group_elements |
|
|
|
|
|
def _generate_reflection_group(self) -> List[np.ndarray]: |
|
|
"""Generate group of reflections across coordinate axes.""" |
|
|
group_elements = [] |
|
|
|
|
|
|
|
|
group_elements.append(np.eye(self.input_dim)) |
|
|
|
|
|
|
|
|
for i in range(self.input_dim): |
|
|
reflection = np.eye(self.input_dim) |
|
|
reflection[i, i] = -1 |
|
|
group_elements.append(reflection) |
|
|
|
|
|
|
|
|
if self.input_dim == 2: |
|
|
|
|
|
diag_reflection = np.array([[0, 1], [1, 0]]) |
|
|
group_elements.append(diag_reflection) |
|
|
|
|
|
|
|
|
anti_diag_reflection = np.array([[0, -1], [-1, 0]]) |
|
|
group_elements.append(anti_diag_reflection) |
|
|
|
|
|
return group_elements |
|
|
|
|
|
def apply_group_actions(self, x: np.ndarray, group_name: str) -> np.ndarray: |
|
|
"""Apply all group actions to input data.""" |
|
|
if x.ndim == 1: |
|
|
x = x.reshape(1, -1) |
|
|
|
|
|
group_elements = self.groups[group_name] |
|
|
results = [] |
|
|
|
|
|
for element in group_elements: |
|
|
if x.shape[1] == element.shape[0]: |
|
|
transformed = x @ element.T |
|
|
else: |
|
|
|
|
|
min_dim = min(x.shape[1], element.shape[0]) |
|
|
transformed = x[:, :min_dim] @ element[:min_dim, :min_dim].T |
|
|
|
|
|
|
|
|
norm = np.linalg.norm(transformed, axis=1, keepdims=True) |
|
|
mean = np.mean(transformed, axis=1, keepdims=True) |
|
|
std = np.std(transformed, axis=1, keepdims=True) + 1e-8 |
|
|
|
|
|
|
|
|
features = np.concatenate([norm, mean, std], axis=1) |
|
|
results.append(features) |
|
|
|
|
|
return np.concatenate(results, axis=1) |
|
|
|
|
|
def forward(self, x: np.ndarray) -> np.ndarray: |
|
|
"""Forward pass through group theory network.""" |
|
|
all_features = [] |
|
|
|
|
|
|
|
|
for group_name in self.group_types: |
|
|
group_features = self.apply_group_actions(x, group_name) |
|
|
all_features.append(group_features) |
|
|
|
|
|
|
|
|
combined_features = np.concatenate(all_features, axis=1) |
|
|
|
|
|
|
|
|
feature_dim = combined_features.shape[1] |
|
|
if feature_dim >= self.output_dim: |
|
|
|
|
|
output = combined_features[:, :self.output_dim] |
|
|
else: |
|
|
|
|
|
repeats = (self.output_dim + feature_dim - 1) // feature_dim |
|
|
repeated = np.tile(combined_features, (1, repeats)) |
|
|
output = repeated[:, :self.output_dim] |
|
|
|
|
|
return output |
|
|
|
|
|
def predict(self, x: np.ndarray) -> np.ndarray: |
|
|
"""Prediction method.""" |
|
|
return self.forward(x) |
|
|
|
|
|
|
|
|
def test_rotation_invariance(): |
|
|
"""Test rotation invariance of the group theory network.""" |
|
|
print("=== Group Theory Network: Rotation Invariance Test ===\n") |
|
|
|
|
|
|
|
|
network = GroupTheoryNetwork( |
|
|
input_dim=2, |
|
|
group_types=["cyclic_8"], |
|
|
output_dim=4 |
|
|
) |
|
|
|
|
|
|
|
|
original_pattern = np.array([[1, 0], [0, 1], [1, 1]]) |
|
|
|
|
|
|
|
|
rotation_angles = [0, np.pi/4, np.pi/2, np.pi, 3*np.pi/2] |
|
|
|
|
|
print("Testing rotation invariance:") |
|
|
outputs = [] |
|
|
|
|
|
for angle in rotation_angles: |
|
|
|
|
|
cos_a, sin_a = np.cos(angle), np.sin(angle) |
|
|
rotation_matrix = np.array([[cos_a, -sin_a], [sin_a, cos_a]]) |
|
|
rotated_pattern = original_pattern @ rotation_matrix.T |
|
|
|
|
|
|
|
|
output = network.predict(rotated_pattern) |
|
|
outputs.append(output) |
|
|
|
|
|
print(f" Rotation {angle:.2f} rad: mean output = {np.mean(output):.4f}") |
|
|
|
|
|
|
|
|
output_array = np.array(outputs) |
|
|
variance_across_rotations = np.var(output_array, axis=0) |
|
|
mean_variance = np.mean(variance_across_rotations) |
|
|
|
|
|
print(f"\nMean variance across rotations: {mean_variance:.6f}") |
|
|
print("(Lower values indicate better rotation invariance)\n") |
|
|
|
|
|
return outputs |
|
|
|
|
|
|
|
|
def test_symmetry_detection(): |
|
|
"""Test the network's ability to detect different symmetries.""" |
|
|
print("=== Group Theory Network: Symmetry Detection ===\n") |
|
|
|
|
|
|
|
|
network = GroupTheoryNetwork( |
|
|
input_dim=2, |
|
|
group_types=["cyclic_4", "dihedral_4", "reflection"], |
|
|
output_dim=6 |
|
|
) |
|
|
|
|
|
|
|
|
patterns = { |
|
|
"Square": np.array([[1, 1], [1, -1], [-1, -1], [-1, 1]]), |
|
|
"Triangle": np.array([[1, 0], [-0.5, np.sqrt(3)/2], [-0.5, -np.sqrt(3)/2]]), |
|
|
"Line": np.array([[1, 0], [0.5, 0], [0, 0], [-0.5, 0], [-1, 0]]), |
|
|
"Circle": np.array([[np.cos(θ), np.sin(θ)] for θ in np.linspace(0, 2*np.pi, 8)]), |
|
|
"Asymmetric": np.array([[1, 0], [0, 1], [0.3, 0.7], [0.8, 0.2]]) |
|
|
} |
|
|
|
|
|
print("Symmetry detection results:") |
|
|
pattern_outputs = {} |
|
|
|
|
|
for pattern_name, pattern_points in patterns.items(): |
|
|
output = network.predict(pattern_points) |
|
|
pattern_outputs[pattern_name] = output |
|
|
|
|
|
print(f"\n{pattern_name}:") |
|
|
print(f" Mean output: {np.mean(output, axis=0)}") |
|
|
print(f" Output std: {np.std(output, axis=0)}") |
|
|
|
|
|
return pattern_outputs |
|
|
|
|
|
|
|
|
def test_group_composition(): |
|
|
"""Test composition of group operations.""" |
|
|
print("=== Group Theory Network: Group Composition ===\n") |
|
|
|
|
|
network = GroupTheoryNetwork( |
|
|
input_dim=3, |
|
|
group_types=["symmetric_3"], |
|
|
output_dim=3 |
|
|
) |
|
|
|
|
|
|
|
|
test_input = np.array([[1, 2, 3]]) |
|
|
|
|
|
print("Testing group composition properties:") |
|
|
|
|
|
|
|
|
group_elements = network.groups["symmetric_3"] |
|
|
|
|
|
|
|
|
identity_result = test_input @ group_elements[0].T |
|
|
print(f"Identity transformation: {test_input[0]} → {identity_result[0]}") |
|
|
|
|
|
|
|
|
for i, g1 in enumerate(group_elements[:3]): |
|
|
for j, g2 in enumerate(group_elements[:3]): |
|
|
|
|
|
intermediate = test_input @ g1.T |
|
|
final = intermediate @ g2.T |
|
|
|
|
|
|
|
|
composition = g2 @ g1 |
|
|
direct = test_input @ composition.T |
|
|
|
|
|
|
|
|
difference = np.linalg.norm(final - direct) |
|
|
print(f" g{j}∘g{i}: composition error = {difference:.8f}") |
|
|
|
|
|
|
|
|
def test_invariant_features(): |
|
|
"""Test extraction of invariant features.""" |
|
|
print("=== Group Theory Network: Invariant Feature Extraction ===\n") |
|
|
|
|
|
|
|
|
network = GroupTheoryNetwork( |
|
|
input_dim=2, |
|
|
group_types=["cyclic_16"], |
|
|
output_dim=8 |
|
|
) |
|
|
|
|
|
|
|
|
test_cases = [ |
|
|
("Unit Circle", np.array([[np.cos(θ), np.sin(θ)] for θ in np.linspace(0, 2*np.pi, 10)])), |
|
|
("Ellipse", np.array([[2*np.cos(θ), np.sin(θ)] for θ in np.linspace(0, 2*np.pi, 10)])), |
|
|
("Square", np.array([[1, 1], [1, -1], [-1, -1], [-1, 1]])), |
|
|
("Random", np.random.randn(8, 2)) |
|
|
] |
|
|
|
|
|
print("Invariant feature analysis:") |
|
|
|
|
|
for case_name, points in test_cases: |
|
|
|
|
|
original_features = network.predict(points) |
|
|
|
|
|
|
|
|
rotation_45 = np.array([[np.cos(np.pi/4), -np.sin(np.pi/4)], |
|
|
[np.sin(np.pi/4), np.cos(np.pi/4)]]) |
|
|
rotated_points = points @ rotation_45.T |
|
|
rotated_features = network.predict(rotated_points) |
|
|
|
|
|
|
|
|
feature_difference = np.linalg.norm(original_features - rotated_features) |
|
|
relative_difference = feature_difference / (np.linalg.norm(original_features) + 1e-8) |
|
|
|
|
|
print(f"\n{case_name}:") |
|
|
print(f" Feature difference: {feature_difference:.6f}") |
|
|
print(f" Relative difference: {relative_difference:.6f}") |
|
|
print(f" Original features mean: {np.mean(original_features):.4f}") |
|
|
print(f" Rotated features mean: {np.mean(rotated_features):.4f}") |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("Group Theory Algebraic Neural Network Demo\n") |
|
|
print("="*60) |
|
|
|
|
|
|
|
|
test_rotation_invariance() |
|
|
test_symmetry_detection() |
|
|
test_group_composition() |
|
|
test_invariant_features() |
|
|
|
|
|
print("\n" + "="*60) |
|
|
print("Group theory demo completed successfully!") |