#![allow(clippy::approx_constant)]
use burn_core as burn;
use burn::config::Config;
use burn::module::{Content, DisplaySettings, Module, ModuleDisplay};
use burn::tensor::Tensor;
use burn_linalg::cosine_similarity;
#[derive(Config, Debug)]
pub struct CosineSimilarityConfig {
#[config(default = 1)]
pub dim: isize,
#[config(default = 1e-8)]
pub eps: f64,
}
impl CosineSimilarityConfig {
pub fn init(&self) -> CosineSimilarity {
CosineSimilarity {
dim: self.dim,
eps: self.eps,
}
}
}
#[derive(Module, Debug)]
#[module(custom_display)]
pub struct CosineSimilarity {
pub dim: isize,
pub eps: f64,
}
impl ModuleDisplay for CosineSimilarity {
fn custom_settings(&self) -> Option<DisplaySettings> {
DisplaySettings::new()
.with_new_line_after_attribute(false)
.optional()
}
fn custom_content(&self, content: Content) -> Option<Content> {
content
.add("dim", &self.dim)
.add("eps", &self.eps)
.optional()
}
}
impl CosineSimilarity {
pub fn forward(&self, x1: Tensor<2>, x2: Tensor<2>) -> Tensor<1> {
cosine_similarity(x1, x2, self.dim, Some(self.eps)).squeeze_dim(self.dim)
}
}
#[cfg(test)]
mod tests {
use super::*;
use burn::tensor::TensorData;
use burn::tensor::Tolerance;
type FT = f32;
#[test]
fn cosine_similarity_dim1() {
let device = Default::default();
let x1 = Tensor::<2>::from_data(TensorData::from([[1.0, 0.0], [1.0, 1.0]]), &device);
let x2 = Tensor::<2>::from_data(TensorData::from([[1.0, 0.0], [0.0, 1.0]]), &device);
let output = CosineSimilarityConfig::new().init().forward(x1, x2);
let expected = TensorData::from([1.0, 0.707107]);
output
.into_data()
.assert_approx_eq::<FT>(&expected, Tolerance::default());
}
#[test]
fn cosine_similarity_negative_dim() {
let device = Default::default();
let x1 = Tensor::<2>::from_data(TensorData::from([[1.0, 0.0], [1.0, 1.0]]), &device);
let x2 = Tensor::<2>::from_data(TensorData::from([[1.0, 0.0], [0.0, 1.0]]), &device);
let output = CosineSimilarityConfig::new()
.with_dim(-1)
.init()
.forward(x1, x2);
let expected = TensorData::from([1.0, 0.707107]);
output
.into_data()
.assert_approx_eq::<FT>(&expected, Tolerance::default());
}
#[test]
fn display() {
let layer = CosineSimilarityConfig::new()
.with_dim(-1)
.with_eps(0.5)
.init();
assert_eq!(
alloc::format!("{layer}"),
"CosineSimilarity {dim: -1, eps: 0.5}"
);
}
}