Contents
Deep Breakdown
What it tells me (Situation Awareness): The derivative of is — identical in magnitude to the derivative, but negative. This negative sign reflects that is a strictly decreasing function: as increases, decreases. The key identity (for ) immediately implies the derivatives are negatives of each other, since . In DA/DS: appears in computing angular distances between unit vectors (cosine similarity to angle conversion: ), in computing gradients through similarity metrics, and in geodesic distance formulas used in geospatial and graph ML.
Flowchart
Real-world use
Where it is used
Computing the gradient of angular distance when using angular loss functions in metric learning and contrastive learning; differentiating geodesic distance formulas for spherical coordinates in geospatial ML; computing gradients through -based activation functions; deriving the gradient of the angular margin in ArcFace loss for face recognition.
Problem it solves
When optimizing over angular quantities (e.g., minimizing the angle between embedding vectors), the gradient must flow through . Without this derivative, backpropagation through angular loss functions cannot be computed.
How it solves the problem
Implicit differentiation: let , so . Differentiate: , giving . Since , , and . Therefore .
Example in a business
A DS trains a recommendation model with ArcFace-style angular margin loss. The logit for class is , and the angular distance is needed. The gradient uses exactly (with ). This gradient flows back through the embedding layer.
The formula, piece by piece
- : inverse cosine function; maps ; decreasing function
- : the argument; domain requires , i.e.,
- : numerator; the negative sign because is decreasing
- : denominator; identical to the case
- Base case:
- Identity:
- General chain rule:
- Cosine similarity gradient:
The formula in words
"The derivative of is the negative of the derivative. It is in the numerator (chain rule factor), in the denominator, and a minus sign because decreases as increases. The magnitude is identical to , only the direction flips." Tiny examples: . . . .
Critical Concepts
- Negative derivative = decreasing function: goes from at to at . Its derivative is always negative (except undefined at the endpoints). This is the opposite of , which increases from to .
- Complementary identity: for all . Differentiating both sides: , so . This is the quickest proof of the formula.
- Angular gradient in embeddings: when and are unit vectors, the gradient of with respect to the cosine similarity is . This blows up when or (nearly identical or opposite vectors). Clip or add in practice.
- Same domain restriction as arcsin: required. Inputs outside to are undefined. When computing , numerical errors can push the similarity slightly outside ; always clip first:
sim = np.clip(sim, -1, 1). - Gradient clipping at boundary: just like , the derivative as .
In ArcFace and similar angular losses, this is handled by clipping the cosine similarity away from .
Step-by-step calculation (tiny example)
Compute the gradient of angular distance at .
Step 1: Identify. with , .
Step 2: Apply formula. .
Step 3: Evaluate. . . . Interpretation: at cosine similarity , a unit increase in similarity decreases the angle by radians (about ). Verify: divided by .
Code examples
Beginner example (Python)
import numpy as np
def arccos_ax_deriv(a: float, x: float) -> float:
"""d/dx(arccos(ax)) = -a / sqrt(1 - (ax)^2). Requires |ax| < 1."""
arg = a * x
if abs(arg) >= 1.0:
raise ValueError(f"|ax| = {abs(arg):.4f} >= 1: outside arccos domain.")
return -a / np.sqrt(1.0 - arg**2) # Verify numerically
h = 1e-7
test_cases = [(1.0, 0.5), (2.0, 0.3), (0.5, 0.8), (3.0, 0.1)]
print(f"{'a':>6} {'x':>6} {'Analytic':>12} {'Numerical':>12} {'Match':>7}")
for a, x in test_cases:
analytic = arccos_ax_deriv(a, x)
numerical = (np.arccos(a*(x+h)) - np.arccos(a*(x-h))) / (2 * h)
match = abs(analytic - numerical) < 1e-5
print(f"{a:>6.2f} {x:>6.3f} {analytic:>12.6f} {numerical:>12.6f} {str(match):>7}") # Angular distance gradient for cosine similarity
print("\nAngular distance gradient d(arccos(sim))/d(sim):")
for sim in [0.0, 0.3, 0.6, 0.8, 0.9, 0.99]:
grad = arccos_ax_deriv(1.0, sim)
angle_deg = np.degrees(np.arccos(sim))
print(f" sim={sim:.2f} (theta={angle_deg:.1f} deg): gradient = {grad:.4f}")Beginner example (R)
# d/dx(arccos(ax)) = -a / sqrt(1 - (ax)^2)
arccos_ax_deriv <- function(a, x) { arg <- a * x
if (abs(arg) >= 1) stop(sprintf("|ax| = %.4f >= 1: outside domain", abs(arg))) -a / sqrt(1 - arg^2)
} # Verify with complement identity: d/dx(arccos) = -d/dx(arcsin)
arcsin_ax_deriv <- function(a, x) a / sqrt(1 - (a*x)^2)
cat("Complement check: arccos_deriv + arcsin_deriv should = 0\n")
for (x in c(0.0, 0.3, 0.6, 0.8)) { sum_derivs <- arccos_ax_deriv(1, x) + arcsin_ax_deriv(1, x)
cat(sprintf(" x=%.1f: arccos'+ arcsin' = %.8f\n", x, sum_derivs))
}Professional example (Python)
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
np.random.seed(42) # -- Angular loss gradient: d/d(embedding) of arccos(cosine_similarity) --
# Use case: metric learning where angular distance between embeddings is minimized.
# Gradient flows back through arccos to update embedding parameters.
EMBED_DIM = 64
N_SAMPLES = 100
def unit_normalize(x: np.ndarray) -> np.ndarray:
"""L2-normalize each row to lie on unit sphere."""
norms = np.linalg.norm(x, axis=1, keepdims=True)
return x / (norms + 1e-10)
def arccos_gradient_wrt_sim(cos_sim: np.ndarray, epsilon: float = 1e-6) -> np.ndarray:
"""d/d(cos_sim)(arccos(cos_sim)) = -1 / sqrt(1 - cos_sim^2). Clip cos_sim away from +-1 to avoid infinite gradient. """
cos_sim_clipped = np.clip(cos_sim, -1 + epsilon, 1 - epsilon)
return -1.0 / np.sqrt(1.0 - cos_sim_clipped**2)
def angular_loss_and_gradient( embeds_a: np.ndarray, embeds_b: np.ndarray ) -> tuple:
"""Angular distance loss = mean arccos(cos_sim(a, b)). Gradient w.r.t. cos_sim: -1/sqrt(1 - sim^2). """
sim = np.sum(embeds_a * embeds_b, axis=1) # dot product of unit vectors = cosine sim
sim_clipped = np.clip(sim, -1 + 1e-6, 1 - 1e-6)
angles = np.arccos(sim_clipped)
loss = np.mean(angles)
grad_wrt_sim = arccos_gradient_wrt_sim(sim)
return float(loss), grad_wrt_sim, angles # Simulate two batches of embeddings
embeds_a = unit_normalize(np.random.randn(N_SAMPLES, EMBED_DIM))
embeds_b = unit_normalize(np.random.randn(N_SAMPLES, EMBED_DIM))
loss, grad_sim, angles = angular_loss_and_gradient(embeds_a, embeds_b)
cos_sims = np.sum(embeds_a * embeds_b, axis=1)
print("Angular Distance Loss: gradient via d/d(sim)(arccos(sim)) = -1/sqrt(1 - sim^2)")
print(f"\nBatch size: {N_SAMPLES}, embed dim: {EMBED_DIM}")
print(f"Cosine similarity stats: mean={cos_sims.mean():.4f}, std={cos_sims.std():.4f}")
print(f" Range: [{cos_sims.min():.4f}, {cos_sims.max():.4f}]")
print(f"Angular distance (degrees): mean={np.degrees(angles).mean():.2f}, " f"range=[{np.degrees(angles.min()):.2f}, {np.degrees(angles.max()):.2f}]")
print(f"\nAngular loss: {loss:.6f} rad = {np.degrees(loss):.2f} deg")
print(f"\nGradient d(loss)/d(sim) stats:")
print(f" mean={grad_sim.mean():.4f}, range=[{grad_sim.min():.4f}, {grad_sim.max():.4f}]")
print(f"\nKey: most negative gradient at sim close to +/-1 (large angle change per similarity change)")
max_grad_idx = np.argmax(np.abs(grad_sim))
print(f" Most extreme gradient: sim={cos_sims[max_grad_idx]:.4f}, " f"grad={grad_sim[max_grad_idx]:.4f} (theta={np.degrees(angles[max_grad_idx]):.2f} deg)")Source: domain knowledge