# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] slideshow={"slide_type": "slide"}
# # Going deeper with Tensorflow
#
# В этом семинаре мы начнем изучать [Tensorflow](https://www.tensorflow.org/) для построения _deep learning_ моделей.
#
# Для установки tf на свою машину:
# * `pip install tensorflow` **cpu-only** TF для Linux & Mac OS
# * для установки tf с автомагической поддержкой GPU смотри [TF install page](https://www.tensorflow.org/install/)

# + slideshow={"slide_type": "slide"}
import tensorflow as tf
gpu_options = tf.GPUOptions(allow_growth=True, per_process_gpu_memory_fraction=0.1)
s = tf.InteractiveSession(config=tf.ConfigProto(gpu_options=gpu_options))

# + [markdown] slideshow={"slide_type": "subslide"}
# # Разминка
#
# Для начала, давайте имплементируем простую функцию на numpy просто для сравнения. Напишите подсчет суммы квадратов чисел от 0 до N-1.
#
# **Подсказка:**
# * Массив чисел от 0 до N-1 включительно - numpy.arange(N)

# + slideshow={"slide_type": "fragment"}
import numpy as np
def sum_squares(N):
    return <what?>


# + slideshow={"slide_type": "skip"}
# %%time
sum_squares(10**8)

# + [markdown] slideshow={"slide_type": "slide"}
# # Tensoflow teaser
#
# Сделаем тоже самое на tf

# +
#Это будет параметром функции
N = tf.placeholder('int64', name="input_to_your_function")

#Рецепт получения суммы квадратов
result = tf.reduce_sum((tf.range(N)**2))
# -

# %%time
#А так вычисляется результат как и в примере на numpy
print(result.eval({N:10**8}))

# # Как это работает?
#
# 1. Сначала объявляем placeholder'ы -- это ячейки в графе вычислений, куда будут подставляться непосредственные значения.
# 2. Затем мы пишем "рецепт" получения выходов по входам. Или преобразования.
# 3. Запуск вычислений с непосредственными значениями входных параметров для каждого placeholder'а.
#   * output.eval({placeholder:value}) 
#   * s.run(output, {placeholder:value})
#
#
# * Итак, мы на самом деле имеем две главных сущности -- это "placeholder" и преобразование.
# * Обе могут представлять собой матрицы, тензоры, просто числа и т.д.
# * Обе могут представляться различными типами данных: int32/64, floats, booleans и др.
#
#
#  * В tf есть аналоги почти для всех функций из numpy. Примеры:
#    * `a+b, a/b, a**b, ...` ведут себя также как в numpy
#    * np.mean -> tf.reduce_mean
#    * np.arange -> tf.range
#    * np.cumsum -> tf.cumsum
#    * Если не можете найти какую-то операцию из numpy, смотри доки: [docs](https://www.tensorflow.org/api_docs/python).
#  
#  
# Ничего не понятно? Сейчас все исправим.:)

# +
# Если не указывать параметр shape, то входной тензор может иметь любую произвольную форму
arbitrary_input = tf.placeholder('float32')

# А так мы задаем вектор произовольной длины
input_vector = tf.placeholder('float32',shape=(None,))

# Вектор фиксированной длины типа int и длины 10
fixed_vector = tf.placeholder('int32',shape=(10,))

# Матрица с произвольным количеством строк и 15 столбцами (например, так можно указать формы для минибатчей)
input_matrix = tf.placeholder('float32',shape=(None,15))

# Итак, используем None везде, где не хотим фиксировать размеры
input1 = tf.placeholder('float64',shape=(None,100,None))
input2 = tf.placeholder('int32',shape=(None,None,3,224,224))

# +
# Это поэлементное умножение
double_the_vector = input_vector*2

# Поэлементный косинус
elementwise_cosine = tf.cos(input_vector)

# Разность между квадратами элементов вектора и самого этого вектора
vector_squares = input_vector**2 - input_vector

# -

# Время немного попрактиковаться.
#
# Создайте два вектора типа **float32**

my_vector = <student.init_float32_vector()>
my_vector2 = <student.init_one_more_such_vector()>

# Напишите такую трансформацию с ними:
#(vec1)*(vec2) / (sin(vec1) +1)
my_transformation = <student.implementwhatwaswrittenabove()>

print(my_transformation)
# Это нормально, это символьный граф

# +
#
dummy = np.arange(5).astype('float32')

my_transformation.eval({my_vector:dummy,my_vector2:dummy[::-1]})
# -

# # Визуализация графов
#
# А вот и приятная фишка Tensorflow -- **Tensorboard**.
#
# Визуализацию графа удобно использовать, когда дело касается дебаггинга или оптимизации. Да и вообще, интерактивные визуализации это приятно.
#
# Для того чтобы запустить _Tensorboard_ выполните команду в терминале:
#
# * ```tensorboard --logdir=/tmp/tboard --port=7007```
#   * тут --logdir и --port можете подставить удобный вам;
#
# Если вы совершенно не рады общению с терминалом, запустить _Tensorboard_ можно и из тетрадки вот так:
#
# * ```os.system("tensorboard --logdir=/tmp/tboard --port=7007 &"```
#
# _(Но, пожалуйста, никому не сообщайте, что мы вас этому научили)_
#
# **Teaser:**

# <img src="https://www.tensorflow.org/images/graph_vis_animation.gif" width=780>

# +
# Некрасивый способ запуска Tensorboard:
import os
port = 6000 + os.getuid()
print("Port: %d" % port)
# #!killall tensorboard
os.system("tensorboard --logdir=./tboard --port=%d &" % port)

# show graph to tensorboard
writer = tf.summary.FileWriter("./tboard", graph=tf.get_default_graph())
writer.close()
# -

# Одна из базовых возможностей Tensorboard это визуализация графа вычислений. Как только вы запустили ячейку сверху, можете проследовать по адресу `localhost:port` на соседней вкладке вашего броузера. Там перейдите во вкладку _graphs_. 
#
# Вы должны наблюдать примерно следующее:
#
# <img src="https://s12.postimg.org/a374bmffx/tensorboard.png" width=480>
#
# Tensorboard также позволяет наблюдать кривую обучения, сохранять картинки и аудио...
#
# Это бывает полезно еще на этапе обучения увидеть, что что-то идет не так.
#
# Один исследователь сказал:
#
# ```
# Если вы провели 4 часа своего рабочего времени наблюдая как алгоритм печатает циферки и рисует фигуры, 
#
# это значит что скорее всего вы неправильно занимаетесь глубоким обучением.
#
# ```
#
# Original:
#
# ```
# If you spent last four hours of your worktime watching as your algorithm prints numbers and draws figures, you're probably doing deep learning wrong.
# ```

# Более подробная информация по использованию **Tensorboard** может быть найдена [тут](https://www.tensorflow.org/get_started/graph_viz)

# # Теперь сам
#
# __[2 points max]__

# +
# Quest #1 - 
# Нужно написать функцию, которая берет 2 вектора и считает среднюю квадратичную ошибку (mse).
# Ваша функция должна брать 2 вектора и возвращать одно единственное число

<student.define_inputs_and_transformations()>

mse =<student.define_transformation()>

compute_mse = lambda vector1, vector2: <how to run you graph?>

# +
# Tests
from sklearn.metrics import mean_squared_error

for n in [1,5,10,10**3]:
    
    elems = [np.arange(n),np.arange(n,0,-1), np.zeros(n),
             np.ones(n),np.random.random(n),np.random.randint(100,size=n)]
    
    for el in elems:
        for el_2 in elems:
            true_mse = np.array(mean_squared_error(el,el_2))
            my_mse = compute_mse(el,el_2)
            if not np.allclose(true_mse,my_mse):
                print('Wrong result:')
                print('mse(%s,%s)' % (el,el_2))
                print("should be: %f, but your function returned %f" % (true_mse,my_mse))
                raise ValueError,"Что-то не так"

print("All tests passed")    
# -

# # tf.Variable
#
# Входы и трансформации не имеют никакого значения за пределами вызова функции.
#
# Но если вы хотите, чтобы ваша модель имела какие-то параметры (например, веса скрытых слоев нейронной сетки), которые могут изменяться в течение времени, то для их представления удобно использовать `tf.Variable`.
#
# Объекты типа `tf.Variable` (переменные) обладают **следующими свойствами**:
#
# * Вы можете присваивать значение переменной в любой момент в вашем графе вычислений;
# * В отличии от `placeholder`'ов нет строгой необходимости инициализировать переменные всякий раз как вы запускаете `s.run(...)`;
#     * _Да, да. Если не передать значения во все плейсхолдеры в графе при запуске сессии, то сессия и не запустится;_
#     
#     
#     
# * Переменные и трансформации могут быть использованы похожим образом; 

# создадим переменную
shared_vector_1 = tf.Variable(initial_value=np.ones(5))

# +
# проинициализируем все переменные в графе;
s.run(tf.global_variables_initializer())

# вычисляем значение переменной 
print("initial value", s.run(shared_vector_1))

# внутри символьного графа переменные используются также как любые другие входы и трансформации,
# нет необходимости делать внутри графа "get value"

# +
# как присвоить переменной другое значение
s.run(shared_vector_1.assign(np.arange(5)))

# и получить его
print("new value", s.run(shared_vector_1))

# -

# # tf.gradients - почему граф это удобно
#
# * Tensorflow может автомагически вычислять градиенты, пользуясь графом вычислений
# * Градиент вычисляется как произведение элементарных производных 
#     * по правилу взятия производной сложной функции 
#     * или в англоязычной терминалогии -- `chain rule`:
#
#
# $$ {\partial f(g(x)) \over \partial x} = {\partial f(g(x)) \over \partial g(x)}\cdot {\partial g(x) \over \partial x} $$
#
# Таким образом можно взять производные по параметрам очень сложной модели до тех пор, пока элементарные функции/операции/трансформации, используемые в модели, являются дифференцируемыми.

# +
my_scalar = tf.placeholder('float32')

scalar_squared = my_scalar**2

# найдем производную функции scalar_squared по переменной my_scalar
derivative = tf.gradients(scalar_squared, my_scalar)[0]

# +
import matplotlib.pyplot as plt
# %matplotlib inline

x = np.linspace(-3,3)
x_squared, x_squared_der = s.run([scalar_squared,derivative],
                                 {my_scalar:x})

plt.plot(x, x_squared,label="x^2")
plt.plot(x, x_squared_der, label="derivative")
plt.legend();
# -

# # Почему это круто

# +
my_vector = tf.placeholder('float32',[None])

# Давайте найдем градиент для weird_psychotic_function по параметрам my_scalar и my_vector
# Осторожно! Попытки понять смысл этой функции могут привести к необратимым повреждениям вашего мозга

weird_psychotic_function = tf.reduce_mean((my_vector+my_scalar)**(1+tf.nn.moments(my_vector,[0])[1]) + 1./ tf.atan(my_scalar))/(my_scalar**2 + 1) + 0.01*tf.sin(2*my_scalar**1.5)*(tf.reduce_sum(my_vector)* my_scalar**2)*tf.exp((my_scalar-4)**2)/(1+tf.exp((my_scalar-4)**2))*(1.-(tf.exp(-(my_scalar-4)**2))/(1+tf.exp(-(my_scalar-4)**2)))**2

der_by_scalar = <student.compute_grad_over_scalar()>
der_by_vector = <student.compute_grad_over_vector()>

# +
# Строим графики
scalar_space = np.linspace(1, 7, 100)

y = [s.run(weird_psychotic_function, {my_scalar:x, my_vector:[1, 2, 3]})
     for x in scalar_space]

plt.plot(scalar_space, y, label='function')

y_der_by_scalar = [s.run(der_by_scalar, {my_scalar:x, my_vector:[1, 2, 3]})
     for x in scalar_space]

plt.plot(scalar_space, y_der_by_scalar, label='derivative')
plt.grid()
plt.legend();
# -

# # Напоследок - optimizers
#
# Теперь, когда мы умеем считать градиенты автомагически с помощью встроенных функций tf, мы могли бы реализовать градиентный спуск и оптимизировать с ним параметры моделей. 
#
# К счастью, в tf также есть готовые оптимайзеры, которые мы можем использовать прямо из коробки.
# Помните что-то про `momentum` и `rmsprop`?
#
# Подробнее про различные виды оптимайзеров можно почитать [тут](http://ruder.io/optimizing-gradient-descent/)

# +
y_guess = tf.Variable(np.zeros(2,dtype='float32'))
y_true = tf.range(1,3,dtype='float32')

loss = tf.reduce_mean((y_guess - y_true + tf.random_normal([2]))**2) 

optimizer = tf.train.MomentumOptimizer(0.01,0.9).minimize(loss,var_list=y_guess)

#same, but more detailed:
#updates = [[tf.gradients(loss,y_guess)[0], y_guess]]
#optimizer = tf.train.MomentumOptimizer(0.01,0.9).apply_gradients(updates)

# +
from IPython.display import clear_output

s.run(tf.global_variables_initializer())

guesses = [s.run(y_guess)]

for _ in range(100):
    s.run(optimizer)
    guesses.append(s.run(y_guess))
    
    clear_output(True)
    plt.plot(*zip(*guesses),marker='.')
    plt.scatter(*s.run(y_true),c='red')
    plt.show()
# -

# # Логистическа регрессия своими руками
#
# **[4 балла]**
#
# Имплементируйте свою простенькую логрегрессию с блэкджеком и кроссэнтропией
#
# **Что потребуется**
# * Используйте tf.Variable для весов
# * X и y это входы
# * Нужно реализовать 2 функции:
#  * `train_function(X,y)` - возвращает ошибку и изменяет веса на 1 шаг по граиденту (через updates)
#  * `predict_fun(X)` - возвращает предсказанные ответы ("y") по данным
#  
#  
# Мы будем работать с двухклассовой версией датасета MNIST;
#
# Обратите внимание, что `y` принимает значения из `{0,1}` в исходной версии датасеты, а не `{-1,1}` (которые скорее всего вам нужны)

# +
from sklearn.datasets import load_digits
mnist = load_digits(2)

X,y = mnist.data, mnist.target

print("y [shape - %s]:" % (str(y.shape)), y[:10])
print("X [shape - %s]:" % (str(X.shape)))
# -

print('X:\n',X[:3,:10])
print('y:\n',y[:10])
plt.imshow(X[0].reshape([8,8]))

# inputs and shareds
weights = <student.code_variable()>
input_X = <student.code_placeholder()>
input_y = <student.code_placeholder()>

# +
predicted_y = <predicted probabilities for input_X>
loss = <logistic loss (scalar, mean over sample)>

optimizer = <optimizer that minimizes loss>
# -

train_function = <compile function that takes X and y, returns log loss and updates weights>
predict_function = <compile function that takes X and computes probabilities of y>

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y)

# +
from sklearn.metrics import roc_auc_score

for i in range(5):
    <run optimizer operation>
    loss_i = <compute loss at iteration i>
    
    print("loss at iter %i:%.4f" % (i, loss_i))
    
    print("train auc:",roc_auc_score(y_train, predict_function(X_train)))
    print("test auc:",roc_auc_score(y_test, predict_function(X_test)))

    
print ("resulting weights:")
plt.imshow(shared_weights.get_value().reshape(8, -1))
plt.colorbar();
# -

# # Bonus: my1stNN
#
# ### Нейроночка
#
# **[базовая часть - 4 балла]**
#
# Ваше финальное задание - сделать свою первую нейронку [почти] из спичек и желудей.
# В этот раз распознавание цифр зашло чуть дальше:
#
# * картинки 28x28
# * 10 классов
# * 50k+ картинок только в обучающей выборке
#
# Вам не нужно (но можно) создавать монстров на 152 слоя, не нужно ничего сворачивать - достаточно сделать простую нейросеть с 1 скрытым и 1 выходным слоем, которая будет работать лучше логистической регрессии.
# В конце есть шаблон отчёта - его желательно вести по ходу работ (ну или хотя бы не забыть).
#
#
# **[bonus score]** Далее, если ваша сетка уже побила результаты линейной модели, а запал остался - можно попробовать улучшить результат ещё дальше. Челлендж - превзойти рубежи 95%/97.5%/98.5% точности на тесте без использования свёрток.
#
#
# __СПОЙЛЕР!__
# В конце тетрадки есть несколько советов по реализации. Если вы чувствуете в себе силы выстрелить в ногу самостоятельно - ваша воля, но наткнувшись на неразрешимые проблемы будьте добры прочитать "хвост" тетрадки, прежде, чем спрашивать семинариста.

# +
from mnist import load_dataset

#качаем (по необходимости) и читаем данные.
#Важно, что для обучения можно использовать только train, 
#а val - для оценки прогресса, сравнения можелей и early stopping.
#Тест вообще лучше положить под камень до самого конца, но ктоЖ вас поймает.

X_train,y_train,X_val,y_val,X_test,y_test = load_dataset()

print (X_train.shape,y_train.shape)
# -

plt.imshow(X_train[0,0])

<Тут, например, можно запилить граф вычислений>

<Это могло бы быть хорошим местом, чтобы запилить использование функции лосса оптимайзера>

<а тут - запилить цикл обучения и нужные метрики>

<и наконец тут - предсказание на тесте - только честно!>

# # Отчёт
# Я делал такое и такое.
#
# Потом попробовал вот-такое и алгоритм выдал вот-такое.
#
# А потом я его стукнул и он стал фиолетовый в крапинку.
#
# А ещё мне очень помогла вон-та статья и вот-этот сорт травы (if any).

# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# ```
#
# # SPOILERS
# Рекомендуемый порядок:
#
# * Адаптировать логистическую регрессию на клаccификацию 1 цифры против всех (например, нулей против ненулей)
# * Обобщить логистическую регрессию до многоклассовой.
#     - для этого придётся вспомнить первую лекцию или загуглить.
#     - вместо одного вектора весов у вас будет матрица (признак, класс)
#     - softmax (экспонента на сумму экспонент) можно сделать самому, а можно - tf.nn.softmax
#     - Лучше использовать стохастический градиентный спуск (минибатчевый)
#         - в котором случае выборку желательно перемешать (ну или брать случайный набор примеров на каждой итерации обучения)
# * Добавить скрытый слой. Теперь ваша логистическая регрессия опирается на нейроны, а не на входы.
#     - Принцип работы первого слоя - такой же, как у выходного, но вместо softmax у него другая нелинейность.
#     - нужно обучать оба слоя, а не только выходной :)
#     - важно не инициализировать веса нулями из-за эффекта симметрии. Для начала - случайный нормальный шум с маленькой "сигмой".
#     - Начать рекоммендую с 50 нейронов и сигмоиды, ибо так труднее прострелить себе ногу.
#     - В идеале у вас будет 2 .dot-а, 1 sigmoid и 1 softmax.
#     - **Убедитесь, что такая нейронка выучивается лучше, чем логистическая регрессия**
# * Теперь время подумать над тем, как улучшить результат. Слои, нейроны, нелинейности, методы оптимизации, инициализация - всё, что хотите, разве что я бы попросил в качестве челленджа обойтись пока без свёрток.



StarcoderdataJupyterScriptsDedupFiltered