blob: 545cb7b3f882c8dd66d8b792b01b2c3503518f8f [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
use strict;
use warnings;
use Test::More tests => 232;
use AI::MXNet qw(mx);
use AI::MXNet::Gluon qw(gluon);
use AI::MXNet::Gluon::NN qw(nn);
use AI::MXNet::TestUtils qw(almost_equal dies_ok);
use Scalar::Util qw(refaddr);
use AI::MXNet::Base;
sub test_parameter
{
my $p = gluon->Parameter('weight', shape=>[10, 10]);
$p->initialize(init=>'xavier', ctx=>[mx->cpu(0), mx->cpu(1)]);
ok(@{$p->list_data} == 2);
ok(@{$p->list_grad} == 2);
ok($p->data(mx->cpu(1))->context eq mx->cpu(1));
is_deeply($p->data(mx->cpu(0))->shape, [10, 10]);
ok($p->var->name eq 'weight');
ok($p->grad(mx->cpu(0))->stype eq 'default');
ok($p->data(mx->cpu(0))->stype eq 'default');
$p->reset_ctx(ctx=>[mx->cpu(1), mx->cpu(2)]);
is_deeply($p->list_ctx, [mx->cpu(1), mx->cpu(2)]);
}
test_parameter();
sub test_invalid_parameter_stype
{
dies_ok(sub { gluon->Parameter('weight', shape=>[10, 10], stype=>'invalid') });
}
test_invalid_parameter_stype();
sub test_invalid_parameter_grad_stype
{
dies_ok(sub { gluon->Parameter('weight', shape=>[10, 10], grad_stype=>'invalid') });
}
test_invalid_parameter_grad_stype();
sub test_sparse_parameter
{
my $p = gluon->Parameter('weight', shape=>[10, 10], stype=>'row_sparse', grad_stype=>'row_sparse');
$p->initialize(init=>'xavier', ctx=>[mx->cpu(0), mx->cpu(1)]);
my $row_id = mx->nd->arange(start => 0, stop => 10, ctx=>mx->cpu(1));
ok(@{ $p->list_grad } == 2);
# getting row_sparse data without trainer throws an exception
dies_ok(sub { $p->list_row_sparse_data($row_id) });
my $trainer = gluon->Trainer([$p], 'sgd');
ok(@{ $p->list_row_sparse_data($row_id) } == 2);
my $weight = $p->row_sparse_data($row_id);
ok($weight->context eq mx->cpu(1));
is_deeply($weight->shape, [10, 10]);
ok($weight->stype eq 'row_sparse');
ok($p->var->name eq 'weight');
ok($p->var->attr('__storage_type__') eq STORAGE_TYPE_STR_TO_ID->{row_sparse});
ok($p->grad(mx->cpu(0))->stype eq 'row_sparse');
$p->reset_ctx(ctx=>[mx->cpu(1), mx->cpu(2)]);
is_deeply($p->list_ctx, [mx->cpu(1), mx->cpu(2)]);
}
test_sparse_parameter();
sub test_parameter_invalid_access
{
# cannot call data on row_sparse parameters
my $p0 = gluon->Parameter('weight', shape=>[10, 10], stype=>'row_sparse', grad_stype=>'row_sparse');
$p0->initialize(init=>'xavier', ctx=>[mx->cpu(0), mx->cpu(1)]);
dies_ok(sub { $p0->data });
dies_ok(sub { $p0->list_data });
my $row_id = mx->nd->arange(start => 0, stop => 10);
# cannot call row_sparse_data on dense parameters
my $p1 = gluon->Parameter('weight', shape=>[10, 10]);
$p1->initialize(init=>'xavier', ctx=>[mx->cpu(0), mx->cpu(1)]);
dies_ok(sub { $p1->row_sparse_data($row_id->copyto(mx->cpu(0))) });
dies_ok(sub { $p1->list_row_sparse_data($row_id) });
}
test_parameter_invalid_access();
sub test_paramdict
{
my $ctx = mx->cpu(1);
my $params0 = gluon->ParameterDict('net_');
$params0->get('w0', shape=>[10, 10]);
$params0->get('w1', shape=>[10, 10], stype=>'row_sparse');
my $all_row_ids = mx->nd->arange(start => 0, stop => 10, ctx=>$ctx);
# check param names
is_deeply([$params0->keys()], ['net_w0', 'net_w1']);
$params0->initialize(ctx=>$ctx);
my $trainer0 = gluon->Trainer($params0, 'sgd');
my $prev_w0 = $params0->get('w0')->data($ctx);
my $prev_w1 = $params0->get('w1')->row_sparse_data($all_row_ids);
# save params
$params0->save('test_paramdict.params');
# load params
my $params1 = gluon->ParameterDict('net_');
$params1->get('w0', shape=>[10, 10]);
$params1->get('w1', shape=>[10, 10], stype=>'row_sparse');
$params1->load('test_paramdict.params', ctx=>$ctx);
my $trainer1 = gluon->Trainer($params1, 'sgd');
# compare the values before and after save/load
my $cur_w0 = $params1->get('w0')->data($ctx);
my $cur_w1 = $params1->get('w1')->row_sparse_data($all_row_ids);
ok(almost_equal($prev_w0->aspdl, $cur_w0->aspdl));
ok(almost_equal($prev_w1->aspdl, $cur_w1->aspdl));
# create a new param dict with dense params, and load from the checkpoint
# of sparse & dense params
my $params2 = gluon->ParameterDict('net_');
$params2->get('w0', shape=>[10, 10]);
$params2->get('w1', shape=>[10, 10]);
$params2->load('test_paramdict.params', ctx=>$ctx);
# compare the values before and after save/load
$cur_w0 = $params2->get('w0')->data($ctx);
$cur_w1 = $params2->get('w1')->data($ctx);
ok(almost_equal($prev_w0->aspdl, $cur_w0->aspdl));
ok(almost_equal($prev_w1->aspdl, $cur_w1->aspdl));
}
test_paramdict();
sub test_parameter_row_sparse_data
{
my $ctx0 = mx->cpu(1);
my $ctx1 = mx->cpu(2);
my $dim0 = 4;
my $x = gluon->Parameter('x', shape=>[$dim0, 2], stype=>'row_sparse');
$x->initialize(init=>'xavier', ctx=>[$ctx0, $ctx1]);
my $trainer = gluon->Trainer([$x], 'sgd');
my $x_param = $x->_data->[0]->copy();
is($x_param->stype, 'row_sparse');
my $row_id_0 = mx->nd->array([0,1], ctx=>$ctx0);
my $retained_0 = $x->row_sparse_data($row_id_0);
my $retained_target_0 = mx->nd->sparse->retain($x_param, $row_id_0->as_in_context($ctx0));
ok(almost_equal($retained_0->aspdl, $retained_target_0->aspdl));
is($retained_0->context, $ctx0);
my $row_id_1 = mx->nd->arange(start => 0, stop => $dim0, ctx=>$ctx1);
my $retained_1 = $x->row_sparse_data($row_id_1);
my $retained_target_1 = $x_param;
ok(almost_equal($retained_1->aspdl, $retained_target_1->aspdl));
is($retained_1->context, $ctx1);
my $row_id_2 = mx->nd->array([0,1,2]);
my $retained_2 = $x->list_row_sparse_data($row_id_2);
my $retained_target_2 = mx->nd->sparse->retain($x_param, $row_id_2->as_in_context($ctx0));
ok(almost_equal($retained_2->[0]->aspdl, $retained_target_2->aspdl));
}
test_parameter_row_sparse_data();
sub test_constant
{
package Test {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::HybridBlock';
sub BUILD
{
my $self = shift;
$self->value(mx->nd->array([[1,2], [3,4]])->aspdl);
$self->const($self->params->get_constant('const', $self->value));
}
sub hybrid_forward
{
my ($self, $F, $x, $name, $const) = @_;
return $x + $const;
}
};
my $test = Test->new();
$test->initialize();
my $trainer = gluon->Trainer(
$test->collect_params(), 'sgd',
{learning_rate => 1.0, momentum => 0.5}
);
my ($x, $y);
mx->autograd->record(sub {
$x = mx->nd->ones([2,2]);
$x->attach_grad();
$y = $test->($x);
$y->backward();
});
$trainer->step(1);
ok(($test->const->data->aspdl == $test->value)->all);
ok(($x->grad->aspdl == 1)->all);
}
test_constant();
package Net;
use AI::MXNet::Gluon::Mouse;
use AI::MXNet::Function::Parameters;
extends 'AI::MXNet::Gluon::Block';
has 'in_units' => (is => 'rw', default => 0);
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->dense0(nn->Dense(5, in_units=>$self->in_units));
$self->dense1(nn->Dense(5, in_units=>$self->in_units));
});
}
method forward($x)
{
return $self->dense1->($self->dense0->($x));
}
package main;
sub test_parameter_sharing
{
my $net1 = Net->new(prefix=>'net1_', in_units => 5);
my $net2 = Net->new(prefix=>'net2_', params=>$net1->collect_params());
$net1->collect_params()->initialize();
$net2->(mx->nd->zeros([3, 5]));
$net1->save_parameters('net1.params');
my $net3 = Net->new(prefix=>'net3_');
$net3->load_parameters('net1.params', ctx => mx->cpu());
my $net4 = Net->new(prefix=>'net4_');
my $net5 = Net->new(prefix=>'net5_', in_units=>5, params=>$net4->collect_params());
$net4->collect_params()->initialize();
$net5->(mx->nd->zeros([3, 5]));
$net4->save_parameters('net4.params');
my $net6 = Net->new(prefix=>'net6_');
$net6->load_parameters('net4.params', ctx => mx->cpu());
}
test_parameter_sharing();
sub test_parameter_str
{
package Net1 {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::Block';
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->dense0(nn->Dense(10, in_units=>5, use_bias=>0));
});
}
};
my $net = Net1->new(prefix=>'net1_');
my @lines = split(/\n/, $net->collect_params());
ok($lines[0] eq 'net1_ (');
ok($lines[1] =~ /net1_dense0_weight/);
ok($lines[1] =~ /\(10, 5\)/);
ok($lines[1] =~ /float32/);
ok($lines[2] eq ')');
}
test_parameter_str();
sub test_collect_parameters
{
my $net = nn->HybridSequential(prefix=>"test_");
$net->name_scope(sub {
$net->add(nn->Conv2D(10, 3));
$net->add(nn->Dense(10, activation=>'relu'));
});
is_deeply(
[$net->collect_params->keys],
['test_conv0_weight', 'test_conv0_bias','test_dense0_weight','test_dense0_bias']
);
is_deeply(
[$net->collect_params('.*weight')->keys],
['test_conv0_weight', 'test_dense0_weight']
);
is_deeply(
[$net->collect_params('test_conv0_bias|test_dense0_bias')->keys],
['test_conv0_bias', 'test_dense0_bias']
)
};
test_collect_parameters();
sub test_basic
{
my $model = nn->Sequential();
$model->add(nn->Dense(128, activation=>'tanh', in_units=>10, flatten=>0));
$model->add(nn->Dropout(0.5));
$model->add(nn->Dense(64, activation=>'tanh', in_units=>256));
$model->add(nn->Dense(32, in_units=>64));
$model->add(nn->Activation('relu'));
# symbol
my $x = mx->sym->var('data');
my $y = $model->($x);
ok(@{ $y->list_arguments } == 7);
# ndarray
$model->collect_params()->initialize(init => mx->init->Xavier(magnitude=>2.24));
$x = $model->(mx->nd->zeros([32, 2, 10]));
is_deeply($x->shape, [32, 32]);
$x->wait_to_read;
$model->collect_params()->setattr(grad_req => 'null');
ok(not defined( ($model->collect_params()->values())[0]->_grad));
$model->collect_params()->setattr(grad_req => 'write');
ok(defined (($model->collect_params()->values())[0]->_grad));
}
test_basic();
sub test_dense
{
my $model = nn->Dense(128, activation=>'tanh', in_units=>10, flatten=>0, prefix=>'test_');
my $inputs = mx->sym->Variable('data');
my $outputs = $model->($inputs);
is_deeply({map { $_ => 1 } $model->collect_params()->keys()}, {'test_weight', 1, 'test_bias', 1});
is_deeply($outputs->list_outputs(), ['test_tanh_fwd_output']);
my ($args, $outs, $auxs) = $outputs->infer_shape(data=>[2, 3, 10]);
is_deeply($outs, [[2, 3, 128]]);
$model = nn->Dense(128, activation=>'relu', in_units=>30, flatten=>1, prefix=>'test2_');
$inputs = mx->sym->Variable('data');
$outputs = $model->($inputs);
is_deeply({map { $_ => 1 } $model->collect_params()->keys()}, {'test2_weight', 1, 'test2_bias', 1});
is_deeply($outputs->list_outputs(), ['test2_relu_fwd_output']);
($args, $outs, $auxs) = $outputs->infer_shape(data=>[17, 2, 5, 3]);
is_deeply($outs, [[17, 128]]);
}
test_dense();
package Net2;
use AI::MXNet::Gluon::Mouse;
use AI::MXNet::Function::Parameters;
extends 'AI::MXNet::Gluon::HybridBlock';
has 'model' => (is => 'rw');
method hybrid_forward($F, $x)
{
my $out = $self->model->($x);
return $F->add_n(map { $_->sum } @{ $out });
}
package main;
sub test_symbol_block
{
my $model = nn->HybridSequential();
$model->add(nn->Dense(128, activation=>'tanh'));
$model->add(nn->Dropout(0.5));
$model->add(nn->Dense(64, activation=>'tanh'));
$model->add(nn->Dense(32, in_units=>64));
$model->add(nn->Activation('relu'));
$model->initialize();
my $inputs = mx->sym->var('data');
my $outputs = $model->($inputs)->get_internals();
my $smodel = gluon->SymbolBlock($outputs, $inputs, params=>$model->collect_params);
ok($smodel->(mx->nd->zeros([16, 10])) == 14);
my $out = $smodel->(mx->sym->var('in'));
ok(@{ $out } == @{ $outputs->list_outputs() });
my $net = Net2->new(model => $smodel);
$net->hybridize();
ok(ref $net->(mx->nd->zeros([16, 10])) eq 'AI::MXNet::NDArray');
$inputs = mx->sym->var('data');
$outputs = $model->($inputs);
$smodel = gluon->SymbolBlock($outputs, $inputs, params=>$model->collect_params);
$net = Net2->new(model => $smodel);
$net->hybridize();
ok(ref $net->(mx->nd->zeros([16, 10])) eq 'AI::MXNet::NDArray');
}
test_symbol_block();
sub test_sparse_symbol_block
{
my $data = mx->sym->var('data');
my $weight = mx->sym->var('weight', stype=>'row_sparse');
my $bias = mx->sym->var('bias');
my $out = mx->sym->broadcast_add(mx->sym->dot($data, $weight), $bias);
# an exception is expected when creating a SparseBlock w/ sparse param
dies_ok(sub { gluon->SymbolBlock($out, $data) });
}
test_sparse_symbol_block();
sub test_sparse_hybrid_block0
{
my $params = gluon->ParameterDict('net_');
$params->get('weight', shape=>[5,5], stype=>'row_sparse', dtype=>'float32', allow_deferred_init => 1);
$params->get('bias', shape=>[5], dtype=>'float32', allow_deferred_init => 1);
my $net = nn->Dense(5, params=>$params);
$net->initialize();
my $x = mx->nd->ones([2,5]);
# an exception is expected when forwarding a HybridBlock w/ sparse param
dies_ok(sub { $net->($x) });
}
test_sparse_hybrid_block0();
sub check_layer_forward
{
my ($layer, $dshape) = @_;
$layer->collect_params()->initialize();
my $x = mx->nd->ones($dshape);
$x->attach_grad();
my $out;
mx->autograd->record(sub {
$out = $layer->($x);
});
$out->backward();
my $pdl_out = $out->aspdl;
my $pdl_dx = $x->grad->aspdl;
$layer->hybridize();
$x = mx->nd->ones($dshape);
$x->attach_grad();
mx->autograd->record(sub {
$out = $layer->($x);
});
$out->backward();
ok(almost_equal($pdl_out, $out->aspdl, 1e-5));
ok(almost_equal($pdl_dx, $x->grad->aspdl, 1e-5));
}
sub test_conv
{
my @layers1d = (
nn->Conv1D(16, 3, in_channels=>4),
nn->Conv1D(16, 3, groups=>2, in_channels=>4),
nn->Conv1D(16, 3, strides=>3, groups=>2, in_channels=>4),
);
for my $layer (@layers1d)
{
check_layer_forward($layer, [1, 4, 10]);
}
my @layers2d = (
nn->Conv2D(16, [3, 4], in_channels=>4),
nn->Conv2D(16, [5, 4], in_channels=>4),
nn->Conv2D(16, [3, 4], groups=>2, in_channels=>4),
nn->Conv2D(16, [3, 4], strides=>4, in_channels=>4),
nn->Conv2D(16, [3, 4], dilation=>4, in_channels=>4),
nn->Conv2D(16, [3, 4], padding=>4, in_channels=>4),
);
for my $layer (@layers2d)
{
check_layer_forward($layer, [1, 4, 20, 20]);
}
my @layers3d = (
nn->Conv3D(16, [1, 8, 4], in_channels=>4, activation=>'relu'),
nn->Conv3D(16, [5, 4, 3], in_channels=>4),
nn->Conv3D(16, [3, 3, 3], groups=>2, in_channels=>4),
nn->Conv3D(16, 4, strides=>4, in_channels=>4),
nn->Conv3D(16, [3, 3, 3], padding=>4, in_channels=>4),
);
for my $layer (@layers3d)
{
check_layer_forward($layer, [1, 4, 10, 10, 10]);
}
# These layouts only supported on GPU for now
my $layer = nn->Conv2D(16, [3, 3], layout=>'NHWC', in_channels=>4);
#check_layer_forward($layer, [1, 10, 10, 4]);
$layer = nn->Conv3D(16, [3, 3, 3], layout=>'NDHWC', in_channels=>4);
# check_layer_forward(layer, (1, 10, 10, 10, 4))
}
test_conv();
sub test_deconv
{
# commented out code is only supported on GPU for now
# my @layers1d = (
# nn->Conv1DTranspose(16, 3, in_channels=>4),
# nn->Conv1DTranspose(16, 3, groups=>2, in_channels=>4),
# nn->Conv1DTranspose(16, 3, strides=>3, groups=>2, in_channels=>4),
# );
# for my $layer (@layers1d)
# {
# check_layer_forward($layer, [1, 4, 10]);
# }
my @layers2d = (
nn->Conv2DTranspose(16, [3, 4], in_channels=>4),
nn->Conv2DTranspose(16, [5, 4], in_channels=>4),
nn->Conv2DTranspose(16, [3, 4], groups=>2, in_channels=>4),
nn->Conv2DTranspose(16, [3, 4], strides=>4, in_channels=>4),
nn->Conv2DTranspose(16, [3, 4], dilation=>4, in_channels=>4),
nn->Conv2DTranspose(16, [3, 4], padding=>4, in_channels=>4),
nn->Conv2DTranspose(16, [3, 4], strides=>4, output_padding=>3, in_channels=>4),
);
for my $layer (@layers2d)
{
check_layer_forward($layer, [1, 4, 20, 20]);
}
# @layers3d = (
# nn->Conv3DTranspose(16, [1, 8, 4], in_channels=>4),
# nn->Conv3DTranspose(16, [5, 4, 3], in_channels=>4),
# nn->Conv3DTranspose(16, [3, 3, 3], groups=>2, in_channels=>4),
# nn->Conv3DTranspose(16, 4, strides=>4, in_channels=>4),
# nn->Conv3DTranspose(16, [3, 3, 3], padding=>4, in_channels=>4),
# );
# for my $layer (@layers3d)
# {
# check_layer_forward($layer, [1, 4, 10, 10, 10]);
# }
#
my $layer = nn->Conv2DTranspose(16, [3, 3], layout=>'NHWC', in_channels=>4);
# check_layer_forward($layer, [1, 10, 10, 4]);
#
# $layer = nn->Conv3DTranspose(16, [3, 3, 3], layout=>'NDHWC', in_channels=>4);
# check_layer_forward(layer, [1, 10, 10, 10, 4]);
}
test_deconv();
sub test_pool
{
my @layers1d = (
nn->MaxPool1D(),
nn->MaxPool1D(3),
nn->MaxPool1D(3, 2),
nn->AvgPool1D(),
nn->AvgPool1D(count_include_pad=>0),
nn->GlobalAvgPool1D(),
);
for my $layer (@layers1d)
{
check_layer_forward($layer, [1, 2, 10]);
}
my @layers2d = (
nn->MaxPool2D(),
nn->MaxPool2D([3, 3]),
nn->MaxPool2D(3, 2),
nn->AvgPool2D(),
nn->AvgPool2D(count_include_pad=>0),
nn->GlobalAvgPool2D(),
);
for my $layer (@layers2d)
{
check_layer_forward($layer, [1, 2, 10, 10]);
}
my @layers3d = (
nn->MaxPool3D(),
nn->MaxPool3D([3, 3, 3]),
nn->MaxPool3D(3, 2),
nn->AvgPool3D(),
nn->AvgPool3D(count_include_pad=>0),
nn->GlobalAvgPool3D(),
);
for my $layer (@layers3d)
{
check_layer_forward($layer, [1, 2, 10, 10, 10]);
}
# test ceil_mode
my $x = mx->nd->zeros([2, 2, 10, 10]);
my $layer = nn->MaxPool2D(3, ceil_mode=>0);
$layer->collect_params()->initialize();
is_deeply($layer->($x)->shape, [2, 2, 3, 3]);
$layer = nn->MaxPool2D(3, ceil_mode=>1);
$layer->collect_params()->initialize();
is_deeply($layer->($x)->shape, [2, 2, 4, 4]);
}
test_pool();
sub test_batchnorm
{
my $layer = nn->BatchNorm(in_channels=>10);
check_layer_forward($layer, [2, 10, 10, 10]);
}
test_batchnorm();
sub test_instancenorm
{
my $layer = nn->InstanceNorm(in_channels=>10);
check_layer_forward($layer, [2, 10, 10, 10]);
}
test_instancenorm();
sub test_layernorm
{
my $layer = nn->LayerNorm(in_channels=>10);
check_layer_forward($layer, [2, 10, 10, 10]);
}
test_layernorm();
sub test_reflectionpad
{
my $layer = nn->ReflectionPad2D(3);
check_layer_forward($layer, [2, 3, 24, 24]);
}
test_reflectionpad();
sub test_reshape
{
my $x = mx->nd->ones([2, 4, 10, 10]);
my $layer = nn->Conv2D(10, 2, in_channels=>4);
$layer->collect_params()->initialize();
mx->autograd->record(sub {
$x = $layer->($x);
$x = $x->reshape([-1]);
$x = $x + 10;
});
$x->backward();
}
test_reshape();
sub test_slice
{
my $x = mx->nd->ones([5, 4, 10, 10]);
my $layer = nn->Conv2D(10, 2, in_channels=>4);
$layer->collect_params()->initialize();
mx->autograd->record(sub {
$x = $layer->($x);
$x = $x->slice([1,3]);
$x = $x + 10;
});
$x->backward();
}
test_slice();
sub test_at
{
my $x = mx->nd->ones([5, 4, 10, 10]);
my $layer = nn->Conv2D(10, 2, in_channels=>4);
$layer->collect_params()->initialize();
mx->autograd->record(sub {
$x = $layer->($x);
$x = $x->at(1);
$x = $x + 10;
});
$x->backward();
}
test_at();
sub test_deferred_init
{
my $x = mx->nd->ones([5, 4, 10, 10]);
my $layer = nn->Conv2D(10, 2);
$layer->collect_params()->initialize();
$layer->($x);
}
test_deferred_init();
sub check_split_data
{
my ($x, $num_slice, $batch_axis, %kwargs) = @_;
my $res = gluon->utils->split_data($x, $num_slice, $batch_axis, %kwargs);
ok(@{ $res } == $num_slice);
ok(almost_equal(mx->nd->concat(@$res, dim=>$batch_axis)->aspdl(), $x->aspdl()));
}
sub test_split_data
{
my $x = mx->nd->random->uniform(shape=>[128, 33, 64]);
check_split_data($x, 8, 0);
check_split_data($x, 3, 1);
check_split_data($x, 4, 1, even_split=>0);
check_split_data($x, 15, 1, even_split=>0);
eval {
check_split_data($x, 4, 1);
};
ok($@);
}
test_split_data();
sub test_flatten
{
my $flatten = nn->Flatten();
my $x = mx->nd->zeros([3,4,5,6]);
is_deeply($flatten->($x)->shape, [3, 4*5*6]);
$x = mx->nd->zeros([3,6]);
is_deeply($flatten->($x)->shape, [3, 6]);
$x = mx->nd->zeros([3]);
is_deeply($flatten->($x)->shape, [3, 1]);
}
test_flatten();
sub test_block_attr_hidden
{
my $b = gluon->Block();
# regular attributes can change types
$b->a(undef);
$b->a(1);
}
test_block_attr_hidden();
sub test_block_attr_block
{
my $b = gluon->Block();
# regular variables can't change types
$b->b(gluon->Block());
eval { $b->b([2]); };
ok($@ =~ /not allowed/i);
}
test_block_attr_block();
sub test_block_attr_param
{
my $b = gluon->Block();
# regular variables can't change types
$b->b(gluon->Parameter(name => 'test'));
eval { $b->b([2]); };
ok($@ =~ /not allowed/i);
}
test_block_attr_param();
sub test_block_attr_regular
{
my $b = gluon->Block();
# set block attribute also sets _children
$b->c(gluon->Block());
my $c2 = gluon->Block();
$b->c($c2);
ok(refaddr($b->c) == refaddr($c2) and refaddr(($b->_children->values)[0]) == refaddr($c2));
}
test_block_attr_regular();
sub test_block_attr_list_of_block
{
package Model1 {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::Block';
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->layers([map { nn->Dense($_ * 10) } 0..5]);
});
}
};
package Model2 {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::Block';
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->layers({});
$self->layers->{a} = [map { nn->Dense($_ * 10) } 0..5];
});
}
};
package Model3 {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::Block';
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->layers(nn->Sequential());
$self->layers->add(map { nn->Dense($_ * 10) } 0..5);
});
}
};
package Model4 {
use AI::MXNet::Gluon::Mouse;
extends 'AI::MXNet::Gluon::Block';
sub BUILD
{
my $self = shift;
$self->name_scope(sub {
$self->data({a => '4', b => 123});
});
}
};
my $w = 0;
local($SIG{__WARN__}) = sub {
$w++;
};
Model1->new->collect_params;
ok($w > 0); $w = 0;
Model2->new->collect_params;
ok($w > 0); $w = 0;
Model3->new->collect_params;
ok($w == 0); $w = 0;
Model4->new->collect_params;
ok($w == 0);
}
test_block_attr_list_of_block();
sub check_sequential
{
my ($net) = @_;
my $dense1 = nn->Dense(10);
$net->add($dense1);
my $dense2 = nn->Dense(10);
$net->add($dense2);
my $dense3 = nn->Dense(10);
$net->add($dense3);
ok(refaddr($net->[1]) == refaddr($dense2));
ok(refaddr($net->[-1]) == refaddr($dense3));
my $slc = $net->slice([1,2]);
ok(@$slc == 2 and refaddr($slc->[0]) == refaddr($dense2) and refaddr($slc->[1]) == refaddr($dense3));
ok(ref $slc eq ref $net);
}
sub test_sequential
{
check_sequential(nn->Sequential());
check_sequential(nn->HybridSequential());
}
test_sequential();
sub test_global_norm_clip
{
my @stypes = ('default', 'row_sparse');
my $check_global_norm_clip = sub { my ($stype) = @_;
my $x1 = mx->nd->ones([3,3])->tostype($stype);
my $x2 = mx->nd->ones([4,4])->tostype($stype);
my $norm = gluon->utils->clip_global_norm([$x1, $x2], 1.0);
ok($norm == 5);
ok(almost_equal($x1->aspdl, mx->nd->ones([3,3])->aspdl/5));
ok(almost_equal($x2->aspdl, mx->nd->ones([4,4])->aspdl/5));
my $x3 = mx->nd->array([1.0, 2.0, 'nan'])->tostype($stype);
my $w = 0;
local($SIG{__WARN__}) = sub {
$w++;
};
gluon->utils->clip_global_norm([$x1, $x3], 2.0);
ok($w == 1);
};
for my $stype (@stypes)
{
$check_global_norm_clip->($stype);
}
}
test_global_norm_clip();
sub test_embedding
{
local($ENV{MXNET_STORAGE_FALLBACK_LOG_VERBOSE}) = 0;
my $check_embedding = sub { my ($sparse_grad) = @_;
my $layer = nn->Embedding(10, 100, sparse_grad=>$sparse_grad);
$layer->initialize();
my $x = mx->nd->array([3,4,2,0,1]); my $y;
mx->autograd->record(sub {
$y = $layer->($x);
$y->backward();
});
ok(($layer->weight->grad->aspdl->slice('X', [0, 4]) == 1)->all);
ok(($layer->weight->grad->aspdl->slice('X', [5, -1]) == 0)->all);
};
my $check_embedding_large_input = sub { my ($sparse_grad) = @_;
my $embedding = nn->Embedding(10, 1, sparse_grad=>$sparse_grad);
$embedding->initialize();
$embedding->hybridize();
my $shape = [20481];
my ($emb_in, $loss);
mx->autograd->record(sub {
$emb_in = $embedding->(mx->nd->ones($shape));
$loss = $emb_in->sum;
});
$loss->backward;
ok($embedding->weight->grad->sum->asscalar == 20481);
};
$check_embedding->(1);
$check_embedding->(0);
$check_embedding_large_input->(1);
$check_embedding_large_input->(0);
}
test_embedding();
sub test_hybrid_stale_cache
{
my $net = nn->HybridSequential();
$net->name_scope(sub {
$net->add(nn->Dense(10, weight_initializer=>'zeros', bias_initializer=>'ones', flatten=>0));
});
$net->hybridize();
$net->initialize();
$net->(mx->nd->ones([2,3,5]));
$net->add(nn->Flatten());
is_deeply($net->(mx->nd->ones([2,3,5]))->shape, [2, 30]);
$net = nn->HybridSequential();
$net->name_scope(sub {
$net->fc1(nn->Dense(10, weight_initializer=>'zeros',
bias_initializer=>'ones', flatten=>0));
$net->fc2(nn->Dense(10, weight_initializer=>'zeros',
bias_initializer=>'ones', flatten=>0));
});
$net->hybridize();
$net->initialize();
$net->(mx->nd->ones([2,3,5]));
$net->fc2(nn->Dense(10, weight_initializer=>'zeros',
bias_initializer=>'ones', flatten=>1));
$net->initialize();
is_deeply($net->(mx->nd->ones([2,3,5]))->shape, [2, 10]);
}
test_hybrid_stale_cache();
sub test_lambda
{
my $net1 = nn->HybridSequential();
$net1->add(nn->Activation('tanh'),
nn->LeakyReLU(0.1));
my $net2 = nn->HybridSequential();
my $op3 = sub { my ($F, $x, @args) = @_; $F->LeakyReLU($x, @args, slope=>0.1); };
$net2->add(nn->HybridLambda('tanh'),
nn->HybridLambda($op3));
my $op4 = sub { mx->nd->LeakyReLU($_[0], slope=>0.1); };
my $net3 = nn->Sequential();
$net3->add(nn->Lambda('tanh'),
nn->Lambda($op4));
my $input_data = mx->nd->random->uniform(shape=>[2, 3, 5, 7]);
my ($out1, $out2, $out3) = ($net1->($input_data), $net2->($input_data), $net3->($input_data));
ok(almost_equal($out1->aspdl, $out2->aspdl, 1e-3));
ok(almost_equal($out1->aspdl, $out3->aspdl, 1e-3));
}
test_lambda();
sub test_fill_shape_deferred
{
my $net = nn->HybridSequential();
$net->name_scope(sub {
$net->add(nn->Conv2D(64, kernel_size=>2, padding=>1),
nn->BatchNorm(),
nn->Dense(10));
});
$net->hybridize();
$net->initialize();
$net->(mx->nd->ones([2,3,5,7]));
ok($net->[0]->weight->shape->[1] == 3);
ok($net->[1]->gamma->shape->[0] == 64);
ok($net->[2]->weight->shape->[1] == 3072);
}
test_fill_shape_deferred();
sub test_fill_shape_load
{
my $ctx = mx->context->current_context();
my $net1 = nn->HybridSequential();
$net1->name_scope(sub {
$net1->add(nn->Conv2D(64, kernel_size=>2, padding=>1),
nn->BatchNorm(),
nn->Dense(10))
});
$net1->hybridize();
$net1->initialize(mx->init->Uniform, ctx => $ctx);
$net1->(mx->nd->ones([2,3,5,7], ctx => $ctx));
$net1->save_parameters('net_fill.params');
my $net2 = nn->HybridSequential();
$net2->name_scope(sub {
$net2->add(nn->Conv2D(64, kernel_size=>2, padding=>1),
nn->BatchNorm(),
nn->Dense(10))
});
$net2->hybridize();
$net2->initialize();
$net2->load_parameters('net_fill.params', ctx=>$ctx);
ok($net2->[0]->weight->shape->[1] == 3);
ok($net2->[1]->gamma->shape->[0] == 64);
ok($net2->[2]->weight->shape->[1] == 3072);
}
test_fill_shape_load();
use JSON::PP qw(decode_json);
sub test_inline
{
my $y;
my $net = nn->HybridSequential();
$net->name_scope(sub {
$net->add(nn->Dense(10));
$net->add(nn->Dense(10));
$net->add(nn->Dense(10));
});
$net->initialize();
$net->hybridize(inline_limit=>3);
mx->autograd->record(sub {
$y = $net->(mx->nd->zeros([1,10]));
});
my $len_1 = @{ decode_json(mx->autograd->get_symbol($y)->tojson())->{nodes} };
$y->backward();
$net->hybridize(inline_limit=>0);
mx->autograd->record(sub {
$y = $net->(mx->nd->zeros([1,10]));
});
my $len_2 = @{ decode_json(mx->autograd->get_symbol($y)->tojson())->{nodes} };
$y->backward();
is($len_1, $len_2 + 2);
}
test_inline();
sub test_activations
{
my $point_to_validate = mx->nd->array([(-0.1, 0.1) x 3]);
my $swish = nn->Swish();
my $swish_test = sub { my ($x) = @_;
return $x * mx->nd->sigmoid($x)
};
for(zip($swish_test->($point_to_validate), $swish->($point_to_validate)))
{
my ($test_point, $ref_point) = @$_;
ok($test_point == $ref_point);
}
my $elu = nn->ELU();
my $elu_test = sub { my ($x) = @_;
my $elu = sub { my ($x) = @_;
return $x < 0 ? 1.0 * (mx->nd->exp($x) - 1) : $x;
};
return [map { $elu->($_) } @{ $x }];
};
for(zip($elu_test->($point_to_validate), $elu->($point_to_validate)))
{
my ($test_point, $ref_point) = @$_;
ok($test_point == $ref_point);
}
my $selu = nn->SELU();
my $selu_test = sub { my ($x) = @_;
my $selu = sub { my ($x) = @_;
my ($scale, $alpha) = (1.0507009873554804934193349852946, 1.6732632423543772848170429916717);
return $x => 0 ? $scale * $x : $alpha * mx->nd->exp($x) - $alpha;
};
return [map { $selu->($_) } @{ $x }];
};
for(zip($selu_test->($point_to_validate), $selu->($point_to_validate)))
{
my ($test_point, $ref_point) = @$_;
ok($test_point == $ref_point);
}
my $prelu = nn->PReLU();
$prelu->initialize();
my $x = $point_to_validate->reshape([1, 3, 2]);
ok(almost_equal($prelu->($x)->aspdl, mx->nd->where($x >= 0, $x, 0.25 * $x)->aspdl));
}
test_activations();
sub test_req
{
my $data = mx->nd->random->uniform(shape=>[1,3,224,224]);
my $label = mx->nd->array([1]);
my $loss = gluon->loss->SoftmaxCrossEntropyLoss();
my $net = nn->HybridSequential();
my $net1 = nn->HybridSequential();
$net1->add(nn->Dense(4));
my $net2 = nn->HybridSequential();
$net2->add(nn->Dense(3));
$net2->add(nn->Dense(2));
$net->add($net1);
$net->add($net2);
$net->initialize();
$net->hybridize();
for my $v ($net->collect_params->values)
{
$v->grad_req('add');
}
$net->collect_params->zero_grad();
my $grad;
mx->autograd->record(sub {
my $pred = $net->($data);
my $l = $loss->($pred, $label);
$l->backward();
$grad = $net->[0][0]->weight->grad->mean->aspdl;
# run twice to check req = add
$pred = $net->($data);
$l = $loss->($pred, $label);
$l->backward;
});
my $grad_double = $net->[0][0]->weight->grad->mean->aspdl;
ok(almost_equal($grad * 2, $grad_double));
}
test_req();
sub test_zero_grad
{
my $data = mx->nd->random->uniform(shape=>[3,3]);
my $net = nn->Embedding(3, 4, sparse_grad=>1, prefix=>'test_zero_grad_');
$net->initialize();
mx->autograd->record(sub {
$net->($data)->backward;
});
$net->collect_params->zero_grad;
my $grad = $net->collect_params->params->get('test_zero_grad_weight')->grad;
ok(almost_equal($grad->aspdl, $grad->aspdl * 0));
}
test_zero_grad();
sub test_hook
{
my $hook_call_count = 0;
my $pre_hook_call_count = 0;
my $call_hook = sub { my ($block, $x, $y) = @_;
$hook_call_count += 1;
};
my $call_pre_hook = sub { my ($block, $x) = @_;
$pre_hook_call_count += 1;
};
my $block = nn->Dense(10);
$block->initialize();
my $handle = $block->register_forward_hook($call_hook);
my $pre_handle = $block->register_forward_pre_hook($call_pre_hook);
$block->(mx->nd->ones([3, 5]));
ok($hook_call_count == 1);
ok($pre_hook_call_count == 1);
$handle->detach();
$block->(mx->nd->ones([3, 5]));
ok($hook_call_count == 1);
ok($pre_hook_call_count == 2);
$pre_handle->detach();
$block->(mx->nd->ones([3, 5]));
ok($hook_call_count == 1);
ok($pre_hook_call_count == 2);
}
test_hook();
sub test_apply
{
my @called_blocks;
my $record_name = sub { my ($block) = @_;
push @called_blocks, $block->name;
};
my $block = nn->HybridSequential(prefix=>'test_');
$block->name_scope(sub {
$block->add(nn->Dense(10));
$block->add(nn->Dropout(0.5));
});
$block->apply($record_name);
is_deeply(\@called_blocks, ['test_dense0', 'test_dropout0', 'test']);
}
test_apply();
sub test_sparse_hybrid_block_grad
{
package Embedding {
use AI::MXNet::Gluon::Mouse;
use AI::MXNet::Function::Parameters;
extends 'AI::MXNet::Gluon::HybridBlock';
has ['num_tokens', 'embedding_size'] => (is => 'rw');
method python_constructor_arguments() { ['num_tokens', 'embedding_size'] }
sub BUILD {
my $self = shift;
$self->name_scope(sub {
$self->embedding(nn->Embedding(
$self->num_tokens, $self->embedding_size, sparse_grad=>1
));
});
}
method hybrid_forward($F, $words)
{
my $emb = $self->embedding->($words);
return $emb + $F->ones_like($emb);
}
};
my $embedding = Embedding->new(20, 3);
$embedding->initialize();
$embedding->hybridize();
my $loss;
mx->autograd->record(sub {
my $emb0 = $embedding->(mx->nd->arange(stop => 10))->sum;
my $emb1 = $embedding->(mx->nd->arange(stop => 10))->sum;
$loss = $emb0 + $emb1;
});
$loss->backward();
my $grad = $embedding->embedding->weight->grad->aspdl;
ok(($grad->slice('X', ':9') == 2)->all);
ok(($grad->slice('X', '10:') == 0)->all);
}
test_sparse_hybrid_block_grad();
sub test_sparse_hybrid_block
{
package Linear {
use AI::MXNet::Gluon::Mouse;
use AI::MXNet::Function::Parameters;
extends 'AI::MXNet::Gluon::HybridBlock';
has ['units'] => (is => 'rw');
method python_constructor_arguments() { ['units'] }
sub BUILD {
my $self = shift;
$self->name_scope(sub {
$self->w($self->params->get(
'w', shape => [$self->units, $self->units]
));
});
}
method hybrid_forward($F, $x, :$w)
{
return $F->dot($x, $w);
}
};
package SparseBlock {
use AI::MXNet::Gluon::Mouse;
use AI::MXNet::Function::Parameters;
extends 'AI::MXNet::Gluon::HybridBlock';
has ['units'] => (is => 'rw');
method python_constructor_arguments() { ['units'] }
sub BUILD {
my $self = shift;
$self->name_scope(sub {
$self->net(Linear->new($self->units));
});
}
method hybrid_forward($F, $x)
{
return $self->net->($x) * $x;
}
};
my $block = SparseBlock->new(2);
$block->initialize();
$block->hybridize();
my $x = mx->nd->ones([2,2])->tostype('csr');
my $z;
mx->autograd->record(sub {
$z = $block->($x) + $block->($x);
});
$z->backward;
ok(($block->net->w->grad->aspdl == 4)->all);
}
test_sparse_hybrid_block();