В этом небольшом эксперименте мы увидим, чем мини-пакетный градиентный спуск лучше, чем ванильный градиентный спуск.

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

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

схема:

  1. генерировать данные
  2. написать класс FeedForwardNeuralNetwork
  3. наблюдать за результатами ванильного 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. Мини-пакет работает лучше, чем ванильный градиентный спуск, с большим отрывом.
  2. когда размер пакета равен 1, мы видим, что у нас есть заметные колебания на кривой, потому что мы меняем наши параметры, видя только одну точку данных, которая корректирует потери только в соответствии с этой точкой данных, таким образом, для всех точек данных мы получим приличную сумму движение вверх и вниз по кривой.
  3. Производительность модели снижается по мере увеличения размера пакета.