diff --git a/example/autoencoder/README.md b/example/autoencoder/README.md index 7efa30a19b78..960636cd7d59 100644 --- a/example/autoencoder/README.md +++ b/example/autoencoder/README.md @@ -1,16 +1,20 @@ -# Example of Autencoder +# Example of a Convolutional Autoencoder -Autoencoder architecture is often used for unsupervised feature learning. This [link](http://ufldl.stanford.edu/tutorial/unsupervised/Autoencoders/) contains an introduction tutorial to autoencoders. This example illustrates a simple autoencoder using stack of fully-connected layers for both encoder and decoder. The number of hidden layers and size of each hidden layer can be customized using command line arguments. +Autoencoder architectures are often used for unsupervised feature learning. This [link](http://ufldl.stanford.edu/tutorial/unsupervised/Autoencoders/) contains an introduction tutorial to autoencoders. This example illustrates a simple autoencoder using a stack of convolutional layers for both the encoder and the decoder. -## Training Stages -This example uses a two-stage training. In the first stage, each layer of encoder and its corresponding decoder are trained separately in a layer-wise training loop. In the second stage the entire autoencoder network is fine-tuned end to end. + +![](https://cdn-images-1.medium.com/max/800/1*LSYNW5m3TN7xRX61BZhoZA.png) + +([Diagram source](https://towardsdatascience.com/autoencoders-introduction-and-implementation-3f40483b0a85)) + + +The idea of an autoencoder is to learn to use bottleneck architecture to encode the input and then try to decode it to reproduce the original. By doing so, the network learns to effectively compress the information of the input, the resulting embedding representation can then be used in several domains. For example as featurized representation for visual search, or in anomaly detection. ## Dataset -The dataset used in this example is [MNIST](http://yann.lecun.com/exdb/mnist/) dataset. This example uses scikit-learn module to download this dataset. -## Simple autoencoder example -mnist_sae.py: this example uses a simple auto-encoder architecture to encode and decode MNIST images with size of 28x28 pixels. It contains several command line arguments. Pass -h (or --help) to view all available options. To start the training on CPU (use --gpu option for training on GPU) using default options: +The dataset used in this example is [FashionMNIST](https://github.com/zalandoresearch/fashion-mnist) dataset. + +## Variational Autoencoder + +You can check an example of variational autoencoder [here](https://gluon.mxnet.io/chapter13_unsupervised-learning/vae-gluon.html) -``` -python mnist_sae.py -``` diff --git a/example/autoencoder/autoencoder.py b/example/autoencoder/autoencoder.py deleted file mode 100644 index 47931e5731df..000000000000 --- a/example/autoencoder/autoencoder.py +++ /dev/null @@ -1,206 +0,0 @@ -# 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. - -# pylint: disable=missing-docstring, arguments-differ -from __future__ import print_function - -import logging - -import mxnet as mx -import numpy as np -import model -from solver import Solver, Monitor - - -class AutoEncoderModel(model.MXModel): - def setup(self, dims, sparseness_penalty=None, pt_dropout=None, - ft_dropout=None, input_act=None, internal_act='relu', output_act=None): - self.N = len(dims) - 1 - self.dims = dims - self.stacks = [] - self.pt_dropout = pt_dropout - self.ft_dropout = ft_dropout - self.input_act = input_act - self.internal_act = internal_act - self.output_act = output_act - - self.data = mx.symbol.Variable('data') - for i in range(self.N): - if i == 0: - decoder_act = input_act - idropout = None - else: - decoder_act = internal_act - idropout = pt_dropout - if i == self.N-1: - encoder_act = output_act - odropout = None - else: - encoder_act = internal_act - odropout = pt_dropout - istack, iargs, iargs_grad, iargs_mult, iauxs = self.make_stack( - i, self.data, dims[i], dims[i+1], sparseness_penalty, - idropout, odropout, encoder_act, decoder_act - ) - self.stacks.append(istack) - self.args.update(iargs) - self.args_grad.update(iargs_grad) - self.args_mult.update(iargs_mult) - self.auxs.update(iauxs) - self.encoder, self.internals = self.make_encoder( - self.data, dims, sparseness_penalty, ft_dropout, internal_act, output_act) - self.decoder = self.make_decoder( - self.encoder, dims, sparseness_penalty, ft_dropout, internal_act, input_act) - if input_act == 'softmax': - self.loss = self.decoder - else: - self.loss = mx.symbol.LinearRegressionOutput(data=self.decoder, label=self.data) - - def make_stack(self, istack, data, num_input, num_hidden, sparseness_penalty=None, - idropout=None, odropout=None, encoder_act='relu', decoder_act='relu'): - x = data - if idropout: - x = mx.symbol.Dropout(data=x, p=idropout) - x = mx.symbol.FullyConnected(name='encoder_%d'%istack, data=x, num_hidden=num_hidden) - if encoder_act: - x = mx.symbol.Activation(data=x, act_type=encoder_act) - if encoder_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_encoder_%d' % istack, penalty=sparseness_penalty) - if odropout: - x = mx.symbol.Dropout(data=x, p=odropout) - x = mx.symbol.FullyConnected(name='decoder_%d'%istack, data=x, num_hidden=num_input) - if decoder_act == 'softmax': - x = mx.symbol.Softmax(data=x, label=data, prob_label=True, act_type=decoder_act) - elif decoder_act: - x = mx.symbol.Activation(data=x, act_type=decoder_act) - if decoder_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_decoder_%d' % istack, penalty=sparseness_penalty) - x = mx.symbol.LinearRegressionOutput(data=x, label=data) - else: - x = mx.symbol.LinearRegressionOutput(data=x, label=data) - - args = {'encoder_%d_weight'%istack: mx.nd.empty((num_hidden, num_input), self.xpu), - 'encoder_%d_bias'%istack: mx.nd.empty((num_hidden,), self.xpu), - 'decoder_%d_weight'%istack: mx.nd.empty((num_input, num_hidden), self.xpu), - 'decoder_%d_bias'%istack: mx.nd.empty((num_input,), self.xpu),} - args_grad = {'encoder_%d_weight'%istack: mx.nd.empty((num_hidden, num_input), self.xpu), - 'encoder_%d_bias'%istack: mx.nd.empty((num_hidden,), self.xpu), - 'decoder_%d_weight'%istack: mx.nd.empty((num_input, num_hidden), self.xpu), - 'decoder_%d_bias'%istack: mx.nd.empty((num_input,), self.xpu),} - args_mult = {'encoder_%d_weight'%istack: 1.0, - 'encoder_%d_bias'%istack: 2.0, - 'decoder_%d_weight'%istack: 1.0, - 'decoder_%d_bias'%istack: 2.0,} - auxs = {} - if encoder_act == 'sigmoid' and sparseness_penalty: - auxs['sparse_encoder_%d_moving_avg' % istack] = mx.nd.ones(num_hidden, self.xpu) * 0.5 - if decoder_act == 'sigmoid' and sparseness_penalty: - auxs['sparse_decoder_%d_moving_avg' % istack] = mx.nd.ones(num_input, self.xpu) * 0.5 - init = mx.initializer.Uniform(0.07) - for k, v in args.items(): - init(mx.initializer.InitDesc(k), v) - - return x, args, args_grad, args_mult, auxs - - def make_encoder(self, data, dims, sparseness_penalty=None, dropout=None, internal_act='relu', - output_act=None): - x = data - internals = [] - N = len(dims) - 1 - for i in range(N): - x = mx.symbol.FullyConnected(name='encoder_%d'%i, data=x, num_hidden=dims[i+1]) - if internal_act and i < N-1: - x = mx.symbol.Activation(data=x, act_type=internal_act) - if internal_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_encoder_%d' % i, penalty=sparseness_penalty) - elif output_act and i == N-1: - x = mx.symbol.Activation(data=x, act_type=output_act) - if output_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_encoder_%d' % i, penalty=sparseness_penalty) - if dropout: - x = mx.symbol.Dropout(data=x, p=dropout) - internals.append(x) - return x, internals - - def make_decoder(self, feature, dims, sparseness_penalty=None, dropout=None, - internal_act='relu', input_act=None): - x = feature - N = len(dims) - 1 - for i in reversed(range(N)): - x = mx.symbol.FullyConnected(name='decoder_%d'%i, data=x, num_hidden=dims[i]) - if internal_act and i > 0: - x = mx.symbol.Activation(data=x, act_type=internal_act) - if internal_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_decoder_%d' % i, penalty=sparseness_penalty) - elif input_act and i == 0: - x = mx.symbol.Activation(data=x, act_type=input_act) - if input_act == 'sigmoid' and sparseness_penalty: - x = mx.symbol.IdentityAttachKLSparseReg( - data=x, name='sparse_decoder_%d' % i, penalty=sparseness_penalty) - if dropout and i > 0: - x = mx.symbol.Dropout(data=x, p=dropout) - return x - - def layerwise_pretrain(self, X, batch_size, n_iter, optimizer, l_rate, decay, - lr_scheduler=None, print_every=1000): - def l2_norm(label, pred): - return np.mean(np.square(label-pred))/2.0 - solver = Solver(optimizer, momentum=0.9, wd=decay, learning_rate=l_rate, - lr_scheduler=lr_scheduler) - solver.set_metric(mx.metric.CustomMetric(l2_norm)) - solver.set_monitor(Monitor(print_every)) - data_iter = mx.io.NDArrayIter({'data': X}, batch_size=batch_size, shuffle=True, - last_batch_handle='roll_over') - for i in range(self.N): - if i == 0: - data_iter_i = data_iter - else: - X_i = list(model.extract_feature( - self.internals[i-1], self.args, self.auxs, data_iter, X.shape[0], - self.xpu).values())[0] - data_iter_i = mx.io.NDArrayIter({'data': X_i}, batch_size=batch_size, - last_batch_handle='roll_over') - logging.info('Pre-training layer %d...', i) - solver.solve(self.xpu, self.stacks[i], self.args, self.args_grad, self.auxs, - data_iter_i, 0, n_iter, {}, False) - - def finetune(self, X, batch_size, n_iter, optimizer, l_rate, decay, lr_scheduler=None, - print_every=1000): - def l2_norm(label, pred): - return np.mean(np.square(label-pred))/2.0 - solver = Solver(optimizer, momentum=0.9, wd=decay, learning_rate=l_rate, - lr_scheduler=lr_scheduler) - solver.set_metric(mx.metric.CustomMetric(l2_norm)) - solver.set_monitor(Monitor(print_every)) - data_iter = mx.io.NDArrayIter({'data': X}, batch_size=batch_size, shuffle=True, - last_batch_handle='roll_over') - logging.info('Fine tuning...') - solver.solve(self.xpu, self.loss, self.args, self.args_grad, self.auxs, data_iter, - 0, n_iter, {}, False) - - def eval(self, X): - batch_size = 100 - data_iter = mx.io.NDArrayIter({'data': X}, batch_size=batch_size, shuffle=False, - last_batch_handle='pad') - Y = list(model.extract_feature( - self.loss, self.args, self.auxs, data_iter, X.shape[0], self.xpu).values())[0] - return np.mean(np.square(Y-X))/2.0 diff --git a/example/autoencoder/convolutional_autoencoder.ipynb b/example/autoencoder/convolutional_autoencoder.ipynb new file mode 100644 index 000000000000..c42ad900ec98 --- /dev/null +++ b/example/autoencoder/convolutional_autoencoder.ipynb @@ -0,0 +1,543 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Convolutional Autoencoder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://cdn-images-1.medium.com/max/800/1*LSYNW5m3TN7xRX61BZhoZA.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example we will demonstrate how you can create a convolutional autoencoder in Gluon" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import mxnet as mx\n", + "from mxnet import autograd, gluon" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data\n", + "\n", + "We will use the FashionMNIST dataset, which is of a similar format to MNIST but is richer and has more variance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "batch_size = 512\n", + "ctx = mx.gpu() if len(mx.test_utils.list_gpus()) > 0 else mx.cpu()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "transform = lambda x,y: (x.transpose((2,0,1)).astype('float32')/255., y)\n", + "\n", + "train_dataset = gluon.data.vision.FashionMNIST(train=True)\n", + "test_dataset = gluon.data.vision.FashionMNIST(train=False)\n", + "\n", + "train_dataset_t = train_dataset.transform(transform)\n", + "test_dataset_t = test_dataset.transform(transform)\n", + "\n", + "train_data = gluon.data.DataLoader(train_dataset_t, batch_size=batch_size, last_batch='rollover', shuffle=True, num_workers=5)\n", + "test_data = gluon.data.DataLoader(test_dataset_t, batch_size=batch_size, last_batch='rollover', shuffle=True, num_workers=5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABIEAAACBCAYAAABXearSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJztnXm4VmW5/2+q0+QQ5iwITkwOCKKghWLOSuZsNqk5HI+WiXoqT9ox09LqKr2wKK/UIjNLvRrMIU3AMENESHECkUkEHBFTGk51PH/8fjx9n297Pb1uNnu/77s+n7/utZ9nr7Xe9YxrXff3vnu9/vrrAQAAAAAAAAAA7c2bevoGAAAAAAAAAABg7cNHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGsBHIAAAAAAAAACAGvCW7rxYr169Xu/O68E/eP3113t1xXlow56jq9owYu2347/9278l+69//WtD/3P66adnx6+++mqy3/GOdyT7rW99a1bvL3/5S7L79++flX3+85/v8FpvetObKo//9re/ZWW9ev3jsb/++po/tu4ei3r/fvy///u/a3wfZ599drIPOOCArOzOO+9M9rRp05K9aNGirN7w4cOT3a9fv6xszJgxyda+dMEFF2T1nnnmmYbutyvas5XGYlcwdOjQZM+ePTvZG2+8cVZv5513Tvbdd9+99m9sDenusajz4v+/vp6j8v90nPrcpf+nc1dXjO0Seh9+7295yz+2ln//+98rz1H6zY2uG3Ubi9/73veSrW2wYsWKrN52222X7Hnz5mVll156abJfeOGFrr7FTsEetfVp5rH45je/OTvWubc0V44ePTrZP/3pT7Oyl156Kdnrr79+sv/nf/4nq/f2t7892ZdccklW9q1vfat02x2i82vEP+9Z15R2Hou+5lTtAb/+9a9nxzrvPvroo5Xn7Ip3hK6g0TbEEwgAAAAAAAAAoAbwEQgAAAAAAAAAoAb06k7XpWZ0DasL7ezeVxea2dW2s0yYMCHZhx56aFa2cuXKZM+ZMyfZG220UVZvp512Svaf/vSnrGz69OnJPvroozt1j60uB+vg//ReKuttttlmyf7hD3+YlS1f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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(20,10))\n", + "for i in range(10):\n", + " ax = plt.subplot(1, 10, i+1)\n", + " ax.imshow(train_dataset[i][0].squeeze().asnumpy(), cmap='gray')\n", + " ax.axis('off')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Network" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "net = gluon.nn.HybridSequential(prefix='autoencoder_')\n", + "with net.name_scope():\n", + " # Encoder 1x28x28 -> 32x1x1\n", + " encoder = gluon.nn.HybridSequential(prefix='encoder_')\n", + " with encoder.name_scope():\n", + " encoder.add(\n", + " gluon.nn.Conv2D(channels=4, kernel_size=3, padding=1, strides=(2,2), activation='relu'),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=8, kernel_size=3, padding=1, strides=(2,2), activation='relu'),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=16, kernel_size=3, padding=1, strides=(2,2), activation='relu'),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=32, kernel_size=3, padding=0, strides=(2,2),activation='relu'),\n", + " gluon.nn.BatchNorm()\n", + " )\n", + " decoder = gluon.nn.HybridSequential(prefix='decoder_')\n", + " # Decoder 32x1x1 -> 1x28x28\n", + " with decoder.name_scope():\n", + " decoder.add(\n", + " gluon.nn.Conv2D(channels=32, kernel_size=3, padding=2, activation='relu'),\n", + " gluon.nn.HybridLambda(lambda F, x: F.UpSampling(x, scale=2, sample_type='nearest')),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=16, kernel_size=3, padding=1, activation='relu'),\n", + " gluon.nn.HybridLambda(lambda F, x: F.UpSampling(x, scale=2, sample_type='nearest')),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=8, kernel_size=3, padding=2, activation='relu'),\n", + " gluon.nn.HybridLambda(lambda F, x: F.UpSampling(x, scale=2, sample_type='nearest')),\n", + " gluon.nn.BatchNorm(),\n", + " gluon.nn.Conv2D(channels=4, kernel_size=3, padding=1, activation='relu'),\n", + " gluon.nn.Conv2D(channels=1, kernel_size=3, padding=1, activation='sigmoid')\n", + " )\n", + " net.add(\n", + " encoder,\n", + " decoder\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "net.initialize(ctx=ctx)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--------------------------------------------------------------------------------\n", + " Layer (type) Output Shape Param #\n", + "================================================================================\n", + " Input (1, 1, 28, 28) 0\n", + " Activation-1 0\n", + " Activation-2 (1, 4, 14, 14) 0\n", + " Conv2D-3 (1, 4, 14, 14) 40\n", + " BatchNorm-4 (1, 4, 14, 14) 16\n", + " Activation-5 0\n", + " Activation-6 (1, 8, 7, 7) 0\n", + " Conv2D-7 (1, 8, 7, 7) 296\n", + " BatchNorm-8 (1, 8, 7, 7) 32\n", + " Activation-9 0\n", + " Activation-10 (1, 16, 4, 4) 0\n", + " Conv2D-11 (1, 16, 4, 4) 1168\n", + " BatchNorm-12 (1, 16, 4, 4) 64\n", + " Activation-13 0\n", + " Activation-14 (1, 32, 1, 1) 0\n", + " Conv2D-15 (1, 32, 1, 1) 4640\n", + " BatchNorm-16 (1, 32, 1, 1) 128\n", + " Activation-17 0\n", + " Activation-18 (1, 32, 3, 3) 0\n", + " Conv2D-19 (1, 32, 3, 3) 9248\n", + " HybridLambda-20 (1, 32, 6, 6) 0\n", + " BatchNorm-21 (1, 32, 6, 6) 128\n", + " Activation-22 0\n", + " Activation-23 (1, 16, 6, 6) 0\n", + " Conv2D-24 (1, 16, 6, 6) 4624\n", + " HybridLambda-25 (1, 16, 12, 12) 0\n", + " BatchNorm-26 (1, 16, 12, 12) 64\n", + " Activation-27 0\n", + " Activation-28 (1, 8, 14, 14) 0\n", + " Conv2D-29 (1, 8, 14, 14) 1160\n", + " HybridLambda-30 (1, 8, 28, 28) 0\n", + " BatchNorm-31 (1, 8, 28, 28) 32\n", + " Activation-32 0\n", + " Activation-33 (1, 4, 28, 28) 0\n", + " Conv2D-34 (1, 4, 28, 28) 292\n", + " Activation-35 0\n", + " Activation-36 (1, 1, 28, 28) 0\n", + " Conv2D-37 (1, 1, 28, 28) 37\n", + "================================================================================\n", + "Parameters in forward computation graph, duplicate included\n", + " Total params: 21969\n", + " Trainable params: 21737\n", + " Non-trainable params: 232\n", + "Shared params in forward computation graph: 0\n", + "Unique parameters in model: 21969\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "net.summary(test_dataset_t[0][0].expand_dims(axis=0).as_in_context(ctx))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the original image goes from 28x28 = 784 pixels to a vector of length 32. That is a ~25x information compression rate.\n", + "Then the decoder brings back this compressed information to the original shape" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "l2_loss = gluon.loss.L2Loss()\n", + "l1_loss = gluon.loss.L1Loss()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': 0.001, 'wd':0.001})\n", + "net.hybridize(static_shape=True, static_alloc=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Training loop" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch [0], Loss 0.2246280246310764\n", + "Epoch [1], Loss 0.14493223337026742\n", + "Epoch [2], Loss 0.13147933666522688\n", + "Epoch [3], Loss 0.12138325943906084\n", + "Epoch [4], Loss 0.11291297684367906\n", + "Epoch [5], Loss 0.10611823453741559\n", + "Epoch [6], Loss 0.09942417470817892\n", + "Epoch [7], Loss 0.09408332955124032\n", + "Epoch [8], Loss 0.08883619716024807\n", + "Epoch [9], Loss 0.08491455795418502\n", + "Epoch [10], Loss 0.0809355994402352\n", + "Epoch [11], Loss 0.07784551636785524\n", + "Epoch [12], Loss 0.07570812029716296\n", + "Epoch [13], Loss 0.07417513366438384\n", + "Epoch [14], Loss 0.07218785571236895\n", + "Epoch [15], Loss 0.07093704352944584\n", + "Epoch [16], Loss 0.0700181406787318\n", + "Epoch [17], Loss 0.0689836893326197\n", + "Epoch [18], Loss 0.06782063459738708\n", + "Epoch [19], Loss 0.06713279088338216\n" + ] + } + ], + "source": [ + "epochs = 20\n", + "for e in range(epochs):\n", + " curr_loss = 0.\n", + " for i, (data, _) in enumerate(train_data):\n", + " data = data.as_in_context(ctx)\n", + " with autograd.record():\n", + " output = net(data)\n", + " # Compute the L2 and L1 losses between the original and the generated image\n", + " l2 = l2_loss(output.flatten(), data.flatten())\n", + " l1 = l1_loss(output.flatten(), data.flatten())\n", + " l = l2 + l1 \n", + " l.backward()\n", + " trainer.step(data.shape[0])\n", + " \n", + " curr_loss += l.mean()\n", + "\n", + " print(\"Epoch [{}], Loss {}\".format(e, curr_loss.asscalar()/(i+1)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Testing reconstruction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We plot 10 images and their reconstruction by the autoencoder. The results are pretty good for a ~25x compression rate!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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/vrrk63PJxH586ifffSZTvvYnxdnz56d7NJzUp3Qc8ScOXOyMn1O0P596KGHsnpjxoxJ9pe+9KU27YiIW2+9NdnHHntsVqYxvnSNP/3007N6Bx544Ko/oovBEwgAAAAAAAAAoAbwEggAAAAAAAAAoAY0pR+fSmwictdVdbVyF3B189K0e/53jboql9wH1SXbZQnqfqnyBU1Z53ja17/85S/J1hSP7qbZTKxJ+s015eyzz062u+tqKk1tO+8nHVc+JrTf1CXa01LvuOOOyb755puzsq997WvJHjp0aLLdlVPHkstpupqNNtqosqwkBys4YHNIAAAgAElEQVSldNf+atSdXWU7Jdd2v4aOSZVNrE469Kpx7e7xXUl7pHNXV/mI3MVcpY0ReVuqG7mn+9bxM2nSpDavHZG78n7+85/Pym666aZkq+zC71ddqX2drEq36muC/p2uARGrrjOtgM6l0t6q/O53v0v24sWLszJde7fffvusbPLkycmePn16sl1uqWugnwU0/ar2v6aAjcjHncvNujuXX3559llT1+r49bFdkshXsXz58uzzxhtvnGxdQ3Wfjcj3gzqkOva02a9ROmsuW7Ys+6x7YWkuapnac+fOzeq5RE/R/aIk49drlORrOherJMBdgUu2Tz311GS7xFnbRPc0fw7Q8fz0009nZdpXej7wNOorVqxI9ssvv9ymHZGfe3yd1OcMLfP5pmuyjyX9bfp3fg0dF6U0881M6XlOZXwR+bzVs46eRSJyOZjie47uWx7SQ89WjzzySLI95TlysFV53/vel2yXg6lcUsfzuHHjsnq//vWvk61nPj+/6LnHn9d1bF188cXJ9jAvKqceNWpUVvbHP/4xugI8gQAAAAAAAAAAagAvgQAAAAAAAAAAagAvgQAAAAAAAAAAakBTxgTydN2qY9W4DK5b1ZgQrvdVDaXartnWzxqPwL+rKk5RRK6H1/v1elX37tfQ+23mdIwaL8BjyUyZMqWha+jfud5W0zZqPY9HoG3pcUMU1YC7Pr8qFbGnftS4OEcccURWpmkDVb/t8St0zLnuvqv19aX20znmKbm1T0oxfEoxE7RtS2lOS/G5tG4pda9ew+eYj6/XUK14V+PxiVR37mnQta7a3nal9Ur/Tsesz6PHH3882RoHxtN2a7wY/y0610vxE7Q/fL2uGge+7moMjAULFmRl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iofoZ5jORmPleNwN/d16vojIY6LpOuPrU58+fZLdt2/frMzXq9copVzXmCR+ptZ29bO/pjjXPVJjAPnfef+WzqWK7ov+W6riSzUDpT1b2+Xwww/PyjT+0uWXX55sXTcj8vVws802S7amJI+ImDhxYrK33nrrrGz69OnJ1vN+1bmzLeoaE0ifLfxcq/GVnn766WRfc801WT09p+he6jGHNYabPy8OHz482Z/73OeSPWXKlKyexlD0awwaNCjZTz31VHQWeAIBAAAAAAAAANQAXgIBAAAAAAAAANSAppSDuQufuiKqq2lJDuYpiFWeom527uqp362usO4qqW6a7n5XlXaxhP+WAw88MNnqrlvlbtpZaF94G6t8RF31I/KUieoO6S582pYlWUhJIqdlJVdVreeprXWclSQZmvrT+0Z/m44DHy9a5ik8Nd1pV+AyIL3XUhrbu+66K9meRrpKguLtp+61+r3e3/p3jz76aFam/aXX8HGnY1elLxGrSsdeo6vlYDo/3M1b5TADBw7MyrQddE65bEw/l6QA+l0+XtYEl/aoW29JetsoVWl8I/I1zde3rkiFq2tUe0huXOqo+4y2y4QJE7J62q86L3/2s59l9W6//fZkb7nlllnZ97///WRr6vdNNtmkoXuPyPtE572PT+2rVnePV1f3KrlcRN4OmgK7hKbZjYjYfvvtk61joiukks2EniO1D3wN0ZTGKt2LiDjggAOSrdJJl3mpDEjH+Z///Oesnu5P3j9z5sxJtsoe/Pyh+6LPdR1fuo90tRxMJTV6DonIzyIuU9eU8SqD97ONziNvryrZVGnf0vOQn5VUFqhS24iISZMmtXmPO++8c1av9Dyic7iUIl5/s/+WZpaCltYllUtqW0bk81klf34W/OY3v5nsffbZJ9kq7YmIeN/73pfs8ePHZ2Ua+kPn23HHHZfVGzVqVLJVrhaR91cz90d7o/u+/25dk1Su5VK9H/3oR8nWsf3BD34wq6eyMZeYL1y4MNkqF917772zero3eB/uv//+yb7pppuis8ATCAAAAAAAAACgBvASCAAAAAAAAACgBvASCAAAAAAAAACgBjRlTCDXo6pOT+NDTJ06tfIarktW7WApno9qeqvSxUfkGlwv08+qm3YtrcY28ZhAVekaPV5GZ3PiiScm+2Mf+1hWphpK1a1H5HELtM09boHGNvEY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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(20,4))\n", + "for i in range(10):\n", + " idx = random.randint(0, len(test_dataset))\n", + " img, _ = test_dataset[idx]\n", + " x, _ = test_dataset_t[idx]\n", + "\n", + " data = x.as_in_context(ctx).expand_dims(axis=0)\n", + " output = net(data)\n", + " \n", + " ax = plt.subplot(2, 10, i+1)\n", + " ax.imshow(img.squeeze().asnumpy(), cmap='gray')\n", + " ax.axis('off')\n", + " ax = plt.subplot(2, 10, 10+i+1)\n", + " ax.imshow((output[0].asnumpy() * 255.).transpose((1,2,0)).squeeze(), cmap='gray')\n", + " _ = ax.axis('off')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Manipulating latent space" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use separately the **encoder** that takes an image to a latent vector and the **decoder** that transform a latent vector into images" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We get two images from the testing set" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAJIAAACPCAYAAAARM4LLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAACZRJREFUeJztnUtsVdcVhv+Feb8JD2Nsg4OwKjFAqhRVoFYC0SJoJmFUBUHEIBKTVmqlSCRph0zKpLNOkEDpoHJVqZWSQSSrRNSoUIE9iKgJAkwRD2Owzdvmadgd3Bv37D/xvde+y/eew/k/yeL851zfsxP93nudvddex0IIEKJaZtS7AeLNQEYSLshIwgUZSbggIwkXZCThgowkXJCRhAtVGcnMdpnZRTPrM7NPvBolsodNdWbbzBoAXAKwA8BNAN0A9oQQvinxO6mdRm9tbS15fWxsLNIzZsz43mMAGB0djfS8efMi/eLFi0i/fv060g0NDZEeGRmJ9JMnT0q21ZnhEMLKch+aWcUNfgSgL4TwXwAws78AeA/AhEZKMwcPHix5/e7du5GeP3/++PHs2bOjaz09PZHetGlTpK9fvx5pNt6iRYsiffr06ZLfP81cq+RD1QxtzQBuJPTN4rkIMztgZj1mVtP/elFbqumRKiKEcATAESDdQ5uojmqM1A8gGVi0FM9lkm3btkV6+fLlkX7w4EGkk0Pdhg0bomtbt26NdHIYBIDjx49H+vnz55HmmOvZs2eRrvHQVhHVDG3dANrN7G0zmw3gfQBf+DRLZI0p90ghhDEz+xWATgANAI6FEM67tUxkiqpipBDClwC+dGqLyDDTHmynldWrV0d61qxZke7s7Iz0nDlzIp2cG1qyZEl0bebM+H/rrVu3Iv348eNI8/QAx0SnTp1C2tESiXBBRhIuyEjChdzGSOvWrYs0L0usXbs20q9evYr03Llzx4+vXLkSXbt06VKkt2/fHuktW7ZE+uHDh5E+fz5++OW1uTSiHkm4ICMJF2Qk4UJuY6SdO3dGenBwMNKcA5SMiQDAzMaPFy9eHF1btmxZpDlNhOeROB9pwYIFkX706BHSjnok4YKMJFzI7dDW3Bzn4JVLf+VlkGRWJA+DTU1NkeahiofJCxculLxXFlCPJFyQkYQLMpJwIbcxUltbW8nrHPcMDQ1FesWKFePHLS0t0TVOreXf5S1gCxcujDTvSuG0lDSiHkm4ICMJF2Qk4UL6B99pgtNIrl69GulycUkyhuIt2cnlE+C7KSp37tyJNC/PlJvjSiPqkYQLMpJwQUYSLuQ2RuL1rHKpHJz6sXLl/yu9HD58OLrGKSr79u2LNFc2Sc5JAd+Nsa5dq6ggSF1RjyRckJGECzKScCE3MRJvuWa4tAxvEeLfb2xsHD/u6OiIrvHa2f79+yPNa228tsaaY6o0oh5JuFDWSGZ2zMwGzaw3ce4tM/uHmV0u/rus1HeIN59KeqTPAOyic58A+CqE0A7gq6IWOaZsjBRCOGlmbXT6PQDbisd/AvBPAB87tssdzj/icsdc2o+vc/nkM2fOTHivrq6uSHNMxPNE9+7dizTHWDyHlUamGiM1hhAGise3ATSW+rB486n6qS2EEEpVqzWzAwAOVHsfkW6m2iPdMbMmACj+OzjRB0MIR0II74QQ3pnivUQGmGqP9AWA/QB+X/z3c7cWTRPr16+PNM8bseZ8pOS8EQAcOnRowntxWRuGXxHB5ZA5PssClTz+dwD4N4AfmNlNM/sQBQPtMLPLAH5W1CLHVPLUtmeCSz91bovIMJrZFi7kZq1t6dKlkeZXVXHpGJ534vJ+PFdUCs7RZjge43mmLKAeSbggIwkXZCThQm5ipDVr1kSa543K1UPifW+T4fbt25HmfCNeiyv1ugoAePr06ZTbMl2oRxIuyEjChdwMbfxGSE7dePnyZcnfP3fu3JTvPTAwEGlebuG3CvAW7TQOZYx6JOGCjCRckJGEC7mJkTg1g+MSrtbPb5Q8ceLEhN/NSxr8OM/LK7xFm5dI+A2SWUA9knBBRhIuyEjChdzESJw2wjESlzTm10Dwax+ScIzDc1Ld3d2R5jdKcgmd+/fvT3ivtKIeSbggIwkXZCThQm5iJJ4X4jQR3rLNMRW/Cmsy8JZrfuMkx0hcTjkLqEcSLshIwgUZSbiQmxhpeHg40rw9abL5SUnKbR/i7+K0Xp7TyiLqkYQLMpJwQUYSLuQmRuItQTyXw1uAOH+Jty8l4ZiH4e3g/F2cC6W1NpFbKqmP1GpmJ8zsGzM7b2a/Lp5XiWQxTiU90hiAj0IIGwFsBvBLM9sIlUgWCSoptDUAYKB4/NjMLgBoRsZKJN+4cSPSnH/E5fjKvXIiyWTL0PC9OJ8pi/NKk4qRivW2fwjgDFQiWSSo+KnNzBYC+BuA34QQHiX/CkuVSFZ55HxQUY9kZrNQMNGfQwh/L56uqESyyiPng7I9khW6nqMALoQQ/pC4lKkSyb29vZHmksQct/DcD6/NJfOXysVI5fa98fVS+eFppZKh7ccAPgDwHzP7unjutygY6K/FcsnXAPxiepooskAlT23/AjDRn5xKJAsAmtkWTuRmrY3303NONud08/oZ79fnHO9S8LwQfzdrLg2YBdQjCRdkJOGCjCRcyE2MxHAON5cgHh0djXR7e3uk+/r6xo/LzSONjIxEmj/PmuewsoB6JOGCjCRcyO3Q1t/fH+lVq1ZFmt90XWobNS95MFzumKcDeOqBpxqygHok4YKMJFyQkYQLuY2R+JUQu3fvjjQvqZSKkcptR+LtRxwj8eM/l8HJAuqRhAsyknBBRhIu5DZGOnnyZKT37t0baV4i2bx5c6SPHj06flxu+xDHSBxv8TyTSv+J3CIjCRdkJOFCbmMk3p7U1dUV6aGhoUgn00aYcmttnLJy9uzZkterKcVcL9QjCRdkJOGCjCRcsHLju+vNzIZQ2JW7AsBwmY/Xi7S2rV7tWhdCWFnuQzU10vhNzXrSWlQirW1La7u+RUObcEFGEi7Uy0hH6nTfSkhr29LaLgB1ipHEm4eGNuFCTY1kZrvM7KKZ9ZlZXcspm9kxMxs0s97EuVTUDs9ibfOaGcnMGgD8EcDPAWwEsKdYr7tefAZgF51LS+3w7NU2DyHU5AfAFgCdCf0pgE9rdf8J2tQGoDehLwJoKh43AbhYz/Yl2vU5gB1pbV8IoaZDWzOAZNX0m8VzaSJ1tcOzUttcwfYEhMKffV0fabm2efJaGtqXpJZG6gfQmtAtxXNpoqLa4bWgmtrm9aCWRuoG0G5mb5vZbADvo1CrO018WzscqGPt8ApqmwNpq21e46DxXQCXAFwB8Ls6B7AdKLys5yUK8dqHAJaj8DR0GcBxAG/VqW0/QWHYOgfg6+LPu2lp3/f9aGZbuKBgW7ggIwkXZCThgowkXJCRhAsyknBBRhIuyEjChf8BBBgORVqd9YYAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "idx = random.randint(0, len(test_dataset))\n", + "img1, _ = test_dataset[idx]\n", + "x, _ = test_dataset_t[idx]\n", + "data1 = x.as_in_context(ctx).expand_dims(axis=0)\n", + "\n", + "idx = random.randint(0, len(test_dataset))\n", + "img2, _ = test_dataset[idx]\n", + "x, _ = test_dataset_t[idx]\n", + "data2 = x.as_in_context(ctx).expand_dims(axis=0)\n", + "\n", + "plt.figure(figsize=(2,2))\n", + "plt.imshow(img1.squeeze().asnumpy(), cmap='gray')\n", + "plt.show()\n", + "plt.figure(figsize=(2,2))\n", + "plt.imshow(img2.squeeze().asnumpy(), cmap='gray')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We get the latent representations of the images by passing them through the network" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "latent1 = encoder(data1)\n", + "latent2 = encoder(data2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the latent vector is made of 32 components" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 32, 1, 1)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "latent1.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We interpolate the two latent representations, vectors of 32 values, to get a new intermediate latent representation, pass it through the decoder and plot the resulting decoded image" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "num = 10\n", + "plt.figure(figsize=(20, 5))\n", + "\n", + "for i in range(int(num)):\n", + " \n", + " new_latent = latent2*(i+1)/num + latent1*(num-i)/num\n", + " output = decoder(new_latent)\n", + " \n", + " #plot result\n", + " ax = plt.subplot(1, num, i+1)\n", + " ax.imshow((output[0].asnumpy() * 255.).transpose((1,2,0)).squeeze(), cmap='gray')\n", + " _ = ax.axis('off')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the latent space learnt by the autoencoder is fairly smooth, there is no sudden jump from one shape to another" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/example/autoencoder/data.py b/example/autoencoder/data.py deleted file mode 100644 index 99dd4eb43fa9..000000000000 --- a/example/autoencoder/data.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -# pylint: disable=missing-docstring -from __future__ import print_function - -import os -import numpy as np -from sklearn.datasets import fetch_mldata - - -def get_mnist(): - np.random.seed(1234) # set seed for deterministic ordering - data_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) - data_path = os.path.join(data_path, '../../data') - mnist = fetch_mldata('MNIST original', data_home=data_path) - p = np.random.permutation(mnist.data.shape[0]) - X = mnist.data[p].astype(np.float32)*0.02 - Y = mnist.target[p] - return X, Y diff --git a/example/autoencoder/mnist_sae.py b/example/autoencoder/mnist_sae.py deleted file mode 100644 index 886f2a16a863..000000000000 --- a/example/autoencoder/mnist_sae.py +++ /dev/null @@ -1,100 +0,0 @@ -# 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. - -# pylint: disable=missing-docstring -from __future__ import print_function - -import argparse -import logging - -import mxnet as mx -import numpy as np -import data -from autoencoder import AutoEncoderModel - -parser = argparse.ArgumentParser(description='Train an auto-encoder model for mnist dataset.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) -parser.add_argument('--print-every', type=int, default=1000, - help='interval of printing during training.') -parser.add_argument('--batch-size', type=int, default=256, - help='batch size used for training.') -parser.add_argument('--pretrain-num-iter', type=int, default=50000, - help='number of iterations for pretraining.') -parser.add_argument('--finetune-num-iter', type=int, default=100000, - help='number of iterations for fine-tuning.') -parser.add_argument('--visualize', action='store_true', - help='whether to visualize the original image and the reconstructed one.') -parser.add_argument('--num-units', type=str, default="784,500,500,2000,10", - help='number of hidden units for the layers of the encoder.' - 'The decoder layers are created in the reverse order. First dimension ' - 'must be 784 (28x28) to match mnist image dimension.') -parser.add_argument('--gpu', action='store_true', - help='whether to start training on GPU.') - -# set to INFO to see less information during training -logging.basicConfig(level=logging.INFO) -opt = parser.parse_args() -logging.info(opt) -print_every = opt.print_every -batch_size = opt.batch_size -pretrain_num_iter = opt.pretrain_num_iter -finetune_num_iter = opt.finetune_num_iter -visualize = opt.visualize -gpu = opt.gpu -layers = [int(i) for i in opt.num_units.split(',')] - - -if __name__ == '__main__': - xpu = mx.gpu() if gpu else mx.cpu() - print("Training on {}".format("GPU" if gpu else "CPU")) - - ae_model = AutoEncoderModel(xpu, layers, pt_dropout=0.2, internal_act='relu', - output_act='relu') - - X, _ = data.get_mnist() - train_X = X[:60000] - val_X = X[60000:] - - ae_model.layerwise_pretrain(train_X, batch_size, pretrain_num_iter, 'sgd', l_rate=0.1, - decay=0.0, lr_scheduler=mx.lr_scheduler.FactorScheduler(20000, 0.1), - print_every=print_every) - ae_model.finetune(train_X, batch_size, finetune_num_iter, 'sgd', l_rate=0.1, decay=0.0, - lr_scheduler=mx.lr_scheduler.FactorScheduler(20000, 0.1), print_every=print_every) - ae_model.save('mnist_pt.arg') - ae_model.load('mnist_pt.arg') - print("Training error:", ae_model.eval(train_X)) - print("Validation error:", ae_model.eval(val_X)) - if visualize: - try: - from matplotlib import pyplot as plt - from model import extract_feature - # sample a random image - original_image = X[np.random.choice(X.shape[0]), :].reshape(1, 784) - data_iter = mx.io.NDArrayIter({'data': original_image}, batch_size=1, shuffle=False, - last_batch_handle='pad') - # reconstruct the image - reconstructed_image = extract_feature(ae_model.decoder, ae_model.args, - ae_model.auxs, data_iter, 1, - ae_model.xpu).values()[0] - print("original image") - plt.imshow(original_image.reshape((28, 28))) - plt.show() - print("reconstructed image") - plt.imshow(reconstructed_image.reshape((28, 28))) - plt.show() - except ImportError: - logging.info("matplotlib is required for visualization") diff --git a/example/autoencoder/model.py b/example/autoencoder/model.py deleted file mode 100644 index 9b6185c9fd18..000000000000 --- a/example/autoencoder/model.py +++ /dev/null @@ -1,78 +0,0 @@ -# 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. - -# pylint: disable=missing-docstring -from __future__ import print_function - -import mxnet as mx -import numpy as np -try: - import cPickle as pickle -except ImportError: - import pickle - - -def extract_feature(sym, args, auxs, data_iter, N, xpu=mx.cpu()): - input_buffs = [mx.nd.empty(shape, ctx=xpu) for k, shape in data_iter.provide_data] - input_names = [k for k, shape in data_iter.provide_data] - args = dict(args, **dict(zip(input_names, input_buffs))) - exe = sym.bind(xpu, args=args, aux_states=auxs) - outputs = [[] for _ in exe.outputs] - output_buffs = None - - data_iter.hard_reset() - for batch in data_iter: - for data, buff in zip(batch.data, input_buffs): - data.copyto(buff) - exe.forward(is_train=False) - if output_buffs is None: - output_buffs = [mx.nd.empty(i.shape, ctx=mx.cpu()) for i in exe.outputs] - else: - for out, buff in zip(outputs, output_buffs): - out.append(buff.asnumpy()) - for out, buff in zip(exe.outputs, output_buffs): - out.copyto(buff) - for out, buff in zip(outputs, output_buffs): - out.append(buff.asnumpy()) - outputs = [np.concatenate(i, axis=0)[:N] for i in outputs] - return dict(zip(sym.list_outputs(), outputs)) - - -class MXModel(object): - def __init__(self, xpu=mx.cpu(), *args, **kwargs): - self.xpu = xpu - self.loss = None - self.args = {} - self.args_grad = {} - self.args_mult = {} - self.auxs = {} - self.setup(*args, **kwargs) - - def save(self, fname): - args_save = {key: v.asnumpy() for key, v in self.args.items()} - with open(fname, 'wb') as fout: - pickle.dump(args_save, fout) - - def load(self, fname): - with open(fname, 'rb') as fin: - args_save = pickle.load(fin) - for key, v in args_save.items(): - if key in self.args: - self.args[key][:] = v - - def setup(self, *args, **kwargs): - raise NotImplementedError("must override this") diff --git a/example/autoencoder/solver.py b/example/autoencoder/solver.py deleted file mode 100644 index 0c990ce7423c..000000000000 --- a/example/autoencoder/solver.py +++ /dev/null @@ -1,151 +0,0 @@ -# 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. - -# pylint: disable=missing-docstring -from __future__ import print_function - -import logging - -import mxnet as mx -import numpy as np - - -class Monitor(object): - def __init__(self, interval, level=logging.DEBUG, stat=None): - self.interval = interval - self.level = level - if stat is None: - def mean_abs(x): - return np.fabs(x).mean() - self.stat = mean_abs - else: - self.stat = stat - - def forward_end(self, i, internals): - if i % self.interval == 0 and logging.getLogger().isEnabledFor(self.level): - for key in sorted(internals.keys()): - arr = internals[key] - logging.log(self.level, 'Iter:%d param:%s\t\tstat(%s):%s', - i, key, self.stat.__name__, str(self.stat(arr.asnumpy()))) - - def backward_end(self, i, weights, grads, metric=None): - if i % self.interval == 0 and logging.getLogger().isEnabledFor(self.level): - for key in sorted(grads.keys()): - arr = grads[key] - logging.log(self.level, 'Iter:%d param:%s\t\tstat(%s):%s\t\tgrad_stat:%s', - i, key, self.stat.__name__, - str(self.stat(weights[key].asnumpy())), str(self.stat(arr.asnumpy()))) - if i % self.interval == 0 and metric is not None: - logging.log(logging.INFO, 'Iter:%d metric:%f', i, metric.get()[1]) - metric.reset() - - -class Solver(object): - def __init__(self, optimizer, **kwargs): - if isinstance(optimizer, str): - self.optimizer = mx.optimizer.create(optimizer, **kwargs) - else: - self.optimizer = optimizer - self.updater = mx.optimizer.get_updater(self.optimizer) - self.monitor = None - self.metric = None - self.iter_end_callback = None - self.iter_start_callback = None - - def set_metric(self, metric): - self.metric = metric - - def set_monitor(self, monitor): - self.monitor = monitor - - def set_iter_end_callback(self, callback): - self.iter_end_callback = callback - - def set_iter_start_callback(self, callback): - self.iter_start_callback = callback - - def solve(self, xpu, sym, args, args_grad, auxs, - data_iter, begin_iter, end_iter, args_lrmult=None, debug=False): - if args_lrmult is None: - args_lrmult = dict() - input_desc = data_iter.provide_data + data_iter.provide_label - input_names = [k for k, shape in input_desc] - input_buffs = [mx.nd.empty(shape, ctx=xpu) for k, shape in input_desc] - args = dict(args, **dict(zip(input_names, input_buffs))) - - output_names = sym.list_outputs() - if debug: - sym_group = [] - for x in sym.get_internals(): - if x.name not in args: - if x.name not in output_names: - x = mx.symbol.BlockGrad(x, name=x.name) - sym_group.append(x) - sym = mx.symbol.Group(sym_group) - exe = sym.bind(xpu, args=args, args_grad=args_grad, aux_states=auxs) - - assert len(sym.list_arguments()) == len(exe.grad_arrays) - update_dict = { - name: nd for name, nd in zip(sym.list_arguments(), exe.grad_arrays) if nd is not None - } - batch_size = input_buffs[0].shape[0] - self.optimizer.rescale_grad = 1.0/batch_size - self.optimizer.set_lr_mult(args_lrmult) - - output_dict = {} - output_buff = {} - internal_dict = dict(zip(input_names, input_buffs)) - for key, arr in zip(sym.list_outputs(), exe.outputs): - if key in output_names: - output_dict[key] = arr - output_buff[key] = mx.nd.empty(arr.shape, ctx=mx.cpu()) - else: - internal_dict[key] = arr - - data_iter.reset() - for i in range(begin_iter, end_iter): - if self.iter_start_callback is not None: - if self.iter_start_callback(i): - return - try: - batch = data_iter.next() - except StopIteration: - data_iter.reset() - batch = data_iter.next() - for data, buff in zip(batch.data+batch.label, input_buffs): - data.copyto(buff) - exe.forward(is_train=True) - if self.monitor is not None: - self.monitor.forward_end(i, internal_dict) - for key in output_dict: - output_dict[key].copyto(output_buff[key]) - - exe.backward() - for key, arr in update_dict.items(): - self.updater(key, arr, args[key]) - - if self.metric is not None: - self.metric.update([input_buffs[-1]], - [output_buff[output_names[0]]]) - - if self.monitor is not None: - self.monitor.backward_end(i, args, update_dict, self.metric) - - if self.iter_end_callback is not None: - if self.iter_end_callback(i): - return - exe.outputs[0].wait_to_read()