경사하강법은 가중치 업데이트에 사용하는 샘플(입력데이터)의 개수에 따라
배치 경사 하강법 : 모든 샘플을 사용
확률적 경사 하강법 : 무작위로 1개의 샘플을 뽑아 사용
미니 배치 경사 하강법 : 무작위로 여러개의 샘플을 뽑아 사용
3가지로 나눌 수 있다.
이번 포스팅에서는 배치 경사 하강법을 통해서 가중치 업데이트를 하는 로지스틱 회귀 모델을 구현해본다.
Library Import
import numpy as np
import matplotlib.pyplot as plt
Data Load
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train_all, x_test, y_train_all, y_test = train_test_split(x,y,stratify=y,test_size=0.2,random_state=42)
x_train, x_val, y_train, y_val = train_test_split(x_train_all, y_train_all, stratify=y_train_all, test_size=0.2, random_state=42)
Model Building
class SingleLayer :
def __init__(self, l1=0, l2=0, learning_rate=0.1) :
self.w = None
self.b = None
self.l1 = l1
self.l2 = l2
self.lr = learning_rate
self.w_history = []
self.losses = []
self.val_losses = []
def forpass(self, x) :
z = np.dot(x,self.w) + self.b
return z
def backprop(self, x, err) :
m = len(x)
w_grad = np.dot(x.T,err) / m
b_grad = np.sum(err) / m
return w_grad, b_grad
def activation(self, z) :
a = 1 / (1+np.exp(-z))
return a
def reg_loss(self) :
return self.l1*np.sum(np.abs(self.w)) + self.l2*np.sum(self.w**2)*1/2
def update_val_loss(self,x_val,y_val) :
z = self.forpass(x_val)
a = self.activation(z)
a = np.clip(a,1e-10,1-1e-10)
val_loss = np.sum(-(y_val*np.log(a) + (1-y_val)*np.log(1-a)))
self.val_losses.append((val_loss + self.reg_loss())/len(y_val))
def fit(self, x, y, epochs=100, x_val=None, y_val=None) :
y = y.reshape(-1,1)
m = len(x)
y_val = y_val.reshape(-1,1)
self.w = np.ones((x.shape[1],1))
self.b = 0
self.w_history.append(self.w.copy())
for i in range(epochs) :
z = self.forpass(x)
a = self.activation(z)
err = - (y - a)
w_grad, b_grad = self.backprop(x,err)
w_grad += (self.l1*np.sign(self.w) + self.l2*self.w)
self.w -= w_grad * self.lr
self.b -= b_grad * self.lr
self.w_history.append(self.w.copy())
a = np.clip(a, 1e-10, 1-1e-10)
loss = np.sum(-(y*np.log(a) + (1-y)*np.log(1-a)))
self.losses.append((loss + self.reg_loss()) / m)
self.update_val_loss(x_val, y_val)
def predict(self, x) :
z = self.forpass(x)
return z > 0
def score(self, x, y) :
return np.mean(self.predict(x) == y.reshape(-1,1))
Data Preprocessing
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train)
x_train_scaled = scaler.transform(x_train)
x_val_scaled = scaler.transform(x_val)
Model Fitting
singlelayer = SingleLayer()
singlelayer.fit(x_train_scaled, y_train, x_val=x_val_scaled, y_val=y_val, epochs=10000)
singlelayer.score(x_val_scaled, y_val)
# 0.978021978021978
확률적 경사 하강법은 훈련 세트의 샘플 개수가 364개일 때 100번의 에포크를 수행하면 총 36,400번의 가중치 업데이트가 일어난다. 반면 배치 경사 하강법은 전체 훈련 세트를 한 번에 계산한 다음 오차를 역전파하기 때문에 100번의 에포크를 수행하면 가중치는 100번만 업데이트 된다. 그래서 에포크 숫자를 10000회로 설정했다.
Loss Graph
plt.ylim(0,0.3)
plt.plot(singlelayer.losses)
plt.plot(singlelayer.val_losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train_loss','val_loss'])
plt.show()
확률적 경사 하강법을 사용했던 이전 포스팅에서는 샘플을 선택하는 순서에 따라 에포크마다 계산된 손실값이 들쭉날쭉했다. 반면 배치 경사 하강법은 전체 샘플을 사용하여 가중치를 업데이트하기 때문에 손실값이 안정적으로 감소한다.
Weights Graph
w2 = []
w3 = []
for w in singlelayer.w_history :
w2.append(w[2])
w3.append(w[3])
plt.plot(w2,w3)
plt.plot(w2[-1], w3[-1], 'ro')
plt.xlabel('w[2]')
plt.ylabel('w[3]')
plt.show()
손실값과 유사하게 가중치 그래프 역시 부드러운 곡선의 형태를 띈다.
장점과 단점.
배치 경사 하강법은 가중치를 1번 업데이트할 때 전체 샘플을 사용하므로 손실함수의 전역 최솟값을 안정적으로 찾는다는 장점이 있다. 하지만 사용되는 데이터 수가 많은 만큼 알고리즘 1번 수행당 계산 비용이 많이 든다는 단점이 있다. 그래서 데이터 개수가 너무 많을 때는 배치 경사 하강법을 사용하지 못하는 경우도 있다.
Reference.
도서 <Do it! 코딩으로 배우는 딥러닝 입문> 이지스 퍼블리싱, 박해선 지음
경사하강법의 종류
[ML] 경사 하강법(Gradient Descent) 개념 및 종류
경사하강법이란? 참고 : https://wooono.tistory.com/91 배치란? GPU가 한 번에 처리하는 데이터의 묶음이다. 단일 반복 시 배치 단위마다 경사하강법의 기울기가 업데이트 된다. 배치가 너무 커지면 단일
wooono.tistory.com
끝.