biolab / biolab/orange3

Add optional L2 normalization for Euclidean metric in kNN widget

Open
#7,270 3 comments 0 reactions 0 assignees View on GitHub
needs discussion
Dominant language
Python
Stars
5.7k
Forks
1.1k
Avg merge
12d 2h
Merged PRs (30d)
1

Description

**What's your use case?**
I am using the kNN widget in Orange for text classification tasks based on document embeddings (e.g., from the Document Embedding widget).

In this context, similarity between instances is typically measured using cosine similarity. However, since cosine distance is not available in the kNN widget, a common workaround is to use Euclidean distance on L2-normalized vectors, which is mathematically equivalent in terms of neighbor ranking. Source Bouhsine, T. (2026, febrero 23). In Defense of Cosine Similarity: Normalization Eliminates the Gauge Freedom: "3. Euclidean k-NN on normalized embeddings = cosine search. By Corollary 5, FAISS flat L2 search on unit-normalized vectors produces the same neighbor rankings as cosine search. Use whichever is faster for your infrastructure."

Currently, Orange does not provide an easy way to apply L2 normalization per instance within the standard workflow. While it is possible to implement this via a Python Script widget, this introduces additional complexity and breaks the simplicity and reproducibility of the pipeline, especially for non-technical users.

Moreover, applying normalization externally via the Preprocess widget overrides the learner’s internal preprocessing steps, which can lead to unintended side effects.

**What's your proposed solution?**
Add an optional checkbox in the kNN widget:

Normalize instances (L2)

This option should:

apply L2 normalization per instance before distance computation
be enabled only when the Euclidean metric is selected
be integrated into the learner’s internal preprocessing pipeline (without overriding existing preprocessing steps)

This would allow users to effectively use cosine-equivalent similarity while preserving the current behavior and optimizations of the kNN implementation.

**Are there any alternative solutions?**
Yes, but they are less ideal:

1.- Using a Python Script widget
· requires custom code
· reduces usability for non-programmers
· makes workflows less transparent
2.- Adding L2 normalization in the Preprocess widget
· applies globally rather than model-specific
· overrides the learner’s internal preprocessing
· may affect other models unintentionally
3.- Adding cosine distance directly to kNN
· would require switching to brute-force search in many cases
· may negatively impact performance
· adds complexity to the implementation

For these reasons, integrating L2 normalization as an optional feature within the kNN widget seems to be the most practical and user-friendly solution.

Contributor guide

Open the contributing guide

Research direction

Start by locating the kNN widget implementation and its existing Euclidean-metric tests. Trace how the learner’s internal preprocessing is assembled, then verify that the option normalizes each instance only for Euclidean distance, preserves current behavior when disabled, and is covered by tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python, scikit-learn
Domain
machine-learning
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.