fig, axes = plt.subplots(2, 3, figsize=(18, 10))
axes = axes.ravel()
titles = [
"1. Data and query point",
"2. Ordinary Euclidean KNN search",
"3. Local region used to estimate DANN metric",
"4. Adaptive DANN neighborhood",
"5. Final DANN nearest neighbors",
"6. Euclidean KNN vs DANN"
]
for ax, title in zip(axes, titles):
ax.scatter(X[y == 0, 0], X[y == 0, 1], s=20, alpha=0.55, label="Class 0")
ax.scatter(X[y == 1, 0], X[y == 1, 1], s=20, alpha=0.55, label="Class 1")
ax.scatter(
x_query[0],
x_query[1],
s=180,
marker="*",
label="Query point",
zorder=5
)
ax.set_title(title)
ax.set_xlabel("$x_1$")
ax.set_ylabel("$x_2$")
ax.set_xlim(-3.2, 3.2)
ax.set_ylim(-2.4, 2.4)
ax.grid(alpha=0.25)
axes[0].legend(loc="upper left")
axes[1].scatter(
X[idx_knn, 0],
X[idx_knn, 1],
s=90,
facecolors="none",
edgecolors="black",
linewidths=1.7,
label="Euclidean KNN neighbors"
)
circle = Circle(
x_query,
r_knn,
fill=False,
linewidth=2,
linestyle="--",
label="Euclidean search circle"
)
axes[1].add_patch(circle)
axes[1].legend(loc="upper left")
axes[2].scatter(
X_local[:, 0],
X_local[:, 1],
s=100,
facecolors="none",
edgecolors="black",
linewidths=1.4,
label="Local metric-estimation region"
)
circle_local = Circle(
x_query,
euclidean_distances(X, x_query)[idx_local[-1]],
fill=False,
linewidth=2,
linestyle="--",
label="Larger local window"
)
axes[2].add_patch(circle_local)
axes[2].legend(loc="upper left")
axes[3].plot(
ellipse_dann[:, 0],
ellipse_dann[:, 1],
linewidth=2.5,
label="DANN adaptive ellipse"
)
axes[3].scatter(
X[idx_dann, 0],
X[idx_dann, 1],
s=90,
facecolors="none",
edgecolors="black",
linewidths=1.7,
label="DANN neighbors"
)
axes[3].legend(loc="upper left")
axes[4].plot(
ellipse_dann[:, 0],
ellipse_dann[:, 1],
linewidth=2.5,
label="Adaptive search boundary"
)
axes[4].scatter(
X[idx_dann, 0],
X[idx_dann, 1],
s=110,
facecolors="none",
edgecolors="black",
linewidths=1.9,
label=f"DANN selected k={k_final}"
)
axes[4].legend(loc="upper left")
circle_compare = Circle(
x_query,
r_knn,
fill=False,
linewidth=2,
linestyle="--",
label="Euclidean KNN circle"
)
axes[5].add_patch(circle_compare)
axes[5].plot(
ellipse_dann[:, 0],
ellipse_dann[:, 1],
linewidth=2.5,
label="DANN ellipse"
)
axes[5].scatter(
X[idx_knn, 0],
X[idx_knn, 1],
s=80,
facecolors="none",
edgecolors="gray",
linewidths=1.4,
label="Euclidean KNN neighbors"
)
axes[5].scatter(
X[idx_dann, 0],
X[idx_dann, 1],
s=130,
facecolors="none",
edgecolors="black",
linewidths=1.9,
label="DANN neighbors"
)
axes[5].legend(loc="upper left")
fig.suptitle(
"Discriminant Adaptive Nearest Neighbor Search: From Circular to Adaptive Neighborhoods",
fontsize=16,
y=1.02
)
plt.tight_layout()
plt.show()