1use burn_core as burn;
2
3use super::gate_controller::GateController;
4use burn::config::Config;
5use burn::module::Initializer;
6use burn::module::Module;
7use burn::module::{Content, DisplaySettings, ModuleDisplay};
8use burn::tensor::Tensor;
9use burn::tensor::activation;
10use burn::tensor::backend::Backend;
11
12#[derive(Config, Debug)]
14pub struct GruConfig {
15 pub d_input: usize,
17 pub d_hidden: usize,
19 pub bias: bool,
21 #[config(default = "true")]
35 pub reset_after: bool,
36 #[config(default = "Initializer::XavierNormal{gain:1.0}")]
38 pub initializer: Initializer,
39}
40
41#[derive(Module, Debug)]
47#[module(custom_display)]
48pub struct Gru<B: Backend> {
49 pub update_gate: GateController<B>,
51 pub reset_gate: GateController<B>,
53 pub new_gate: GateController<B>,
55 pub d_hidden: usize,
57 pub reset_after: bool,
59}
60
61impl<B: Backend> ModuleDisplay for Gru<B> {
62 fn custom_settings(&self) -> Option<DisplaySettings> {
63 DisplaySettings::new()
64 .with_new_line_after_attribute(false)
65 .optional()
66 }
67
68 fn custom_content(&self, content: Content) -> Option<Content> {
69 let [d_input, _] = self.update_gate.input_transform.weight.shape().dims();
70 let bias = self.update_gate.input_transform.bias.is_some();
71
72 content
73 .add("d_input", &d_input)
74 .add("d_hidden", &self.d_hidden)
75 .add("bias", &bias)
76 .add("reset_after", &self.reset_after)
77 .optional()
78 }
79}
80
81impl GruConfig {
82 pub fn init<B: Backend>(&self, device: &B::Device) -> Gru<B> {
84 let d_output = self.d_hidden;
85
86 let update_gate = GateController::new(
87 self.d_input,
88 d_output,
89 self.bias,
90 self.initializer.clone(),
91 device,
92 );
93 let reset_gate = GateController::new(
94 self.d_input,
95 d_output,
96 self.bias,
97 self.initializer.clone(),
98 device,
99 );
100 let new_gate = GateController::new(
101 self.d_input,
102 d_output,
103 self.bias,
104 self.initializer.clone(),
105 device,
106 );
107
108 Gru {
109 update_gate,
110 reset_gate,
111 new_gate,
112 d_hidden: self.d_hidden,
113 reset_after: self.reset_after,
114 }
115 }
116}
117
118impl<B: Backend> Gru<B> {
119 pub fn forward(
130 &self,
131 batched_input: Tensor<B, 3>,
132 state: Option<Tensor<B, 2>>,
133 ) -> Tensor<B, 3> {
134 let device = batched_input.device();
135 let [batch_size, seq_length, _] = batched_input.shape().dims();
136
137 let mut batched_hidden_state =
138 Tensor::empty([batch_size, seq_length, self.d_hidden], &device);
139
140 let mut hidden_t = match state {
141 Some(state) => state,
142 None => Tensor::zeros([batch_size, self.d_hidden], &device),
143 };
144
145 for (t, input_t) in batched_input.iter_dim(1).enumerate() {
146 let input_t = input_t.squeeze_dim(1);
147 let biased_ug_input_sum =
149 self.gate_product(&input_t, &hidden_t, None, &self.update_gate);
150 let update_values = activation::sigmoid(biased_ug_input_sum); let biased_rg_input_sum =
154 self.gate_product(&input_t, &hidden_t, None, &self.reset_gate);
155 let reset_values = activation::sigmoid(biased_rg_input_sum); let biased_ng_input_sum = if self.reset_after {
159 self.gate_product(&input_t, &hidden_t, Some(&reset_values), &self.new_gate)
160 } else {
161 let reset_t = hidden_t.clone().mul(reset_values); self.gate_product(&input_t, &reset_t, None, &self.new_gate)
163 };
164 let candidate_state = biased_ng_input_sum.tanh(); hidden_t = candidate_state
169 .clone()
170 .mul(update_values.clone().sub_scalar(1).mul_scalar(-1)) + update_values.clone().mul(hidden_t);
172
173 let unsqueezed_hidden_state = hidden_t.clone().unsqueeze_dim(1);
174
175 batched_hidden_state = batched_hidden_state.slice_assign(
176 [0..batch_size, t..(t + 1), 0..self.d_hidden],
177 unsqueezed_hidden_state,
178 );
179 }
180
181 batched_hidden_state
182 }
183
184 fn gate_product(
195 &self,
196 input: &Tensor<B, 2>,
197 hidden: &Tensor<B, 2>,
198 reset: Option<&Tensor<B, 2>>,
199 gate: &GateController<B>,
200 ) -> Tensor<B, 2> {
201 let input_product = input.clone().matmul(gate.input_transform.weight.val());
202 let hidden_product = hidden.clone().matmul(gate.hidden_transform.weight.val());
203
204 let input_bias = gate
205 .input_transform
206 .bias
207 .as_ref()
208 .map(|bias_param| bias_param.val());
209 let hidden_bias = gate
210 .hidden_transform
211 .bias
212 .as_ref()
213 .map(|bias_param| bias_param.val());
214
215 match (input_bias, hidden_bias, reset) {
216 (Some(input_bias), Some(hidden_bias), Some(r)) => {
217 input_product
218 + input_bias.unsqueeze()
219 + r.clone().mul(hidden_product + hidden_bias.unsqueeze())
220 }
221 (Some(input_bias), Some(hidden_bias), None) => {
222 input_product + input_bias.unsqueeze() + hidden_product + hidden_bias.unsqueeze()
223 }
224 (Some(input_bias), None, Some(r)) => {
225 input_product + input_bias.unsqueeze() + r.clone().mul(hidden_product)
226 }
227 (Some(input_bias), None, None) => {
228 input_product + input_bias.unsqueeze() + hidden_product
229 }
230 (None, Some(hidden_bias), Some(r)) => {
231 input_product + r.clone().mul(hidden_product + hidden_bias.unsqueeze())
232 }
233 (None, Some(hidden_bias), None) => {
234 input_product + hidden_product + hidden_bias.unsqueeze()
235 }
236 (None, None, Some(r)) => input_product + r.clone().mul(hidden_product),
237 (None, None, None) => input_product + hidden_product,
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::{LinearRecord, TestBackend};
246 use burn::module::Param;
247 use burn::tensor::{Distribution, TensorData};
248 use burn::tensor::{Tolerance, ops::FloatElem};
249
250 type FT = FloatElem<TestBackend>;
251
252 fn init_gru<B: Backend>(reset_after: bool, device: &B::Device) -> Gru<B> {
253 fn create_gate_controller<B: Backend>(
254 weights: f32,
255 biases: f32,
256 d_input: usize,
257 d_output: usize,
258 bias: bool,
259 initializer: Initializer,
260 device: &B::Device,
261 ) -> GateController<B> {
262 let record_1 = LinearRecord {
263 weight: Param::from_data(TensorData::from([[weights]]), device),
264 bias: Some(Param::from_data(TensorData::from([biases]), device)),
265 };
266 let record_2 = LinearRecord {
267 weight: Param::from_data(TensorData::from([[weights]]), device),
268 bias: Some(Param::from_data(TensorData::from([biases]), device)),
269 };
270 GateController::create_with_weights(
271 d_input,
272 d_output,
273 bias,
274 initializer,
275 record_1,
276 record_2,
277 )
278 }
279
280 let config = GruConfig::new(1, 1, false).with_reset_after(reset_after);
281 let mut gru = config.init::<B>(device);
282
283 gru.update_gate = create_gate_controller(
284 0.5,
285 0.0,
286 1,
287 1,
288 false,
289 Initializer::XavierNormal { gain: 1.0 },
290 device,
291 );
292 gru.reset_gate = create_gate_controller(
293 0.6,
294 0.0,
295 1,
296 1,
297 false,
298 Initializer::XavierNormal { gain: 1.0 },
299 device,
300 );
301 gru.new_gate = create_gate_controller(
302 0.7,
303 0.0,
304 1,
305 1,
306 false,
307 Initializer::XavierNormal { gain: 1.0 },
308 device,
309 );
310 gru
311 }
312
313 #[test]
321 fn tests_forward_single_input_single_feature() {
322 let device = Default::default();
323 TestBackend::seed(&device, 0);
324
325 let mut gru = init_gru::<TestBackend>(false, &device);
326
327 let input = Tensor::<TestBackend, 3>::from_data(TensorData::from([[[0.1]]]), &device);
328 let expected = TensorData::from([[0.034]]);
329
330 let state = gru.forward(input.clone(), None);
332
333 let output = state
334 .select(0, Tensor::arange(0..1, &device))
335 .squeeze_dim::<2>(0);
336
337 let tolerance = Tolerance::default();
338 output
339 .to_data()
340 .assert_approx_eq::<FT>(&expected, tolerance);
341
342 gru.reset_after = true; let state = gru.forward(input, None);
345
346 let output = state
347 .select(0, Tensor::arange(0..1, &device))
348 .squeeze_dim::<2>(0);
349
350 output
351 .to_data()
352 .assert_approx_eq::<FT>(&expected, tolerance);
353 }
354
355 #[test]
356 fn tests_forward_seq_len_3() {
357 let device = Default::default();
358 TestBackend::seed(&device, 0);
359 let mut gru = init_gru::<TestBackend>(true, &device);
360
361 let input =
362 Tensor::<TestBackend, 3>::from_data(TensorData::from([[[0.1], [0.2], [0.3]]]), &device);
363 let expected = TensorData::from([[0.0341], [0.0894], [0.1575]]);
364
365 let result = gru.forward(input.clone(), None);
366 let output = result
367 .select(0, Tensor::arange(0..1, &device))
368 .squeeze_dim::<2>(0);
369
370 let tolerance = Tolerance::default();
371 output
372 .to_data()
373 .assert_approx_eq::<FT>(&expected, tolerance);
374
375 gru.reset_after = false; let state = gru.forward(input, None);
378
379 let output = state
380 .select(0, Tensor::arange(0..1, &device))
381 .squeeze_dim::<2>(0);
382
383 output
384 .to_data()
385 .assert_approx_eq::<FT>(&expected, tolerance);
386 }
387
388 #[test]
389 fn test_batched_forward_pass() {
390 let device = Default::default();
391 let gru = GruConfig::new(64, 1024, true).init::<TestBackend>(&device);
392 let batched_input =
393 Tensor::<TestBackend, 3>::random([8, 10, 64], Distribution::Default, &device);
394
395 let hidden_state = gru.forward(batched_input, None);
396
397 assert_eq!(hidden_state.shape().dims, [8, 10, 1024]);
398 }
399
400 #[test]
401 fn display() {
402 let config = GruConfig::new(2, 8, true);
403
404 let layer = config.init::<TestBackend>(&Default::default());
405
406 assert_eq!(
407 alloc::format!("{layer}"),
408 "Gru {d_input: 2, d_hidden: 8, bias: true, reset_after: true, params: 288}"
409 );
410 }
411}