use burn::{
module::Module,
nn::{
Linear, PaddingConfig2d,
conv::{Conv2d, Conv2dConfig},
},
tensor::{Tensor, activation, backend::Backend},
};
use super::mlp::linear_with_init;
#[derive(Module, Debug)]
pub struct SnakeCnnBurnPolicy<B: Backend> {
conv1: Conv2d<B>,
conv2: Conv2d<B>,
conv3: Conv2d<B>,
fc_common: Linear<B>,
fc_policy: Linear<B>,
fc_value: Linear<B>,
flat_size: usize,
}
impl<B: Backend> SnakeCnnBurnPolicy<B> {
pub fn new(grid_size: usize, input_channels: usize, device: &B::Device) -> Self {
let conv1 = Conv2dConfig::new([input_channels, 8], [3, 3])
.with_padding(PaddingConfig2d::Same)
.init(device);
let conv2 = Conv2dConfig::new([8, 16], [3, 3])
.with_padding(PaddingConfig2d::Same)
.init(device);
let conv3 = Conv2dConfig::new([16, 16], [3, 3])
.with_padding(PaddingConfig2d::Same)
.init(device);
let flat_size = 16 * grid_size * grid_size;
let init = burn::nn::Initializer::KaimingUniform {
gain: 1.0_f64 / 3.0_f64.sqrt(),
fan_out_only: false,
};
let fc_common = linear_with_init::<B>(flat_size, 64, init.clone(), device);
let fc_policy = linear_with_init::<B>(64, 4, init.clone(), device);
let fc_value = linear_with_init::<B>(64, 1, init, device);
Self { conv1, conv2, conv3, fc_common, fc_policy, fc_value, flat_size }
}
pub fn forward(&self, grid: Tensor<B, 4>) -> (Tensor<B, 2>, Tensor<B, 2>) {
let x = activation::relu(self.conv1.forward(grid));
let x = activation::relu(self.conv2.forward(x));
let x = activation::relu(self.conv3.forward(x));
let batch = x.dims()[0];
let x_flat: Tensor<B, 2> = x.reshape([batch, self.flat_size]);
let features = activation::relu(self.fc_common.forward(x_flat));
let logits = self.fc_policy.forward(features.clone());
let values = self.fc_value.forward(features);
(logits, values)
}
}
#[cfg(test)]
mod tests {
use burn::backend::{Autodiff, NdArray};
use super::*;
type B = Autodiff<NdArray<f32>>;
#[test]
fn test_snake_cnn_creation() {
let device = Default::default();
let _policy = SnakeCnnBurnPolicy::<B>::new(20, 5, &device);
}
#[test]
fn test_snake_cnn_forward_shape() {
let device = Default::default();
let policy = SnakeCnnBurnPolicy::<B>::new(20, 5, &device);
let grid = Tensor::<B, 4>::zeros([1, 5, 20, 20], &device);
let (logits, values) = policy.forward(grid);
assert_eq!(logits.dims(), [1, 4]);
assert_eq!(values.dims(), [1, 1]);
}
#[test]
fn test_snake_cnn_batch_forward_shape() {
let device = Default::default();
let policy = SnakeCnnBurnPolicy::<B>::new(10, 5, &device);
let grid = Tensor::<B, 4>::zeros([4, 5, 10, 10], &device);
let (logits, values) = policy.forward(grid);
assert_eq!(logits.dims(), [4, 4]);
assert_eq!(values.dims(), [4, 1]);
}
}