[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🛠 기타/Data & AI

단층 퍼셉트론 - 이진판단 구현 확장

inu 2020. 7. 27. 23:57
반응형

https://inuplace.tistory.com/510?category=912534

 

단층 퍼셉트론 - 이진판단 구현

https://inuplace.tistory.com/460?category=912534 단층 퍼셉트론 - 회귀분석 구현 데이터를 기반으로 전복의 고리수를 예측하는 단층 퍼셉트론을 구현해보자. 주어진 데이터는 'Sex', 'Length', 'Diameter', 'He..

inuplace.tistory.com

  • 위 링크에서 구현한 코드를 실행해보면 테스트 데이터에 대해 꽤나 좋은 정확도를 얻게 된다. (97% 상회)
  • 하지만 이는 전체 데이터셋의 90%가 일반 별이고 10%가 펄서이기 때문이다. (따라서 테스트 데이터셋도 비슷한 비율을 가질 것이다.)
  • 이런 경우 일반적 데이터에서 모델을 사용하면 정확도가 매우 떨어질 확률이 높다.
  • 수업에 사용된 코드를 분석한 자료입니다.

균형 잡힌 데이터셋과 착시 없는 평가 방법

  • 일반 별 데이터를 버리는 방법도 있지만 어렵게 구한 데이터를 버리는 것은 그리 좋은 방법이 아니다.
  • 상대적으로 데이터가 적은 펄서 데이터를 중복 사용해 일반 별 데이터와의 크기를 맞춘다.
  • 약간의 노이즈를 추가해 데이터를 중복 적용한다.
  • 이런 과정을 진행하면 정확도는 떨어지지만, 데이터셋으로 인해 발생하던 정확도의 '착시현상'이 없어지면서 신경망의 추정 성능은 향상된다.
  • 이 때문에 정확도 대신 신경망의 성능을 더 잘 보여줄 수 있는 평가 지표가 필요하다.

정밀도와 재현율

  • 그에 따라 등장한 것이 정밀도와 재현율이다.
  • 정밀도 : 신경망이 참으로 추정한 것 중 정답이 참인 것의 비율
  • 재현율 : 정답이 참인 것들 중 신경망이 참으로 추정한 것의 비율
  • F1값 : 정밀도와 재현율의 조화 평균 (2ab /(a+b))
  • 미니배치 데이터는 정답이 참인지 거짓인지에 따라 T(True), F(False)의 두 집단으로 나누고 추정이 참인지 거짓인지에 따라 P(Positive), N(Negative) 두 집단으로 나눌 수 있다. 즉, 미니배치데이터를 TP, TN, FP, FN 네 집단으로 나눌 수 있는 것이다.
  • 이를 이용해 정확도, 정밀도와 재현율을 수식으로 표현할 수 있다.
  • $정확도 = {TP + FN \over TP + TN + FP + FN}$
  • $정밀도 = {TP \over TP + FP}$
  • $재현율 = {TP \over TP + TN}$

메인 함수 재정의

def pulsar_exec(epoch_count=10, mb_size=10, report=1, adjust_ratio=False):
    load_pulsar_dataset(adjust_ratio)
    init_model()
    train_and_test(epoch_count, mb_size, report)
  • 데이터를 불러올 때 adjust_ratio(불린값)을 추가하여 데이터를 중복적용해 불러올지를 결정하도록 한다.

데이터 적재 함수 재정의

def load_pulsar_dataset(adjust_ratio):
    pulsars, stars = [], []
    with open('../../data/chap02/pulsar_stars.csv') as csvfile:
        csvreader = csv.reader(csvfile)
        next(csvreader, None)
  #      rows = []
        for row in csvreader:
            if row[8] == '1': pulsars.append(row)
            else: stars.append(row)

    global data, input_cnt, output_cnt
    input_cnt, output_cnt = 8, 1

    star_cnt, pulsar_cnt = len(stars), len(pulsars)

    if adjust_ratio:
        data = np.zeros([2*star_cnt, 9])
        data[0:star_cnt, :] = np.asarray(stars, dtype='float32')
        for n in range(star_cnt):
            data[star_cnt+n] = np.asarray(pulsars[n % pulsar_cnt], dtype='float32')
    else:
        data = np.zeros([star_cnt+pulsar_cnt, 9])
        data[0:star_cnt, :] = np.asarray(stars, dtype='float32')
        data[star_cnt:, :] = np.asarray(pulsars, dtype='float32')
  • 기존의 rows 대신 pulsars와 stars에 데이터를 나눠담는다.
  • 그리고 adjust_ratio가 false인 경우 기존에 했던 방식과 유사하게 data 변수를 초기화한다.
  • 단, adjust_ratio가 true일 경우 stars 데이터를 담고 stars의 길이만큼 pulsars 데이터를 반복해서 담는다. (강제로 별 데이터와 펄서 데이터 개수를 동일하도록 조정한 것이다.)

정확도 계산 함수 재정의와 safe div

def safe_div(p, q):
    p, q = float(p), float(q)
    if np.abs(q) < 1.0e-20: return np.sign(p)
    return p / q
  • 나눗셈시 분모가 0이거나 타입이 안맞는 등의 이유로 오류가 발생할 수 있다.
  • 따라서 float형으로 형변환을 해두고, 분모(q)의 절대값이 매우 작으면 분자(p)의 부호만 리턴해준다,
def eval_accuracy(output, y):
    est_yes = np.greater(output, 0)
    ans_yes = np.greater(y, 0.5)
    est_no = np.logical_not(est_yes)
    ans_no = np.logical_not(ans_yes)

    tp = np.sum(np.logical_and(est_yes, ans_yes))
    fp = np.sum(np.logical_and(est_yes, ans_no))
    fn = np.sum(np.logical_and(est_no, ans_yes))
    tn = np.sum(np.logical_and(est_no, ans_no))

    accuracy = safe_div(tp+tn, tp+tn+fp+fn)
    precision = safe_div(tp, tp+fp)
    recall = safe_div(tp, tp+fn)
    f1 = 2 * safe_div(recall*precision, recall+precision)

    return [accuracy, precision, recall, f1]
  • 앞서 언급한 tp, fp, fn, tn 값을 구하고 그를 통해 정확도(accuracy), 정밀도(precision), 재현율(recall), f1을 구한다.
  • 해당 값들을 순서대로 리턴한다.

출력문 수정을 위한 실행함수 재정의

def train_and_test(epoch_count, mb_size, report):
    step_count = arrange_data(mb_size)
    test_x, test_y = get_test_data()

    for epoch in range(epoch_count):
        losses = []

        for n in range(step_count):
            train_x, train_y = get_train_data(mb_size, n)
            loss, _ = run_train(train_x, train_y)
            losses.append(loss)

        if report > 0 and (epoch+1) % report == 0:
            acc = run_test(test_x, test_y)
            acc_str = ','.join(['%5.3f']*4) % tuple(acc)
            print('Epoch {}: loss={:5.3f}, result={}'. \
                  format(epoch+1, np.mean(losses), acc_str))

    acc = run_test(test_x, test_y)
    acc_str = ','.join(['%5.3f']*4) % tuple(acc)
    print('\nFinal Test: final result = {}'.format(acc_str))
  • run_test 내부에서 처리되는 eval_accuracy 함수의 리턴값이 리스트로 변경됨에 따라 acc는 리스트의 형태를 갖게 된다.
  • 따라서 그에 맞게 형식을 바꿔 출력해준다.

실행결과

  • 중복 데이터 처리 X
Final Test : final result = 0.967,0.649,0.976,0.780
  • 중복 데이터 처리 O
Final Test : final result = 0.915,0.919,0.909,0.914
  • 정확도는 떨어지지만 F1값을 확실히 올라갔음을 확인할 수 있다.
  • 정밀도도 약간 떨어졌지만 재현율이 급격히 상승했기 때문이다.
반응형