Credit https://github.com/yandexdataschool/YSDA_deeplearning17, https://github.com/DmitryUlyanov
Зачем это всё нужно?! Зачем понимать как работают нейросети внутри когда уже есть куча библиотек?
The goal of this homework is simple, yet an actual implementation may take some time :). We are going to write an Artificial Neural Network (almost) from scratch. The software design of was heavily inspired by Torch which is the most convenient neural network environment when the work involves defining new layers.
This homework requires sending "multiple files, please do not forget to include all the files when sending to TA. The list of files:
If you want to read more about backprop this links can be helpfull:
Вопрос 1: Чем нейросети отличаются от линейных моделей а чем похожи?
Нейросеть - это обобщение линейных моделей - их многократное применение с добавлением нелинейностей, чтобы итоговая модель стала нелинейной. Так же в линейных моделях для каждого класса мы определяем один объект-экземпляр этого класса, т.е. экземпляр соответствующий классу, и новый объект относится к этому классу, если он наиболее похож на экземпляр этого класса. В нейросети для каждого класса мы можем искать сколь-угодно больше число экземпляров соответствующих ему. Поэтому объект сравнивается не с одним, а несколькими экземплярами при выборке к какому классу отнести. Так же, например, с помощью сверточной сети экземпляры становятся непривязанными к месту на картинке.
Вопрос 2: В чем недостатки полносвзяных нейронных сетей какая мотивация к использованию свёрточных?
Рассмотрим классификацию картинок. Например мы хотим определять изображен ли на картинке кот или другое животное. В случае полносвязных слоев нам придется заводить много признаков, чтобы находить кота в разных частях картинки. При использовании сверточного слоя мы избавимся от необходимости в таком количестве признаков, так как он позволяет находить объект в любой части изображения.
Вопрос 3: Какие слои используются в современных нейронных сетях? Опишите как работает каждый слой и свою интуицию зачем он нужен.
- DenceLayer -- полносвязный слой, линейное преобразование Wx + b, можно интерпретировать как подсчет результата для набора объектов (линейная модель для большого количества объектов), очень похож на линейную алгебру и по этому работает так быстро и хорошо параллелиться.
- ConvolutionalLayer -- сворачиваем матрицу изображения с линейным фильтром. В зависимости от параметров фильтра и их количества получаем различный размер выхода. Позволяет искать объекты вне зависимости от положения на изображении.
- MaxPool(ing)Layer -- уменьшаем матрицу исходного изображения, разбивая на небольшие подматрицы и усредняя по ним. Упрощает модель.
- DropOut - аля регуляризация для нейросети - выкидываем нейроны из слоя с заданной вероятностью. Имеется 2 интерпретации: 1) выбираем одну сеть из ансамбля нейросетей, что в среднем дает хороший результат(что-то вроде ансамбля решающих деревьев); 2) стараемся распознать объект на картинке, даже если какие-то его части не видны.
-SoftMax Layer(before output) -- чтобы посчитать вероятность принадлежности классам. По сути выдает желаемый нами output.
Так же после Dence и Convolutional идет нелинейность, ReLU, LeakyReLU, softmax, arcth и другие, иначе два последовательных линейных преобразования можно свернуть в одно.
Вопрос 4: Может ли нейросеть решать задачу регрессии, какой компонент для этого нужно заменить в нейросети из лекции 1?
Да, может, если заменить последний слой, так как softmax нас теперь не устраивает и выдавать среднее/медиану и тд. Так же стоит поменять критерий, например на MSE.
Вопрос 5: Почему обычные методы оптимизации плохо работают с нейросетями? А какие работают хорошо? Почему они работают хорошо?
Потому что итоговая функция от параметров получается очень сложной с возможными неприятностями, на которых обычные методы работают либо долго, либо совсем не работают ("седло", с-уровни в виде эллипсов, когда постоянно перепрыгиваем). Поэтому используются оптимизации, такие как усреднение шага с помощью скользящего экспоненциального среднего (momentum), нормировки на предыдущие значения (AdaGrad), все вместе (Adam). Работают хорошо за счет усреднения и нормировки, за счет чего мы в среднем шагаем в нужную сторону.
Вопрос 6: Для чего нужен backprop, чем это лучше/хуже чем считать градиенты без него? Почему backprop эффективно считается на GPU?
Backprop нужен для обучения - метод удобно и эффективно считать производные функии потерь по параметрам, чтобы оптимизировать их двигаясь по антиградиенту (грубо). Удобство достигается тем, что не нужно рассматривать все параметры модели вместо, а отдельно в каждом нейроне. Так как модель сложная и получить зависимость функции потерь от всех параметров сложно, а найти все производные еще сложнее, то лучше использовать backprop и считать на каждом нейроне отдельно. При вычислении производных нам часто нужно перемножать матрицы и вектора (например матрицу Якобиана), а произведение матриц хорошо параллелиться, поэтому эффективно считать на GPU.
Вопрос 7: Почему для нейросетей не используют кросс валидацию, что вместо неё? Можно-ли ее использовать?
Так как нейросети очень долго учатся, чем больше сеть - тем логично дольше, поэтому делать кросс-валидацию - очень-очень долго. Вместо этого можно использовать обычную проверку разделением выборки на train и test, обучением на train и тестом качества на test.
Вопрос 8: Небольшой quiz который поможет разобраться со свертками https://www.youtube.com/watch?v=DDRa5ASNdq4
<Ответ-Картинка :)>
Политика списывания. Вы можете обсудить решение с одногрупниками, так интереснее и веселее :) Не шарьте друг-другу код, в этом случаи вы ничему не научитесь -- "мыши плакали кололись но продолжали жрать кактус".
Теперь формально. Разница между списыванием и помощью товарища иногда едва различима. Мы искренне надеемся, что при любых сложностях вы можете обратиться к семинаристам и с их подсказками самостоятельно справиться с заданием. При зафиксированных случаях списывания (одинаковый код, одинаковые ошибки), баллы за задание будут обнулены всем участникам инцидента.
In [1]:
%matplotlib inline
from time import time, sleep
import numpy as np
import matplotlib.pyplot as plt
from IPython import display
Implement everything in Modules.ipynb
. Read all the comments thoughtfully to ease the pain. Please try not to change the prototypes.
Do not forget, that each module should return AND store output
and gradInput
.
The typical assumption is that module.backward
is always executed after module.forward
,
so output
is stored, this would be useful for SoftMax
.
In [2]:
"""
--------------------------------------
-- Tech note
--------------------------------------
Inspired by torch I would use
np.multiply, np.add, np.divide, np.subtract instead of *,+,/,-
for better memory handling
Suppose you allocated a variable
a = np.zeros(...)
So, instead of
a = b + c # will be reallocated, GC needed to free
I would go for:
np.add(b,c,out = a) # puts result in `a`
But it is completely up to you.
"""
%run hw1_Modules.ipynb
Optimizer is implemented for you.
In [3]:
def sgd_momentum(x, dx, config, state):
"""
This is a very ugly implementation of sgd with momentum
just to show an example how to store old grad in state.
config:
- momentum
- learning_rate
state:
- old_grad
"""
# x and dx have complex structure, old dx will be stored in a simpler one
state.setdefault('old_grad', {})
i = 0
for cur_layer_x, cur_layer_dx in zip(x,dx):
for cur_x, cur_dx in zip(cur_layer_x,cur_layer_dx):
cur_old_grad = state['old_grad'].setdefault(i, np.zeros_like(cur_dx))
np.add(config['momentum'] * cur_old_grad, config['learning_rate'] * cur_dx, out = cur_old_grad)
cur_x -= cur_old_grad
i += 1
Use this example to debug your code, start with logistic regression and then test other layers. You do not need to change anything here. This code is provided for you to test the layers. Also it is easy to use this code in MNIST task.
In [4]:
# Generate some data
N = 500
X1 = np.random.randn(N,2) + np.array([2,2])
X2 = np.random.randn(N,2) + np.array([-2,-2])
Y = np.concatenate([np.ones(N),np.zeros(N)])[:,None]
Y = np.hstack([Y, 1-Y])
X = np.vstack([X1,X2])
plt.scatter(X[:,0],X[:,1], c = Y[:,0], edgecolors= 'none')
Out[4]:
Define a logistic regression for debugging.
In [5]:
net = Sequential()
net.add(Linear(2, 2))
net.add(SoftMax())
criterion = ClassNLLCriterion()
print(net)
# Test something like that then
net = Sequential()
net.add(Linear(2, 4))
net.add(ReLU())
net.add(Linear(4, 2))
net.add(SoftMax())
Start with batch_size = 1000 to make sure every step lowers the loss, then try stochastic version.
In [6]:
# Iptimizer params
optimizer_config = {'learning_rate' : 1e-1, 'momentum': 0.9}
optimizer_state = {}
# Looping params
n_epoch = 20
batch_size = 128
In [7]:
# batch generator
def get_batches(dataset, batch_size):
X, Y = dataset
n_samples = X.shape[0]
# Shuffle at the start of epoch
indices = np.arange(n_samples)
np.random.shuffle(indices)
for start in range(0, n_samples, batch_size):
end = min(start + batch_size, n_samples)
batch_idx = indices[start:end]
yield X[batch_idx], Y[batch_idx]
Basic training loop. Examine it.
In [8]:
loss_history = []
for i in range(n_epoch):
for x_batch, y_batch in get_batches((X, Y), batch_size):
net.zeroGradParameters()
# Forward
predictions = net.forward(x_batch)
loss = criterion.forward(predictions, y_batch)
# Backward
dp = criterion.backward(predictions, y_batch)
net.backward(x_batch, dp)
# Update weights
sgd_momentum(net.getParameters(),
net.getGradParameters(),
optimizer_config,
optimizer_state)
loss_history.append(loss)
# Visualize
display.clear_output(wait=True)
plt.figure(figsize=(8, 6))
plt.title("Training loss")
plt.xlabel("#iteration")
plt.ylabel("loss")
plt.plot(loss_history, 'b')
plt.show()
print('Current loss: %f' % loss)
We are using MNIST as our dataset. Lets start with cool visualization. The most beautiful demo is the second one, if you are not familiar with convolutions you can return to it in several lectures.
In [9]:
import os
from sklearn.datasets import fetch_mldata
# Fetch MNIST dataset and create a local copy.
if os.path.exists('mnist.npz'):
with np.load('mnist.npz', 'r') as data:
X = data['X']
y = data['Y']
else:
mnist = fetch_mldata("mnist-original")
X, y = mnist.data / 255.0, mnist.target
np.savez('mnist.npz', X=X, y=y)
One-hot encode the labels first.
In [10]:
# Your code goes here. ################################################
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
encoder = OneHotEncoder()
y = encoder.fit_transform(y).toarray()
print np.max(X)
X = 1. * X / np.max(X) # normalize
ReLU
, ELU
activation functions.
You would better pick the best optimizer params for each of them, but it is overkill for now. Use an architecture of your choice for the comparison.Finally, use all your knowledge to build a super cool model on this dataset, do not forget to split dataset into train and validation. Use dropout to prevent overfitting, play with learning rate decay. You can use data augmentation such as rotations, translations to boost your score. Use your knowledge and imagination to train a model.
In [11]:
# Your code goes here. ################################################
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1)
print y_train.shape, y_test.shape
Print here your accuracy. It should be around 90%.
In [12]:
net = Sequential()
net.add(Linear(X_train.shape[1], X_train.shape[1] / 4))
net.add(LeakyReLU())
net.add(Linear(X_train.shape[1] / 4, X_train.shape[1] / 8))
net.add(LeakyReLU())
net.add(Linear(X_train.shape[1] / 8, 10))
net.add(SoftMax())
In [13]:
# Your code goes here. ################################################
optimizer_config = {'learning_rate' : 1, 'momentum': 0.95}
optimizer_state = {}
# Looping params
n_epoch = 150
batch_size = X.shape[0] / 10
loss_history = []
for i in range(n_epoch):
for x_batch, y_batch in get_batches((X_train, y_train), batch_size):
net.zeroGradParameters()
# Forward
predictions = net.forward(x_batch)
loss = criterion.forward(predictions, y_batch)
# Backward
dp = criterion.backward(predictions, y_batch)
net.backward(x_batch, dp)
# Update weights
sgd_momentum(net.getParameters(),
net.getGradParameters(),
optimizer_config,
optimizer_state)
loss_history.append(loss)
# Visualize
display.clear_output(wait=True)
plt.figure(figsize=(8, 6))
plt.title("Training loss")
plt.xlabel("#iteration")
plt.ylabel("loss")
plt.plot(loss_history, 'b')
plt.show()
print('Current loss: %f' % loss)
Print here your accuracy. It should be around 90%.
In [14]:
# Your answer goes here. ################################################
net.training = False
pred = net.forward(X_test)
print 'accuracy: {}'.format(float((pred.argmax(axis = 1) == y_test.argmax(axis = 1)).sum(0)) / pred.shape[0])
PS: Напоминаем, что дедлайны жесткие, прием дз заканчивается ровно в дедлайн
This part is OPTIONAL, you may not do it. It will not be scored, but it is easy and interesting.
Now we are going to build a cool model, named autoencoder. The aim is simple: encode the data to a lower dimentional representation. Why? Well, if we can decode this representation back to original data with "small" reconstuction loss then we can store only compressed representation saving memory. But the most important thing is -- we can reuse trained autoencoder for classification.
Picture from this site.
Now implement an autoencoder:
Build it such that dimetionality inside autoencoder changes like that:
$$784 \text{ (data)} -> 512 -> 256 -> 128 -> 30 -> 128 -> 256 -> 512 -> 784$$Use MSECriterion to score the reconstruction.
You may train it for 9 epochs with batch size = 256, initial lr = 0.1 droping by a factor of 2 every 3 epochs. The reconstruction loss should be about 6.0 and visual quality decent already. Do not spend time on changing architecture, they are more or less the same.
In [15]:
# Your code goes here. ################################################
Some time ago NNs were a lot poorer and people were struggling to learn deep models. To train a classification net people were training autoencoder first (to train autoencoder people were pretraining single layers with RBM), then substituting the decoder part with classification layer (yeah, they were struggling with training autoencoders a lot, and complex techniques were used at that dark times). We are going to this now, fast and easy.
In [16]:
# Extract inner representation for train and validation,
# you should get (n_samples, 30) matrices
# Your code goes here. ################################################
# Now build a logistic regression or small classification net
cnet = Sequential()
cnet.add(Linear(30, 2))
cnet.add(SoftMax())
# Learn the weights
# Your code goes here. ################################################
# Now chop off decoder part
# (you may need to implement `remove` method for Sequential container)
# Your code goes here. ################################################
# And add learned layers ontop.
autoenc.add(cnet[0])
autoenc.add(cnet[1])
# Now optimize whole model
# Your code goes here. ################################################
Run PCA with 30 components on the train set, plot original image, autoencoder and PCA reconstructions side by side for 10 samples from validation set. Probably you need to use the following snippet to make aoutpencoder examples look comparible.
In [ ]:
# np.clip(prediction,0,1)
#
# Your code goes here. ################################################
In [ ]:
In [ ]: