Nailed it on the first try :3c (or not, and I'll regret writing this in a few years)main
parent
6c1d6874d7
commit
d7eb6de34e
@ -0,0 +1,138 @@
|
|||||||
|
use crate::algebra::{NeuraMatrix, NeuraVector};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// A 1-dimensional convolutional
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NeuraConv1DPad<
|
||||||
|
const LENGTH: usize,
|
||||||
|
const IN_FEATS: usize,
|
||||||
|
const WINDOW: usize,
|
||||||
|
Layer: NeuraLayer<Input = NeuraVector<{ IN_FEATS * WINDOW }, f64>>,
|
||||||
|
> {
|
||||||
|
inner_layer: Layer,
|
||||||
|
pad_with: NeuraVector<IN_FEATS, f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const LENGTH: usize,
|
||||||
|
const IN_FEATS: usize,
|
||||||
|
const WINDOW: usize,
|
||||||
|
Layer: NeuraLayer<Input = NeuraVector<{ IN_FEATS * WINDOW }, f64>>,
|
||||||
|
> NeuraConv1DPad<LENGTH, IN_FEATS, WINDOW, Layer>
|
||||||
|
where
|
||||||
|
[u8; IN_FEATS * WINDOW]: Sized,
|
||||||
|
{
|
||||||
|
pub fn new(inner_layer: Layer, pad_with: NeuraVector<IN_FEATS, f64>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner_layer,
|
||||||
|
pad_with,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iterate_windows<'a>(
|
||||||
|
&'a self,
|
||||||
|
input: &'a NeuraMatrix<IN_FEATS, LENGTH, f64>,
|
||||||
|
) -> impl Iterator<Item = (usize, Layer::Input)> + 'a {
|
||||||
|
(0..LENGTH).map(move |window_center| {
|
||||||
|
let mut virtual_input: NeuraVector<{ IN_FEATS * WINDOW }, f64> = NeuraVector::default();
|
||||||
|
for i in 0..WINDOW {
|
||||||
|
let input_index = i as isize + window_center as isize - (WINDOW - 1) as isize / 2;
|
||||||
|
|
||||||
|
if input_index < 0 || input_index >= LENGTH as isize {
|
||||||
|
for j in 0..IN_FEATS {
|
||||||
|
virtual_input[i * IN_FEATS + j] = self.pad_with[j];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for j in 0..IN_FEATS {
|
||||||
|
virtual_input[i * IN_FEATS + j] = input[input_index as usize][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(window_center, virtual_input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const LENGTH: usize,
|
||||||
|
const IN_FEATS: usize,
|
||||||
|
const OUT_FEATS: usize,
|
||||||
|
const WINDOW: usize,
|
||||||
|
Layer: NeuraLayer<
|
||||||
|
Input = NeuraVector<{ IN_FEATS * WINDOW }, f64>,
|
||||||
|
Output = NeuraVector<OUT_FEATS, f64>,
|
||||||
|
>,
|
||||||
|
> NeuraLayer for NeuraConv1DPad<LENGTH, IN_FEATS, WINDOW, Layer>
|
||||||
|
{
|
||||||
|
type Input = NeuraMatrix<IN_FEATS, LENGTH, f64>;
|
||||||
|
type Output = NeuraMatrix<OUT_FEATS, LENGTH, f64>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &Self::Input) -> Self::Output {
|
||||||
|
let mut res = NeuraMatrix::default();
|
||||||
|
|
||||||
|
for (window_center, virtual_input) in self.iterate_windows(input) {
|
||||||
|
res.set_row(window_center, self.inner_layer.eval(&virtual_input));
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
const LENGTH: usize,
|
||||||
|
const IN_FEATS: usize,
|
||||||
|
const OUT_FEATS: usize,
|
||||||
|
const WINDOW: usize,
|
||||||
|
Layer: NeuraLayer<
|
||||||
|
Input = NeuraVector<{ IN_FEATS * WINDOW }, f64>,
|
||||||
|
Output = NeuraVector<OUT_FEATS, f64>,
|
||||||
|
>,
|
||||||
|
> NeuraTrainableLayer for NeuraConv1DPad<LENGTH, IN_FEATS, WINDOW, Layer>
|
||||||
|
where
|
||||||
|
Layer: NeuraTrainableLayer,
|
||||||
|
{
|
||||||
|
type Delta = <Layer as NeuraTrainableLayer>::Delta;
|
||||||
|
|
||||||
|
fn backpropagate(
|
||||||
|
&self,
|
||||||
|
input: &Self::Input,
|
||||||
|
epsilon: Self::Output,
|
||||||
|
) -> (Self::Input, Self::Delta) {
|
||||||
|
let mut next_epsilon = Self::Input::default();
|
||||||
|
let mut weights_gradient_sum = Self::Delta::zero();
|
||||||
|
|
||||||
|
// TODO: consume epsilon efficiently
|
||||||
|
for (window_center, virtual_input) in self.iterate_windows(input) {
|
||||||
|
let epsilon = NeuraVector::from(&epsilon[window_center]);
|
||||||
|
|
||||||
|
let (layer_next_epsilon, weights_gradient) =
|
||||||
|
self.inner_layer.backpropagate(&virtual_input, epsilon);
|
||||||
|
weights_gradient_sum.add_assign(&weights_gradient);
|
||||||
|
|
||||||
|
for i in 0..WINDOW {
|
||||||
|
// Re-compute the positions in `input` matching the positions in `layer_next_epsilon` and `virtual_input`
|
||||||
|
let input_index = window_center as isize + i as isize - (WINDOW - 1) as isize / 2;
|
||||||
|
if input_index < 0 || input_index >= LENGTH as isize {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let input_index = input_index as usize;
|
||||||
|
|
||||||
|
for j in 0..IN_FEATS {
|
||||||
|
next_epsilon[input_index][j] += layer_next_epsilon[i * WINDOW + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(next_epsilon, weights_gradient_sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regularize(&self) -> Self::Delta {
|
||||||
|
self.inner_layer.regularize()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_gradient(&mut self, gradient: &Self::Delta) {
|
||||||
|
self.inner_layer.apply_gradient(gradient);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue