퍼셉트론
1957년 코넬 항공 연구소에서 이진 분류 문제에서 최적의 가중치를 학습하는 퍼셉트론(Perceptron)알고리즘을 발표.
퍼셉트론은 마지막 단계에서 샘플을 이진 분류하기 위하여 계단 함수 사용.
아달린
퍼셉트론을 개선한 적응형 선형 뉴런
로지스틱 회귀
아달린에서 조금 더 발전한 형태.
(활성화 함수는 비선형 함수를 사용)
시그모이드 함수
만드는 과정
오즈 비(odds ratio) > 로짓 함수 > 시그모이드 함수
odds ratio 란 성공 확률과 실패확률의 비율을 나타내는 통계
로짓 함수
> odds ratio에서 로그 함수를 취하여 만든 함수.
로짓 함수를 z라 놓고 z에 관해 식을 변환하면
로지스틱회귀는 이진분류가 목적이므로 -무한대 ~ +무한대까지 같은 z의 값을 조절해야 합니다. 그래서 시그모이드 함수를 사용한 이유입니다. 시그모이드 함수를 통과시키면 z를 확률처럼 해석할 수 있기 때문입니다. 그후 a를 0,1로 구분하기 위해 임계함수에 통과 시켜 이진 분류를 실행하였습니다.
로지스틱 손실함수를 경사하강법에 적용하기.
> 로지스틱 손실함수는 다중분류를 위한 손실함수인 "크로스 엔트로피" 손실함수를 이진 분류 버전으로 만든 것입니다.
위 두식을 분석해 보면, 양성클래스의 경우 로지스틱 손실 함수의 값을 최소로 만들면 a는 1에 가까워지고 음성클래스의 경우 로지스틱 손실함수의 값을 최소로 만들면 a는 0에 가까워집을 알 수 있습니다.
즉 , 손실함수를 최소화 시키면 a의 값이 이상적임을 알 수 있습니다.
로지스틱 손실함수의 최솟값을 만드는 가중치와 절편을 찾기 위해 미분을 한 결과 다음과 같은 결과를 같습니다.
위 식을 바탕으로 가중치와 절편을 업데이트 하면 됩니다.
가중치 업데이트 : 로지스틱 손실함수를 가중치에 대해 미분한 식을 가중치에서 빼면됩니다.
절편 업데이트 : 로지스틱 손실함수를 절편에 대해 미분한 식을 절편에서 빼면됩니다.
그럼이제 실제 분류 실습을 해보겠습니다.
1. load_breast_cancer()함수 불러오기
2. 입력데이터 확인하기
3.박스플롯으로 특성의 사분위 관찰하기
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(cancer.data.shape , cancer.target.shape
(569, 30) (569,)
import matplotlib.pyplot as plt
plt.boxplot(cancer.data)
plt.show()
4.눈에 띄는 특성 살펴보기
5.타겟 데이터 확인하기
6.훈련 데이터 세트 저장하기
cancer.feature_names[[3,13,23]]
array(['mean area', 'area error', 'worst area'], dtype='<U23')
import numpy as np
np.unique(cancer.target, return_counts=True)
(array([0, 1]), array([212, 357]))
x = cancer.data
y = cancer.target
로지스틱 회귀를 위한 뉴런을 만들어 봅시다.
1. train_test_split()함수로 훈련 데이터 세트 나누기
2. 결과 확인하기
3. unique()함수로 훈련 세트의 타겟 확인하기
from sklearn.model_selection import train_test_split
x_train , x_test , y_train , y_test = train_test_split(x ,y ,stratify=y , test_size=0.2)
print(x_train.shape , x_test.shape)
(455, 30) (114, 30)
np.unique(y_train,return_counts=True)
(array([0, 1]), array([170, 285]))
로지스틱 회귀 구현하기
전장과 다르게 __init__ 메서드에서 가중치와 절편을 미리 초기화하지 않았습니다. 그 이유는 입력 데이터의 특성이 많았기 때문입니다.
x*self.w 에서 x ,w는 넘파이 배열이므로 forpass() 메소드에서 넘파이 함수를 사용했습니다.
class LogisticNeuron:
def __init__(self):
self.w = None
self.b = None
def __init__(self,x):
z = np.sum(x*self.w) + self.b
return z
def __init__(self,x,err):
w_grad = x * err
b_grad = 1 * err
return w_grad , b_grad
활성화 함수 activation()메소드 구현하기
def activation(self,z):
a = 1 / (1 + np.exp(-z)) #시그모이드 계산
return a
훈련 메소드 구현하기
def fit(self,x,y,epochs=100):
self.w = np.ones(x.shape[1])
self.b = 0
for i in range(epochs):
for x_i , y_i in zip(x,y):
z = self.forpass(x_i) #정방향 계산
a = self.activation(z) #활성화 함수 적용
err = -(y_i -a) #오차 계산
w_grad , b_grad = self.backprop(x_i,err) #역방향 계산
self.w -= w_grad #업데이트
self.b -= b_grad
예측 메소드 구현하기
def predict(self,x):
z = [self.forpass(x_i) for x_i in x] #선형 함수 적용
a = self.activation(np.array(z))
return a > 0.5 #계단 함수 적용
이때까지 구현한 class
class LogisticNeuron:
def __init__(self):
self.w = None
self.b = None
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):
a = 1 / (1 + np.exp(-z)) #시그모이드 계산
return a
def fit(self,x,y,epochs=100):
self.w = np.ones(x.shape[1])
self.b = 0
for i in range(epochs):
for x_i , y_i in zip(x,y):
z = self.forpass(x_i) #정방향 계산
a = self.activation(z) #활성화 함수 적용
err = -(y_i -a) #오차 계산
w_grad , b_grad = self.backprop(x_i,err) #역방향 계산
self.w -= w_grad #업데이트
self.b -= b_grad
def predict(self,x):
z = [self.forpass(x_i) for x_i in x] #선형 함수 적용
a = self.activation(np.array(z))
return a > 0.5 #계단 함수 적용
모델을 훈련해 봅시다.
#모델 훈련하기
neuron = LogisticNeuron()
neuron.fit(x_train,y_train)
np.mean(neuron.predict(x_test) == y_test)
0.9210526315789473
약 92%의 정확도를 보였습니다.
로지스틱 회귀 뉴런으로 단일층 신경망 만들기.
일반적으로 신경망은 input layer , hidden layer , output layer로 구성됩니다.
로지스틱 회귀는 은닉층이 없는 신경망이라 할 수 있습니다. 이러한 입력층과 출력층만 가지는 신경망을 단일층 신경망이라 합니다.
여기 input layer는 입력 그자체라고 생각하면 됩니다.
여러가지 경사하강법
확률적 경사 하강법 : 샘플 데이터 1개에 대한 그레디언트 계산
전체 훈련 세트를 사용하여 한번에 그레디언트를 계산하는 방식인 배치 경사 하강법 , 배치 크기를 작게 하여 처리하는 방식인 미니 배치 경사 하강법이 존재합니다.
확률적 경사 하강법은 샘플 데이터 1개마다 그레디언트를 계산하여 가중치를 업데이트 하므로 계산 비용은 적은 대신 가중치가 최적값에 수렴하는 과정이 불안정합니다. 반면 , 배치 경사 하강법은 전체 훈련 데이터 세트를 사용하여 한 번에 그레디언트를 계산하므로 가중치가 최적값에 수렴하는 과정은 안정적이지만 그만큼 계산 비용이 많이듭니다.
>> 이러한 해결방법이 미니 배치 경사 하강법. (둘의 중간)
매 에포크마다 훈련 세트의 샘플 순서를 섞어 사용합니다.
def fit(self, x, y, epochs=100):
self.w = np.ones(x.shape[1]) # 가중치를 초기화합니다.
self.b = 0 # 절편을 초기화합니다.
for i in range(epochs): # epochs만큼 반복합니다
loss = 0
# 인덱스를 섞습니다
indexes = np.random.permutation(np.arange(len(x)))
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) # 역방향 계산
self.w -= w_grad # 가중치 업데이트
self.b -= b_grad # 절편 업데이트
# 안전한 로그 계산을 위해 클리핑한 후 손실을 누적합니다
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))
score()메소드 추가하기
def predict(self, x):
z = [self.forpass(x_i) for x_i in x] # 정방향 계산
return np.array(z) > 0 # 스텝 함수 적용
def score(self, x, y):
return np.mean(self.predict(x) == y)
최종 완성된 단일층 신경망 클래스
class SingleLayer:
def __init__(self):
self.w = None
self.b = None
self.losses = []
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
def fit(self, x, y, epochs=100):
self.w = np.ones(x.shape[1]) # 가중치를 초기화합니다.
self.b = 0 # 절편을 초기화합니다.
for i in range(epochs): # epochs만큼 반복합니다
loss = 0
# 인덱스를 섞습니다
indexes = np.random.permutation(np.arange(len(x)))
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) # 역방향 계산
self.w -= w_grad # 가중치 업데이트
self.b -= b_grad # 절편 업데이트
# 안전한 로그 계산을 위해 클리핑한 후 손실을 누적합니다
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))
def predict(self, x):
z = [self.forpass(x_i) for x_i in x] # 정방향 계산
return np.array(z) > 0 # 스텝 함수 적용
def score(self, x, y):
return np.mean(self.predict(x) == y)
단일층 신경망 훈련하기
layer = SingleLayer()
layer.fit(x_train, y_train)
layer.score(x_test,y_test)
0.868421052631579
손실 함수 누적값 확인하기
plt.plot(layer.losses)
plt.show()
사이킷런으로 로지스틱 회귀 수행
SGDClassifier사용
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', max_iter=100, tol=1e-3, random_state=42) #max_liter : 반복횟수 , tol : 설정값 만큼 감소되지 않으면 반복 중단. 설정 안되있으면 max_iter 높이라는 경고 뜸
sgd.fit(x_train, y_train)
sgd.score(x_test, y_test)
0.9298245614035088
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!