use crate::tensor::{AsTensor, Tensor};
use crate::Float;
use crate::tensor_ops::{activation_ops, scalar, shape, xent_ops};
#[allow(dead_code)]
pub fn sigmoid<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Sigmoid)
}
#[allow(dead_code)]
pub fn tanh<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(crate::tensor_ops::math_ops::Tanh)
}
#[allow(dead_code)]
pub fn relu<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::ReLU)
}
#[allow(dead_code)]
pub fn leaky_relu<'graph, A, F: Float>(x: A, alpha: F) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
crate::tensor_ops::arithmetic::maximum(x, scalar(alpha, g) * x)
}
#[allow(dead_code)]
pub fn elu<'graph, A, F: Float>(x: A, alpha: F) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Elu { alpha })
}
#[allow(dead_code)]
pub fn softplus<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Softplus)
}
#[allow(dead_code)]
pub fn swish<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Swish)
}
#[allow(dead_code)]
pub fn gelu<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Gelu)
}
#[allow(dead_code)]
pub fn mish<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(activation_ops::Mish)
}
#[allow(dead_code)]
pub fn softmax<'graph, A, F: Float>(x: A, axis: isize) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
let op = activation_ops::Softmax { axis };
Tensor::builder(g).append_input(x.as_ref(), false).build(op)
}
#[allow(dead_code)]
pub fn log_softmax<'graph, A, F: Float>(x: A, axis: isize) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
Tensor::builder(g)
.append_input(x.as_ref(), false)
.build(xent_ops::LogSoftmax { axis })
}
#[allow(dead_code)]
pub fn sigmoid_cross_entropy<'graph, A, B, F: Float>(y: A, t: B) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
B: AsRef<Tensor<'graph, F>> + Copy,
{
let y = y.as_ref();
let g = y.graph();
let op = xent_ops::SigmoidCrossEntropy;
Tensor::builder(g)
.setshape(&shape(y))
.append_input(y.as_ref(), false)
.append_input(t.as_ref(), false)
.build(op)
}
#[allow(dead_code)]
pub fn softmax_cross_entropy<'graph, A, B, F: Float>(y: A, t: B) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
B: AsRef<Tensor<'graph, F>> + Copy,
{
let y = y.as_ref();
let g = y.graph();
let op = xent_ops::SoftmaxCrossEntropy;
Tensor::builder(g)
.append_input(y.as_ref(), false)
.append_input(t.as_ref(), false)
.build(op)
}
#[allow(dead_code)]
pub fn sparse_softmax_cross_entropy<'graph, A, B, F: Float>(y: A, t: B) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
B: AsRef<Tensor<'graph, F>> + Copy,
{
let y = y.as_ref();
let g = y.graph();
let op = xent_ops::SparseSoftmaxCrossEntropy;
Tensor::builder(g)
.append_input(y.as_ref(), false)
.append_input(t.as_ref(), false)
.build(op)
}
#[allow(dead_code)]
pub fn mean_squared_error<'graph, A, B, F: Float>(y: A, t: B) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
B: AsRef<Tensor<'graph, F>> + Copy,
{
crate::tensor_ops::reduction::reduce_mean(
crate::tensor_ops::arithmetic::square(y.as_ref() - t.as_ref()),
&[-1],
false,
)
}
#[allow(dead_code)]
pub fn normalize<'graph, A, AT, F: Float>(x: A, axes: &AT) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
AT: AsTensor<'graph, F>,
{
let _x = x.as_ref();
let g = _x.graph();
let _axes = axes.as_tensor(g);
let mean = crate::tensor_ops::reduction::reduce_mean(_x, &_axes, true);
let centered = _x - mean;
let variance = crate::tensor_ops::reduction::reduce_mean(
crate::tensor_ops::arithmetic::square(centered),
&_axes,
true,
);
let em5 = scalar(
F::from(1e-5).expect("Failed to convert constant to float"),
g,
);
centered * crate::tensor_ops::arithmetic::inv_sqrt(variance + em5)
}
#[allow(dead_code)]
pub fn batch_norm<'graph, A, B, C, F: Float>(x: A, scale: B, shift: C) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
B: AsRef<Tensor<'graph, F>> + Copy,
C: AsRef<Tensor<'graph, F>> + Copy,
{
normalize(x, &[0]) * scale.as_ref() + shift.as_ref()
}
#[allow(dead_code)]
pub fn hard_sigmoid<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
let one = scalar(F::one(), g);
let two = scalar(
F::from(2.0).expect("Failed to convert constant to float"),
g,
);
let zero = scalar(F::zero(), g);
let shifted = (x + one) / two;
crate::tensor_ops::arithmetic::maximum(
zero,
crate::tensor_ops::arithmetic::minimum(one, shifted),
)
}
#[allow(dead_code)]
pub fn hard_tanh<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
let one = scalar(F::one(), g);
let neg_one = scalar(-F::one(), g);
crate::tensor_ops::arithmetic::maximum(neg_one, crate::tensor_ops::arithmetic::minimum(one, x))
}
#[allow(dead_code)]
pub fn relu6<'graph, A, F: Float>(x: A) -> Tensor<'graph, F>
where
A: AsRef<Tensor<'graph, F>> + Copy,
{
let x = x.as_ref();
let g = x.graph();
let zero = scalar(F::zero(), g);
let six = scalar(
F::from(6.0).expect("Failed to convert constant to float"),
g,
);
crate::tensor_ops::arithmetic::minimum(crate::tensor_ops::arithmetic::maximum(zero, x), six)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tensor_ops::convert_to_tensor;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_basic_activations() {
crate::run(|g| {
let x = convert_to_tensor(array![-1.0_f32, 0.0, 1.0], g);
let relu_result = relu(x);
let expected_relu = array![0.0_f32, 0.0, 1.0];
assert_eq!(
relu_result.eval(g).expect("Operation failed"),
expected_relu.into_dyn()
);
let leaky_result = leaky_relu(x, 0.1);
let expected_leaky = array![-0.1_f32, 0.0, 1.0];
assert_eq!(
leaky_result.eval(g).expect("Operation failed"),
expected_leaky.into_dyn()
);
let sigmoid_result = sigmoid(x);
let actual_sigmoid = sigmoid_result.eval(g).expect("Operation failed");
assert_relative_eq!(actual_sigmoid[0], 0.2689414, epsilon = 1e-6);
assert_relative_eq!(actual_sigmoid[1], 0.5, epsilon = 1e-6);
assert_relative_eq!(actual_sigmoid[2], 0.7310586, epsilon = 1e-6);
});
}
#[test]
fn test_advanced_activations() {
crate::run(|g| {
let x = convert_to_tensor(array![-1.0_f32, 0.0, 1.0], g);
let elu_result = elu(x, 1.0);
let actual_elu = elu_result.eval(g).expect("Operation failed");
assert_relative_eq!(actual_elu[1], 0.0, epsilon = 1e-6);
assert_relative_eq!(actual_elu[2], 1.0, epsilon = 1e-6);
let softplus_result = softplus(x);
let actual_softplus = softplus_result.eval(g).expect("Operation failed");
assert!(actual_softplus[0] > 0.0);
assert!(actual_softplus[1] > 0.0);
assert!(actual_softplus[2] > 0.0);
});
}
#[test]
fn test_softmax() {
crate::run(|g| {
let x = convert_to_tensor(array![[1.0_f32, 2.0, 3.0]], g);
let softmax_result = softmax(x, 1); let actual = softmax_result.eval(g).expect("Operation failed");
let sum: f32 = actual.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-6);
let log_softmax_result = log_softmax(x, 1);
let log_actual = log_softmax_result.eval(g).expect("Operation failed");
let log_slice = log_actual.index_axis(scirs2_core::ndarray::Axis(0), 0);
assert!(log_slice[0] < 0.0);
assert!(log_slice[1] < 0.0);
assert!(log_slice[2] < 0.0);
});
}
#[test]
fn test_loss_functions() {
crate::run(|g| {
let logits = convert_to_tensor(array![0.5_f32, -0.5], g);
let targets = convert_to_tensor(array![1.0_f32, 0.0], g);
let loss = sigmoid_cross_entropy(logits, targets);
let loss_val = loss.eval(g).expect("Operation failed");
assert!(loss_val[0] > 0.0);
assert!(loss_val[1] > 0.0);
let predictions = convert_to_tensor(array![1.0_f32, 2.0, 3.0], g);
let true_vals = convert_to_tensor(array![1.5_f32, 2.5, 2.5], g);
let mse = mean_squared_error(predictions, true_vals);
let mse_val = mse.eval(g).expect("Operation failed");
assert!(mse_val[scirs2_core::ndarray::IxDyn(&[])] > 0.0);
});
}
#[test]
fn test_hard_activations() {
crate::run(|g| {
let x = convert_to_tensor(array![-2.0_f32, 0.0, 2.0], g);
let hard_sig_result = hard_sigmoid(x);
let expected_hard_sig = array![0.0_f32, 0.5, 1.0];
assert_eq!(
hard_sig_result.eval(g).expect("Operation failed"),
expected_hard_sig.into_dyn()
);
let hard_tanh_result = hard_tanh(x);
let expected_hard_tanh = array![-1.0_f32, 0.0, 1.0];
assert_eq!(
hard_tanh_result.eval(g).expect("Operation failed"),
expected_hard_tanh.into_dyn()
);
let x2 = convert_to_tensor(array![-1.0_f32, 3.0, 8.0], g);
let relu6_result = relu6(x2);
let expected_relu6 = array![0.0_f32, 3.0, 6.0];
assert_eq!(
relu6_result.eval(g).expect("Operation failed"),
expected_relu6.into_dyn()
);
});
}
#[test]
fn test_normalization() {
crate::run(|g| {
let x = convert_to_tensor(array![[1.0_f32, 2.0], [3.0, 4.0]], g);
let normalized = normalize(x, &[0]);
let result = normalized.eval(g).expect("Operation failed");
assert_eq!(result.shape(), &[2, 2]);
});
}
}