def concatenate(*layers):  
if not layers:<EOL><INDENT>return noop()<EOL><DEDENT>ops = layers[<NUM_LIT:0>].ops<EOL>def begin_update(X, *a, **k):<EOL><INDENT>forward, backward = split_backward(layers)<EOL>values = [fwd(X, *a, **k) for fwd in forward]<EOL>output = ops.xp.hstack(values)<EOL>shapes = [val.shape for val in values]<EOL>def finish_update(gradient, *args, **kwargs):<EOL><INDENT>layer_grads = []<EOL>start = <NUM_LIT:0><EOL>for bwd, shape in zip(backward, shapes):<EOL><INDENT>end = start + shape[<NUM_LIT:1>]<EOL>if bwd is not None:<EOL><INDENT>d = bwd(<EOL>ops.xp.ascontiguousarray(gradient[:, start:end]),<EOL>*args,<EOL>**kwargs<EOL>)<EOL>if d is not None and hasattr(X, "<STR_LIT>"):<EOL><INDENT>if not layer_grads:<EOL><INDENT>layer_grads.append(d)<EOL><DEDENT>else:<EOL><INDENT>layer_grads[-<NUM_LIT:1>] += d<EOL><DEDENT><DEDENT><DEDENT>start = end<EOL><DEDENT>if layer_grads:<EOL><INDENT>return ops.asarray(layer_grads[-<NUM_LIT:1>])<EOL><DEDENT>else:<EOL><INDENT>return None<EOL><DEDENT><DEDENT>return output, finish_update<EOL><DEDENT>layer = FunctionLayer(begin_update)<EOL>layer._layers = list(layers)<EOL>def on_data(self, X, y=None):<EOL><INDENT>for layer in self._layers:<EOL><INDENT>for hook in layer.on_data_hooks:<EOL><INDENT>hook(layer, X, y)<EOL><DEDENT><DEDENT><DEDENT>layer.on_data_hooks.append(on_data)<EOL>return layer<EOL>
Compose two or more models `f`, `g`, etc, such that their outputs are
    concatenated, i.e. `concatenate(f, g)(x)` computes `hstack(f(x), g(x))`