В этом небольшом эксперименте мы увидим, чем мини-пакетный градиентный спуск лучше, чем ванильный градиентный спуск.
Для нашего эксперимента мы будем использовать небольшую сеть, как показано ниже.
В классе нейронной сети мы будем кодировать как ванильный градиентный спуск, так и мини-пакетный градиентный спуск.
схема:
- генерировать данные
- написать класс FeedForwardNeuralNetwork
- наблюдать за результатами ванильного GD, а также мини-пакетного GD
Некоторый импорт
import numpy as np import matplotlib.pyplot as plt import matplotlib.colors import pandas as pd from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, mean_squared_error, log_loss from tqdm import tqdm_notebook import seaborn as sns from sklearn.datasets import make_blobs my_cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["red","yellow","green"]) np.random.seed(0)
Создание и визуализация данных
# creating complex binary classification data data, labels = make_blobs(n_samples=1000, centers=4, n_features=2, random_state=0) labels_orig = labels labels = np.mod(labels_orig, 2) plt.scatter(data[:,0], data[:,1], c=labels, cmap=my_cmap) plt.show()
Внедрение нейронной сети с нуля
class FFNetwork: def __init__(self,algo): np.random.seed(0) self.w1 = np.random.randn() self.w2 = np.random.randn() self.w3 = np.random.randn() self.w4 = np.random.randn() self.w5 = np.random.randn() self.w6 = np.random.randn() self.b1 = 0 self.b2 = 0 self.b3 = 0 self.algo = algo def sigmoid(self, x): return 1.0/(1.0 + np.exp(-x)) def forward_pass(self, x): self.x1, self.x2 = x # x will contain 2 value. self.a1 = self.w1*self.x1 + self.w2*self.x2 + self.b1 self.h1 = self.sigmoid(self.a1) self.a2 = self.w3*self.x1 + self.w4*self.x2 + self.b2 self.h2 = self.sigmoid(self.a2) self.a3 = self.w5*self.h1 + self.w6*self.h2 + self.b3 self.h3 = self.sigmoid(self.a3) return self.h3 def grad(self, x, y): # just treat it as black box. self.forward_pass(x) self.dw5 = (self.h3-y) * self.h3*(1-self.h3) * self.h1 self.dw6 = (self.h3-y) * self.h3*(1-self.h3) * self.h2 self.db3 = (self.h3-y) * self.h3*(1-self.h3) self.dw1 = (self.h3-y) * self.h3*(1-self.h3) * self.w5 * self.h1*(1-self.h1) * self.x1 self.dw2 = (self.h3-y) * self.h3*(1-self.h3) * self.w5 * self.h1*(1-self.h1) * self.x2 self.db1 = (self.h3-y) * self.h3*(1-self.h3) * self.w5 * self.h1*(1-self.h1) self.dw3 = (self.h3-y) * self.h3*(1-self.h3) * self.w6 * self.h2*(1-self.h2) * self.x1 self.dw4 = (self.h3-y) * self.h3*(1-self.h3) * self.w6 * self.h2*(1-self.h2) * self.x2 self.db2 = (self.h3-y) * self.h3*(1-self.h3) * self.w6 * self.h2*(1-self.h2) def fit(self, X, Y, epochs=1, learning_rate=1, initialise=True, display_loss=False, mini_batch_size = 1): # initialise w, b if initialise: self.w1 = np.random.randn() self.w2 = np.random.randn() self.w3 = np.random.randn() self.w4 = np.random.randn() self.w5 = np.random.randn() self.w6 = np.random.randn() self.b1 = 0 self.b2 = 0 self.b3 = 0 self.X = X self.Y = Y if display_loss: loss = {} if self.algo == "GD": for i in tqdm_notebook(range(epochs), total=epochs, unit="epoch"): dw1, dw2, dw3, dw4, dw5, dw6, db1, db2, db3 = [0]*9 for x, y in zip(X, Y): self.grad(x, y) dw1 += self.dw1 # self.dw1 is initialized in the derevative function dw2 += self.dw2 dw3 += self.dw3 dw4 += self.dw4 dw5 += self.dw5 dw6 += self.dw6 db1 += self.db1 db2 += self.db2 db3 += self.db3 m = X.shape[1] self.w1 -= learning_rate * dw1 / m self.w2 -= learning_rate * dw2 / m self.w3 -= learning_rate * dw3 / m self.w4 -= learning_rate * dw4 / m self.w5 -= learning_rate * dw5 / m self.w6 -= learning_rate * dw6 / m self.b1 -= learning_rate * db1 / m self.b2 -= learning_rate * db2 / m self.b3 -= learning_rate * db3 / m if display_loss: Y_pred = self.predict(X) loss[i] = mean_squared_error(Y_pred, Y) if self.algo == "MiniBatch": for i in tqdm_notebook(range(epochs), total=epochs, unit="epoch"): dw1, dw2, dw3, dw4, dw5, dw6, db1, db2, db3 = [0]*9 points_seen = 0 for x, y in zip(X, Y): self.grad(x, y) dw1 += self.dw1 dw2 += self.dw2 dw3 += self.dw3 dw4 += self.dw4 dw5 += self.dw5 dw6 += self.dw6 db1 += self.db1 db2 += self.db2 db3 += self.db3 points_seen += 1 if points_seen % mini_batch_size == 0: m = mini_batch_size self.w1 -= learning_rate * dw1 / m self.w2 -= learning_rate * dw2 / m self.w3 -= learning_rate * dw3 / m self.w4 -= learning_rate * dw4 / m self.w5 -= learning_rate * dw5 / m self.w6 -= learning_rate * dw6 / m self.b1 -= learning_rate * db1 / m self.b2 -= learning_rate * db2 / m self.b3 -= learning_rate * db3 / m dw1, dw2, dw3, dw4, dw5, dw6, db1, db2, db3 = [0]*9 if display_loss: Y_pred = self.predict(X) loss[i] = mean_squared_error(Y_pred, Y) if display_loss: plt.plot(loss.values()) plt.xlabel('Epochs') plt.ylabel('Mean Squared Error') plt.show() def predict(self, X): Y_pred = [] for x in X: y_pred = self.forward_pass(x) Y_pred.append(y_pred) return np.array(Y_pred)
Подготовка данных
X_train, X_val, Y_train, Y_val = train_test_split(data, labels, stratify=labels, random_state=0)
Использование сети
n = FFNetwork(algo = "GD") # using vanilla gradient descent n.fit(X_train,Y_train,epochs = 10,display_loss = True,mini_batch_size=128)
Критерий эффективности: измерение показателя точности.
y_pred_train = n.predict(X_train) y_pred_bin_train = (y_pred_train >= 0.5).astype('int').ravel() y_pred_val = n.predict(X_val) y_pred_bin_val = (y_pred_val >=0.5).astype('int').ravel() acc_train = accuracy_score(y_pred_bin_train,Y_train) acc_val = accuracy_score(y_pred_bin_val,Y_val) print('train accuracy : ',round(acc_train,2)) print('validation accuracy',round(acc_val,2))
Вывод :
точность поезда: 0,5
точность проверки: 0,5
# using mini-batch GD with batch size of 1 ,i.e updating my parameters every time i see a new data point. n = FirstFFNetwork(algo = "MiniBatch") n.fit(X_train,Y_train,epochs = 10,display_loss = True,mini_batch_size=1)
Мера производительности:
точность поезда: 0,92
точность проверки: 0,91
# using mini-batch GD with batch size of 64 ,i.e updating my parameters for every 64 data points. n = FirstFFNetwork(algo = "MiniBatch") n.fit(X_train,Y_train,epochs = 10,display_loss = True,mini_batch_size=64)
Мера производительности:
точность поезда: 0,72
точность проверки: 0,71
# using mini-batch GD with batch size of 128 ,i.e updating my parameters for every 128 data points. n = FirstFFNetwork(algo = "MiniBatch") n.fit(X_train,Y_train,epochs = 10,display_loss = True,mini_batch_size=128)
Мера производительности:
точность поезда: 0,71
точность проверки: 0,70
Наблюдения:
- Мини-пакет работает лучше, чем ванильный градиентный спуск, с большим отрывом.
- когда размер пакета равен 1, мы видим, что у нас есть заметные колебания на кривой, потому что мы меняем наши параметры, видя только одну точку данных, которая корректирует потери только в соответствии с этой точкой данных, таким образом, для всех точек данных мы получим приличную сумму движение вверх и вниз по кривой.
- Производительность модели снижается по мере увеличения размера пакета.