이진 분류 문제(임의의 샘플 데이터를 True False로 구분하는 문제)에서 최적의 가중치를 학습하는 알고리즘
선형함수를 통과한 값 z를 계단 함수로 보내 0보다 큰지, 작은지 검사하여 1과 -1로 분류하는 알고리즘
마지막 단계에서 샘플을 이진 분류하기 위해 계단함수를 사용 → 계단 함수를 통과한 값을 다시 가중치와 절편을 업데이트 하는데 사용 ⇒ 계단 함수의 값을 학습에 사용
계단 함수는 z≥0이면 1(양성 클래스)로, z<0이면 -1(음성 클래스)로 분류
선형 함수
이진 분류 → 그렇다(1)과 아니다(0)으로 나뉨 → y가 1(양성 클래스)의 경우 L=-log(a), y가 -1(음성 클래스)인 경우 L=-log(1-a)
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(cancer.data.shape, cancer.target.shape)
=> (569,30) (569,)
특성이 30개, 산점도로 표현하기 어려움 → 박스 플롯을 이용하여 각 특성의 사분위 값을 나타내기
import matplotlib.pyplot as plt
import numpy as np
plt.boxplot(cancer.data)
plt.xlabel('feature')
plt.ylabel('value')
plt.show()
np.unique(cancer.target, return_counts=True)
x = cancer.data
y = cancer.target
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, random_state=42)
print(x_train.shape, x_test.shape)
np.unique(y_tarin, return_counts = True)
class LogisticNeuron:
def __init__(self):
#1.__init__() 메서드 작성
self.w = None
self.b = None #초기화하지 않음; 입력 데이터의 특성이 많아 #5에서 입력 데이털르 보고 특성 개수에 맞게 결정
#2.정방향 계산
def forpass(self,x):
z = np.sum(x*self.w)+self.b #직선 방정식 계산, np.sum() : 함수의 인자로 전달하면 각 요소를 모두 더한 값을 반환
return z
#3.역방향 계산
def backprop(self,x,err):
w_grad = x*err #가중치에 대한 그레디언트 계산
b_grad = 1*err #절편에 대한 그레디언트 계산
return w_grad, b_grad
#4.activation()메서드 구현;시그모이드 계산
def activation(self,z):
a = 1/(1+np.exp(-z))
return a
#5.훈련을 위한 fit()메서드 구현
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 #절편 업데이트
#6.에측하는 메서드 구현
def perdict(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.8245614035087719
확률적 경사 하강법 : 샘플 데이터 1개마다 그레디언트를 계산하여 가중치를 업데이트
배치 경사 하강법 : 전체 훈련 세트를 사용하여 한 번에 그레디언트를 계산
미니 배치 경사 하강법 : 배치 크기를 작게 하여(훈련 세트를 여러번 나눠) 처리
위의 경사 하강법들은 매 에포크마다 훈련 세트의 샘플 순서를 섞어 가중치의 최적값을 계산해야 함 → 훈련 세트의 샘플을 섞으면 가중치 최적값의 탐색 과정이 다양해져 가중치 최적값을 제대로 찾을 수 있음
class SingleLayer:
def __init__(self):
#1.__init__() 메서드 작성
self.w = None
self.b = None
self.losses=[] #손실함수의 결과값을 저장할 리스트. 샘플마다 손실 함수를 계산하고 그 결괏값을 모두 더해 샘플 개수로 나눈 평균값을 저장
#2.정방향 계산
def forpass(self,x):
z = np.sum(x*self.w)+self.b
return z
#3.역방향 계산
def backprop(self,x,err):
w_grad = x*err
b_grad = 1*err
return w_grad, b_grad
def add_bias(self,x):
return np.c_[np.ones((x.shpae[0],1)),x] #행렬의 맨 앞에 1로 채워진 열 벡터를 추가
#5.activation()메서드 구현;시그모이드 계산
def activation(self,z):
a = 1/(1+np.exp(-z))
return a
#6.훈련을 위한 fit()메서드 구현
def fit(self, x, y, epochs=100):
self.w = np.ones(x.shape[1])
self.b = 0
for i in range(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) #np.clip() : 주어진 범위 밖의 값을 범위 양 끝의 값으로 잘라냄.
loss +=-(y[i]*np.log(a)+(1-y[i])*np.log(1-a))
self.losses.append(loss/len(y)) #에포크마다 평균 손실 저장
#7.에측하는 메서드 구현
def predict(self,x):
z = [self.forpass(x_i) for x_i in x]
return np.array(z) > 0
#8.정확도 계산을 위한 score()메서드 구현
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)
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', max_iter=100, tol=1e-3, random_state=42)
#loss 매개변수에 손실함수로 log 지정, max_iter로 반복회수 100으로 지정, 난수 초깃값 42로 설정, 반복할 때 마다 로지스틱 손실함수의 값이 tol에 저장된 값만큼 감소하지 않으면 반복을 중단하도록 설정
sgd.fit(x_train,y_train) #사이킷런의 fit()메서드로 훈련
sgd.score(x_test,y_test) #사이킷런의 score()메서드로 정확도 계산
sgd.predict(x_test[0:10]) #사이킷런의 predict()메서드로 예측