Answer by panvaf
import numpy as np
def softmax(z, axis=-1):
z = z - np.max(z, axis=axis, keepdims=True) # for numerical stability
exp_z = np.exp(z)
return exp_z / np.sum(exp_z, axis=axis, keepdims=True)
def relu(z):
return np.maximum(z, 0.0)
---------- Part 1: vectorized 1-NN (squared L2) ----------
def one_nn_predict(X_train, y_train, X_query):
# ||q - x||^2 = ||q||^2 - 2 q.x + ||x||^2
# ||q||^2 is the same for every training point, so it can't change the argmin -> skip it.
train_sq_norms = np.sum(X_train ** 2, axis=1) # (n,)
cross = X_query @ X_train.T # (m, n)
scores = train_sq_norms[None, :] - 2 * cross # (m, n)
nearest = np.argmin(scores, axis=1) # (m,), argmin takes the first min on ties
return y_train[nearest]
---------- Part 2: 1-NN as a forward pass (Linear + softmax) ----------
def one_nn_network_forward(X_train, X_query, beta=1.0):
# One output unit per training example:
# logit_i = 2 x_i.q - ||x_i||^2 = ||q||^2 - ||q - x_i||^2
# so the largest logit belongs to the nearest neighbor.
W = 2 * X_train.T # (d, n)
b = -np.sum(X_train ** 2, axis=1) # (n,)
logits = X_query @ W + b # (m, n)
return softmax(beta * logits, axis=1) # (m, n); bigger beta -> closer to one-hot
---------- L1 version ----------
def one_nn_predict_l1(X_train, y_train, X_query):
diffs = X_query[:, None, :] - X_train[None, :, :] # (m, n, d)
l1_dists = np.sum(np.abs(diffs), axis=2) # (m, n)
nearest = np.argmin(l1_dists, axis=1)
return y_train[nearest]
def one_nn_l1_network_forward(X_train, X_query, beta=1.0):
# No abs