use std::fmt::Display;
use std::mem;
use itertools::Itertools;
use thiserror::Error;
use super::builder::Layer;
use crate::linalg::affine::AffFunc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TensorShape {
Flat { in_dim: usize },
}
impl TensorShape {
pub fn max_dim(&self) -> usize {
match self {
TensorShape::Flat { in_dim } => *in_dim,
}
}
pub fn compatible_dim(&self, dim: usize) -> Result<(), ShapeError> {
match self {
TensorShape::Flat { in_dim } => {
if *in_dim == dim {
Ok(())
} else {
Err(ShapeError::Dim {
expected: *in_dim,
got: dim,
})
}
}
}
}
pub fn valid_index(&self, idx: usize) -> Result<(), ShapeError> {
match self {
TensorShape::Flat { in_dim } => {
if idx < *in_dim {
Ok(())
} else {
Err(ShapeError::Index {
index: idx,
len: *in_dim,
})
}
}
}
}
}
impl Display for TensorShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TensorShape::Flat { in_dim } => {
write!(f, "[{}]", in_dim)
}
}
}
}
#[derive(Error, Clone, Debug)]
pub enum ShapeError {
#[error("Shape mismatch: expected {expected}, got {got}")]
Dim { expected: usize, got: usize },
#[error("Index {index} is out of bounds (length: {len})")]
Index { index: usize, len: usize },
#[error("Input type is not compatible")]
Type,
}
#[derive(Clone, Debug)]
pub struct Architecture {
pub input_shape: TensorShape,
pub current_shape: TensorShape,
pub operators: Vec<(Layer, TensorShape)>,
}
impl Architecture {
pub fn new(input_shape: TensorShape) -> Architecture {
Architecture {
input_shape,
current_shape: input_shape,
operators: Vec::new(),
}
}
pub fn linear(&mut self, aff: AffFunc) -> Result<(), ShapeError> {
self.current_shape.compatible_dim(aff.indim())?;
let in_dim = aff.outdim();
self.current_shape = TensorShape::Flat { in_dim };
self.operators
.push((Layer::Linear(aff), self.current_shape));
Ok(())
}
pub fn partial_relu(&mut self, idx: usize) -> Result<(), ShapeError> {
self.current_shape.valid_index(idx)?;
self.operators.push((Layer::ReLU(idx), self.current_shape));
Ok(())
}
pub fn relu(&mut self) -> Result<(), ShapeError> {
for idx in 0..self.current_shape.max_dim() {
self.partial_relu(idx)?;
}
Ok(())
}
pub fn partial_leaky_relu(&mut self, idx: usize, alpha: f64) -> Result<(), ShapeError> {
self.current_shape.valid_index(idx)?;
self.operators
.push((Layer::LeakyReLU(idx, alpha), self.current_shape));
Ok(())
}
pub fn leaky_relu(&mut self, alpha: f64) -> Result<(), ShapeError> {
for idx in 0..self.current_shape.max_dim() {
self.partial_leaky_relu(idx, alpha)?;
}
Ok(())
}
pub fn partial_hard_tanh(&mut self, idx: usize) -> Result<(), ShapeError> {
self.current_shape.valid_index(idx)?;
self.operators
.push((Layer::HardTanh(idx), self.current_shape));
Ok(())
}
pub fn hard_tanh(&mut self) -> Result<(), ShapeError> {
for idx in 0..self.current_shape.max_dim() {
self.partial_hard_tanh(idx)?;
}
Ok(())
}
pub fn partial_hard_sigmoid(&mut self, idx: usize) -> Result<(), ShapeError> {
self.current_shape.valid_index(idx)?;
self.operators
.push((Layer::HardSigmoid(idx), self.current_shape));
Ok(())
}
pub fn hard_sigmoid(&mut self) -> Result<(), ShapeError> {
for idx in 0..self.current_shape.max_dim() {
self.partial_hard_sigmoid(idx)?;
}
Ok(())
}
pub fn argmax(&mut self) -> Result<(), ShapeError> {
self.operators.push((Layer::Argmax, self.current_shape));
Ok(())
}
#[rustfmt::skip]
pub fn operators(&self) -> impl Iterator<Item = &Layer> {
self.operators.iter()
.map(|(op, _)| op)
}
pub fn extract_range(&self, start: usize, end: usize) -> Result<Architecture, ShapeError> {
if start >= end || end > self.operators.len() {
return Err(ShapeError::Index {
index: end,
len: self.operators.len(),
});
}
let iter = self.operators.iter();
let (iter_skip, input_shape) = if start == 0 {
(iter.skip(0), self.input_shape)
} else {
let mut iter_skip = iter.skip(start - 1);
let (_, input_shape) = iter_skip.next().ok_or(ShapeError::Index {
index: start,
len: self.operators.len(),
})?;
(iter_skip, *input_shape)
};
let mut sub_arch = Architecture::new(input_shape);
sub_arch.operators.reserve(end - start);
for (layer, shape) in iter_skip.take(end - start) {
sub_arch.operators.push((layer.clone(), *shape));
sub_arch.current_shape = *shape;
}
Ok(sub_arch)
}
}
impl Display for Architecture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:<30} {:<5} {:<16}", "Layer", "Count", "Shape")?;
writeln!(f, "{:=<30}==={:=>5}==={:=>16}", "=", "=", "=")?;
let chunks = self
.operators
.iter()
.chunk_by(|(layer, _)| mem::discriminant(layer));
for (_, mut layers) in chunks.into_iter() {
let (layer, shape) = layers.next().unwrap();
let descr = match layer {
Layer::Linear(aff) => {
format!("Linear ({}x{})", aff.indim(), aff.outdim())
}
Layer::ReLU(idx) => {
format!("ReLU (idx={})", idx)
}
Layer::LeakyReLU(idx, alpha) => {
format!("LeakyReLU (alpha={} idx={})", alpha, idx)
}
Layer::HardTanh(idx) => {
format!("HardTanh (idx={})", idx)
}
Layer::HardSigmoid(idx) => {
format!("HardSigmoid (idx={})", idx)
}
Layer::Argmax => "Argmax".to_string(),
Layer::ClassChar(class) => {
format!("ClassChar (class={})", class)
}
};
let count = layers.count() + 1;
writeln!(f, "{:<30} {:>5} {:>16}", descr, count, shape)?;
}
Ok(())
}
}
impl IntoIterator for Architecture {
type IntoIter =
std::iter::Map<std::vec::IntoIter<(Layer, TensorShape)>, fn((Layer, TensorShape)) -> Layer>;
type Item = Layer;
fn into_iter(self) -> Self::IntoIter {
self.operators.into_iter().map(|(op, _)| op)
}
}
#[cfg(test)]
mod tests {
use ndarray::{Array1, Array2};
use super::*;
#[test]
fn test_init() {
let mut arch = Architecture::new(TensorShape::Flat { in_dim: 8 });
arch.linear(AffFunc::from_mats(Array2::ones((4, 8)), Array1::zeros(4)))
.unwrap();
assert_eq!(arch.operators.len(), 1);
assert_eq!(arch.current_shape, TensorShape::Flat { in_dim: 4 });
}
#[test]
fn test_shape_error() {
let mut arch = Architecture::new(TensorShape::Flat { in_dim: 4 });
assert!(
arch.linear(AffFunc::from_mats(Array2::ones((4, 8)), Array1::zeros(4)))
.is_err()
);
}
#[test]
fn test_relu_layer() {
let mut arch = Architecture::new(TensorShape::Flat { in_dim: 4 });
arch.relu().unwrap();
assert_eq!(arch.operators.len(), 4);
}
#[test]
fn test_extract_range_start() {
let mut arch = Architecture::new(TensorShape::Flat { in_dim: 8 });
arch.linear(AffFunc::from_mats(Array2::ones((4, 8)), Array1::zeros(4)))
.unwrap();
arch.relu().unwrap();
arch.linear(AffFunc::from_mats(Array2::ones((2, 4)), Array1::zeros(2)))
.unwrap();
arch.relu().unwrap();
let subnetwork = arch.extract_range(0, 7).unwrap();
assert_eq!(subnetwork.operators.len(), 7);
assert_eq!(subnetwork.input_shape, TensorShape::Flat { in_dim: 8 });
assert_eq!(subnetwork.current_shape, TensorShape::Flat { in_dim: 2 });
}
#[test]
fn test_extract_range() {
let mut arch = Architecture::new(TensorShape::Flat { in_dim: 8 });
arch.linear(AffFunc::from_mats(Array2::ones((4, 8)), Array1::zeros(4)))
.unwrap();
arch.relu().unwrap();
arch.linear(AffFunc::from_mats(Array2::ones((2, 4)), Array1::zeros(2)))
.unwrap();
arch.relu().unwrap();
let subnetwork = arch.extract_range(2, 7).unwrap();
assert_eq!(subnetwork.operators.len(), 5);
assert_eq!(subnetwork.input_shape, TensorShape::Flat { in_dim: 4 });
assert_eq!(subnetwork.current_shape, TensorShape::Flat { in_dim: 2 });
}
}