AI-MXNet
view release on metacpan or search on metacpan
lib/AI/MXNet/RNN/Cell.pm view on Meta::CPAN
my $bias = delete $args{ sprintf('%s%s_bias', $self->_prefix, $group_name) };
enumerate(sub {
my ($j, $name) = @_;
my $wname = sprintf('%s%s%s_weight', $self->_prefix, $group_name, $name);
$args->{$wname} = $weight->slice([$j*$h,($j+1)*$h-1])->copy;
my $bname = sprintf('%s%s%s_bias', $self->_prefix, $group_name, $name);
$args->{$bname} = $bias->slice([$j*$h,($j+1)*$h-1])->copy;
}, $self->_gate_names);
}
return \%args;
}
=head2 pack_weights
Pack fused weight matrices into common
weight matrices
Parameters
----------
args : HashRef[AI::MXNet::NDArray]
hash ref containing unpacked weights.
Returns
-------
$args : HashRef[AI::MXNet::NDArray]
hash ref with weights associated with
this cell, packed.
=cut
method pack_weights(HashRef[AI::MXNet::NDArray] $args)
{
my %args = %{ $args };
my $h = $self->_num_hidden;
for my $group_name ('i2h', 'h2h')
{
my @weight;
my @bias;
for my $name (@{ $self->_gate_names })
{
my $wname = sprintf('%s%s%s_weight', $self->_prefix, $group_name, $name);
push @weight, delete $args{$wname};
my $bname = sprintf('%s%s%s_bias', $self->_prefix, $group_name, $name);
push @bias, delete $args{$bname};
}
$args{ sprintf('%s%s_weight', $self->_prefix, $group_name) } = AI::MXNet::NDArray->concatenate(
\@weight
);
$args{ sprintf('%s%s_bias', $self->_prefix, $group_name) } = AI::MXNet::NDArray->concatenate(
\@bias
);
}
return \%args;
}
=head2 unroll
Unroll an RNN cell across time steps.
Parameters
----------
:$length : Int
number of steps to unroll
:$inputs : AI::MXNet::Symbol, array ref of Symbols, or undef
if inputs is a single Symbol (usually the output
of Embedding symbol), it should have shape
of [$batch_size, $length, ...] if layout == 'NTC' (batch, time series)
or ($length, $batch_size, ...) if layout == 'TNC' (time series, batch).
If inputs is a array ref of symbols (usually output of
previous unroll), they should all have shape
($batch_size, ...).
If inputs is undef, a placeholder variables are
automatically created.
:$begin_state : array ref of Symbol
input states. Created by begin_state()
or output state of another cell. Created
from begin_state() if undef.
:$input_prefix : str
prefix for automatically created input
placehodlers.
:$layout : str
layout of input symbol. Only used if the input
is a single Symbol.
:$merge_outputs : Bool
If 0, returns outputs as an array ref of Symbols.
If 1, concatenates the output across the time steps
and returns a single symbol with the shape
[$batch_size, $length, ...) if the layout equal to 'NTC',
or [$length, $batch_size, ...) if the layout equal tp 'TNC'.
If undef, output whatever is faster
Returns
-------
$outputs : array ref of Symbol or Symbol
output symbols.
$states : Symbol or nested list of Symbol
has the same structure as begin_state()
=cut
method unroll(
Int $length,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=,
Str :$input_prefix='',
Str :$layout='NTC',
Maybe[Bool] :$merge_outputs=
)
{
$self->reset;
my $axis = index($layout, 'T');
if(not defined $inputs)
{
$inputs = [
map { AI::MXNet::Symbol->Variable("${input_prefix}t${_}_data") } (0..$length-1)
];
}
elsif(blessed($inputs))
{
assert(
(@{ $inputs->list_outputs() } == 1),
"unroll doesn't allow grouped symbol as input. Please "
."convert to list first or let unroll handle slicing"
);
$inputs = AI::MXNet::Symbol->SliceChannel(
$inputs,
axis => $axis,
num_outputs => $length,
squeeze_axis => 1
);
}
else
{
assert(@$inputs == $length);
}
$begin_state //= $self->begin_state;
my $states = $begin_state;
my $outputs;
my @inputs = @{ $inputs };
for my $i (0..$length-1)
{
my $output;
($output, $states) = &{$self}(
$inputs[$i],
$states
);
push @$outputs, $output;
}
if($merge_outputs)
{
@$outputs = map { AI::MXNet::Symbol->expand_dims($_, axis => $axis) } @$outputs;
$outputs = AI::MXNet::Symbol->Concat(@$outputs, dim => $axis);
}
return($outputs, $states);
}
method _get_activation($inputs, $activation, @kwargs)
{
if(not ref $activation)
{
return AI::MXNet::Symbol->Activation($inputs, act_type => $activation, @kwargs);
}
else
{
return &{$activation}($inputs, @kwargs);
}
}
method _cells_state_shape($cells)
{
return [map { @{ $_->state_shape } } @$cells];
}
method _cells_state_info($cells)
{
return [map { @{ $_->state_info } } @$cells];
}
method _cells_begin_state($cells, @kwargs)
{
return [map { @{ $_->begin_state(@kwargs) } } @$cells];
}
method _cells_unpack_weights($cells, $args)
{
$args = $_->unpack_weights($args) for @$cells;
return $args;
}
method _cells_pack_weights($cells, $args)
{
$args = $_->pack_weights($args) for @$cells;
return $args;
}
package AI::MXNet::RNN::Cell;
use Mouse;
extends 'AI::MXNet::RNN::Cell::Base';
=head1 NAME
lib/AI/MXNet/RNN/Cell.pm view on Meta::CPAN
{
for my $direction (@directions)
{
for my $gate (@gate_names)
{
my $name = sprintf('%s%s%d_i2h%s_bias', $self->_prefix, $direction, $layer, $gate);
$args{$name} = $arr->slice([$p,$p+$lh-1]);
$p += $lh;
}
for my $gate (@gate_names)
{
my $name = sprintf('%s%s%d_h2h%s_bias', $self->_prefix, $direction, $layer, $gate);
$args{$name} = $arr->slice([$p,$p+$lh-1]);
$p += $lh;
}
}
}
assert($p == $arr->size, "Invalid parameters size for FusedRNNCell");
return %args;
}
method unpack_weights(HashRef[AI::MXNet::NDArray] $args)
{
my %args = %{ $args };
my $arr = delete $args{ $self->_parameter->name };
my $b = @{ $self->_directions };
my $m = $self->_num_gates;
my $h = $self->_num_hidden;
my $num_input = int(int(int($arr->size/$b)/$h)/$m) - ($self->_num_layers - 1)*($h+$b*$h+2) - $h - 2;
my %nargs = $self->_slice_weights($arr, $num_input, $self->_num_hidden);
%args = (%args, map { $_ => $nargs{$_}->copy } keys %nargs);
return \%args
}
method pack_weights(HashRef[AI::MXNet::NDArray] $args)
{
my %args = %{ $args };
my $b = @{ $self->_directions };
my $m = $self->_num_gates;
my @c = @{ $self->_gate_names };
my $h = $self->_num_hidden;
my $w0 = $args{ sprintf('%sl0_i2h%s_weight', $self->_prefix, $c[0]) };
my $num_input = $w0->shape->[1];
my $total = ($num_input+$h+2)*$h*$m*$b + ($self->_num_layers-1)*$m*$h*($h+$b*$h+2)*$b;
my $arr = AI::MXNet::NDArray->zeros([$total], ctx => $w0->context, dtype => $w0->dtype);
my %nargs = $self->_slice_weights($arr, $num_input, $h);
while(my ($name, $nd) = each %nargs)
{
$nd .= delete $args{ $name };
}
$args{ $self->_parameter->name } = $arr;
return \%args;
}
method call(AI::MXNet::Symbol $inputs, SymbolOrArrayOfSymbols $states)
{
confess("AI::MXNet::RNN::FusedCell cannot be stepped. Please use unroll");
}
method unroll(
Int $length,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=,
Str :$input_prefix='',
Str :$layout='NTC',
Maybe[Bool] :$merge_outputs=
)
{
$self->reset;
my $axis = index($layout, 'T');
$inputs //= AI::MXNet::Symbol->Variable("${input_prefix}data");
if(blessed($inputs))
{
assert(
(@{ $inputs->list_outputs() } == 1),
"unroll doesn't allow grouped symbol as input. Please "
."convert to list first or let unroll handle slicing"
);
if($axis == 1)
{
AI::MXNet::Logging->warning(
"NTC layout detected. Consider using "
."TNC for RNN::FusedCell for faster speed"
);
$inputs = AI::MXNet::Symbol->SwapAxis($inputs, dim1 => 0, dim2 => 1);
}
else
{
assert($axis == 0, "Unsupported layout $layout");
}
}
else
{
assert(@$inputs == $length);
$inputs = [map { AI::MXNet::Symbol->expand_dims($_, axis => 0) } @{ $inputs }];
$inputs = AI::MXNet::Symbol->Concat(@{ $inputs }, dim => 0);
}
$begin_state //= $self->begin_state;
my $states = $begin_state;
my @states = @{ $states };
my %states;
if($self->_mode eq 'lstm')
{
%states = (state => $states[0], state_cell => $states[1]);
}
else
{
%states = (state => $states[0]);
}
my $rnn = AI::MXNet::Symbol->RNN(
data => $inputs,
parameters => $self->_parameter,
state_size => $self->_num_hidden,
num_layers => $self->_num_layers,
bidirectional => $self->_bidirectional,
p => $self->_dropout,
state_outputs => $self->_get_next_state,
mode => $self->_mode,
name => $self->_prefix.'rnn',
%states
);
my $outputs;
my %attr = (__layout__ => 'LNC');
if(not $self->_get_next_state)
{
($outputs, $states) = ($rnn, []);
}
elsif($self->_mode eq 'lstm')
{
my @rnn = @{ $rnn };
$rnn[1]->_set_attr(%attr);
$rnn[2]->_set_attr(%attr);
($outputs, $states) = ($rnn[0], [$rnn[1], $rnn[2]]);
}
else
{
my @rnn = @{ $rnn };
$rnn[1]->_set_attr(%attr);
($outputs, $states) = ($rnn[0], [$rnn[1]]);
}
if(defined $merge_outputs and not $merge_outputs)
{
AI::MXNet::Logging->warning(
"Call RNN::FusedCell->unroll with merge_outputs=1 "
."for faster speed"
);
$outputs = [@ {
AI::MXNet::Symbol->SliceChannel(
$outputs,
axis => 0,
num_outputs => $length,
squeeze_axis => 1
)
}];
}
elsif($axis == 1)
{
$outputs = AI::MXNet::Symbol->SwapAxis($outputs, dim1 => 0, dim2 => 1);
}
return ($outputs, $states);
}
=head2 unfuse
Unfuse the fused RNN
Returns
-------
$cell : AI::MXNet::RNN::SequentialCell
unfused cell that can be used for stepping, and can run on CPU.
=cut
method unfuse()
{
my $stack = AI::MXNet::RNN::SequentialCell->new;
my $get_cell = {
rnn_relu => sub {
AI::MXNet::RNN::Cell->new(
num_hidden => $self->_num_hidden,
activation => 'relu',
prefix => shift
)
},
rnn_tanh => sub {
AI::MXNet::RNN::Cell->new(
num_hidden => $self->_num_hidden,
activation => 'tanh',
prefix => shift
)
},
lstm => sub {
AI::MXNet::RNN::LSTMCell->new(
num_hidden => $self->_num_hidden,
prefix => shift
)
},
gru => sub {
AI::MXNet::RNN::GRUCell->new(
num_hidden => $self->_num_hidden,
prefix => shift
)
},
}->{ $self->_mode };
for my $i (0..$self->_num_layers-1)
{
if($self->_bidirectional)
{
$stack->add(
AI::MXNet::RNN::BidirectionalCell->new(
$get_cell->(sprintf('%sl%d_', $self->_prefix, $i)),
$get_cell->(sprintf('%sr%d_', $self->_prefix, $i)),
lib/AI/MXNet/RNN/Cell.pm view on Meta::CPAN
=cut
method add(AI::MXNet::RNN::Cell::Base $cell)
{
push @{ $self->_cells }, $cell;
if($self->_override_cell_params)
{
assert(
$cell->_own_params,
"Either specify params for SequentialRNNCell "
."or child cells, not both."
);
%{ $cell->params->_params } = (%{ $cell->params->_params }, %{ $self->params->_params });
}
%{ $self->params->_params } = (%{ $self->params->_params }, %{ $cell->params->_params });
}
method state_info()
{
return $self->_cells_state_info($self->_cells);
}
method begin_state(@kwargs)
{
assert(
(not $self->_modified),
"After applying modifier cells (e.g. DropoutCell) the base "
."cell cannot be called directly. Call the modifier cell instead."
);
return $self->_cells_begin_state($self->_cells, @kwargs);
}
method unpack_weights(HashRef[AI::MXNet::NDArray] $args)
{
return $self->_cells_unpack_weights($self->_cells, $args)
}
method pack_weights(HashRef[AI::MXNet::NDArray] $args)
{
return $self->_cells_pack_weights($self->_cells, $args);
}
method call($inputs, $states)
{
$self->_counter($self->_counter + 1);
my @next_states;
my $p = 0;
for my $cell (@{ $self->_cells })
{
assert(not $cell->isa('AI::MXNet::BidirectionalCell'));
my $n = scalar(@{ $cell->state_info });
my $state = [@{ $states }[$p..$p+$n-1]];
$p += $n;
($inputs, $state) = &{$cell}($inputs, $state);
push @next_states, $state;
}
return ($inputs, [map { @$_} @next_states]);
}
method unroll(
Int $length,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=,
Str :$input_prefix='',
Str :$layout='NTC',
Maybe[Bool] :$merge_outputs=
)
{
my $num_cells = @{ $self->_cells };
$begin_state //= $self->begin_state;
my $p = 0;
my $states;
my @next_states;
enumerate(sub {
my ($i, $cell) = @_;
my $n = @{ $cell->state_info };
$states = [@{$begin_state}[$p..$p+$n-1]];
$p += $n;
($inputs, $states) = $cell->unroll(
$length,
inputs => $inputs,
input_prefix => $input_prefix,
begin_state => $states,
layout => $layout,
merge_outputs => ($i < $num_cells-1) ? undef : $merge_outputs
);
push @next_states, $states;
}, $self->_cells);
return ($inputs, [map { @{ $_ } } @next_states]);
}
package AI::MXNet::RNN::BidirectionalCell;
use Mouse;
use AI::MXNet::Base;
extends 'AI::MXNet::RNN::Cell::Base';
=head1 NAME
AI::MXNet::RNN::BidirectionalCell
=cut
=head1 DESCRIPTION
Bidirectional RNN cell
Parameters
----------
l_cell : AI::MXNet::RNN::Cell::Base
cell for forward unrolling
r_cell : AI::MXNet::RNN::Cell::Base
cell for backward unrolling
output_prefix : str, default 'bi_'
prefix for name of output
=cut
has 'l_cell' => (is => 'ro', isa => 'AI::MXNet::RNN::Cell::Base', required => 1);
has 'r_cell' => (is => 'ro', isa => 'AI::MXNet::RNN::Cell::Base', required => 1);
has '_output_prefix' => (is => 'ro', init_arg => 'output_prefix', isa => 'Str', default => 'bi_');
has [qw/_override_cell_params _cells/] => (is => 'rw', init_arg => undef);
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if(@_ >= 2 and blessed $_[0] and blessed $_[1])
{
my $l_cell = shift(@_);
my $r_cell = shift(@_);
return $class->$orig(
l_cell => $l_cell,
r_cell => $r_cell,
@_
);
}
return $class->$orig(@_);
};
sub BUILD
{
my ($self, $original_arguments) = @_;
$self->_override_cell_params(defined $original_arguments->{params});
if($self->_override_cell_params)
{
assert(
($self->l_cell->_own_params and $self->r_cell->_own_params),
"Either specify params for BidirectionalCell ".
"or child cells, not both."
);
%{ $self->l_cell->params->_params } = (%{ $self->l_cell->params->_params }, %{ $self->params->_params });
%{ $self->r_cell->params->_params } = (%{ $self->r_cell->params->_params }, %{ $self->params->_params });
}
%{ $self->params->_params } = (%{ $self->params->_params }, %{ $self->l_cell->params->_params });
%{ $self->params->_params } = (%{ $self->params->_params }, %{ $self->r_cell->params->_params });
$self->_cells([$self->l_cell, $self->r_cell]);
}
method unpack_weights(HashRef[AI::MXNet::NDArray] $args)
{
return $self->_cells_unpack_weights($self->_cells, $args)
}
method pack_weights(HashRef[AI::MXNet::NDArray] $args)
{
return $self->_cells_pack_weights($self->_cells, $args);
}
method call($inputs, $states)
{
confess("Bidirectional cannot be stepped. Please use unroll");
}
method state_info()
{
return $self->_cells_state_info($self->_cells);
}
method begin_state(@kwargs)
{
assert((not $self->_modified),
"After applying modifier cells (e.g. DropoutCell) the base "
."cell cannot be called directly. Call the modifier cell instead."
);
return $self->_cells_begin_state($self->_cells, @kwargs);
}
method unroll(
Int $length,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=,
Str :$input_prefix='',
Str :$layout='NTC',
Maybe[Bool] :$merge_outputs=
)
{
my $axis = index($layout, 'T');
if(not defined $inputs)
{
$inputs = [
map { AI::MXNet::Symbol->Variable("${input_prefix}t${_}_data") } (0..$length-1)
];
}
elsif(blessed($inputs))
{
assert(
(@{ $inputs->list_outputs() } == 1),
"unroll doesn't allow grouped symbol as input. Please "
."convert to list first or let unroll handle slicing"
);
$inputs = [ @{ AI::MXNet::Symbol->SliceChannel(
$inputs,
axis => $axis,
num_outputs => $length,
squeeze_axis => 1
) }];
}
else
{
assert(@$inputs == $length);
}
$begin_state //= $self->begin_state;
my $states = $begin_state;
my ($l_cell, $r_cell) = @{ $self->_cells };
my ($l_outputs, $l_states) = $l_cell->unroll(
$length, inputs => $inputs,
begin_state => [@{$states}[0..@{$l_cell->state_info}-1]],
layout => $layout,
merge_outputs => $merge_outputs
);
my ($r_outputs, $r_states) = $r_cell->unroll(
$length, inputs => [reverse @{$inputs}],
begin_state => [@{$states}[@{$l_cell->state_info}..@{$states}-1]],
layout => $layout,
merge_outputs => $merge_outputs
);
if(not defined $merge_outputs)
{
$merge_outputs = (
blessed $l_outputs and $l_outputs->isa('AI::MXNet::Symbol')
and
blessed $r_outputs and $r_outputs->isa('AI::MXNet::Symbol')
);
if(not $merge_outputs)
{
if(blessed $l_outputs and $l_outputs->isa('AI::MXNet::Symbol'))
{
$l_outputs = [
@{ AI::MXNet::Symbol->SliceChannel(
$l_outputs, axis => $axis,
num_outputs => $length,
squeeze_axis => 1
) }
];
}
if(blessed $r_outputs and $r_outputs->isa('AI::MXNet::Symbol'))
{
$r_outputs = [
@{ AI::MXNet::Symbol->SliceChannel(
$r_outputs, axis => $axis,
num_outputs => $length,
squeeze_axis => 1
) }
];
}
}
}
if($merge_outputs)
{
$l_outputs = [@{ $l_outputs }];
$r_outputs = [@{ AI::MXNet::Symbol->reverse(blessed $r_outputs ? $r_outputs : @{ $r_outputs }, axis=>$axis) }];
}
else
{
$r_outputs = [reverse(@{ $r_outputs })];
}
my $outputs = [];
zip(sub {
my ($i, $l_o, $r_o) = @_;
push @$outputs, AI::MXNet::Symbol->Concat(
$l_o, $r_o, dim=>(1+($merge_outputs?1:0)),
name => $merge_outputs
? sprintf('%sout', $self->_output_prefix)
: sprintf('%st%d', $self->_output_prefix, $i)
);
}, [0..@{ $l_outputs }-1], [@{ $l_outputs }], [@{ $r_outputs }]);
if($merge_outputs)
{
$outputs = @{ $outputs }[0];
}
$states = [$l_states, $r_states];
return($outputs, $states);
}
package AI::MXNet::RNN::ConvCell::Base;
use Mouse;
use AI::MXNet::Base;
extends 'AI::MXNet::RNN::Cell::Base';
=head1 NAME
AI::MXNet::RNN::Conv::Base
=cut
=head1 DESCRIPTION
Abstract base class for Convolutional RNN cells
=cut
has '_h2h_kernel' => (is => 'ro', isa => 'Shape', init_arg => 'h2h_kernel');
has '_h2h_dilate' => (is => 'ro', isa => 'Shape', init_arg => 'h2h_dilate');
has '_h2h_pad' => (is => 'rw', isa => 'Shape', init_arg => undef);
has '_i2h_kernel' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_kernel');
has '_i2h_stride' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_stride');
has '_i2h_dilate' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_dilate');
has '_i2h_pad' => (is => 'ro', isa => 'Shape', init_arg => 'i2h_pad');
has '_num_hidden' => (is => 'ro', isa => 'DimSize', init_arg => 'num_hidden');
has '_input_shape' => (is => 'ro', isa => 'Shape', init_arg => 'input_shape');
has '_conv_layout' => (is => 'ro', isa => 'Str', init_arg => 'conv_layout', default => 'NCHW');
has '_activation' => (is => 'ro', init_arg => 'activation');
lib/AI/MXNet/RNN/Cell.pm view on Meta::CPAN
my ($next_output, $next_states) = &{$cell}($inputs, $states);
my $mask = sub {
my ($p, $like) = @_;
AI::MXNet::Symbol->Dropout(
AI::MXNet::Symbol->ones_like(
$like
),
p => $p
);
};
my $prev_output = $self->prev_output // AI::MXNet::Symbol->zeros(shape => [0, 0]);
my $output = $p_outputs != 0
? AI::MXNet::Symbol->where(
&{$mask}($p_outputs, $next_output),
$next_output,
$prev_output
)
: $next_output;
my @states;
if($p_states != 0)
{
zip(sub {
my ($new_s, $old_s) = @_;
push @states, AI::MXNet::Symbol->where(
&{$mask}($p_states, $new_s),
$new_s,
$old_s
);
}, $next_states, $states);
}
$self->prev_output($output);
return ($output, @states ? \@states : $next_states);
}
package AI::MXNet::RNN::ResidualCell;
use Mouse;
use AI::MXNet::Base;
extends 'AI::MXNet::RNN::ModifierCell';
=head1 NAME
AI::MXNet::RNN::ResidualCell
=cut
=head1 DESCRIPTION
Adds residual connection as described in Wu et al, 2016
(https://arxiv.org/abs/1609.08144).
Output of the cell is output of the base cell plus input.
=cut
method call(AI::MXNet::Symbol $inputs, SymbolOrArrayOfSymbols $states)
{
my $output;
($output, $states) = &{$self->base_cell}($inputs, $states);
$output = AI::MXNet::Symbol->elemwise_add($output, $inputs, name => $output->name.'_plus_residual');
return ($output, $states)
}
method unroll(
Int $length,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$inputs=,
Maybe[AI::MXNet::Symbol|ArrayRef[AI::MXNet::Symbol]] :$begin_state=,
Str :$input_prefix='',
Str :$layout='NTC',
Maybe[Bool] :$merge_outputs=
)
{
$self->reset;
$self->base_cell->_modified(0);
my ($outputs, $states) = $self->base_cell->unroll($length, inputs=>$inputs, begin_state=>$begin_state,
layout=>$layout, merge_outputs=>$merge_outputs);
$self->base_cell->_modified(1);
$merge_outputs //= (blessed($outputs) and $outputs->isa('AI::MXNet::Symbol'));
($inputs) = _normalize_sequence($length, $inputs, $layout, $merge_outputs);
if($merge_outputs)
{
$outputs = AI::MXNet::Symbol->elemwise_add($outputs, $inputs, name => $outputs->name . "_plus_residual");
}
else
{
my @temp;
zip(sub {
my ($output_sym, $input_sym) = @_;
push @temp, AI::MXNet::Symbol->elemwise_add($output_sym, $input_sym,
name=>$output_sym->name."_plus_residual");
}, [@{ $outputs }], [@{ $inputs }]);
$outputs = \@temp;
}
return ($outputs, $states);
}
func _normalize_sequence($length, $inputs, $layout, $merge, $in_layout=)
{
assert((defined $inputs),
"unroll(inputs=>undef) has been deprecated. ".
"Please create input variables outside unroll."
);
my $axis = index($layout, 'T');
my $in_axis = defined $in_layout ? index($in_layout, 'T') : $axis;
if(blessed($inputs))
{
if(not $merge)
{
assert(
(@{ $inputs->list_outputs() } == 1),
"unroll doesn't allow grouped symbol as input. Please "
."convert to list first or let unroll handle splitting"
);
$inputs = [ @{ AI::MXNet::Symbol->split(
$inputs,
axis => $in_axis,
num_outputs => $length,
squeeze_axis => 1
) }];
}
}
else
{
assert(not defined $length or @$inputs == $length);
if($merge)
{
$inputs = [map { AI::MXNet::Symbol->expand_dims($_, axis=>$axis) } @{ $inputs }];
$inputs = AI::MXNet::Symbol->Concat(@{ $inputs }, dim=>$axis);
$in_axis = $axis;
}
}
if(blessed($inputs) and $axis != $in_axis)
{
$inputs = AI::MXNet::Symbol->swapaxes($inputs, dim0=>$axis, dim1=>$in_axis);
}
return ($inputs, $axis);
}
1;
( run in 2.341 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )