59doit
다중분류 신경망(1) 본문
다중분류의 경사하강법은 이진분류의 경사하강법과 원리는 같고
소프트맥스 함수가 추가된 점만 다름
소프트맥스 함수 추가
# 필요한 모듈 및 임포트
import numpy as np |
# MultiClassNetwork 뉴런생성
class MultiClassNetwork: def __init__(self, units=10, batch_size=32, learning_rate=0.1, l1=0, l2=0): self.units = units # 은닉층의 뉴런 개수 self.batch_size = batch_size # 배치 크기 self.w1 = None # 은닉층의 가중치 self.b1 = None # 은닉층의 절편 self.w2 = None # 출력층의 가중치 self.b2 = None # 출력층의 절편 self.a1 = None # 은닉층의 활성화 출력 self.losses = [] # 훈련 손실 self.val_losses = [] # 검증 손실 self.lr = learning_rate # 학습률 self.l1 = l1 # L1 손실 하이퍼파라미터 self.l2 = l2 # L2 손실 하이퍼파라미터 |
# 정방향계산하기
def forpass(self, x): z1 = np.dot(x, self.w1) + self.b1 # 첫 번째 층의 선형 식을 계산합니다 self.a1 = self.sigmoid(z1) # 활성화 함수를 적용합니다 z2 = np.dot(self.a1, self.w2) + self.b2 # 두 번째 층의 선형 식을 계산합니다. return z2 |
▶activation 메서드 이름을 sigmoid 로 바꿨으니 self.a1 = self.sigmoid 로 바꿔준다
#
def sigmoid(self, z): z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해 a = 1 / (1 + np.exp(-z)) # 시그모이드 계산 return a |
▶ 다중분류에서는 마지막 출력층에 소프트맥스 함수를 사용해야하므로
은닉층과 출력층에 다른 함수를 적용해야 한다
activation -> sigmoid 함수로 변경
# 소프트맥스 함수 추가
def softmax(self, z): # 소프트맥스 함수 z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해 exp_z = np.exp(z) return exp_z / np.sum(exp_z, axis=1).reshape(-1, 1) |
▶ z의 각 행의 합을 계산
형태는 행 벡터이므로 형태를 열벡터로 바꿔야 나눗셈에 적용할 수 있다.
# 가중치추가하기
def init_weights(self, n_features, n_classes): self.w1 = np.random.normal(0, 1, (n_features, self.units)) # (특성 개수, 은닉층의 크기) self.b1 = np.zeros(self.units) # 은닉층의 크기 self.w2 = np.random.normal(0, 1, (self.units, n_classes)) # (은닉층의 크기, 클래스 개수) self.b2 = np.zeros(n_classes) |
▶ 출력층의 뉴런이 2개이상이므로 가중치 w2의 크기는 (은닉층의 크기, 클래스 개수) 가 된다
random.normal 함수를 이용하여 배열의 각 원소의 값을
정규분포를 따르는 무작위 수로 초기화한다.
▶ b2의 크기는 클래스 개수에 따라 지정한다
b2는 0으로 초기화한다.
# 훈련의 진행상황을 살펴볼 수 있도록 에포크마다'.' 출력되도록한다.
# epochs만큼 반복합니다. for i in range(epochs): loss = 0 print('.', end='') |
# training메서드
def training(self, x, y): m = len(x) # 샘플 개수를 저장합니다. z = self.forpass(x) # 정방향 계산을 수행합니다. a = self.softmax(z) # 활성화 함수를 적용합니다. err = -(y - a) # 오차를 계산합니다. # 오차를 역전파하여 그래디언트를 계산합니다. w1_grad, b1_grad, w2_grad, b2_grad = self.backprop(x, err) # 그래디언트에서 페널티 항의 미분 값을 뺍니다 w1_grad += (self.l1 * np.sign(self.w1) + self.l2 * self.w1) / m w2_grad += (self.l1 * np.sign(self.w2) + self.l2 * self.w2) / m # 은닉층의 가중치와 절편을 업데이트합니다. self.w1 -= self.lr * w1_grad self.b1 -= self.lr * b1_grad # 출력층의 가중치와 절편을 업데이트합니다. self.w2 -= self.lr * w2_grad self.b2 -= self.lr * b2_grad return a |
▶activation -> softmax 함수로 변경
# predict메서드
def predict(self, x): z = self.forpass(x) # 정방향 계산을 수행합니다. return np.argmax(z, axis=1) # 가장 큰 값의 인덱스를 반환합니다. |
▶ 정방향 계산에서 얻은 출력 중 가장 큰 값의 인덱스를 구한다. 이값이 예측 클래스가 된다.
클래스를 예측 할 때는 활성화 함수, 소프트 맥스 함수를 거칠 필요가 없다.
출력층에서 계산된 선형 계산만으로 클래스를 예측 할 수 있다.
# sore메서드
def score(self, x, y): # 예측과 타깃 열 벡터를 비교하여 True의 비율을 반환합니다. return np.mean(self.predict(x) == np.argmax(y, axis=1)) def reg_loss(self): # 은닉층과 출력층의 가중치에 규제를 적용합니다. return self.l1 * (np.sum(np.abs(self.w1)) + np.sum(np.abs(self.w2))) + \ self.l2 / 2 * (np.sum(self.w1 ** 2) + np.sum(self.w2 ** 2)) |
▶ predict결과와 타킷 y의 클래스를 비교한다.
배열 y의 행을 따라 가장 큰 값의 인덱스를 구해 사용한다.
# 검증 손실 계산하기
def update_val_loss(self, x_val, y_val): z = self.forpass(x_val) # 정방향 계산을 수행합니다. a = self.softmax(z) # 활성화 함수를 적용합니다. a = np.clip(a, 1e-10, 1 - 1e-10) # 출력 값을 클리핑합니다. # 크로스 엔트로피 손실과 규제 손실을 더하여 리스트에 추가합니다. val_loss = np.sum(-y_val * np.log(a)) self.val_losses.append((val_loss + self.reg_loss()) / len(y_val)) |
▶ 사용하는 활성화 함수를 softmax로 바꾼다
로지스틱 손실 계산을 크로스 엔트로피 손실 계산으로 바꾼다.
# 최종코드
class MultiClassNetwork: def __init__(self, units=10, batch_size=32, learning_rate=0.1, l1=0, l2=0): self.units = units # 은닉층의 뉴런 개수 self.batch_size = batch_size # 배치 크기 self.w1 = None # 은닉층의 가중치 self.b1 = None # 은닉층의 절편 self.w2 = None # 출력층의 가중치 self.b2 = None # 출력층의 절편 self.a1 = None # 은닉층의 활성화 출력 self.losses = [] # 훈련 손실 self.val_losses = [] # 검증 손실 self.lr = learning_rate # 학습률 self.l1 = l1 # L1 손실 하이퍼파라미터 self.l2 = l2 # L2 손실 하이퍼파라미터 """ <정방향 계산하기> activation 메서드 이름을 sigmoid 로 바꿨으니 self.a1 = self.sigmoid 로 바꿔준다 """ def forpass(self, x): z1 = np.dot(x, self.w1) + self.b1 # 첫 번째 층의 선형 식을 계산합니다 self.a1 = self.sigmoid(z1) # 활성화 함수를 적용합니다 z2 = np.dot(self.a1, self.w2) + self.b2 # 두 번째 층의 선형 식을 계산합니다. return z2 def backprop(self, x, err): m = len(x) # 샘플 개수 # 출력층의 가중치와 절편에 대한 그래디언트를 계산합니다. w2_grad = np.dot(self.a1.T, err) / m b2_grad = np.sum(err) / m # 시그모이드 함수까지 그래디언트를 계산합니다. err_to_hidden = np.dot(err, self.w2.T) * self.a1 * (1 - self.a1) # 은닉층의 가중치와 절편에 대한 그래디언트를 계산합니다. w1_grad = np.dot(x.T, err_to_hidden) / m b1_grad = np.sum(err_to_hidden, axis=0) / m return w1_grad, b1_grad, w2_grad, b2_grad """ 다중분류에서는 마지막 출력층에 소프트맥스 함수를 사용해야하므로 은닉층과 출력층에 다른 함수를 적용해야 한다 activation -> sigmoid 함수로 변경 """ def sigmoid(self, z): z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해 a = 1 / (1 + np.exp(-z)) # 시그모이드 계산 return a """ <소트트맥스 함수 추가하기> softmax 추가 """ def softmax(self, z): # 소프트맥스 함수 z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해 exp_z = np.exp(z) """ z의 각 행의 합을 계산 형태는 행 벡터이므로 형태를 열벡터로 바꿔야 나눗셈에 적용할 수 있다. """ return exp_z / np.sum(exp_z, axis=1).reshape(-1, 1) """ <가중치 초기화하기> """ def init_weights(self, n_features, n_classes): self.w1 = np.random.normal(0, 1, (n_features, self.units)) # (특성 개수, 은닉층의 크기) self.b1 = np.zeros(self.units) # 은닉층의 크기 """ 출력층의 뉴런이 2개이상이므로 가중치 w2의 크기는 (은닉층의 크기, 클래스 개수) 가 된다 random.normal 함수를 이용하여 배열의 각 원소의 값을 정규분포를 따르는 무작위 수로 초기화한다. """ self.w2 = np.random.normal(0, 1, (self.units, n_classes)) # (은닉층의 크기, 클래스 개수) """ b2의 크기는 클래스 개수에 따라 지정한다 b2는 0으로 초기화한다. """ self.b2 = np.zeros(n_classes) def fit(self, x, y, epochs=100, x_val=None, y_val=None): np.random.seed(42) self.init_weights(x.shape[1], y.shape[1]) # 은닉층과 출력층의 가중치를 초기화합니다. # epochs만큼 반복합니다. for i in range(epochs): loss = 0 """ 훈련의 진행 상황을 살펴복 수 있도록 에포크마다 '.'이 출력되도록한다 """ print('.', end='') # 제너레이터 함수에서 반환한 미니배치를 순환합니다. for x_batch, y_batch in self.gen_batch(x, y): a = self.training(x_batch, y_batch) # 안전한 로그 계산을 위해 클리핑합니다. a = np.clip(a, 1e-10, 1 - 1e-10) # 로그 손실과 규제 손실을 더하여 리스트에 추가합니다. loss += np.sum(-y_batch * np.log(a)) self.losses.append((loss + self.reg_loss()) / len(x)) # 검증 세트에 대한 손실을 계산합니다. self.update_val_loss(x_val, y_val) # 미니배치 제너레이터 함수 def gen_batch(self, x, y): length = len(x) bins = length // self.batch_size # 미니배치 횟수 if length % self.batch_size: bins += 1 # 나누어 떨어지지 않을 때 indexes = np.random.permutation(np.arange(len(x))) # 인덱스를 섞습니다. x = x[indexes] y = y[indexes] for i in range(bins): start = self.batch_size * i end = self.batch_size * (i + 1) yield x[start:end], y[start:end] # batch_size만큼 슬라이싱하여 반환합니다. def training(self, x, y): m = len(x) # 샘플 개수를 저장합니다. z = self.forpass(x) # 정방향 계산을 수행합니다. """ <training메서드> activation -> softmax 함수로 변경 """ a = self.softmax(z) # 활성화 함수를 적용합니다. err = -(y - a) # 오차를 계산합니다. # 오차를 역전파하여 그래디언트를 계산합니다. w1_grad, b1_grad, w2_grad, b2_grad = self.backprop(x, err) # 그래디언트에서 페널티 항의 미분 값을 뺍니다 w1_grad += (self.l1 * np.sign(self.w1) + self.l2 * self.w1) / m w2_grad += (self.l1 * np.sign(self.w2) + self.l2 * self.w2) / m # 은닉층의 가중치와 절편을 업데이트합니다. self.w1 -= self.lr * w1_grad self.b1 -= self.lr * b1_grad # 출력층의 가중치와 절편을 업데이트합니다. self.w2 -= self.lr * w2_grad self.b2 -= self.lr * b2_grad return a """ <predict메서드> 정방향 계산에서 얻은 출력 중 가장 큰 값의 인덱스를 구한다. 이값이 예측 클래스가 된다. 클래스를 예측 할 때는 활성화 함수, 소프트 맥스 함수를 거칠 필요가 없다. 출력층에서 계산된 선형 계산만으로 클래스를 예측 할 수 있다. """ def predict(self, x): z = self.forpass(x) # 정방향 계산을 수행합니다. return np.argmax(z, axis=1) # 가장 큰 값의 인덱스를 반환합니다. """ <sore메서드> predict결과와 타킷 y의 클래스를 비교한다. 배열 y의 행을 따라 가장 큰 값의 인덱스를 구해 사용한다. """ def score(self, x, y): # 예측과 타깃 열 벡터를 비교하여 True의 비율을 반환합니다. return np.mean(self.predict(x) == np.argmax(y, axis=1)) def reg_loss(self): # 은닉층과 출력층의 가중치에 규제를 적용합니다. return self.l1 * (np.sum(np.abs(self.w1)) + np.sum(np.abs(self.w2))) + \ self.l2 / 2 * (np.sum(self.w1 ** 2) + np.sum(self.w2 ** 2)) """ <검증 손실 계산하기> 사용하는 활성화 함수를 softmax로 바꾼다 로지스틱 손실 계산을 크로스 엔트로피 손실 계산으로 바꾼다. """ def update_val_loss(self, x_val, y_val): z = self.forpass(x_val) # 정방향 계산을 수행합니다. a = self.softmax(z) # 활성화 함수를 적용합니다. a = np.clip(a, 1e-10, 1 - 1e-10) # 출력 값을 클리핑합니다. # 크로스 엔트로피 손실과 규제 손실을 더하여 리스트에 추가합니다. val_loss = np.sum(-y_val * np.log(a)) self.val_losses.append((val_loss + self.reg_loss()) / len(y_val)) |
'인공지능' 카테고리의 다른 글
다중분류 신경망(3) (8) | 2023.01.13 |
---|---|
다중분류 신경망(2) (0) | 2023.01.12 |
다층신경망(2) 미니배치 (7) | 2023.01.11 |
다층신경망(1) (3) | 2023.01.11 |
신경망 알고리즘 (7) | 2023.01.10 |