| # 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. |
| |
| ''' Unit tests for Gluon Estimator ''' |
| |
| import sys |
| import unittest |
| import warnings |
| import pytest |
| |
| import mxnet as mx |
| from mxnet import gluon |
| from mxnet.gluon import nn |
| from mxnet.gluon.contrib.estimator import * |
| from mxnet.gluon.contrib.estimator.event_handler import * |
| |
| mx.npx.reset_np() |
| |
| def _get_test_network(params=None): |
| net = nn.Sequential() |
| net.add(nn.Dense(4, activation='relu', flatten=False)) |
| net.share_parameters(params) |
| return net |
| |
| def _get_test_data(): |
| batch_size = 4 |
| in_data = mx.np.random.uniform(size=(10, 3)) |
| out_data = mx.np.random.uniform(size=(10, 4)) |
| # Input dataloader |
| dataset = gluon.data.dataset.ArrayDataset(in_data, out_data) |
| dataloader = gluon.data.DataLoader(dataset, batch_size=batch_size) |
| dataiter = mx.io.NDArrayIter(data=in_data, label=out_data, batch_size=batch_size) |
| return dataloader, dataiter |
| |
| |
| @mx.util.use_np |
| def test_fit(): |
| ''' test estimator with different train data types ''' |
| net = _get_test_network() |
| dataloader, dataiter = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| loss = gluon.loss.L2Loss() |
| acc = mx.gluon.metric.Accuracy() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| trainer=trainer, |
| device=device) |
| |
| est.fit(train_data=dataloader, |
| epochs=num_epochs) |
| |
| with pytest.raises(ValueError): |
| est.fit(train_data=dataiter, |
| epochs=num_epochs) |
| |
| # Input NDArray |
| with pytest.raises(ValueError): |
| est.fit(train_data=[mx.np.ones(shape=(10, 3))], |
| epochs=num_epochs) |
| |
| |
| @mx.util.use_np |
| def test_validation(): |
| ''' test different validation data types''' |
| net = _get_test_network() |
| dataloader, dataiter = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| loss = gluon.loss.L2Loss() |
| acc = mx.gluon.metric.Accuracy() |
| val_loss = gluon.loss.L1Loss() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| trainer=trainer, |
| device=device, |
| val_loss=val_loss) |
| # Input dataloader |
| est.fit(train_data=dataloader, |
| val_data=dataloader, |
| epochs=num_epochs) |
| |
| # using validation handler |
| train_metrics = est.train_metrics |
| val_metrics = est.val_metrics |
| validation_handler = ValidationHandler(val_data=dataloader, eval_fn=est.evaluate) |
| |
| with pytest.raises(ValueError): |
| est.fit(train_data=dataiter, |
| val_data=dataiter, |
| epochs=num_epochs) |
| # Input NDArray |
| with pytest.raises(ValueError): |
| est.fit(train_data=[mx.np.ones(shape=(10, 3))], |
| val_data=[mx.np.ones(shape=(10, 3))], |
| epochs=num_epochs) |
| |
| |
| @mx.util.use_np |
| def test_initializer(): |
| ''' test with no initializer, inconsistent initializer ''' |
| net = _get_test_network() |
| train_data, _ = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| |
| loss = gluon.loss.L2Loss() |
| acc = mx.gluon.metric.Accuracy() |
| # no initializer |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| device=device) |
| est.fit(train_data=train_data, |
| epochs=num_epochs) |
| |
| # different initializer for net and estimator |
| net = _get_test_network() |
| net.initialize(mx.init.Xavier(), device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| # catch reinit warning |
| with warnings.catch_warnings(record=True) as w: |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| initializer=mx.init.MSRAPrelu(), |
| trainer=trainer, |
| device=device) |
| assert 'Network already fully initialized' in str(w[-1].message) |
| # net partially initialized, fine tuning use case |
| net = gluon.model_zoo.vision.resnet18_v1(pretrained=False, device=device) |
| net.features.initialize(device=device) |
| net.features(mx.np.zeros((1, 3, 224, 224))) |
| net.output = gluon.nn.Dense(10) #last layer not initialized |
| est = Estimator(net, loss=loss, train_metrics=acc, device=device) |
| dataset = gluon.data.ArrayDataset(mx.np.zeros((10, 3, 224, 224)), mx.np.zeros((10, 10))) |
| train_data = gluon.data.DataLoader(dataset=dataset, batch_size=5) |
| est.fit(train_data=train_data, |
| epochs=num_epochs) |
| |
| |
| @mx.util.use_np |
| def test_trainer(): |
| ''' test with no trainer and invalid trainer ''' |
| net = _get_test_network() |
| train_data, _ = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| |
| loss = gluon.loss.L2Loss() |
| acc = mx.gluon.metric.Accuracy() |
| net.initialize(device=device) |
| # input no trainer |
| with warnings.catch_warnings(record=True) as w: |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| device=device) |
| assert 'No trainer specified' in str(w[-1].message) |
| est.fit(train_data=train_data, |
| epochs=num_epochs) |
| |
| # input invalid trainer |
| trainer = 'sgd' |
| with pytest.raises(ValueError): |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| trainer=trainer, |
| device=device) |
| |
| |
| @mx.util.use_np |
| def test_metric(): |
| ''' test with no metric, list of metrics, invalid metric ''' |
| net = _get_test_network() |
| train_data, _ = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| |
| loss = gluon.loss.L2Loss() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| # input no metric |
| est = Estimator(net=net, |
| loss=loss, |
| trainer=trainer, |
| device=device) |
| est.fit(train_data=train_data, |
| epochs=num_epochs) |
| # input list of metrics |
| metrics = [mx.gluon.metric.Accuracy(), mx.gluon.metric.Accuracy()] |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=metrics, |
| trainer=trainer, |
| device=device) |
| est.fit(train_data=train_data, |
| epochs=num_epochs) |
| # input invalid metric |
| with pytest.raises(ValueError): |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics='acc', |
| trainer=trainer, |
| device=device) |
| # test default metric |
| loss = gluon.loss.SoftmaxCrossEntropyLoss() |
| est = Estimator(net=net, |
| loss=loss, |
| trainer=trainer, |
| device=device) |
| assert isinstance(est.train_metrics[0], mx.gluon.metric.Accuracy) |
| |
| |
| @mx.util.use_np |
| def test_loss(): |
| ''' test with invalid loss ''' |
| net = _get_test_network() |
| device = mx.cpu() |
| acc = mx.gluon.metric.Accuracy() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| # input invalid loss |
| with pytest.raises(ValueError): |
| est = Estimator(net=net, |
| loss='mse', |
| train_metrics=acc, |
| trainer=trainer, |
| device=device) |
| |
| |
| @mx.util.use_np |
| def test_device(): |
| ''' test with no device, list of device, invalid device ''' |
| net = _get_test_network() |
| loss = gluon.loss.L2Loss() |
| metrics = mx.gluon.metric.Accuracy() |
| # input no device |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=metrics) |
| # input list of device |
| gpus = mx.device.num_gpus() |
| device = [mx.gpu(i) for i in range(gpus)] if gpus > 0 else [mx.cpu()] |
| net = _get_test_network() |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=metrics, |
| device=device) |
| # input invalid device |
| with pytest.raises(ValueError): |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=metrics, |
| device='cpu') |
| |
| with pytest.raises(AssertionError): |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=metrics, |
| device=[mx.gpu(0), mx.gpu(100)]) |
| |
| |
| @mx.util.use_np |
| def test_categorize_handlers(): |
| class CustomHandler1(TrainBegin): |
| |
| def train_begin(self): |
| print("custom train begin") |
| |
| class CustomHandler2(EpochBegin, BatchBegin, TrainEnd): |
| |
| def epoch_begin(self): |
| print("custom epoch begin") |
| |
| def batch_begin(self): |
| print("custom batch begin") |
| |
| def train_end(self): |
| print("custom train end") |
| |
| class CustomHandler3(EpochBegin, BatchBegin, BatchEnd, TrainEnd): |
| |
| def epoch_begin(self): |
| print("custom epoch begin") |
| |
| def batch_begin(self): |
| print("custom batch begin") |
| |
| def batch_end(self): |
| print("custom batch end") |
| |
| def train_end(self): |
| print("custom train end") |
| |
| net = nn.Sequential() |
| net.add(nn.Dense(10)) |
| loss = gluon.loss.SoftmaxCrossEntropyLoss() |
| est = Estimator(net, loss=loss) |
| event_handlers = [CustomHandler1(), CustomHandler2(), CustomHandler3()] |
| train_begin, epoch_begin, batch_begin, \ |
| batch_end, epoch_end, train_end = est._categorize_handlers(event_handlers) |
| assert len(train_begin) == 1 |
| assert len(epoch_begin) == 2 |
| assert len(batch_begin) == 2 |
| assert len(batch_end) == 1 |
| assert len(train_end) == 2 |
| |
| |
| @mx.util.use_np |
| def test_default_handlers(): |
| net = _get_test_network() |
| train_data, _ = _get_test_data() |
| |
| num_epochs = 1 |
| device = mx.cpu() |
| |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| |
| train_acc = mx.gluon.metric.RMSE() |
| loss = gluon.loss.L2Loss() |
| |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=train_acc, |
| trainer=trainer, |
| device=device) |
| # no handler(all default handlers), no warning |
| with warnings.catch_warnings(record=True) as w: |
| est.fit(train_data=train_data, epochs=num_epochs) |
| |
| # handler with prepared loss and metrics |
| # use mix of default and user defined handlers |
| train_metrics = est.train_metrics |
| val_metrics = est.val_metrics |
| logging = LoggingHandler(metrics=train_metrics) |
| est.fit(train_data=train_data, epochs=num_epochs, event_handlers=[logging]) |
| |
| # handler with all user defined metrics |
| # use mix of default and user defined handlers |
| metric = MetricHandler(metrics=[train_acc]) |
| logging = LoggingHandler(metrics=[train_acc]) |
| est.fit(train_data=train_data, epochs=num_epochs, event_handlers=[metric, logging]) |
| |
| # handler with mixed metrics, some handler use metrics prepared by estimator |
| # some handler use metrics user prepared |
| logging = LoggingHandler(metrics=[mx.gluon.metric.RMSE("val acc")]) |
| with pytest.raises(ValueError): |
| est.fit(train_data=train_data, epochs=num_epochs, event_handlers=[logging]) |
| |
| # test handler order |
| train_metrics = est.train_metrics |
| val_metrics = est.val_metrics |
| early_stopping = EarlyStoppingHandler(monitor=val_metrics[0]) |
| handlers = est._prepare_default_handlers(val_data=None, event_handlers=[early_stopping]) |
| assert len(handlers) == 5 |
| assert isinstance(handlers[0], GradientUpdateHandler) |
| assert isinstance(handlers[1], MetricHandler) |
| assert isinstance(handlers[4], LoggingHandler) |
| |
| @mx.util.use_np |
| def test_val_net(): |
| ''' test estimator with different training and validation networks ''' |
| net = _get_test_network() |
| val_net = _get_test_network(params=net.collect_params()) |
| dataloader, dataiter = _get_test_data() |
| num_epochs = 1 |
| device = mx.cpu() |
| loss = gluon.loss.L2Loss() |
| val_loss = gluon.loss.L2Loss() |
| acc = mx.gluon.metric.Accuracy() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| trainer=trainer, |
| device=device, |
| val_loss=val_loss, |
| val_net=val_net) |
| |
| est.fit(train_data=dataloader, |
| val_data=dataloader, |
| epochs=num_epochs) |
| |
| ''' test partial weight sharing of two resnets ''' |
| net = gluon.model_zoo.vision.resnet18_v1(pretrained=False, device=device) |
| net.output = gluon.nn.Dense(10) |
| val_net = gluon.model_zoo.vision.resnet18_v1(pretrained=False, device=device) |
| val_net.output = net.output |
| dataset = gluon.data.ArrayDataset(mx.np.zeros((10, 3, 224, 224)), mx.np.zeros((10, 10))) |
| dataloader = gluon.data.DataLoader(dataset=dataset, batch_size=5) |
| net.initialize(device=device) |
| val_net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=acc, |
| trainer=trainer, |
| device=device, |
| val_loss=val_loss, |
| val_net=val_net) |
| |
| est.fit(train_data=dataloader, |
| val_data=dataloader, |
| epochs=num_epochs) |
| |
| @mx.util.use_np |
| def test_val_handlers(): |
| net = _get_test_network() |
| train_data, _ = _get_test_data() |
| val_data, _ = _get_test_data() |
| |
| num_epochs = 1 |
| device = mx.cpu() |
| net.initialize(device=device) |
| trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.001}) |
| |
| train_acc = mx.gluon.metric.RMSE() |
| loss = gluon.loss.L2Loss() |
| |
| est = Estimator(net=net, |
| loss=loss, |
| train_metrics=train_acc, |
| trainer=trainer, |
| device=device) |
| |
| with warnings.catch_warnings(record=True) as w: |
| est.fit(train_data=train_data, epochs=num_epochs) |
| est.evaluate(val_data=val_data) |
| |
| logging = LoggingHandler(log_interval=1, metrics=est.val_metrics) |
| est.evaluate(val_data=val_data, event_handlers=[logging]) |
| |