분류 = 임의의 숫자를 예측하는 것이 아니라 정해진 종류 중에 한가지를 골라주는 문제 (이진분류 = 2가지 중 하나, 다진분류 = 여러가지 중 하나)
로지스틱 회귀는 이름은 회귀이지만 분류 알고리즘임
이진 분류
- True(1) 양성클래스
- False(0 or -1) 음성 클래스
w1x1 + w2x2 + b = z
(x1, x2 두가지 특성을 이용한다)
w 와 x 를 따로 표현하지 않고 w1x1, w2x2로 같이 표현하고 뉴런에는 덧셈만 표현해준다
특성의 수가 많아지면 ∑ 이용해서 표현
퍼셉트론과 유사한데 역방향 계산이 일어나는 부분이 다르다
역방향 계산이 퍼셉트론은 y_hat, 아달린은 z에서 시작함
z 값을 활성화 함수로 시그모이드 함수를 사용하여 a 값으로 변환한다. (a는 0~1 값)
이 값이 >0.5면 양성, ≤ 0.5면 음성 클래스로 판단한다
이 판단을 위해 활성화 함수를 사용하는 것
시그모이드 함수 = 로지스틱 함수
z값이 -∞에서 +∞로 변할때 0에서 1 사이 값만 가짐
선형 방정식의 값을 뉴런에서 계산 후 임의의 숫자인 z값을 로지스틱 함수 (시그모이드 함수)를 이용해 0 부터 1 사이의 값으로 압축한 후 0.5보다 크면 양성, 0.5 이하면 음성 클래스라고 판단한다 ⇒ 이진분류를 수행한 것
최적의 w와 b를 찾기 위해 a 값을 기준으로 오차역전파를 수행하게 됨(역방향 계산)
분류의 정확도 : 정확하게 분류된 횟수, 비율
회귀의 경우에는 제곱오차를 낮추는 것이 목표, 미분가능하기때문에 손실함수 사용해 경사하강법을 구현
하지만 분류는 정확도가 미분불가 → 다른 손실함수 사용 = 로지스틱 손실 함수.
(y : 타깃값, a : 활성화 함수의 출력)
y가 1일 경우 a는 1에 가까워야 하고, y가 0일 경우 a는 0에 가까워야 함
합성함수의 도함수
y를 x에 대해 미분할 때 함수가 복잡하다면 f와 g를 각각 나눠서 미분할 수 있다
y를 먼저 u에 대해 미분하고, u를 x에 대해 미분해서 각각 곱하면 됨
로지스틱 손실함수를 w로 미분하는 3단계
a = 1 / (1+e^-z)
유방암 데이터 셋 사용
- 양성 종양(정상종양) → 음성 샘플 0
- 악성 종양 → 양성 샘플(해결 과제) 1
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
#bunch클래스로 예제 데이터셋을 반환해주고 있음
#데이터 속성에는 훈련데이터(특성값), 타겟데이터에는 타겟값(레이블값)이 들어감
print(cancer.data.shape, cancer.target.shape)
(569, 30) (569,) // numpy 배열 형식 // 데이터 속성은 569개 샘플과 30개 특성, 타깃데이터는 1차원 배열
cancer.data[:3]
#cancer데이터의 처음 데이터부터 3개 출력 0,1,2 행
#상자수염그래프 그려서 데이터 파악하기
plt.boxplot(cancer.data)
plt.xlabel('feature')
plt.ylabel('value')
plt.show()
np.unique(cancer.target, return_counts=True)
#unique함수 -> 배열 값 중 고유한 값만 출력해줌
#return_counts매개변수의 기본값은 원래 false임 true로 바꿔주면
0에 대한 카운트, 1에 대한 카운트 개수를 알려줌
(array([0, 1]), array([212,357]))
x = cancer.data
y = cancer.target
훈련 데이터 세트를 테스트 세트와 훈련 세트로 나눈다.
테스트 세트는 사용안하고 보관, 훈련 세트로 훈련 후 평가 때 테스트 세트 사용. 이 결과 좋으면 →일반화 성능 good
+) 훈련 세트가 테스트 세트보다 더 많아야 하고 양성, 음성 클래스는 두 세트에 골고루 섞여야 함
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)
#20%정도 테스트 세트로 지정한 것.
#stratify 매개변수는 양성,음성 클래스 비율을 동일하게 유지하는 옵션임
print(x_train.shape, x_text.shape)
(455, 30) (114, 30)
np.unique(y_train, return_counts=True)
(array([0,1]), array([170, 285])
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
a = np.array([1,2,3])
b = np.array([3,4,5])
print(a+b)
print(a*b)
np.sum(a*b) #a*b 결과 배열을 모두 더함
array([4, 6, 8])
array([3, 8, 15])
26
def fit(self, x, y, epochs=100):
self.w = np.ones(x.shape[1]) #가중치 초기화
self.b = 0 #절편 초기화
for i in range(epochs): #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 activation(self, z):
a = 1 / (1 + np.exp(-z)) #시그모이드 계산
return a
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.8245614035087719
→82%정도 맞춤
은닉층이 여러개면 딥러닝, 심층 신경망
은닉층 없으면 단일층 신경망 = 로지스틱 회귀 모델
def __init__(self):
self.w = None
self.b = None
self.losses = [] #손실값 저장하는 리스트 만들기
'''
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
indexs = np.random.permutation(np.arrange(len(x))) #인덱스 섞기
for i in index: #모든 샘플에 대해 반복
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.92983456
plt.plot(layer.losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
손실값이 감소하는 모습을 볼 수 있음 → 훈련이 잘 됨
sgd = SGDClassifier(loss='log', max_iter=100, tol=1e-3, random_state=42)
#경사하강법 분류기 여러 문제를 경사하강법으로 풀어줌
# loss='log'->로지스틱손실함수 지정한 것
sgd.fit(x_train, y_train)
sgd.score(x_test, y_test)
0.833333333334
sgd.predict(x_test[0:10])
array([0, 1, 0, 0, 0, 0, 1, 0, 0, 0])