|
| 1 | +import torch |
| 2 | +import argparse |
| 3 | +import numpy as np |
| 4 | +import torch.utils.data |
| 5 | + |
| 6 | +from torch import nn, optim |
| 7 | +from torch.autograd import Variable |
| 8 | +from torchvision import datasets, transforms |
| 9 | +from torchvision.utils import save_image |
| 10 | + |
| 11 | + |
| 12 | +class AutoEncoder(nn.Module): |
| 13 | + def __init__(self, inp_size, hid_size): |
| 14 | + super(AutoEncoder, self).__init__() |
| 15 | + """ |
| 16 | + Here you should define layers of your autoencoder |
| 17 | + Please note, if a layer has trainable parameters, it should be nn.Linear. |
| 18 | + ## !! CONVOLUTIONAL LAYERS MUST NOT BE HERE !! ## |
| 19 | + However, you can use any noise inducing layers, e.g. Dropout. |
| 20 | +
|
| 21 | + Your network must not have more than six layers with trainable parameters. |
| 22 | + :param inp_size: integer, dimension of the input object |
| 23 | + :param hid_size: integer, dimension of the hidden representation |
| 24 | + """ |
| 25 | + pass |
| 26 | + |
| 27 | + def encode(self, x): |
| 28 | + """ |
| 29 | + Encodes objects to hidden representations (E: R^inp_size -> R^hid_size) |
| 30 | +
|
| 31 | + :param x: inputs, Variable of shape (batch_size, inp_size) |
| 32 | + :return: hidden represenation of the objects, Variable of shape (batch_size, hid_size) |
| 33 | + """ |
| 34 | + pass |
| 35 | + |
| 36 | + def decode(self, h): |
| 37 | + """ |
| 38 | + Decodes objects from hidden representations (D: R^hid_size -> R^inp_size) |
| 39 | +
|
| 40 | + :param h: hidden represenatations, Variable of shape (batch_size, hid_size) |
| 41 | + :return: reconstructed objects, Variable of shape (batch_size, inp_size) |
| 42 | + """ |
| 43 | + pass |
| 44 | + |
| 45 | + def forward(self, x): |
| 46 | + """ |
| 47 | + Encodes inputs to hidden representations and decodes back. |
| 48 | +
|
| 49 | + x: inputs, Variable of shape (batch_size, inp_size) |
| 50 | + return: reconstructed objects, Variable of shape (batch_size, inp_size) |
| 51 | + """ |
| 52 | + return self.decode(self.encode(x)) |
| 53 | + |
| 54 | + @staticmethod |
| 55 | + def loss_function(recon_x, x): |
| 56 | + """ |
| 57 | + Calculates the loss function. |
| 58 | +
|
| 59 | + :params recon_x: reconstructed object, Variable of shape (batch_size, inp_size) |
| 60 | + :params x: original object, Variable of shape (batch_size, inp_size) |
| 61 | + :return: loss |
| 62 | + """ |
| 63 | + pass |
| 64 | + |
| 65 | + |
| 66 | +def train(model, optimizer, train_loader, test_loader): |
| 67 | + for epoch in range(10): |
| 68 | + model.train() |
| 69 | + train_loss, test_loss = 0, 0 |
| 70 | + for data, _ in train_loader: |
| 71 | + data = Variable(data).view(-1, 784) |
| 72 | + x_rec = model(data) |
| 73 | + loss = model.loss_function(x_rec, data) |
| 74 | + |
| 75 | + optimizer.zero_grad() |
| 76 | + loss.backward() |
| 77 | + optimizer.step() |
| 78 | + train_loss += loss.data[0] |
| 79 | + print('=> Epoch: %s Average loss: %.3f' % (epoch, train_loss / len(train_loader.dataset))) |
| 80 | + |
| 81 | + model.eval() |
| 82 | + for data, _ in test_loader: |
| 83 | + data = Variable(data, volatile=True).view(-1, 784) |
| 84 | + x_rec = model(data) |
| 85 | + test_loss += model.loss_function(x_rec, data).data[0] |
| 86 | + |
| 87 | + test_loss /= len(test_loader.dataset) |
| 88 | + print('=> Test set loss: %.3f' % test_loss) |
| 89 | + |
| 90 | + n = min(data.size(0), 8) |
| 91 | + comparison = torch.cat([data.view(-1, 1, 28, 28)[:n], x_rec.view(-1, 1, 28, 28)[:n]]) |
| 92 | + save_image(comparison.data.cpu(), 'pics/reconstruction_' + str(epoch) + '.png', nrow=n) |
| 93 | + return model |
| 94 | + |
| 95 | + |
| 96 | +def test_work(): |
| 97 | + print('Start test') |
| 98 | + get_loader = lambda train: torch.utils.data.DataLoader( |
| 99 | + datasets.MNIST('../data', train=train, download=True, transform=transforms.ToTensor()), |
| 100 | + batch_size=50, shuffle=True) |
| 101 | + train_loader, test_loader = get_loader(True), get_loader(False) |
| 102 | + |
| 103 | + try: |
| 104 | + model = AutoEncoder(inp_size=784, hid_size=20) |
| 105 | + optimizer = optim.Adam(model.parameters(), lr=1e-3) |
| 106 | + except Exception: |
| 107 | + assert False, 'Error during model creation' |
| 108 | + return |
| 109 | + |
| 110 | + try: |
| 111 | + model = train(model, optimizer, train_loader, test_loader) |
| 112 | + except Exception: |
| 113 | + assert False, 'Error during training' |
| 114 | + return |
| 115 | + |
| 116 | + test_x = Variable(torch.randn(1, 784)) |
| 117 | + rec_x, hid_x = model(test_x), model.encode(test_x) |
| 118 | + submodules = dict(model.named_children()) |
| 119 | + layers_with_params = np.unique(['.'.join(n.split('.')[:-1]) for n, _ in model.named_parameters()]) |
| 120 | + |
| 121 | + assert (hid_x.dim() == 2) and (hid_x.size(1) == 20), 'Hidden representation size must be equal to 20' |
| 122 | + assert (rec_x.dim() == 2) and (rec_x.size(1) == 784), 'Reconstruction size must be equal to 784' |
| 123 | + assert len(layers_with_params) <= 6, 'The model must contain not more than 6 layers' |
| 124 | + assert np.all(np.concatenate([list(p.shape) for p in model.parameters()]) <= 800), 'All hidden sizes must be less than 800' |
| 125 | + assert np.all([isinstance(submodules[name], nn.Linear) for name in layers_with_params]), 'All layers with parameters must be nn.Linear' |
| 126 | + print('Success!🎉') |
| 127 | + |
| 128 | + |
| 129 | +test_work() |
0 commit comments