J'ai fini par utiliser une technique appelée "itération de Lloyd" pour résoudre ce problème. L'idée derrière l'algorithme est assez simple; pour exécuter une itération de Lloyd sur un ensemble de points, nous :
- Construisons un diagramme de Voronoi des points
- Recentrons chaque point dans sa cellule de Voronoi
- Répétons jusqu'à ce que les points soient suffisamment séparés
Cette encapsulation contient un peu de code d'exemple (avec des visualisations) :
from lloyd import Field
from scipy.spatial import voronoi_plot_2d
import matplotlib.pyplot as plt
import numpy as np
import umap, os
def plot(vor, name, e=0.3):
'''Trace la carte de Voronoi du tableau numpy 2D X'''
plot = voronoi_plot_2d(vor, show_vertices=False, line_colors='y', line_alpha=0.5, point_size=5)
plot.set_figheight(14)
plot.set_figwidth(20)
plt.axis([field.bb[0]-e, field.bb[1]+e, field.bb[2]-e, field.bb[3]+e])
if not os.path.exists('plots'): os.makedirs('plots')
if len(str(name)) < 2: name = '0' + str(name)
plot.savefig( 'plots/' + str(name) + '.png' )
# obtenir 1000 observations en deux dimensions et tracer leur carte de Voronoi
np.random.seed(1144392507)
X = np.random.rand(1000, 4)
X = umap.UMAP().fit_transform(X)
# exécuter 20 itérations de l'algorithme de Lloyd
field = Field(X)
for i in range(20):
print(' * exécution de l\'itération', i)
plot(field.voronoi, i)
field.relax()
Le point clé est que les points se déplacent exactement comme dans le gif suivant :
NB : Le gif ci-dessus montre l'itération de Lloyd non contrainte, où le domaine de sortie peut être très grand. Pour une discussion et du code pour construire des itérations de Lloyd contraintes en Python, j'ai écrit un petit article de blog.