use crate::autograd::ops::reduce::{MeanBackward, SumAxisBackward, SumBackward};
use crate::cuda::kernels;
use crate::tensor::Tensor;
use crate::tensor::shape;
use crate::tensor::storage::Storage;
impl Tensor {
pub fn sum(&self) -> Tensor {
let out = match self.storage() {
Storage::Cpu(data) => Tensor::scalar(data.iter().sum()),
Storage::Cuda(buf) => {
let out = kernels::sum(buf, self.numel()).expect("cuda sum");
Tensor::raw(Storage::Cuda(out), vec![1], self.device())
}
};
let shape = self.shape().to_vec();
out.with_grad(&[self], || SumBackward { shape })
}
pub fn mean(&self) -> Tensor {
let out = match self.storage() {
Storage::Cpu(data) => Tensor::scalar(data.iter().sum::<f32>() / data.len() as f32),
Storage::Cuda(buf) => {
let out = kernels::mean(buf, self.numel()).expect("cuda mean");
Tensor::raw(Storage::Cuda(out), vec![1], self.device())
}
};
let (shape, count) = (self.shape().to_vec(), self.numel());
out.with_grad(&[self], || MeanBackward { shape, count })
}
pub fn sum_axis(&self, axis: usize) -> Tensor {
let (outer, size, inner) = shape::split_at_axis(self.shape(), axis);
let mut out_shape = self.shape().to_vec();
out_shape.remove(axis);
if out_shape.is_empty() {
out_shape.push(1);
}
let out = match self.storage() {
Storage::Cuda(buf) => {
let out = kernels::sum_axis(buf, self.shape(), axis).expect("cuda sum_axis");
Tensor::raw(Storage::Cuda(out), out_shape.clone(), self.device())
}
Storage::Cpu(data) => {
let mut acc = vec![0.0f32; outer * inner];
for o in 0..outer {
for a in 0..size {
for i in 0..inner {
acc[o * inner + i] += data[(o * size + a) * inner + i];
}
}
}
Tensor::from_vec(acc, &out_shape)
}
};
let shape = self.shape().to_vec();
out.with_grad(&[self], || SumAxisBackward { shape, axis })
}
pub fn sum_axis_keep(&self, axis: usize) -> Tensor {
self.sum_axis(axis).unsqueeze(axis)
}
pub fn mean_axis(&self, axis: usize) -> Tensor {
let size = self.dim(axis) as f32;
self.sum_axis(axis).div_scalar(size)
}
pub fn mean_axis_keep(&self, axis: usize) -> Tensor {
self.mean_axis(axis).unsqueeze(axis)
}
pub fn max(&self) -> f32 {
match self.storage() {
Storage::Cpu(data) => data.iter().copied().fold(f32::NEG_INFINITY, f32::max),
Storage::Cuda(buf) => single(kernels::max(buf, self.numel()).expect("cuda max")),
}
}
pub fn min(&self) -> f32 {
match self.storage() {
Storage::Cpu(data) => data.iter().copied().fold(f32::INFINITY, f32::min),
Storage::Cuda(buf) => single(kernels::min(buf, self.numel()).expect("cuda min")),
}
}
pub fn variance(&self) -> f32 {
assert!(self.numel() > 0, "variance() of an empty tensor");
let data = self.to_vec();
let mean = data.iter().sum::<f32>() / data.len() as f32;
data.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / data.len() as f32
}
pub fn argmax(&self, axis: usize) -> Vec<usize> {
let (outer, size, inner) = shape::split_at_axis(self.shape(), axis);
let data = self.to_vec();
let mut out = vec![0usize; outer * inner];
for o in 0..outer {
for i in 0..inner {
let mut best = f32::NEG_INFINITY;
for a in 0..size {
let value = data[(o * size + a) * inner + i];
if value > best {
best = value;
out[o * inner + i] = a;
}
}
}
}
out
}
}
fn single(buf: crate::cuda::CudaBuffer) -> f32 {
buf.to_vec().expect("cuda download")[0]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "variance() of an empty tensor")]
fn variance_rejects_an_empty_vector() {
let tensor = Tensor::from_vec(vec![], &[0]);
assert_eq!(tensor.numel(), 0);
tensor.variance();
}
#[test]
#[should_panic(expected = "variance() of an empty tensor")]
fn variance_rejects_a_zero_sized_dimension() {
let tensor = Tensor::from_vec(vec![], &[2, 0]);
assert_eq!(tensor.numel(), 0);
tensor.variance();
}
#[test]
fn variance_of_a_singleton_is_zero() {
assert_eq!(Tensor::from_vec(vec![3.0], &[1]).variance(), 0.0);
}
#[test]
fn variance_uses_the_population_denominator() {
let tensor = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
assert_eq!(tensor.variance(), 1.25);
}
}