In [1]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn import preprocessing
%matplotlib inline
In [152]:
!wget --no-check-certificate -O space_ga.csv https://www.dropbox.com/s/t1webrwixdlaacv/space_ga.txt
SYSTEM_WGETRC = c:/progra~1/wget/etc/wgetrc
syswgetrc = C:\Program Files (x86)\GnuWin32/etc/wgetrc
--2019-09-17 21:26:15--  https://www.dropbox.com/s/t1webrwixdlaacv/space_ga.txt
Распознаётся www.dropbox.com... 162.125.70.1
Устанавливается соединение с www.dropbox.com|162.125.70.1|:443... соединение установлено.
ПРЕДУПРЕЖДЕНИЕ: невозможно проверить сертификат www.dropbox.com, запрошенный `/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 Extended Validation Server CA':
  Невозможно локально проверить подлинность запрашивающего.
Запрос HTTP послан, ожидается ответ... 301 Moved Permanently
Адрес: /s/raw/t1webrwixdlaacv/space_ga.txt [переход]
--2019-09-17 21:26:16--  https://www.dropbox.com/s/raw/t1webrwixdlaacv/space_ga.txt
Повторное использование соединения с www.dropbox.com:443.
Запрос HTTP послан, ожидается ответ... 302 Found
Адрес: https://uc1cf77b4122690b12903830ea96.dl.dropboxusercontent.com/cd/0/inline/Aov7B9e8kKEiwZK4-asYvtFSOCeXyeKR1bFR85kKQIgD-91BXgMCzuGMTB7oC7MNCa4IAJLu6qB80TmOb80R21qoZamB3-dek_YxZP3rn04J_wy9SH7Iz5qmWEw7ra00NH4/file# [переход]
--2019-09-17 21:26:16--  https://uc1cf77b4122690b12903830ea96.dl.dropboxusercontent.com/cd/0/inline/Aov7B9e8kKEiwZK4-asYvtFSOCeXyeKR1bFR85kKQIgD-91BXgMCzuGMTB7oC7MNCa4IAJLu6qB80TmOb80R21qoZamB3-dek_YxZP3rn04J_wy9SH7Iz5qmWEw7ra00NH4/file
Распознаётся uc1cf77b4122690b12903830ea96.dl.dropboxusercontent.com... 162.125.70.6
Устанавливается соединение с uc1cf77b4122690b12903830ea96.dl.dropboxusercontent.com|162.125.70.6|:443... соединение установлено.
ПРЕДУПРЕЖДЕНИЕ: невозможно проверить сертификат uc1cf77b4122690b12903830ea96.dl.dropboxusercontent.com, запрошенный `/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA':
  Невозможно локально проверить подлинность запрашивающего.
Запрос HTTP послан, ожидается ответ... 200 OK
Длина: 294009 (287K) [text/plain]
Сохраняется в каталог: `space_ga.csv'.

     0K .......... .......... .......... .......... .......... 17%  497K 0s
    50K .......... .......... .......... .......... .......... 34% 1,42M 0s
   100K .......... .......... .......... .......... .......... 52% 1,62M 0s
   150K .......... .......... .......... .......... .......... 69%  823K 0s
   200K .......... .......... .......... .......... .......... 87% 1,38M 0s
   250K .......... .......... .......... .......              100% 1,15M=0,3s

2019-09-17 21:26:17 (980 KB/s) - `space_ga.csv' сохранён [294009/294009]

Линейная регрессия с батч-оптимизацией(2 балла)

Рассмотрим случай, когда данных в выборке много. В таких случаях используется стохастическая или батч-оптимизация.

Загрузите данные из файла space_ga.csv и нормализуйте их. Мы будем предсказывать первый столбец по шести остальным. Эти данные получены с выборов в США в 1980 году.

In [2]:
df = pd.read_csv('space_ga.csv', header=None)
y = df[0].values
scaler = preprocessing.StandardScaler().fit(df)
df = pd.DataFrame(data=scaler.transform(df))
X = df[[1, 2, 3, 4, 5, 6]].values
In [3]:
X = np.hstack((np.ones(shape=(X.shape[0])).reshape((X.shape[0], 1)), X))
In [4]:
X
Out[4]:
array([[ 1.        ,  0.16387217,  0.16394269, ...,  0.17698859,
         0.43554499, -1.18627805],
       [ 1.        ,  0.86864003,  0.87261523, ...,  0.83016854,
         0.33851875, -1.57640498],
       [ 1.        , -0.02603615, -0.19424144, ..., -0.18037448,
         0.54470449, -1.32666104],
       ...,
       [ 1.        , -0.54962106, -0.36197441, ..., -0.27888291,
        -1.64789224,  0.62113899],
       [ 1.        , -0.76037493, -0.53517479, ..., -0.54653732,
        -1.39843617,  1.16297034],
       [ 1.        , -0.96280307, -0.77031372, ..., -0.72339845,
        -1.12640962,  1.14817537]])
In [5]:
y
Out[5]:
array([-0.66155884, -0.65085895, -0.61711449, ..., -0.69986989,
       -0.4847772 , -0.48917887])

Как вы могли заметить, датасет больше предыдущего. На нём мы попробуем батч-оптимизацию.

Измените функцию для минимизации написанную на семинаре так, чтобы на вход они принимала дополнительный параметр — размер батча. Запустите функцию при разных размерах батча. Прокомментируйте результаты.

In [6]:
def loss(X:np.ndarray, y:np.ndarray, w:np.ndarray, batch_size=None):
    if batch_size is None or batch_size > y.shape[0]:
        X_i, y_i = X, y
    else:
        index = np.random.choice(y.shape[0], batch_size, replace=False)
        X_i, y_i = X[index], y[index]
    return np.mean((X_i @ w - y_i)**2) / 2
def grad(X:np.ndarray, y:np.ndarray, w:np.ndarray, batch_size=None):
    if batch_size is None or batch_size > y.shape[0]:
        X_i, y_i = X, y
    else:
        index = np.random.choice(y.shape[0], batch_size, replace=False)
        X_i, y_i = X[index], y[index]
    return np.mean((X_i @ w - y_i)[..., None] * X_i, axis=0)
In [7]:
w = np.ones((X.shape[1]))
w
Out[7]:
array([1., 1., 1., 1., 1., 1., 1.])
In [8]:
def log_int_iterator(start, end, step):
    i = start
    last = None
    while i <= end:
        if int(i) != last:
            last = int(i)
            yield last
        i *= step
In [9]:
N = 3000
plt.figure(figsize=[20,5])
plt.xscale("log")
plt.xlim([0.9, X.shape[0]])
plt.title("Loss в зависимости от batch_size")
plt.xlabel("Batch size")
plt.ylabel("Loss")
history_y = []
history_x = []
np.random.seed(42)
for i in log_int_iterator(1, X.shape[0], 1.3):
    losses = [loss(X,y,w,i) for _ in range(N)]
    print(f"Batch_size:{i}\tloss_min:{np.min(losses)}\tmax:{np.max(losses)}\tmean:{np.mean(losses)}\tstd:{np.std(losses)}")
    plt.scatter([i] * len(losses), losses)
    history_x.append(i)
    history_y.append(np.mean(losses))
plt.plot(history_x, history_y)
Batch_size:1	loss_min:7.959173037699578e-06	max:142.49553060293263	mean:11.455074662900602	std:18.570715077194468
Batch_size:2	loss_min:0.005771680539743954	max:92.21223333814788	mean:11.278209979463908	std:13.733544359543814
Batch_size:3	loss_min:0.014319138409256275	max:91.06159696335686	mean:10.969667587886855	std:10.705300244261107
Batch_size:4	loss_min:0.04555522756687449	max:63.66078522468557	mean:11.28710506109287	std:9.503841676278237
Batch_size:6	loss_min:0.2675007234158316	max:49.95107771010779	mean:11.13550378693948	std:7.711796821219621
Batch_size:8	loss_min:0.5979022630274062	max:46.340225552505	mean:10.973549677170823	std:6.3864513914619705
Batch_size:10	loss_min:1.3907636721523482	max:44.26313320667451	mean:11.017790820596385	std:5.914881990922075
Batch_size:13	loss_min:1.3440580598776157	max:37.62042841493986	mean:11.268218337625285	std:5.174515292570247
Batch_size:17	loss_min:1.9720054142466077	max:32.44390452265916	mean:11.178888790757483	std:4.563712262211154
Batch_size:23	loss_min:3.060234725419909	max:28.763412569018023	mean:11.264573400960415	std:3.9849468189051667
Batch_size:30	loss_min:2.880695743203707	max:24.66010966277238	mean:11.272165533790588	std:3.388581943469953
Batch_size:39	loss_min:3.217606890175206	max:23.92050416536532	mean:11.111467795206972	std:3.0007578115362583
Batch_size:51	loss_min:4.590945262763588	max:23.456545151017366	mean:11.21255687454923	std:2.629265176399722
Batch_size:66	loss_min:5.3100010738688965	max:20.008226192988328	mean:11.160732999944381	std:2.2601568783821686
Batch_size:86	loss_min:5.387455354311777	max:19.777393970816686	mean:11.178010810454799	std:1.978503875193646
Batch_size:112	loss_min:6.010745930245266	max:18.123977413385848	mean:11.160953657291044	std:1.763524727919367
Batch_size:146	loss_min:7.051601871963085	max:16.56602145707879	mean:11.201524894052987	std:1.5200432598215259
Batch_size:190	loss_min:6.784887943341635	max:15.868728534165223	mean:11.18277285758524	std:1.3027169491977535
Batch_size:247	loss_min:7.5480032546556535	max:15.469532618570678	mean:11.173583551813271	std:1.134317475495257
Batch_size:321	loss_min:7.264080887037502	max:15.017767236003714	mean:11.148876328308395	std:0.9865301200621456
Batch_size:417	loss_min:8.253052971114839	max:14.409326974278887	mean:11.168077810223995	std:0.8586247997874863
Batch_size:542	loss_min:8.498554526186409	max:13.711524564308455	mean:11.160677782284509	std:0.7249211465505733
Batch_size:705	loss_min:9.241050372583251	max:13.674496839238454	mean:11.15392941589541	std:0.6292979014839881
Batch_size:917	loss_min:8.837293996231942	max:13.088129007466593	mean:11.155121075872742	std:0.5262131849752445
Batch_size:1192	loss_min:9.519464622833436	max:12.76579381711672	mean:11.163589196314486	std:0.4304570343439265
Batch_size:1550	loss_min:10.018492978200358	max:12.285013131072196	mean:11.181914745029712	std:0.3337769877117651
Batch_size:2015	loss_min:10.331398554448276	max:11.952261932987982	mean:11.163409041411875	std:0.24481410823867192
Batch_size:2619	loss_min:10.698038595848374	max:11.63075027186389	mean:11.172204090258699	std:0.1438075035807572
Out[9]:
[<matplotlib.lines.Line2D at 0x20b30aa5cf8>]

Усредненный результат функции для минимизации практически не зависит от размера выборки, так как при каждом вычислении высчитывается средний результат для одной записи, который затем усредняется от количества экспериментов.

Loss для отдельного эксперимента сильно зависит от размера батча, при большем размере разброс значений невелик, однако при малых - сильно растет. Это можно объяснить природой данных - некоторые хорошо описывают обобщенное значение выборки, некоторые - плохо и, возможно, явяляются выбросами.

Возможный идеальный результат:

In [10]:
from sklearn.linear_model import LinearRegression
reg = LinearRegression(fit_intercept=False).fit(X, y)
print("Weights:", *[np.round(it, 5) for it in reg.coef_], sep='\t')
print("Loss: ", loss(X, y, reg.coef_))
Weights:	-0.57623	-0.9289	0.41618	0.5624	-0.11008	-0.01466	0.0589
Loss:  0.008124286633345435
In [11]:
import time

def super_optimizer_and_plotter_steps(learning_rate, X, y, 
                                      batch_size=None,
                                      w=None,
                                      eps = 0.0001,
                                      max_iter=10000, 
                                      learning_rate_mul=0.99):
    if w is None:
        w = np.zeros(shape=(X.shape[1]))
#     eps *= X.shape[1]
    np.random.seed(42)
    start = time.time()
    w_old = w + eps
    history = [loss(X, y, w)]
    i = 0
    while np.linalg.norm(w_old - w) > eps:
        learning_rate *= learning_rate_mul
        w_old = w
        w = w - learning_rate * grad(X, y, w, batch_size)
        history.append(loss(X, y, w))
        i += 1
        if i > max_iter:
            break
    print("Batch: {}\tSteps:{}\tLoss: {}\tTime: {}\tW: {weights}".format(batch_size, 
                                                                  i, 
                                                                  np.round(history[-1], 5), 
                                                                  np.round(time.time() - start, 3),
                                                                  weights=[np.round(it, 3) for it in w]))
#     print("Batch:", batch_size, "\tКоличество шагов:", len(history), "\tloss: ", history[-1], "\ttime:", time.time() - start)
    
#     
    plt.grid(True)
    plt.plot(history)
    plt.title("Descent trajectory. Batch: {} Steps: {}\nLoss: {}\nTime: {}".format(batch_size, i, history[-1], time.time() - start))
    plt.xlabel("# iteration")
    plt.ylabel("Loss")
In [12]:
plt.figure(figsize=[20,20])
i = 0
max_step = None
min_loss = None
max_loss = None
for batch in [None, 3000, 2000, 1000, 500, 200, 100, 50, 10, 7, 5, 2]:
    plt.subplot(4, 3, i + 1)
    super_optimizer_and_plotter_steps(0.4, X, y, batch)
    _, r = plt.xlim()
    if max_step is None or r > max_step:
        max_step = r
    l, r = plt.ylim()
    if min_loss is None or l < min_loss:
        min_loss = l
    if max_loss is None or r > max_loss:
        max_loss = r
    i += 1
    
for i in range(i):
    plt.subplot(4, 3, i + 1)
#     plt.subplot2grid((4,3),(i//3, i%3))
#     plt.subplot(431 + j)
    plt.xlim([0, max_step])
    plt.ylim([0.008, 0.05])
plt.tight_layout()
Batch: None	Steps:298	Loss: 0.01048	Time: 0.191	W: [-0.576, -0.167, 0.071, 0.096, -0.059, -0.031, 0.089]
Batch: 3000	Steps:300	Loss: 0.01048	Time: 0.511	W: [-0.576, -0.167, 0.071, 0.096, -0.059, -0.031, 0.089]
Batch: 2000	Steps:301	Loss: 0.01047	Time: 0.463	W: [-0.576, -0.169, 0.071, 0.099, -0.059, -0.031, 0.089]
Batch: 1000	Steps:315	Loss: 0.01049	Time: 0.386	W: [-0.576, -0.167, 0.074, 0.093, -0.057, -0.031, 0.089]
Batch: 500	Steps:362	Loss: 0.01047	Time: 0.346	W: [-0.577, -0.17, 0.074, 0.097, -0.059, -0.031, 0.09]
Batch: 200	Steps:374	Loss: 0.01046	Time: 0.345	W: [-0.576, -0.171, 0.076, 0.098, -0.06, -0.031, 0.089]
Batch: 100	Steps:408	Loss: 0.01047	Time: 0.391	W: [-0.575, -0.168, 0.071, 0.098, -0.058, -0.031, 0.089]
Batch: 50	Steps:396	Loss: 0.01048	Time: 0.368	W: [-0.574, -0.166, 0.074, 0.097, -0.063, -0.032, 0.09]
Batch: 10	Steps:459	Loss: 0.01071	Time: 0.43	W: [-0.575, -0.141, 0.079, 0.057, -0.054, -0.033, 0.095]
Batch: 7	Steps:495	Loss: 0.01077	Time: 0.458	W: [-0.573, -0.128, 0.079, 0.053, -0.066, -0.033, 0.095]
Batch: 5	Steps:455	Loss: 0.01067	Time: 0.433	W: [-0.574, -0.142, 0.09, 0.061, -0.067, -0.035, 0.094]
Batch: 2	Steps:475	Loss: 0.04235	Time: 0.401	W: [-0.568, -1.398, 1.191, -1.06, 1.189, 0.107, -0.017]
C:\Users\alex1\Anaconda3\lib\site-packages\ipykernel_launcher.py:20: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance.  In a future version, a new instance will always be created and returned.  Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance.

Масштаб по обоим осям на графиках привен к одному виду.

Итерации с $batch\_size < 50$ сильно колеблются, у них наблюдается увеличение количества шагов обучения, так как шаг в сторону антиградиента сильно зависит от тех элементов, которые были выбраны случайным образом.

В целом, все выборки с $batch\_size > 2$ привели к примерно одинаковым результатам.

Все выборки с $batch\_size >= 1000$ затратили примерно одинаковое количество шагов.

Все итерации с батч оптимизацией затратили больше времени, чем итерации без нее. Это можно объяснить тем, что тратится много времени для генерации случайных чисел и взятии элементов по данным индексам. Возможно, на больших выборках генерация случайных чисел и взятие индекса будет занимать меньше времени, чем перемножение больших матриц.

Двумерная классификация(1 балл)

Решим задачу 2D классификации синтетических данных.

In [3]:
with open('train.npy', 'rb') as fin:
    X = np.load(fin)
    
with open('target.npy', 'rb') as fin:
    y = np.load(fin)

plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Paired, s=20)
plt.show()

Как можно заметить, данные сверху линейно неразделимы. Поэтому мы должны добавить дополнительные признаки(или использовать нелинейную модель). Можно заметить, что гиперплоскость разделяющая два класса принимает форму круга, поэтому мы можем добавить квадратичные признаки чтобы сделать классы линейно разделимыми.

In [4]:
def expand(X):
    """
    Adds quadratic features. 
    This expansion allows your linear model to make non-linear separation.
    
    For each sample (row in matrix), compute an expanded row:
    [feature0, feature1, feature0^2, feature1^2, feature0*feature1, 1]
    
    :param X: matrix of features, shape [n_samples,2]
    :returns: expanded features of shape [n_samples,6]
    """
    X_expanded = np.zeros((X.shape[0], 6))
    X_expanded[:,0] = X[:, 0]
    X_expanded[:,1] = X[:, 1]
    X_expanded[:,2] = X[:, 0]**2
    X_expanded[:,3] = X[:, 1]**2
    X_expanded[:,4] = X[:, 0] * X[:,1]
    X_expanded[:,5] = np.ones(X.shape[0])
    return X_expanded
In [5]:
X_expanded = expand(X)
In [6]:
# simple test on random numbers

dummy_X = np.array([
        [0,0],
        [1,0],
        [2.61,-1.28],
        [-0.59,2.1]
    ])

# call your expand function
dummy_expanded = expand(dummy_X)

# what it should have returned:   x0       x1       x0^2     x1^2     x0*x1    1
dummy_expanded_ans = np.array([[ 0.    ,  0.    ,  0.    ,  0.    ,  0.    ,  1.    ],
                               [ 1.    ,  0.    ,  1.    ,  0.    ,  0.    ,  1.    ],
                               [ 2.61  , -1.28  ,  6.8121,  1.6384, -3.3408,  1.    ],
                               [-0.59  ,  2.1   ,  0.3481,  4.41  , -1.239 ,  1.    ]])

#tests
assert isinstance(dummy_expanded,np.ndarray), "please make sure you return numpy array"
assert dummy_expanded.shape == dummy_expanded_ans.shape, "please make sure your shape is correct"
assert np.allclose(dummy_expanded,dummy_expanded_ans,1e-3), "Something's out of order with features"

print("Seems legit!")
Seems legit!

Логистическая регрессия(3 балла)

Для классификации объектов мы будем получать вероятность того что объект принадлежит к классу '1'. Чтобы предсказывать вероятность мы будем использовать вывод линейной модели и логистической функции:

$$ a(x; w) = \langle w, x \rangle $$$$ P( y=1 \; | \; x, \, w) = \dfrac{1}{1 + \exp(- \langle w, x \rangle)} = \sigma(\langle w, x \rangle)$$
In [7]:
def probability(X, w):
    """
    Given input features and weights
    return predicted probabilities of y==1 given x, P(y=1|x), see description above
        
    Don't forget to use expand(X) function (where necessary) in this and subsequent functions.
    
    :param X: feature matrix X of shape [n_samples,6] (expanded)
    :param w: weight vector w of shape [6] for each of the expanded features
    :returns: an array of predicted probabilities in [0,1] interval.
    """

    return 1/(1 + np.exp(-X.dot(w)))
In [8]:
dummy_weights = np.linspace(-1, 1, 6)
ans_part1 = probability(X_expanded[:1, :], dummy_weights)[0]

Для логистической регрессии оптимальное значение весов $w$ находится с помощью минимизации кросс-энтропии:

Loss для одного сэмпла: $$ l(x_i, y_i, w) = - \left[ {y_i \cdot log P(y_i = 1 \, | \, x_i,w) + (1-y_i) \cdot log (1-P(y_i = 1\, | \, x_i,w))}\right] $$

Loss для нескольких сэмплов: $$ L(X, \vec{y}, w) = {1 \over \ell} \sum_{i=1}^\ell l(x_i, y_i, w) $$

In [9]:
def compute_loss(X, y, w):
    """
    Given feature matrix X [n_samples,6], target vector [n_samples] of 1/0,
    and weight vector w [6], compute scalar loss function L using formula above.
    Keep in mind that our loss is averaged over all samples (rows) in X.
    """
    n = X.shape[0]
    prob = probability(X, w)
    return -np.sum(y * np.log(prob) + (1-y) * np.log(1 - prob))/n

Т.к мы обучаем нашу модель с помощью градиентного спуска мы должны считать градиенты. Для этого нам нужны производные функции потерь по каждому из весов.

$$ \nabla_w L = {1 \over \ell} \sum_{i=1}^\ell \nabla_w l(x_i, y_i, w) $$

Выведите формулу для подсчета градиента.

In [10]:
def compute_grad(X, y, w):
    """
    Given feature matrix X [n_samples,6], target vector [n_samples] of 1/0,
    and weight vector w [6], compute vector [6] of derivatives of L over each weights.
    Keep in mind that our loss is averaged over all samples (rows) in X.
    """
    prob = probability(X, w) - y
    return prob.dot(X)/X.shape[0]

Вспомогательная функция для визуализации предсказаний:

In [11]:
from IPython import display

h = 0.01
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))

def visualize(X, y, w, history):
    """draws classifier prediction with matplotlib magic"""
    Z = probability(expand(np.c_[xx.ravel(), yy.ravel()]), w)
    Z = Z.reshape(xx.shape)
    plt.subplot(1, 2, 1)
    plt.contourf(xx, yy, Z, alpha=0.8)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Paired)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    
    plt.subplot(1, 2, 2)
    plt.plot(history)
    plt.grid()
    ymin, ymax = plt.ylim()
    plt.ylim(0, ymax)
    display.clear_output(wait=True)
    plt.show()
In [12]:
visualize(X, y, dummy_weights, [0.5, 0.5, 0.25])

Обучение

В данной секции мы будем использовать функции, написанные вами, чтобы обучить наш классификатор с помощью стохастического градиентного спуска.

Mini-batch SGD(1 балл)

Стохастический градиентный спуск берет рандомный батч из $m$ сэмплов на каждой итерации, подсчитывает градиент функции потерь на этом батче и делает шаг градиентного спуска:

$$ w_t = w_{t-1} - \eta \dfrac{1}{m} \sum_{j=1}^m \nabla_w l(x_{i_j}, y_{i_j}, w_t) $$
In [13]:
np.random.seed(42)
w = np.array([0, 0, 0, 0, 0, 1])

eta= 0.1 # learning rate

n_iter = 100
batch_size = 4
loss = np.zeros(n_iter)
plt.figure(figsize=(12, 5))

for i in range(n_iter):
    ind = np.random.choice(X_expanded.shape[0], batch_size)
    loss[i] = compute_loss(X_expanded, y, w)
    if i % 10 == 0:
        visualize(X_expanded[ind, :], y[ind], w, loss)

    # Keep in mind that compute_grad already does averaging over batch for you!
    # TODO:<your code here>
    w = w - eta * compute_grad(X_expanded[ind, :], y[ind], w)
    
visualize(X, y, w, loss)
plt.clf()
<Figure size 432x288 with 0 Axes>

SGD with momentum(1 балл)

Momentum это метод позволяющий корректировать шаг SGD в нужное направление и уменьшать осцилляции как показано на рисунке. Данный эффект достигается с помощью добавления предыдущих шагов с коэффициентом $\alpha$ к текущему градиенту для каждого шага с обновлением весов.

$$ \nu_t = \alpha \nu_{t-1} + \eta\dfrac{1}{m} \sum_{j=1}^m \nabla_w l(x_{i_j}, y_{i_j}, w_t) $$$$ w_t = w_{t-1} - \nu_t$$


In [14]:
np.random.seed(42)
w = np.array([0, 0, 0, 0, 0, 1])

eta = 0.05 # learning rate
alpha = 0.9 # momentum
nu = np.zeros_like(w)

n_iter = 100
batch_size = 4
loss = np.zeros(n_iter)
plt.figure(figsize=(12, 5))

for i in range(n_iter):
    ind = np.random.choice(X_expanded.shape[0], batch_size)
    loss[i] = compute_loss(X_expanded, y, w)
    if i % 10 == 0:
        visualize(X_expanded[ind, :], y[ind], w, loss)

    nu = nu * alpha + eta * compute_grad(X_expanded[ind, :], y[ind], w)
    w = w - nu

visualize(X, y, w, loss)
plt.clf()
<Figure size 432x288 with 0 Axes>

ADAM(2 балла)

Реализуйте метод ADAM, использующий градиенты и квадраты градиентов сглаженные экспоненциальным скользящим средним:

\begin{eqnarray} m_t &=& \beta_1 m_{t-1} + (1-\beta_1) g_t\\ s_t &=& \beta_2 s_{t-1} + (1-\beta_2) g_t^2 \\ w_t &=& w_{t-1} - \eta \times \frac{\sqrt{ 1 - \beta_2^t}}{ 1 - \beta_1^t} \times \frac{ m_t }{ \sqrt{s_t+eps}} \end{eqnarray}
In [16]:
np.random.seed(42)

w = np.array([0, 0, 0, 0, 0, 1.])
m = np.zeros(w.shape)
s = np.zeros(w.shape)


eta = 0.1 # learning rate
beta_1 = 0.9 # moving average of gradient
beta_2 = 0.999 # moving average of gradient norm squared
g2 = None # we start with None so that you can update this value correctly on the first iteration
eps = 1e-8

n_iter = 100
batch_size = 4
loss = np.zeros(n_iter)
plt.figure(figsize=(12,5))
for i in range(n_iter):
    ind = np.random.choice(X_expanded.shape[0], batch_size)
    loss[i] = compute_loss(X_expanded, y, w)
    if i % 10 == 0:
        visualize(X_expanded[ind, :], y[ind], w, loss)

    # TODO:<your code here>
    g = compute_grad(X_expanded[ind, :], y[ind], w)
    m = beta_1 * m + (1 - beta_1) * g
    s = beta_2 * s + (1 - beta_2) * g**2
    w = w - eta * np.sqrt(1 - beta_2 ** (i + 1)) / (1 - beta_1 ** (i + 1)) * m / np.sqrt(s + eps)
visualize(X, y, w, loss)
plt.clf()
<Figure size 432x288 with 0 Axes>
In [ ]: