[딥러닝 입문] 이진 분류

do it! 딥러닝 입문 강의 들으며 필기용..


초기 인공지능 알고리즘과 로지스틱 회귀


이진분류는 True(1) /False(0 또는 -1)로 구분하는 문제
y는 step function(계단함수)
y = 1 (if z>0),과 -1
w_1 x_1 + w_2 x_2 + b = z
hat(y) 에서 시작해서 w 와 b절편 계산

로지스틱 회귀

로지스틱 회귀는 분류 알고리즘이다.
중간에 활성화 함수(activation function) 거쳐서 계산한다. 선형방정식 의 값인 z에서

활성화 함수를 거치면서 0과 1사이의 a를 계산.
a>0.5
0.5보다 크면 양성클래스,
a<=0.5
0.5보다 작으면 음성클래스

시그모이드 함수는 오즈비 (odds ration)에서 시작한다.
오즈비-> 로짓 함수 -> 시모드이드 함수
odds = p/(1-p) (p: 성공확률)
logit(p) = log(p/(1-p))

그리고 로짓함수를 확률 p에 대해 정리한 것을 시그모이드(sigmoid) 함수라 부름 (아닌 책도 있음)

정리하면,
선형 방정식 값을 뉴련에서 계산, w와 x 특성 다 더해서 임의의 값 (-무한대~ 무한대의 z값)
이 값을 시그모이드 함수에 통과시키면 0과 1사이의 값으로 압축. 임계함수 통과해서 0.5 기준으로 음성/양성 클래스 판단해 이진분류 실행
최적의 w와 b 찾기위해서 a를 기준으로 알고리즘 실행

로지스틱 손실 함수의 경사하강법 적용

분류의 정확도는 미분 불가 함수임
대신 이진 크로스 엔트로피 또는 로지스틱 손실 함수를 사용
L = -(ylog(a) + (1-y)log(1-a))
y: 타깃 값
a : 활성화 함수(=시그모이드) 함수의 출력
양성클래스 경우 y=1 , L= -log(a) , a 는 1에 가깝
음성클래스 경우 y=0 , L= -log(1-a), a 는 0에 가깝

목표값과 유사값을 보이는 손실함수이다.

...
각 가중치에 대한 미분 계산 후..
b = b+(y-a)*1

이진 분류

로지스틱 회귀 클래스로 만들고 분류 데이터 가지고 악성 종양 구분하는 분류모델 만들기

예제 데이터 셋 준비

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(cancer.data.shape, cancer.target.shape)

#box Plot 그리기 
plt.boxplot(cancer.data)
plt.xlabel('feature')
plt.ylabel('value')
plt.show()

상자그림을 통해서 데이터 분포 확인하기
대부분 0에 가까운 작은 값으로 이루어짐.

어떤값은 스케일 작고 어떤 값은 스케일 크다.
데이터 스케일 범위의 차이가 있음.

타깃 데이터 확인하고 훈련 데이터 준비하기.

np.unique(cancer.target, return_counts=True)

x = cancer.data
y = cancer.target

음성클래스 212개 (=정상 종양), 양성클래스 357개 (=악성종양)
1.6-7배 정도 양성이 많다. 편중되어있음

로지스틱 회귀 위한 뉴런 만들기

일반화 성능을 평가하기 위해 훈련 세트와 테스트 세트로 나눈다.
나누는 규칙은 테스트보다 훈련이 많아야 하고,
훈련 데이터 세트 나누기 전에 양성, 음성 클래스가 훈련 세트나 테스트 세트의 어느 한쪽에 몰리지 않도록 골고루 섞기

이 예제에서는 20프로를 테스트로 한다.

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) #전체결과 20퍼 정도 

np.unique(y_train, return_counts=True) #음성 양성 클래스 비율을 유지 1.6정도 

로지스틱 뉴런 구현하기

앞 장의 Neuron 클래스와 비슷

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):
        z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
        a = 1 / (1 + np.exp(-z))  # 시그모이드 계산
        return a

#fit 메소드 구현 , 초기화는 1로 

    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 predict(self, x):
        z = [self.forpass(x_i) for x_i in x]    # 정방향 계산
        a = self.activation(np.array(z))        # 활성화 함수 적용
        return a > 0.5

모델 훈련하고 결과 확인

참은 1 거짓은 0
맞춘 것의 비율을 볼 수 있음 결과는 0.82
test 샘플의 성능을 확인할 수 있음

neuron = LogisticNeuron()
neuron.fit(x_train, y_train)

np.mean(neuron.predict(x_test) == y_test)

로지스틱 회귀 뉴런으로 단일층 신경망

로지스틱 회귀 이용해서 단일층 신경망 알고리즘 만들기
로지스틱 회귀가 가장 작은 신경망 단위로 생각할 수 있고
로지스틱 회귀가 쌓여서 신경망 알고리즘을 구현한다고 표현해도 틀리지 않는다.

입력층, 은닉층, 출력층
은닉층이 하나 이상 여러개면 딥러닝이라 부름

신경망에서 활성화 함수는 시그모이드 또는 다른 것 씀

그래서 앞에서 만든거 거의 수정하지 않고 간단하게 구현해보기

추가할 것은

손실 함수 결과값 저장 기능 추가하기

경사하강법 - 손실함수 최소로 하는 방향으로 학습해가는 알고리즘,
그니까 손실함수 값이 반복마다 줄어드는지 확인하는 것이 필요하다.

loss저장하는 리스트 만들고 epoke마다 리스트에 저장

지금 경사하강법은 확률적 경사하강법 아니다. (에포크마다 랜덤으로 꺼내야한다.)
배치 경사하강법은 전체 샘플 모두 선택하는 것

배치 경사하강법은 전체 샘플을 사용하기 때문에 좋은 가중치의 수렴을 보여준다. 최적의 방향 손실함수 찾아간다.

확률적 경사하강법은 데이터 셋이 너무 클때 사용.

이 둘의 절충안은 미니 배치 경사 하강법

무작위 몇개 뽑아서 그리디언트 계산하고 다시 무작위 뽑고..
배치보다는 빠르게 최적점 다가간다.

에포크마다 훈련 샘플 섞어야 하니까 index를 섞어준다.
에포크 시작 시점에서 정수 배열 크기만 가지면 섞을 수 있음.

score 메소드로 정확하게 맞춘 개수 출력

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)

사이킷런으로 로지스틱 회귀 수행

회귀는 SGDRegressor
loss='log'로 로지스틱 손실 함수 지정

from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', max_iter=100, tol=1e-3, random_state=42)

sgd.fit(x_train, y_train)
sgd.score(x_test, y_test)

sgd.predict(x_test[0:10])

사이키럿 api구조는 전체 사이킷런 모델 걸쳐서 일관되게 유지
하나 모델 잘 사용하면 다른 모델의 메소드도 쉽게 사용가능
다만, 클래스마다 지정 메소드 달라서 잘 보기

좋은 웹페이지 즐겨찾기