from typing import Callable, Optional, Tuple, cast from ..config import registry from ..model import Model from ..types import Floats1d, Floats2d from ..util import get_width InT = Tuple[Floats2d, Floats2d] OutT = Floats1d @registry.layers("CauchySimilarity.v1") def CauchySimilarity(nI: Optional[int] = None) -> Model[InT, OutT]: """Compare input vectors according to the Cauchy similarity function proposed by Chen (2013). Primarily used within Siamese neural networks. """ return Model( "cauchy_similarity", forward, init=init, dims={"nI": nI, "nO": 1}, params={"W": None}, ) def forward( model: Model[InT, OutT], X1_X2: InT, is_train: bool ) -> Tuple[OutT, Callable]: X1, X2 = X1_X2 W = cast(Floats2d, model.get_param("W")) diff = X1 - X2 square_diff = diff**2 total = (W * square_diff).sum(axis=1) sim, bp_sim = inverse(total) def backprop(d_sim: OutT) -> InT: d_total = bp_sim(d_sim) d_total = model.ops.reshape2f(d_total, -1, 1) model.inc_grad("W", (d_total * square_diff).sum(axis=0)) d_square_diff = W * d_total d_diff = 2 * d_square_diff * diff return (d_diff, -d_diff) return sim, backprop def init( model: Model[InT, OutT], X: Optional[InT] = None, Y: Optional[OutT] = None ) -> None: if X is not None: model.set_dim("nI", get_width(X[0])) # Initialize weights to 1 W = model.ops.alloc1f(model.get_dim("nI")) W += 1 model.set_param("W", W) def inverse(total: OutT) -> Tuple[OutT, Callable]: inv = 1.0 / (1 + total) def backward(d_inverse: OutT) -> OutT: return d_inverse * (-1 / (total + 1) ** 2) return inv, backward