Skip to content

Commit aaacc1e

Browse files
committed
feat/layer: add Sequential layer
Introduce the Sequential layer which serves as a replacement for the Network struct.
1 parent 3478586 commit aaacc1e

File tree

10 files changed

+1260
-2235
lines changed

10 files changed

+1260
-2235
lines changed

benches/network_benches.rs

+417-413
Large diffs are not rendered by default.

examples/benchmarks.rs

+118-515
Large diffs are not rendered by default.

src/layer.rs

+269-169
Large diffs are not rendered by default.

src/layers/common/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ pub use self::convolution::{Convolution, ConvolutionConfig};
1414
pub use self::linear::{Linear, LinearConfig};
1515
pub use self::log_softmax::LogSoftmax;
1616
pub use self::pooling::{Pooling, PoolingConfig, PoolingMode};
17+
pub use self::sequential::{Sequential, SequentialConfig};
1718
pub use self::softmax::Softmax;
1819

1920
pub mod convolution;
2021
pub mod linear;
2122
pub mod log_softmax;
2223
pub mod pooling;
24+
pub mod sequential;
2325
pub mod softmax;
2426

2527
/// Provides common utilities for Layers that utilize a filter with stride and padding.

src/layers/common/sequential.rs

+342
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
//! A container layer that runs operations sequentially on the contained layers.
2+
use std::cell::RefCell;
3+
use std::collections::{HashMap, HashSet};
4+
use std::rc::Rc;
5+
use std::sync::{Arc, RwLock};
6+
use co::{IBackend, SharedTensor};
7+
use layer::*;
8+
use util::{ArcLock, LayerOps};
9+
10+
#[derive(Debug)]
11+
/// Sequential Layer
12+
pub struct Sequential<B: IBackend + LayerOps<f32>> {
13+
layers: Vec<RefCell<Layer<B>>>,
14+
15+
input_tensor_names: Vec<String>,
16+
input_data_tensors: Vec<ArcLock<SharedTensor<f32>>>,
17+
input_gradient_tensors: Vec<ArcLock<SharedTensor<f32>>>,
18+
19+
output_data_tensors: Vec<ArcLock<SharedTensor<f32>>>,
20+
output_gradient_tensors: Vec<ArcLock<SharedTensor<f32>>>,
21+
22+
registry: HashMap<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>)>,
23+
}
24+
25+
impl<B: IBackend + LayerOps<f32> + 'static> Sequential<B> {
26+
/// Create a empty Sequential container layer.
27+
pub fn empty() -> Sequential<B> {
28+
Sequential {
29+
layers: vec![],
30+
31+
input_tensor_names: vec![],
32+
input_data_tensors: vec![],
33+
input_gradient_tensors: vec![],
34+
35+
output_data_tensors: vec![],
36+
output_gradient_tensors: vec![],
37+
38+
registry: HashMap::new(),
39+
}
40+
}
41+
42+
/// Create a Sequential layer from a SequentialConfig.
43+
pub fn from_config(backend: Rc<B>, config: &SequentialConfig) -> Sequential<B> {
44+
let mut layer = Self::empty();
45+
46+
layer.init_layers(backend, &config.clone());
47+
48+
layer
49+
}
50+
51+
/// Initializes a sequential container.
52+
///
53+
/// Sets up the structure of the sequential container. It reads the supplied [SequentialConfig][1],
54+
/// connects the input and output blobs of each layer and determines if the backpropagation has
55+
/// to be executed for each tensor and layer.
56+
///
57+
/// [1]: ./struct.SequentialConfig.html
58+
pub fn init_layers(&mut self, backend: Rc<B>, in_config: &SequentialConfig) {
59+
let mut config = in_config.clone();
60+
let mut registry = HashMap::<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>)>::new();
61+
let weight_registry = &mut HashMap::<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>, Option<f32>, Option<f32>)>::new();
62+
63+
for (input_name, input_shape) in config.inputs {
64+
self.init_input_blob(backend.clone(), &input_name, &input_shape, &mut registry);
65+
}
66+
67+
// add input names to first layer so they correctly connect
68+
if let Some(first_layer) = config.layers.first_mut() {
69+
for container_input in &self.input_tensor_names {
70+
first_layer.add_input(&container_input);
71+
}
72+
}
73+
// connect each layer to the next one
74+
for (i, _) in config.layers.clone().iter().enumerate() {
75+
match i == (config.layers.len() - 1) {
76+
false => {
77+
// layers have already been manually connected
78+
if config.layers[i].outputs.get(0).is_some() && config.layers[i + 1].inputs.get(0).is_some() &&
79+
config.layers[i].outputs.get(0) == config.layers[i + 1].inputs.get(0) {
80+
continue;
81+
}
82+
// TODO: make use of in-place
83+
config.layers[i].add_output(&format!("SEQUENTIAL_{}", i));
84+
config.layers[i + 1].add_input(&format!("SEQUENTIAL_{}", i));
85+
},
86+
// last layer
87+
true => {
88+
config.layers[i].add_output(&format!("SEQUENTIAL_OUTPUT_{}", i));
89+
},
90+
}
91+
}
92+
93+
for layer_config in &config.layers {
94+
self.init_layer(backend.clone(), &layer_config, &mut registry, weight_registry);
95+
}
96+
97+
// Go through the net backwards to determine which blobs contribute to the
98+
// loss. We can skip backward computation for blobs that don't contribute
99+
// to the loss.
100+
// Also checks if all bottom blobs don't need backward computation (possible
101+
// because the skip_propagate_down config) and so we can skip backward
102+
// computation for the entire layer
103+
let blobs_under_loss = &mut HashSet::<String>::new();
104+
let blobs_skip_backp = &mut HashSet::<String>::new();
105+
for layer in &mut self.layers.iter_mut().rev() {
106+
layer.borrow_mut().init_backprop( blobs_under_loss, blobs_skip_backp);
107+
}
108+
109+
if config.force_backward {
110+
for layer in &mut self.layers {
111+
layer.borrow_mut().init_force_backward();
112+
}
113+
}
114+
115+
// Outputs of the last layer are considered output of the container
116+
if let Some(last_layer) = self.layers.last() {
117+
for data_tensor in &last_layer.borrow().output_blobs_data {
118+
self.output_data_tensors.push(data_tensor.clone());
119+
}
120+
for gradient_tensor in &last_layer.borrow().output_blobs_gradient {
121+
self.output_gradient_tensors.push(gradient_tensor.clone());
122+
}
123+
}
124+
125+
self.registry = registry;
126+
127+
info!("Sequential container initialization done.");
128+
}
129+
130+
/// Initialize a input tensor for the Sequential container.
131+
///
132+
/// Appends a input blob to the network, so the first [Layer][1] can
133+
/// [connect][2] to them.
134+
///
135+
/// Used during initialization of the Sequential container.
136+
/// [1]: ../layer/struct.Layer.html
137+
/// [2]: ../layer/struct.Layer.html#method.connect
138+
fn init_input_blob(&mut self,
139+
backend: Rc<B>,
140+
tensor_name: &str,
141+
input_shape: &[usize],
142+
registry: &mut HashMap<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>)> ) {
143+
144+
if registry.contains_key(tensor_name) {
145+
// If we are not doing in-place computation but see two layers trying
146+
// to produce the same tensor, raise an error.
147+
error!("Output tensor {} produced by multiple sources.", tensor_name);
148+
return
149+
} else {
150+
info!("Input {} -> {}", self.input_data_tensors.len(), tensor_name);
151+
152+
let ibackend: Rc<IBackend<F=B::F>> = backend;
153+
let data_tensor: ArcLock<SharedTensor<f32>> = Arc::new(RwLock::new(SharedTensor::new(ibackend.device(), &input_shape).unwrap()));
154+
let gradient_tensor: ArcLock<SharedTensor<f32>> = Arc::new(RwLock::new(SharedTensor::new(ibackend.device(), &input_shape).unwrap()));
155+
156+
self.input_data_tensors.push(data_tensor.clone());
157+
self.input_gradient_tensors.push(gradient_tensor.clone());
158+
self.input_tensor_names.push(tensor_name.to_owned());
159+
registry.insert(tensor_name.to_owned(), (data_tensor, gradient_tensor));
160+
}
161+
}
162+
163+
/// Initializes a single layer of the Sequential container.
164+
///
165+
/// Appends input and output tensors to the [Layer][3]. Apart from explicitly named
166+
/// output tensors it will also append anonymous output tensors that are required by the specific
167+
/// [Layer implemenations][4]. It also sets up the backpropagation flags.
168+
///
169+
/// [3]: ../layer/struct.Layer.html
170+
/// [4]: ../layers/index.html
171+
fn init_layer(&mut self,
172+
backend: Rc<B>,
173+
layer_config: &LayerConfig,
174+
registry: &mut HashMap<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>)>,
175+
weight_registry: &mut HashMap<String, (ArcLock<SharedTensor<f32>>, ArcLock<SharedTensor<f32>>, Option<f32>, Option<f32>)>) {
176+
// Setup layer.
177+
if let Err(e) = layer_config.validate() {
178+
error!("{}", e);
179+
}
180+
181+
info!("Creating Layer {}", &layer_config.name);
182+
let mut layer = Layer::from_config(backend, &layer_config);
183+
184+
// Figure out this layer's input and output
185+
layer.connect(registry, weight_registry);
186+
187+
self.layers.push(RefCell::new(layer));
188+
}
189+
}
190+
191+
impl<B: IBackend + LayerOps<f32> + 'static> ILayer<B> for Sequential<B> {
192+
fn is_container(&self) -> bool {
193+
true
194+
}
195+
196+
fn inputs_data(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
197+
Some(self.input_data_tensors.clone())
198+
}
199+
200+
fn inputs_gradients(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
201+
Some(self.input_gradient_tensors.clone())
202+
}
203+
204+
fn outputs_data(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
205+
Some(self.output_data_tensors.clone())
206+
}
207+
208+
fn outputs_gradients(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
209+
Some(self.output_gradient_tensors.clone())
210+
}
211+
212+
fn learnable_weights(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
213+
let weights = self.layers.iter().flat_map(|layer| layer.borrow().learnable_weights_data()).collect();
214+
Some(weights)
215+
}
216+
217+
fn learnable_weights_gradients(&self) -> Option<Vec<ArcLock<SharedTensor<f32>>>> {
218+
let gradients = self.layers.iter().flat_map(|layer| layer.borrow().learnable_weights_gradients()).collect();
219+
Some(gradients)
220+
}
221+
222+
fn forward(&self,
223+
backend: &B,
224+
input_data: &[ArcLock<SharedTensor<f32>>],
225+
weights_data: &[ArcLock<SharedTensor<f32>>],
226+
output_data: &mut [ArcLock<SharedTensor<f32>>]) {
227+
if let Some(first_layer) = self.layers.first() {
228+
for (i, input) in input_data.iter().enumerate() {
229+
first_layer.borrow_mut().input_blobs_data[i] = input.clone();
230+
}
231+
}
232+
for layer in &self.layers {
233+
layer.borrow_mut().forward(&[]);
234+
}
235+
}
236+
237+
fn backward_input(&self,
238+
backend: &B,
239+
weights_data: &[ArcLock<SharedTensor<f32>>],
240+
output_data: &[ArcLock<SharedTensor<f32>>],
241+
output_gradients: &[ArcLock<SharedTensor<f32>>],
242+
input_data: &[ArcLock<SharedTensor<f32>>],
243+
input_gradients: &mut [ArcLock<SharedTensor<f32>>]) {
244+
if let Some(last_layer) = self.layers.last() {
245+
for (i, output_gradient) in output_gradients.iter().enumerate() {
246+
last_layer.borrow_mut().output_blobs_gradient[i] = output_gradient.clone();
247+
}
248+
}
249+
for layer in self.layers.iter().rev() {
250+
layer.borrow_mut().backward_input(&[]);
251+
}
252+
}
253+
254+
fn backward_parameters(&self,
255+
backend: &B,
256+
output_data: &[ArcLock<SharedTensor<f32>>],
257+
output_gradients: &[ArcLock<SharedTensor<f32>>],
258+
input_data: &[ArcLock<SharedTensor<f32>>],
259+
weights_gradients: &mut [ArcLock<SharedTensor<f32>>]) {
260+
for layer in &self.layers {
261+
layer.borrow_mut().backward_parameters();
262+
}
263+
}
264+
}
265+
266+
impl<B: IBackend + LayerOps<f32> + 'static> ComputeOutput<f32, B> for Sequential<B> {
267+
// we are overriding `forward` and not calling `compute_output`
268+
fn compute_output(&self,
269+
backend: &B,
270+
weights: &[&SharedTensor<f32>],
271+
input_data: &[&SharedTensor<f32>],
272+
output_data: &mut [&mut SharedTensor<f32>]) { }
273+
}
274+
275+
impl<B: IBackend + LayerOps<f32> + 'static> ComputeInputGradient<f32, B> for Sequential<B> {
276+
// we are overriding `backward_input` and not calling `compute_input_gradient`
277+
fn compute_input_gradient(&self,
278+
backend: &B,
279+
weights_data: &[&SharedTensor<f32>],
280+
output_data: &[&SharedTensor<f32>],
281+
output_gradients: &[&SharedTensor<f32>],
282+
input_data: &[&SharedTensor<f32>],
283+
input_gradients: &mut [&mut SharedTensor<f32>]) { }
284+
}
285+
286+
impl<B: IBackend + LayerOps<f32> + 'static> ComputeParametersGradient<f32, B> for Sequential<B> {
287+
// we are overriding `backward_parameters` and not calling `compute_parameters_gradient`
288+
fn compute_parameters_gradient(&self,
289+
backend: &B,
290+
output_data: &[&SharedTensor<f32>],
291+
output_gradients: &[&SharedTensor<f32>],
292+
input_data: &[&SharedTensor<f32>],
293+
parameters_gradients: &mut [&mut SharedTensor<f32>]) { }
294+
}
295+
296+
#[derive(Debug, Clone)]
297+
#[allow(missing_copy_implementations)]
298+
/// Specifies configuration parameters for a Sequential Layer.
299+
pub struct SequentialConfig {
300+
/// Defines the layers of the container via [LayerConfig][1]s.
301+
/// [1]: ../layer/struct.LayerConfig.html
302+
pub layers: Vec<LayerConfig>,
303+
304+
/// Defines the names and shapes of the input tensors.
305+
///
306+
/// The inputs are identified by name so they can be referenced as input tensors
307+
/// in a [LayerConfig][layer_config].
308+
///
309+
/// [layer_config]: ../layer/struct.LayerConfig.html
310+
pub inputs: Vec<(String, Vec<usize>)>,
311+
312+
/// Defines if the container will force every layer to do [backpropagation][1].
313+
/// [1]: https://en.wikipedia.org/wiki/Backpropagation
314+
///
315+
/// If set to `false`, then the execution of backpropagation is determined automatically
316+
/// according to the network structure and learning rates.
317+
///
318+
/// Default: `false`
319+
pub force_backward: bool,
320+
}
321+
322+
impl SequentialConfig {
323+
/// Add layer at the end of the sequential container.
324+
pub fn add_layer(&mut self, layer: LayerConfig) {
325+
self.layers.push(layer);
326+
}
327+
328+
/// Add a input to the network.
329+
pub fn add_input(&mut self, input_name: &str, shape: &[usize]) {
330+
self.inputs.push((input_name.to_owned(), shape.to_owned()));
331+
}
332+
}
333+
334+
impl ::std::default::Default for SequentialConfig {
335+
fn default() -> SequentialConfig {
336+
SequentialConfig {
337+
layers: vec![],
338+
inputs: vec![],
339+
force_backward: false,
340+
}
341+
}
342+
}

src/layers/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub use self::common::{
5959
Linear, LinearConfig,
6060
LogSoftmax,
6161
Pooling, PoolingConfig, PoolingMode,
62+
Sequential, SequentialConfig,
6263
Softmax,
6364
};
6465

src/lib.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@
7777
//! ## Examples
7878
//!
7979
//! ```
80-
//! # extern crate leaf;
81-
//! # use leaf::network::{NetworkConfig};
82-
//! # fn main() {
83-
//! # }
8480
//! ```
8581
//!
8682
//! ## Development
@@ -133,11 +129,10 @@ extern crate collenchyma_blas as coblas;
133129
extern crate collenchyma_nn as conn;
134130
pub mod layer;
135131
pub mod layers;
136-
#[cfg(feature="cuda")]
137-
pub mod solver;
138-
#[cfg(feature="cuda")]
139-
pub mod solvers;
140-
pub mod network;
132+
// #[cfg(feature="cuda")]
133+
// pub mod solver;
134+
// #[cfg(feature="cuda")]
135+
// pub mod solvers;
141136
pub mod weight;
142137

143138
pub mod util;

0 commit comments

Comments
 (0)