use std::marker::PhantomData;
use static_assertions::assert_impl_all;
use crate::{Element, Symbol, Tape, Tensor, Value};
use super::{Module, Visitor};
assert_impl_all!(Linear<f64>: Send, Sync);
#[derive(Debug, Clone)]
pub struct Linear<E> {
weights: Symbol,
bias: Symbol,
_marker: PhantomData<E>,
}
impl<E: Element> Linear<E> {
pub fn new(tape: &Tape<E>, weights: Tensor<E>, bias: Tensor<E>) -> Self {
let weights_shape = weights.shape();
let bias_shape = bias.shape();
assert_eq!(
weights_shape.rank(),
2,
"linear weights must be rank 2, got {weights_shape}"
);
assert_eq!(
bias_shape.rank(),
1,
"linear bias must be rank 1, got {bias_shape}"
);
assert_eq!(
weights_shape.axes()[1],
bias_shape.axes()[0],
"linear weights {weights_shape} and bias {bias_shape} disagree on outputs"
);
Self {
weights: tape.parameter(weights).symbol(),
bias: tape.parameter(bias).symbol(),
_marker: PhantomData,
}
}
pub fn weights(&self) -> Symbol {
self.weights
}
pub fn bias(&self) -> Symbol {
self.bias
}
pub fn parameters(&self) -> impl Iterator<Item = Symbol> + '_ {
super::parameters(self).into_iter()
}
}
impl<E: Element> Module<E> for Linear<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let tape = input.tape();
let weights = tape.resolve(self.weights);
let bias = tape.resolve(self.bias);
let product = input.matmul(weights);
product + bias.broadcast_along_like(0, product)
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.parameter("weights", self.weights);
visitor.parameter("bias", self.bias);
}
}
#[cfg(test)]
#[path = "tests/linear_tests.rs"]
mod tests;