<jupyter_start><jupyter_text>Задание 1.2 - Линейный классификатор (Linear classifier)В этом задании мы реализуем другую модель машинного обучения - линейный классификатор. Линейный классификатор подбирает для каждого класса веса, на которые нужно умножить значение каждого признака и потом сложить вместе.Тот класс, у которого эта сумма больше, и является предсказанием модели.В этом задании вы:- потренируетесь считать градиенты различных многомерных функций- реализуете подсчет градиентов через линейную модель и функцию потерь softmax- реализуете процесс тренировки линейного классификатора- подберете параметры тренировки на практикеНа всякий случай, еще раз ссылка на туториал по numpy:  http://cs231n.github.io/python-numpy-tutorial/<jupyter_code>import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

%load_ext autoreload
%autoreload 2
from dataset import load_svhn, random_split_train_val
from gradient_check import check_gradient
from metrics import multiclass_accuracy 
import linear_classifer<jupyter_output><empty_output><jupyter_text>Как всегда, первым делом загружаем данныеМы будем использовать все тот же SVHN.<jupyter_code>def prepare_for_linear_classifier(train_X, test_X):
    train_flat = train_X.reshape(train_X.shape[0], -1).astype(np.float) / 255.0
    test_flat = test_X.reshape(test_X.shape[0], -1).astype(np.float) / 255.0
    
    # Subtract mean
    mean_image = np.mean(train_flat, axis = 0)
    train_flat -= mean_image
    test_flat -= mean_image
    
    # Add another channel with ones as a bias term
    train_flat_with_ones = np.hstack([train_flat, np.ones((train_X.shape[0], 1))])
    test_flat_with_ones = np.hstack([test_flat, np.ones((test_X.shape[0], 1))])    
    return train_flat_with_ones, test_flat_with_ones
    
train_X, train_y, test_X, test_y = load_svhn("data", max_train=10000, max_test=1000)    
train_X, test_X = prepare_for_linear_classifier(train_X, test_X)
# Split train into train and val
train_X, train_y, val_X, val_y = random_split_train_val(train_X, train_y, num_val = 1000)<jupyter_output><empty_output><jupyter_text>Играемся с градиентами!В этом курсе мы будем писать много функций, которые вычисляют градиенты аналитическим методом.Необходимым инструментом во время реализации кода, вычисляющего градиенты, является функция его проверки. Эта функция вычисляет градиент численным методом и сверяет результат с градиентом, вычисленным аналитическим методом.Мы начнем с того, чтобы реализовать вычисление численного градиента (numeric gradient) в этой функции.Вычислите градиент с помощью численной производной для каждой координаты. Для вычисления производной используйте так называемую two-point formula (https://en.wikipedia.org/wiki/Numerical_differentiation):![image](https://wikimedia.org/api/rest_v1/media/math/render/svg/22fc2c0a66c63560a349604f8b6b39221566236d)<jupyter_code># TODO: Implement gradient check function
def sqr(x):
    return x*x, 2*x

check_gradient(sqr, np.array([3.0]))

def array_sum(x):
    assert x.shape == (2,), x.shape
    return np.sum(x), np.ones_like(x)

check_gradient(array_sum, np.array([3.0, 2.0]))

def array_2d_sum(x):
    assert x.shape == (2,2)
    return np.sum(x), np.ones_like(x)

check_gradient(array_2d_sum, np.array([[3.0, 2.0], [1.0, 0.0]]))<jupyter_output>Gradient check passed!
Gradient check passed!
Gradient check passed!<jupyter_text>Теперь реализуем функцию softmax, которая получает на вход оценки для каждого класса и преобразует их в вероятности от 0 до 1:![image](https://wikimedia.org/api/rest_v1/media/math/render/svg/e348290cf48ddbb6e9a6ef4e39363568b67c09d3)**Важно:** Практический аспект вычисления этой функции заключается в том, что в ней учавствует вычисление экспоненты от потенциально очень больших чисел - это может привести к очень большим значениям в числителе и знаменателе за пределами диапазона float.К счастью, у этой проблемы есть простое решение -- перед вычислением softmax вычесть из всех оценок максимальное значение среди всех оценок:```predictions -= np.max(predictions)```(подробнее здесь - http://cs231n.github.io/linear-classify/softmax, секция `Practical issues: Numeric stability`)<jupyter_code># TODO Implement softmax and cross-entropy for single sample
probs = linear_classifer.softmax(np.array([-10, 0, 10]))

# Make sure it works for big numbers too!
probs = linear_classifer.softmax(np.array([1000, 0, 0]))
assert np.isclose(probs[0], 1.0)<jupyter_output><empty_output><jupyter_text>Кроме этого, мы реализуем cross-entropy loss, которую мы будем использовать как функцию ошибки (error function).В общем виде cross-entropy определена следующим образом:![image](https://wikimedia.org/api/rest_v1/media/math/render/svg/0cb6da032ab424eefdca0884cd4113fe578f4293)где x - все классы, p(x) - истинная вероятность принадлежности сэмпла классу x, а q(x) - вероятность принадлежности классу x, предсказанная моделью.  В нашем случае сэмпл принадлежит только одному классу, индекс которого передается функции. Для него p(x) равна 1, а для остальных классов - 0. Это позволяет реализовать функцию проще!<jupyter_code>probs = linear_classifer.softmax(np.array([-5, 0, 5]))
linear_classifer.cross_entropy_loss(probs, 1)<jupyter_output><empty_output><jupyter_text>После того как мы реализовали сами функции, мы можем реализовать градиент.Оказывается, что вычисление градиента становится гораздо проще, если объединить эти функции в одну, которая сначала вычисляет вероятности через softmax, а потом использует их для вычисления функции ошибки через cross-entropy loss.Эта функция `softmax_with_cross_entropy` будет возвращает и значение ошибки, и градиент по входным параметрам. Мы проверим корректность реализации с помощью `check_gradient`.<jupyter_code># TODO Implement combined function or softmax and cross entropy and produces gradient
loss, grad = linear_classifer.softmax_with_cross_entropy(np.array([1, 0, 0]), 1)
check_gradient(lambda x: linear_classifer.softmax_with_cross_entropy(x, 1), np.array([1, 0, 0], np.float))<jupyter_output>Gradient check passed!<jupyter_text>В качестве метода тренировки мы будем использовать стохастический градиентный спуск (stochastic gradient descent или SGD), который работает с батчами сэмплов. Поэтому все наши фукнции будут получать не один пример, а батч, то есть входом будет не вектор из `num_classes` оценок, а матрица размерности `batch_size, num_classes`. Индекс примера в батче всегда будет первым измерением.Следующий шаг - переписать наши функции так, чтобы они поддерживали батчи.Финальное значение функции ошибки должно остаться числом, и оно равно среднему значению ошибки среди всех примеров в батче.<jupyter_code># TODO Extend combined function so it can receive a 2d array with batch of samples

# Test batch_size = 1
batch_size = 1
predictions = np.zeros((batch_size, 3))
target_index = np.ones(batch_size, np.int)
check_gradient(lambda x: linear_classifer.softmax_with_cross_entropy(x, target_index), predictions)

# Test batch_size = 3
batch_size = 3
predictions = np.zeros((batch_size, 3))
target_index = np.ones(batch_size, np.int)
check_gradient(lambda x: linear_classifer.softmax_with_cross_entropy(x, target_index), predictions)<jupyter_output>Gradient check passed!
Gradient check passed!<jupyter_text>Наконец, реализуем сам линейный классификатор!softmax и cross-entropy получают на вход оценки, которые выдает линейный классификатор.Он делает это очень просто: для каждого класса есть набор весов, на которые надо умножить пиксели картинки и сложить. Получившееся число и является оценкой класса, идущей на вход softmax.Таким образом, линейный классификатор можно представить как умножение вектора с пикселями на матрицу W размера `num_features, num_classes`. Такой подход легко расширяется на случай батча векторов с пикселями X размера `batch_size, num_features`:`predictions = X * W`, где `*` - матричное умножение.Реализуйте функцию подсчета линейного классификатора и градиентов по весам `linear_softmax` в файле `linear_classifer.py`<jupyter_code># TODO Implement linear_softmax function that uses softmax with cross-entropy for linear classifier
batch_size = 2
num_classes = 2
num_features = 3
np.random.seed(42)
W = np.random.randint(-1, 3, size=(num_features, num_classes)).astype(np.float)
X = np.random.randint(-1, 3, size=(batch_size, num_features)).astype(np.float)
target_index = np.ones(batch_size, dtype=np.int)

loss, dW = linear_classifer.linear_softmax(X, W, target_index)
check_gradient(lambda w: linear_classifer.linear_softmax(X, w, target_index), W)<jupyter_output>Gradient check passed!<jupyter_text>И теперь регуляризацияМы будем использовать L2 regularization для весов как часть общей функции ошибки.Напомним, L2 regularization определяется какl2_reg_loss = regularization_strength * sumij W[i, j]2Реализуйте функцию для его вычисления и вычисления соотвествующих градиентов.<jupyter_code># TODO Implement l2_regularization function that implements loss for L2 regularization
linear_classifer.l2_regularization(W, 0.01)
check_gradient(lambda w: linear_classifer.l2_regularization(w, 0.01), W)<jupyter_output>Gradient check passed!<jupyter_text>Тренировка! Градиенты в порядке, реализуем процесс тренировки!<jupyter_code># TODO: Implement LinearSoftmaxClassifier.fit function
classifier = linear_classifer.LinearSoftmaxClassifier()
loss_history = classifier.fit(train_X, train_y, epochs=10, learning_rate=1e-3, batch_size=300, reg=1e1)
# let's look at the loss history!
plt.plot(loss_history)
# Let's check how it performs on validation set
pred = classifier.predict(val_X)
accuracy = multiclass_accuracy(pred, val_y)
print("Accuracy: ", accuracy)

# Now, let's train more and see if it performs better
classifier.fit(train_X, train_y, epochs=100, learning_rate=1e-3, batch_size=300, reg=1e1)
pred = classifier.predict(val_X)
accuracy = multiclass_accuracy(pred, val_y)
print("Accuracy after training for 100 epochs: ", accuracy)
print(pred, '\n', val_y)<jupyter_output>[9 8 7 8 5 4 9 8 9 7 7 0 8 7 7 0 9 9 7 7 7 5 8 0 8 7 8 7 1 7 5 7 8 8 8 8 8
 5 7 7 7 8 6 8 8 6 8 5 7 6 7 7 7 1 7 8 1 6 8 7 8 8 9 5 7 8 7 7 7 8 9 8 4 7
 8 7 7 0 8 9 8 7 8 8 8 8 7 9 8 7 7 7 8 1 0 2 9 8 7 8 8 5 0 8 6 8 0 6 4 7 7
 8 6 8 8 7 7 5 4 7 7 8 5 7 7 9 7 8 8 8 7 8 4 8 6 8 7 7 5 1 7 7 5 6 8 6 0 8
 5 6 8 6 4 7 9 8 1 7 7 5 8 0 8 7 7 8 8 7 7 9 9 8 5 8 8 7 8 8 8 5 7 8 7 7 8
 5 0 9 8 2 8 7 9 8 8 8 8 4 9 9 9 0 4 8 1 9 7 8 0 0 8 8 1 8 1 0 8 8 0 8 7 8
 7 6 0 7 0 1 7 1 7 6 9 5 8 5 5 7 1 0 7 7 8 7 7 5 7 7 7 6 7 9 1 0 8 5 7 4 7
 7 7 5 5 7 1 9 7 8 7 7 8 7 7 8 8 9 7 8 8 8 0 7 7 8 7 8 4 7 7 1 6 8 9 7 8 7
 9 9 8 8 7 9 1 7 7 8 8 8 8 8 5 8 9 9 6 8 8 7 7 1 7 7 7 7 7 7 8 5 8 6 8 7 8
 7 7 7 9 7 7 7 1 1 8 7 7 8 8 7 7 7 5 6 8 4 7 4 8 7 4 7 8 9 7 7 7 6 9 7 8 7
 7 5 4 7 8 8 7 8 8 7 1 8 8 7 8 8 7 7 8 8 6 7 8 8 7 7 7 8 8 7 7 7 8 7 0 6 7
 9 8 8 0 7 7 0 8 6 8 5 8 7 6 8 1 7 8 8 1 8 6 4 8 7 8 8 9 6 7 7 0 8 7 8 8 7
 7 7 5 7 8 4 8 7 9 7 5 7 8 8 7 7 8 7 4 8 8 8 1 8 8 7 7 8 7 7 9 8 5 9 7 7 5
 8 7 7 8 8 7 6 7 8 7 6 9 [...]<jupyter_text>Как и раньше, используем кросс-валидацию для подбора гиперпараметтов.В этот раз, чтобы тренировка занимала разумное время, мы будем использовать только одно разделение на тренировочные (training) и проверочные (validation) данные.Теперь нам нужно подобрать не один, а два гиперпараметра! Не ограничивайте себя изначальными значениями в коде.  Добейтесь точности более чем **20%** на проверочных данных (validation data).<jupyter_code>train_folds_X = np.array(np.array_split(train_X, 2));
train_folds_y = np.array(np.array_split(train_y, 2));
validation_y = train_folds_y[1];
print(np.concatenate((train_folds_X[:1], train_folds_X[2:]), axis=None)\
     .reshape((-1, train_X.shape[1])));
print(np.concatenate((train_folds_y[:1], train_folds_y[2:]),\
        axis=None).reshape((train_y.shape[0] - validation_y.shape[0])));
import itertools
num_epochs = 200;
batch_size = 300;

learning_rates = [1e-3, 1e-4, 1e-5];
reg_strengths = [1e1, 1e-0, 1e-1];

num_folds = 2;
train_folds_X = np.array(np.array_split(train_X, num_folds));
train_folds_y = np.array(np.array_split(train_y, num_folds));
parameters = list(itertools.product(learning_rates, reg_strengths));
print("parameters: ", parameters);

k_to_accuracy = {};

for j, k in enumerate(parameters):
# TODO: perform cross-validation
    print("k = (%.10f, %.10f), j = %i" % (k[0], k[1], j));
    accuracy_k = [];
    for i in range(num_folds):
        print("number_fold = ", i);
        
        validation_X = train_folds_X[i];
        validation_y = train_folds_y[i];
        
        train_X_i = np.concatenate((train_folds_X[:1], train_folds_X[2:]), axis=None)\
     .reshape((-1, train_X.shape[1]));
        
        train_y_i =  np.concatenate((train_folds_y[:i], train_folds_y[i+1:]),\
        axis=None).reshape((train_y.shape[0] - validation_y.shape[0]));
        
        classifier = linear_classifer.LinearSoftmaxClassifier();
        classifier.fit(train_X_i, train_y_i, epochs=num_epochs,\
                       learning_rate=k[0], batch_size=batch_size, reg=k[1]);
        pred = classifier.predict(validation_X);
        accuracy = multiclass_accuracy(pred, validation_y);
        accuracy_k.append(accuracy);
    k_to_accuracy[j] = sum(accuracy_k)/len(accuracy_k);
    
index_best = max(k_to_accuracy, key = k_to_accuracy.get);
k = parameters[index_best];
best_classifier = linear_classifer.LinearSoftmaxClassifier();
best_classifier.fit(train_X, train_y, epochs=num_epochs,\
                       learning_rate=k[0], batch_size=batch_size, reg=k[1]);
best_val_accuracy = k_to_accuracy[index_best];

# TODO use validation set to find the best hyperparameters
# hint: for best results, you might need to try more values for learning rate and regularization strength 
# than provided initially

print('best validation accuracy achieved: %f' % best_val_accuracy);
print(k_to_accuracy);
print(k, index_best);<jupyter_output>{0: 0.14622222222222223, 1: 0.1631111111111111, 2: 0.19077777777777777, 3: 0.1461111111111111, 4: 0.14822222222222223, 5: 0.15433333333333332, 6: 0.11822222222222223, 7: 0.12366666666666667, 8: 0.09622222222222222}
(0.001, 0.1) 2<jupyter_text>Какой же точности мы добились на тестовых данных?<jupyter_code>test_pred = best_classifier.predict(test_X);
test_accuracy = multiclass_accuracy(test_pred, test_y);
print('Linear softmax classifier test set accuracy: %f' % (test_accuracy, ));<jupyter_output>Linear softmax classifier test set accuracy: 0.240000
{'fraction_non_alphanumeric': 0.0873705428511851, 'fraction_numerical': 0.06582331271286578, 'mean_word_length': 4.977565434150395, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 9, 'word_list_counts': {'cursed_substrings.json': 79, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '435240', 'license': 'MIT', 'n_tokens_mistral': 6268, 'n_tokens_neox': 5966, 'n_words': 1692, 'path': 'assignments/assignment1/Linear classifier.ipynb', 'repo_name': 'INeedTractorPlz/dlcourse_ai', 'size': '14387'}