규제적용하기
가중치규제 : 과대적합 해결방법중 하나
가중치의 값이 커지지 않도록 제한하는 기법
가중치를 규제하면 일반화 성능이 올라간다.
경사가 급한 그래프보다는 경사가 완만한 그래프가 성능이 좋다
알파 : 규제의 양을 조절하는 파라미터
알파값이 크면 전체 손실함수의 값이 커지지 않도록 w의 값의 합이 작아져야한다 : 규제강화
알파값이 작으면 w의 값이 커져도 손실함수 값은 큰폭으로 커지지않는다 : 규제완화
L1규제의 미분
w_grad += alpha * np.sign(w)
np.sign() : 배열 요소의 부호를 반환한다
절편은 규제하면 모델을 어떤 방향으로 이동시킬 뿐 복잡도에는 영향 없다
회귀모델 손실함수에 l1규제 : 라쏘
로지스틱 회귀에 규제 적용하기
# 1. 그레이디언트 업데이트 수식에 패널티 항 반영하기
#1-1) l1,l2의 기본값은 0
class SingleLayer: def __init__(self, learning_rate=0.1, l1=0, l2=0) : #l1,l2의 기본값은 0 self.w = None self.b = None self.losses = [] self.val_losses = [] self.w_history = [] self.lr = learning_rate |
# 1-2) L1규제와 L2규제의 강도를 조절하는 l1,l2 추가
# L1규제와 L2규제의 강도를 조절하는 l1,l2 추가 self.l1 = l1 self.l2 = l2 |
# 2. fit 메서드에서 역방향 계산 수행할때 그레이디언트 패널티 항의 미분값을 더하기
def fit(self, x, y, epochs=100, x_val=None, y_val=None) : self.w = np.ones(x.shape[1]) self.b = 0 self.w_history.append(self.w.copy()) #가중치 기록 np.random.seed(42) for i in range(epochs) : loss = 0 #인덱스 섞기 indexes = np.random.permutation(np.arange(len(x))) # arrange 아님 for i in indexes : z = self.forpass(x[i]) # 정방향 계산 a = self.activation(z) # 활성화 함수 적용 err = -(y[i] - a) # 오차계산 w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산 |
# 2-1). 그레이디언트에서 패널티 항의 미분값을 더하기
# 그레이디언트에서 패널티 항의 미분값을 더하기 w_grad += self.l1 * np.sign(self.w) + self.l2 * self.w self.w -= self.lr * w_grad # 가중치 업데이트(학습률 적용) |
# 2-2) 절편 업데이트 : self.lr * b_grad
self.b -= b_grad # 절편 업데이트 : self.lr * b_grad # 가중치 기록 self.w_history.append(self.w.copy()) # 안전한 로그게산 위한 클리핑 a = np.clip(a, 1e-10, 1 - 1e-10) # 손실 누적 loss += -(y[i] * np.log(a) + (1 - y[i]) * np.log(1 - a)) |
▶
# 2-3) 에포크마다 평균 손실 저장 (+ self.reg_loss()/len(y))
# 에포크마다 평균 손실 저장 self.losses.append(loss / len(y) + self.reg_loss()/len(y)) # 검증 세트에 대한 손실을 계산 ->update_val_loss 메서드 있음 self.update_val_loss(x_val, y_val) |
# 3. 로지스틱 손실 함수 계산에 패널티 항 추가하기
def reg_loss(self) : return self.l1 * np.sum(np.abs(self.w)) + self.l2 / 2 * np.sum(self.w**2) def update_val_loss(self, x_val, y_val): if x_val is None: return val_loss = 0 for i in range(len(x_val)): z = self.forpass(x_val[i]) # 검증세트 샘플 정방향으로 계산 a = self.activation(z) # 활성화 함수 적용 a = np.clip(a, 1e-10, 1 - 1e-10) # 계산된 값을 val_losses리스트에 추가 val_loss += -(y_val[i] * np.log(a) + (1 - y_val[i]) * np.log(1 - a)) |
▶
# 4. 검증세트 손실 계산 : update_val_loss() 메서드에서 reg_loss() 호출하기
self.val_losses.append(val_loss / len(y_val) + self.reg_loss()/len(y_val)) layer5 = SingleLayer(l1=0.001) layer5.fit(x_train_scaled, y_train, epochs=20) layer5.score(x_val_scaled, y_val) |
# 5. 최종코드 (단일층 신경망의 SingleLayer 클래스 이용)
class SingleLayer: def __init__(self, learning_rate=0.1, l1=0, l2=0) : #l1,l2의 기본값은 0 self.w = None self.b = None self.losses = [] self.val_losses = [] self.w_history = [] self.lr = learning_rate # L1규제와 L2규제의 강도를 조절하는 l1,l2 추가 self.l1 = l1 self.l2 = l2 def forpass(self, x): z = np.sum(x * self.w) + self.b # 직선 방정식을 계산 return z def backprop(self, x, err): w_grad = x * err # 가중치에 대한 그래디언트를 계산 b_grad = 1 * err # 절편에 대한 그래디언트를 계산 return w_grad, b_grad def activation(self, z): z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해 a = 1 / (1 + np.exp(-z)) # 시그모이드 계산 return a # fit 메서드에서 역방향 계산 수행할때 그레이디언트 패널티 항의 미분값을 더하기 def fit(self, x, y, epochs=100, x_val=None, y_val=None) : self.w = np.ones(x.shape[1]) self.b = 0 self.w_history.append(self.w.copy()) #가중치 기록 np.random.seed(42) for i in range(epochs) : loss = 0 #인덱스 섞기 indexes = np.random.permutation(np.arange(len(x))) # arrange 아님 for i in indexes : z = self.forpass(x[i]) # 정방향 계산 a = self.activation(z) # 활성화 함수 적용 err = -(y[i] - a) # 오차계산 w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산 # 그레이디언트에서 패널티 항의 미분값을 더하기 w_grad += self.l1 * np.sign(self.w) + self.l2 * self.w self.w -= self.lr * w_grad # 가중치 업데이트(학습률 적용) self.b -= b_grad # 절편 업데이트 : self.lr * b_grad # 가중치 기록 self.w_history.append(self.w.copy()) # 안전한 로그게산 위한 클리핑 a = np.clip(a, 1e-10, 1 - 1e-10) # 손실 누적 loss += -(y[i] * np.log(a) + (1 - y[i]) * np.log(1 - a)) # 에포크마다 평균 손실 저장 self.losses.append(loss / len(y) + self.reg_loss()/len(y)) # 검증 세트에 대한 손실을 계산 ->update_val_loss 메서드 있음 self.update_val_loss(x_val, y_val) # 로지스틱 손실 함수 계산에 패널티 항 추가하기 def reg_loss(self) : return self.l1 * np.sum(np.abs(self.w)) + self.l2 / 2 * np.sum(self.w**2) |
# 6. 검증세트 손실 계산 : update_val_loss() 메서드에서 reg_loss() 호출하기
def update_val_loss(self, x_val, y_val): if x_val is None: return val_loss = 0 for i in range(len(x_val)): z = self.forpass(x_val[i]) # 검증세트 샘플 정방향으로 계산 a = self.activation(z) # 활성화 함수 적용 a = np.clip(a, 1e-10, 1 - 1e-10) # 계산된 값을 val_losses리스트에 추가 val_loss += -(y_val[i] * np.log(a) + (1 - y_val[i]) * np.log(1 - a)) # 검증세트에 대한 손실함수 값을 계산 self.val_losses.append(val_loss / len(y_val) + self.reg_loss()/len(y_val)) layer5 = SingleLayer(l1=0.001) layer5.fit(x_train_scaled, y_train, epochs=20) layer5.score(x_val_scaled, y_val) |
# 7. cancer데이터 세트에 L1규제 적용하기
l1_list = [0.0001, 0.001, 0.01] # 규제의 강도 for l1 in l1_list: lyr = SingleLayer(l1=l1) lyr.fit(x_train_scaled, y_train, x_val=x_val_scaled, y_val=y_val) plt.plot(lyr.losses) plt.plot(lyr.val_losses) plt.title('Learning Curve (l1={})'.format(l1)) plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train_loss', 'val_loss']) plt.ylim(0, 0.3) plt.show() plt.plot(lyr.w, 'bo') plt.title('Weight (l1={})'.format(l1)) plt.ylabel('value') plt.xlabel('weight') plt.ylim(-4, 4) plt.show() |
# 8. cancer데이터 세트에 L2규제 적용하기
l2_list = [0.0001, 0.001, 0.01] for l2 in l2_list: lyr = SingleLayer(l2=l2) lyr.fit(x_train_scaled, y_train, x_val=x_val_scaled, y_val=y_val) plt.plot(lyr.losses) plt.plot(lyr.val_losses) plt.title('Learning Curve (l2={})'.format(l2)) plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train_loss', 'val_loss']) plt.ylim(0, 0.3) plt.show() plt.plot(lyr.w, 'bo') plt.title('Weight (l2={})'.format(l2)) plt.ylabel('value') plt.xlabel('weight') plt.ylim(-4, 4) plt.show() |
# 9. 검증세트 손실 계산
layer6 = SingleLayer(l2=0.01) layer6.fit(x_train_scaled, y_train, epochs=50) layer6.score(x_val_scaled, y_val) np.sum(layer6.predict(x_val_scaled) == y_val) sgd = SGDClassifier(loss='log', penalty='l2', alpha=0.001, random_state=42) sgd.fit(x_train_scaled, y_train) sgd.score(x_val_scaled, y_val) |