(10주차) 5월9일
- 의문: 왜 다양한 평가지표가 필요한가? (accuray면 끝나는거 아닌가? 더 이상 뭐가 필요해?)
- 여러가지 평가지표들: https://en.wikipedia.org/wiki/Positive_and_negative_predictive_values
- 이걸 다 암기하는건 불가능함.
- 몇 개만 뽑아서 암기하고 왜 쓰는지만 생각해보고 넘어가자!
- 표1
| 퇴사(예측) | 안나감(예측) | |
|---|---|---|
| 퇴사(실제) | TP | FN |
| 안나감(실제) | FP | TN |
- 표2 (책에없음)
| 퇴사(예측) | 안나감(예측) | |
|---|---|---|
| 퇴사(실제) | $(y,\hat{y})= $ (O,O) | $(y,\hat{y})= $(O,X) |
| 안나감(실제) | $(y,\hat{y})= $(X,O) | $(y,\hat{y})= $(X,X) |
- 표3 (책에없음)
| 퇴사(예측) | 안나감(예측) | |
|---|---|---|
| 퇴사(실제) | TP, $\# O/O$ | FN, $\#O/X$ |
| 안나감(실제) | FP, $\#X/O$ | TN, $\#X/X$ |
- 암기법, (1) 두번째 글자를 그대로 쓴다 (2) 첫글자가 T이면 분류를 제대로한것, 첫글자가 F이면 분류를 잘못한것
- 표4 (위키등에 있음)
| 퇴사(예측) | 안나감(예측) | ||
|---|---|---|---|
| 퇴사(실제) | TP, $\# O/O$ | FN, $\# O/X$ | Sensitivity(민감도)=Recall(재현율)=$\frac{TP}{TP+FN}$=$\frac{\#O/O}{\# O/O+ \#O/X}$ |
| 안나감(실제) | FP, $\# X/O$ | TN, $\# X/X$ | |
| Precision(프리시즌)=$\frac{TP}{TP+FP}$=$\frac{\# O/O}{\# O/O+\# X/O}$ | Accuracy(애큐러시)=$\frac{TP+TN}{total}$=$\frac{\#O/O+\# X/X}{total}$ |
- 최규빈은 입사하여 "퇴사자 예측시스템"의 개발에 들어갔다.
- 자료의 특성상 대부분의 사람이 퇴사하지 않고 회사에 잘 다닌다. 즉 1000명이 있으면 10명정도 퇴사한다.
- 정의: Accuracy(애큐러시)=$\frac{TP+TN}{total}$=$\frac{\#O/O+ \#X/X}{total}$
- 한국말로는 정확도, 정분류율이라고 한다.
- 한국말이 헷갈리므로 그냥 영어를 외우는게 좋다. (어차피 Keras에서 옵션도 영어로 넣음)
- (상확극 시점1) 왜 애큐러시는 불충분한가?
- 회사: 퇴사자예측프로그램 개발해
- 최규빈: 귀찮은데 다 안나간다고 하자! -> 99퍼의 accuracy
모델에 사용한 파라메터 = 0. 그런데 애큐러시 = 99! 이거 엄청 좋은 모형이다?
- 정의: Sensitivity(민감도)=Recall(재현율)=$\frac{TP}{TP+FN}$=$\frac{\# O/O}{\# O/O+\# O/X}$
- 분모: 실제 O인 관측치 수
- 분자: 실제 O를 O라고 예측한 관측치 수
- 뜻: 실제 O를 O라고 예측한 비율
- (상황극 시점2) recall을 봐야하는 이유
- 인사팀: 실제 퇴사자를 퇴사자로 예측해야 의미가 있음! 우리는 퇴사할것 같은 10명을 찍어달란 의미였어요! (그래야 면담을 하든 할거아냐!)
- 최규빈: 가볍고(=파라메터 적고) 잘 맞추는 모형 만들어 달라면서요?
- 인사팀: (고민중..) 사실 생각해보니까 이 경우는 애큐러시는 의미가 없네. 실제 나간 사람 중 최규빈이 나간다고 한 사람이 몇인지 카운트 하는게 더 의미가 있겠다. 우리는 앞으로 리컬(혹은 민감도)를 보겠다!
예시1:실제로 퇴사한 10명중 최규빈이 나간다고 찍은 사람이 5명이면 리컬이 50% > 예시2:최규빈이 아무도 나가지 않는다고 예측해버린다? 실제 10명중에서 최규빈이 나간다고 적중시킨사람은 0명이므로 이 경우 리컬은 0%
- 결론: 우리가 필요한건 recall이니까 앞으로 recall을 가져와! accuracy는 큰 의미없어. (그래도 명색이 모델인데 accuracy가 90은 되면 좋겠다)
- 정의: Precision(프리시즌)=$\frac{TP}{TP+FP}$=$\frac{\# O/O}{\# O/O+\# X/O}$
- 분모: O라고 예측한 관측치
- 분자: O라고 예측한 관측치중 진짜 O인 관측치
- 뜻: O라고 예측한 관측치중 진짜 O인 비율
- (상황극 시점3) recall 만으로 불충분한 이유
- 최규빈: 에휴.. 귀찮은데 그냥 좀만 수틀리면 다 나갈것 같다고 해야겠다. -> 한 100명 나간다고 했음 -> 실제로 최규빈이 찍은 100명중에 10명이 다 나감!
이 경우 애큐러시는 91%, 리컬은 100% (퇴사자 10명을 일단은 다 맞췄으므로).
-
인사팀: (화가 많이 남) 멀쩡한 사람까지 다 퇴사할 것 같다고 하면 어떡해요? 최규빈 연구원이 나간다고 한 100명중에 실제로 10명만 나갔어요.
-
인사팀: 마치 총으로 과녁중앙에 맞춰 달라고 했더니 기관총을 가져와서 한번 긁은것이랑 뭐가 달라요? 맞추는게 문제가 아니고 precision이 너무 낮아요.
- 최규빈: accuracy 90% 이상, recall은 높을수록 좋다는게 주문 아니었나요?
- 인사팀: (고민중..) 앞으로는 recall과 함께 precision도 같이 제출하세요. precision은 당신이 나간다고 한 사람중에 실제 나간사람의 비율을 의미해요. 이 경우는 $\frac{10}{100}$이니까 precision이 10%입니다. (속마음: recall 올리겠다고 무작정 너무 많이 예측하지 말란 말이야!)
- 정의: recall과 precision의 조화평균
- (상황극 시점4) recall, precision을 모두 고려
-
최규빈: recall/precision을 같이 내는건 좋은데요, 둘은 trade off의 관계에 있습니다. 물론 둘다 올리는 모형이 있다면 좋지만 그게 쉽지는 않아요. 보통은 precision을 올리려면 recall이 희생되는 면이 있고요, recall을 올리려고 하면 precision이 다소 떨어집니다.
-
최규빈: 평가기준이 애매하다는 의미입니다. 모형1,2가 있는데 모형1은 모형2보다 precision이 약간 좋고 대신 recall이 떨어진다면 모형1이 좋은것입니까? 아니면 모형2가 좋은것입니까?
- 인사팀: 그렇다면 둘을 평균내서 F1score를 계산해서 제출해주세요.
- 정의:
(1) Specificity(특이도)=$\frac{TN}{FP+TN}$=$\frac{\# X/X}{\# X/O+\# X/X}$
(2) False Positive Rate (FPR) = 1-Specificity(특이도) = $\frac{FP}{FP+TN}$=$\frac{\# X/O}{\# X/O+\# X/X}$
- 의미: FPR = 오해해서 미안해, recall(=TPR)을 올리려고 보니 어쩔 수 없었어 ㅠㅠ
- specificity는 안나간 사람을 안나갔다고 찾아낸 비율인데 별로 안중요하다.
- FPR은 recall을 올리기 위해서 "실제로는 회사 잘 다니고 있는 사람 중 최규빈이 나갈것 같다고 찍은 사람들" 의 비율이다.
즉 생사람잡은 비율.. 오해해서 미안한 사람의 비율..
- 정의: $x$축=FPR, $y$축=TPR 을 그린 커브
- 의미:
- 결국 "오해해서 미안해 vs recall"을 그린 곡선이 ROC커브이다.
- 생각해보면 오해하는 사람이 많을수록 당연히 recall은 올라간다. 따라서 우상향하는 곡선이다.
- 오해한 사람이 매우 적은데 recall이 우수하면 매우 좋은 모형이다. 그래서 초반부터 ROC값이 급격하게 올라가면 좋은 모형이다.
import tensorflow as tf
import tensorflow.experimental.numpy as tnp
tnp.experimental_enable_numpy_behavior()
import matplotlib.pyplot as plt
- fashion mnist data 다시 불러오자
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
x_train.shape
- 이미지는 원래 가로픽셀 세로픽셀 3 이어야 한다. (색을 표현하는 basis는 빨,녹,파)
- 따라서 이미지의 차원이 단지 (28,28)이라는 것은 흑백이미지라는 뜻이다.
plt.imshow(x_train[0])
- 아닌데요?! 칼라인데요?! -> 흑백이다. 그냥 밝을수록 노란색, 어두울수록 남색으로 표현한것 뿐임 (colormap이 viridis일 뿐임)
- 일반적으로 분석할 이미지는 칼라를 의미하는 채널도 포함할테니 아래와 같이 자료형을 정리하는게 일반적으로 이미지 자료를 분석하는 정석적인 처리방법이다.
X = tf.constant(x_train.reshape(-1,28,28,1),dtype=tf.float64)
y = tf.keras.utils.to_categorical(y_train)
XX = tf.constant(x_test.reshape(-1,28,28,1),dtype=tf.float64)
yy = tf.keras.utils.to_categorical(y_test)
X.shape, XX.shape
- keras에서 이미지자료는 (관측치수,픽셀,픽셀,채널)과 같은 형식을 가진다.
- 예를들어 256*256 size인 칼라이미지(채널수=3)가 10개 있다면 X.shape은 (10,256,256,3)이다.
- 이러한 아키텍처를 돌리기 위해서는 X의 shape을 미리 바꿔야 했었다. 혹시 바꾸지 않는 방법도 있을까?
- tf.keras.layers.Flatten()
flttn = tf.keras.layers.Flatten()
flttn
- type: flatten <- 머 어쩌란거야..
set(dir(flttn)) & {'__call__'}
- call이 있음 -> 써보자
X.shape
flttn(X) # 오..?
펴진다? 즉 X.reshape(-1,784)와 같은 기능!
- 근데 이거 레이어다? 즉 네트워크에 add 할 수 있다는 의미!
- 그렇다면 아래와 같이 예제를 풀어도 괜찮겠다.
tf.random.set_seed(43052)
net1 = tf.keras.Sequential()
net1.add(tf.keras.layers.Flatten())
net1.add(tf.keras.layers.Dense(30,activation='relu'))
net1.add(tf.keras.layers.Dense(10,activation='softmax'))
net1.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=['accuracy'])
net1.fit(X,y,epochs=5)
- 관찰
net1.layers
net1.layers[0](X) # 레이어를 통과하는 순간 전처리!
net1.layers[1](net1.layers[0](X)) # 출력이 30이니까~ + 렐루를 거쳐서 0또는 양수인 모습!
net1.layers[2](net1.layers[1](net1.layers[0](X))) # 최종출력 10차원, 각각은 확률을 의미하게 된다.
- (참고) metrics=['accuracy'] 대신에 이렇게 해도된다~
tf.random.set_seed(43052)
net1 = tf.keras.Sequential()
net1.add(tf.keras.layers.Flatten())
net1.add(tf.keras.layers.Dense(30,activation='relu'))
net1.add(tf.keras.layers.Dense(10,activation='softmax'))
net1.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=[tf.metrics.CategoricalAccuracy()])
net1.fit(X,y,epochs=5)
id(tf.metrics.CategoricalAccuracy), id(tf.keras.metrics.CategoricalAccuracy)
- 주소가 똑같네요, 이게 무슨말인지 알죠?
- 주의사항: tf.metrics.Accuracy() 말고 tf.metrics.CategoricalAccuracy() 를 써야함
- (참고2) 메트릭을 추가할수도 있다
tf.random.set_seed(43052)
net1 = tf.keras.Sequential()
net1.add(tf.keras.layers.Flatten())
net1.add(tf.keras.layers.Dense(30,activation='relu'))
net1.add(tf.keras.layers.Dense(10,activation='softmax'))
net1.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=[tf.metrics.CategoricalAccuracy(),tf.metrics.Recall()])
net1.fit(X,y,epochs=5)
- 리콜을 추가하면 test set의 성능평가에도 리콜을 볼 수 있다.
net1.evaluate(XX,yy)
- 다른모형으로도 적합시켜보자.
tf.random.set_seed(43052)
net2 = tf.keras.Sequential()
net2.add(tf.keras.layers.Flatten())
net2.add(tf.keras.layers.Dense(500,activation='relu'))
net2.add(tf.keras.layers.Dense(500,activation='relu'))
net2.add(tf.keras.layers.Dense(10,activation='softmax'))
net2.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=['accuracy'])
net2.fit(X,y,epochs=5)
net2.fit(XX,yy)
- 좀 더 돌려보자.
tf.random.set_seed(43052)
net2 = tf.keras.Sequential()
net2.add(tf.keras.layers.Flatten())
net2.add(tf.keras.layers.Dense(500,activation='relu'))
net2.add(tf.keras.layers.Dense(500,activation='relu'))
net2.add(tf.keras.layers.Dense(10,activation='softmax'))
net2.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=['accuracy'])
net2.fit(X,y,epochs=10)
net2.fit(XX,yy)
- 이 이상은 비효율적인듯..
- 아 몰라 딥러닝이 해주겠지
tf.random.set_seed(43052)
net3 = tf.keras.Sequential()
net3.add(tf.keras.layers.Flatten())
net3.add(tf.keras.layers.Dense(500,activation='relu'))
net3.add(tf.keras.layers.Dense(500,activation='relu'))
net3.add(tf.keras.layers.Dense(500,activation='relu'))
net3.add(tf.keras.layers.Dense(500,activation='relu'))
net3.add(tf.keras.layers.Dense(500,activation='relu'))
net3.add(tf.keras.layers.Dense(10,activation='softmax'))
net3.compile(loss=tf.losses.categorical_crossentropy, optimizer='adam',metrics=['accuracy'])
net3.fit(X,y,epochs=10)
net3.evaluate(XX,yy)
net2.summary()
net3.summary()
- 파라메터 증가대비 그닥..
- 왠지 DNN계열로는 한계가 있어보인다.
- Flattne 레이어를 보면서 느낀점: 생각해보니까 $X \to \hat{y}$를 만드는 과정이 꼭 Full Linear Transform(Dense layer) + Activation(Activation layer)일 필요는 없잖아?
- 뭐가있지?
dir(tf.keras.layers)
- 엄청많아..
- 우리는 이중에서 2D conv, max pooling 에 관심이 있다! (이번수업은 max pooling 정도만)
id(tf.keras.layers.MaxPooling2D), id(tf.keras.layers.MaxPool2D)
- 테스트1: (2,2) 이미지
mp = tf.keras.layers.MaxPool2D() # pool size의 디폴트는 (2,2)
XXX = tnp.arange(1*2*2*1).reshape(1,2,2,1)
XXX
XXX.reshape(1,2,2) # 채널때문에 살짝 헷갈리지만 실제로는 이렇게 생긴 이미지!
mp(XXX)
- 테스트2: (4,4) 이미지로 변경
mp = tf.keras.layers.MaxPool2D() # pool size의 디폴트는 (2,2)
XXX = tnp.arange(1*4*4*1).reshape(1,4,4,1)
XXX,XXX.reshape(1,4,4)
mp(XXX),mp(XXX).reshape(1,2,2)
- 테스트3: (6,6) 이미지
XXX = tnp.arange(1*6*6*1).reshape(1,6,6,1)
XXX.reshape(1,6,6)
mp(XXX).reshape(1,3,3) # 왜 (2,2)씩...?
- 테스트4: (6,6) 이미지 + pool_size=(3, 3)
mp3 = tf.keras.layers.MaxPool2D(pool_size=(3,3))
mp3(XXX).reshape(1,2,2)
이 부분은 5월11일 강의에서 추가된 내용입니다. 설명은 5월11일 영상을 참고하세요
- 테스트5: 관측치증가
XXX = tnp.arange(2*4*4*1).reshape(2,4,4,1)
XXX.reshape(2,4,4)
mp(XXX).reshape(2,2,2)
- 테스트6: 채널증가
XXX = tnp.arange(1*4*4*3).reshape(1,4,4,3)
XXX
XXX[...,0] # XXX[:,:,:,0]
XXX1 = XXX[...,0]
XXX2 = XXX[...,1]
XXX3 = XXX[...,2]
XXX1,XXX2,XXX3
YYY1 = mp(XXX)[...,0]
YYY2 = mp(XXX)[...,1]
YYY3 = mp(XXX)[...,2]
YYY1,YYY2,YYY3
- 관측치와 채널은 처음에만 따져보고 외울때는 1observation/흑백 버전만 고려해도 무방! (나머지는 복붙이니까)
- 테스트7: 숫자가 좀 안맞으면?...
XXX = - tnp.arange(1*5*5*1).reshape(1,5,5,1)
XXX.reshape(1,5,5)
mp(XXX).reshape(1,2,2)
tf.keras.layers.MaxPool2D?
mp = tf.keras.layers.MaxPool2D(padding='same')
mp(XXX).reshape(1,3,3)
XXX.reshape(1,5,5)