!mkdir cifar10
!curl -o cifar-10-python.tar.gz https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
!tar -xvzf cifar-10-python.tar.gz -C cifar10

import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

from cifar import load_CIFAR10
plt.rcParams['figure.figsize'] = (10.0, 8.0) 

cifar10_dir = './cifar10/cifar-10-batches-py'
X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)

classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
num_classes = len(classes)
samples_per_class = 7
for y, cls in enumerate(classes):
    idxs = np.flatnonzero(y_train == y)
    idxs = np.random.choice(idxs, samples_per_class, replace=False)
    for i, idx in enumerate(idxs):
        plt_idx = i * num_classes + y + 1
        plt.subplot(samples_per_class, num_classes, plt_idx)
        plt.imshow(X_train[idx].astype('uint8').transpose(1, 2, 0))
        plt.axis('off')
        if i == 0:
            plt.title(cls)
plt.show()

"""
Explanation: CIFAR Conv Net
И так, в этом ноутбуке Вы сделаете превую в своей жизни сверточную сеть! На сложном датасете. Cкачайте его кстати,
End of explanation
"""


import lasagne
from theano import tensor as T
from lasagne.nonlinearities import *

input_X = T.tensor4("X")
target_y = T.vector("target Y integer",dtype='int64')

"""
Explanation: <h1 align="center">First of all -- Checking Questions</h1>

Вопрос 1: Чем отличаются современные сверточные сети от сетей 5 летней давности?
Большие свертки, например, 11х11, заменяют на несколько маленьких (в данном случае 4 свертки 3х3), что значительно уменьшает количество параметров: 36 против 121. 
Вопрос 2: Какие неприятности могут возникнуть во время обучения современных нейросетей?
Затухание градиентов, ухудшение качества при увеличении количества слоев (решение - Residual networks, состоящие из блоков с несколькими ветками, по одной из которых на последний слой блока передается то, что подавалось на вход другим веткам).
Вопрос 3: У вас есть очень маленький датасет из 100 картинок, классификация, но вы очень хотите использовать нейросеть, какие неприятности вас ждут и как их решить? что делать если первый вариант  решения не заработает?
Много параметров, мало данных. Решение - увеличить датасет путем искажения исходных изображений (повороты, сжатия, отражения) и наложения шумов. 
Вопрос 4: Как сделать стайл трансфер для музыки? oO
<Ответ>
End of explanation
"""


net = lasagne.layers.InputLayer(shape=(None, 3, 32, 32), input_var=input_X)

# net = <сверочная нейросеть>
net = lasagne.layers.Conv2DLayer(net, 11, 5, pad='valid') # сверточный слой
net = lasagne.layers.Conv2DLayer(net, 7, 3, pad='valid') # сверточный слой
net = lasagne.layers.MaxPool2DLayer(net, 3)
net = lasagne.layers.Conv2DLayer(net, 3, 2, pad='valid') # сверточный слой
net = lasagne.layers.Conv2DLayer(net, 3, 2, pad='valid') # сверточный слой
net = lasagne.layers.Conv2DLayer(net, 3, 2, pad='valid') # сверточный слой

net = lasagne.layers.DenseLayer(net, num_units=300) # полносвязный слой
net = lasagne.layers.DropoutLayer(net, 0.5)         # регуляризатор
net = lasagne.layers.DenseLayer(net, num_units=100) # полносвязный слой

# net = lasagne.layers.DenseLayer(net, num_units=10, nonlinearity=lasagne.nonlinearities.softmax)  # полносвязный слой

net = lasagne.layers.DenseLayer(net,num_units = 10, nonlinearity=lasagne.nonlinearities.softmax)

y_predicted = lasagne.layers.get_output(net)
all_weights = lasagne.layers.get_all_params(net)
print all_weights

# loss = <функция потерь>
# accuracy = <вычисление точност>
loss = lasagne.objectives.categorical_crossentropy(y_predicted, target_y).mean()
accuracy = lasagne.objectives.categorical_accuracy(y_predicted, target_y).mean()

updates = lasagne.updates.momentum(loss, all_weights, learning_rate=0.1, momentum=0.9)
train_fun = theano.function([input_X,target_y],[loss, accuracy], updates=updates)
accuracy_fun = theano.function([input_X,target_y],accuracy)

"""
Explanation: Соберите нейронку:

Many times x (Conv+Pool)
Many small convolutions like 3x3
Batch Norm 
Residual Connection
Data Augmentation 
Learning rate Schedule 
...

Для вдохновения

http://torch.ch/blog/2015/07/30/cifar.html
https://github.com/szagoruyko/wide-residual-networks 

Самое интересное

Для сдачи задания нужно набрать на точность тесте > 92.5% (это займет много времени, торопитесь :) )
Для получения бонусных баллов > 95.0%
Будет очень хорошо если вы придумаете свою архитектуру или сможете обучить что-то из вышеперечисленного :)
А для обучения всего этого добра вам будет куда удобнее использовать GPU на Amazon 
Инструкция https://github.com/persiyanov/ml-mipt/tree/master/amazon-howto 
Вам помогут tmux, CuDNN, ssh tunnel, nvidia-smi, ... 
Have fun :)
End of explanation
"""


def iterate_minibatches(inputs, targets, batchsize, shuffle=False):
    assert len(inputs) == len(targets)
    if shuffle:
        indices = np.arange(len(inputs))
        np.random.shuffle(indices)
    for start_idx in range(0, len(inputs) - batchsize + 1, batchsize):
        if shuffle:
            excerpt = indices[start_idx:start_idx + batchsize]
        else:
            excerpt = slice(start_idx, start_idx + batchsize)
        yield inputs[excerpt], targets[excerpt]

"""
Explanation: Вот и всё, пошли её учить
End of explanation
"""


import time

num_epochs = 10 #количество проходов по данным

batch_size = 50 #размер мини-батча

for epoch in range(num_epochs):
    # In each epoch, we do a full pass over the training data:
    train_err = 0
    train_acc = 0
    train_batches = 0
    start_time = time.time()
    for batch in iterate_minibatches(X_train, y_train,batch_size):
        inputs, targets = batch
        train_err_batch, train_acc_batch= train_fun(inputs, targets)
        train_err += train_err_batch
        train_acc += train_acc_batch
        train_batches += 1

    # And a full pass over the validation data:
    val_acc = 0
    val_batches = 0
    for batch in iterate_minibatches(X_train, y_train, batch_size):
        inputs, targets = batch
        val_acc += accuracy_fun(inputs, targets)
        val_batches += 1

    # Then we print the results for this epoch:
    print("Epoch {} of {} took {:.3f}s".format(epoch + 1, num_epochs, time.time() - start_time))
    print("  training loss (in-iteration):\t\t{:.6f}".format(train_err / train_batches))
    print("  train accuracy:\t\t{:.2f} %".format(train_acc / train_batches * 100))
    print("  validation accuracy:\t\t{:.2f} %".format(val_acc / val_batches * 100))

test_acc = 0
test_batches = 0
for batch in iterate_minibatches(X_test, y_test, 500):
    inputs, targets = batch
    acc = accuracy_fun(inputs, targets)
    test_acc += acc
    test_batches += 1
print("Final results:")
print("  test accuracy:\t\t{:.2f} %".format(
    test_acc / test_batches * 100))

if test_acc / test_batches * 100 > 92.5:
    print "Achievement unlocked: колдун 80 уровня"
else:
    print "Нужно больше магии!"

"""
Explanation: Процесс обучения
End of explanation
"""


