강의영상

평가지표

다양한 평가지표들

- 의문: 왜 다양한 평가지표가 필요한가? (accuray면 끝나는거 아닌가? 더 이상 뭐가 필요해?)

- 여러가지 평가지표들: https://en.wikipedia.org/wiki/Positive_and_negative_predictive_values

  • 이걸 다 암기하는건 불가능함.
  • 몇 개만 뽑아서 암기하고 왜 쓰는지만 생각해보고 넘어가자!

confusion matrix의 이해

- 표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

- 정의: Accuracy(애큐러시)=$\frac{TP+TN}{total}$=$\frac{\#O/O+ \#X/X}{total}$

  • 한국말로는 정확도, 정분류율이라고 한다.
  • 한국말이 헷갈리므로 그냥 영어를 외우는게 좋다. (어차피 Keras에서 옵션도 영어로 넣음)

- (상확극 시점1) 왜 애큐러시는 불충분한가?

  • 회사: 퇴사자예측프로그램 개발해
  • 최규빈: 귀찮은데 다 안나간다고 하자! -> 99퍼의 accuracy

모델에 사용한 파라메터 = 0. 그런데 애큐러시 = 99! 이거 엄청 좋은 모형이다?

Sensitivity(민감도), Recall(재현율), True Positive Rate(TPR)

- 정의: 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

- 정의: 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 올리겠다고 무작정 너무 많이 예측하지 말란 말이야!)

F1 score

- 정의: recall과 precision의 조화평균

- (상황극 시점4) recall, precision을 모두 고려

  • 최규빈: recall/precision을 같이 내는건 좋은데요, 둘은 trade off의 관계에 있습니다. 물론 둘다 올리는 모형이 있다면 좋지만 그게 쉽지는 않아요. 보통은 precision을 올리려면 recall이 희생되는 면이 있고요, recall을 올리려고 하면 precision이 다소 떨어집니다.

  • 최규빈: 평가기준이 애매하다는 의미입니다. 모형1,2가 있는데 모형1은 모형2보다 precision이 약간 좋고 대신 recall이 떨어진다면 모형1이 좋은것입니까? 아니면 모형2가 좋은것입니까?

  • 인사팀: 그렇다면 둘을 평균내서 F1score를 계산해서 제출해주세요.

Specificity(특이도), False Positive Rate(FPR)

- 정의:

(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을 올리기 위해서 "실제로는 회사 잘 다니고 있는 사람 중 최규빈이 나갈것 같다고 찍은 사람들" 의 비율이다.

즉 생사람잡은 비율.. 오해해서 미안한 사람의 비율..

ROC curve

- 정의: $x$축=FPR, $y$축=TPR 을 그린 커브

- 의미:

  • 결국 "오해해서 미안해 vs recall"을 그린 곡선이 ROC커브이다.
  • 생각해보면 오해하는 사람이 많을수록 당연히 recall은 올라간다. 따라서 우상향하는 곡선이다.
  • 오해한 사람이 매우 적은데 recall이 우수하면 매우 좋은 모형이다. 그래서 초반부터 ROC값이 급격하게 올라가면 좋은 모형이다.

fashion_mnist (revisit)

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
(60000, 28, 28)
  • 이미지는 원래 가로픽셀 세로픽셀 3 이어야 한다. (색을 표현하는 basis는 빨,녹,파)

- 따라서 이미지의 차원이 단지 (28,28)이라는 것은 흑백이미지라는 뜻이다.

plt.imshow(x_train[0]) 
<matplotlib.image.AxesImage at 0x7fdf95b91240>
  • 아닌데요?! 칼라인데요?! -> 흑백이다. 그냥 밝을수록 노란색, 어두울수록 남색으로 표현한것 뿐임 (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)
2022-05-11 11:04:15.023063: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:939] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
X.shape, XX.shape
(TensorShape([60000, 28, 28, 1]), TensorShape([10000, 28, 28, 1]))
  • keras에서 이미지자료는 (관측치수,픽셀,픽셀,채널)과 같은 형식을 가진다.
  • 예를들어 256*256 size인 칼라이미지(채널수=3)가 10개 있다면 X.shape은 (10,256,256,3)이다.

X의 차원이 (관측치수,픽셀,픽셀,채널)일 경우 DNN 쓰기

(예제1) X -> Dense(30,relu) -> Dense(10,softmax):=> y

- 이러한 아키텍처를 돌리기 위해서는 X의 shape을 미리 바꿔야 했었다. 혹시 바꾸지 않는 방법도 있을까?

- tf.keras.layers.Flatten()

flttn = tf.keras.layers.Flatten()
flttn
<keras.layers.core.flatten.Flatten at 0x7fdf95bd6410>
  • type: flatten <- 머 어쩌란거야..
set(dir(flttn)) & {'__call__'}
{'__call__'}
  • call이 있음 -> 써보자
X.shape
TensorShape([60000, 28, 28, 1])
flttn(X) # 오..? 
<tf.Tensor: shape=(60000, 784), dtype=float32, numpy=
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]], dtype=float32)>

펴진다? 즉 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)
Epoch 1/5
1875/1875 [==============================] - 2s 676us/step - loss: 2.5317 - accuracy: 0.4077
Epoch 2/5
1875/1875 [==============================] - 1s 658us/step - loss: 1.2412 - accuracy: 0.4935
Epoch 3/5
1875/1875 [==============================] - 1s 666us/step - loss: 1.1642 - accuracy: 0.5133
Epoch 4/5
1875/1875 [==============================] - 1s 665us/step - loss: 1.0876 - accuracy: 0.5432
Epoch 5/5
1875/1875 [==============================] - 1s 654us/step - loss: 1.0204 - accuracy: 0.5777
<keras.callbacks.History at 0x7fdf20233cd0>

- 관찰

net1.layers
[<keras.layers.core.flatten.Flatten at 0x7fdf8a5ba860>,
 <keras.layers.core.dense.Dense at 0x7fe088c06a70>,
 <keras.layers.core.dense.Dense at 0x7fdf95b559c0>]
net1.layers[0](X) # 레이어를 통과하는 순간 전처리!
<tf.Tensor: shape=(60000, 784), dtype=float32, numpy=
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]], dtype=float32)>
net1.layers[1](net1.layers[0](X)) # 출력이 30이니까~ + 렐루를 거쳐서 0또는 양수인 모습!
<tf.Tensor: shape=(60000, 30), dtype=float32, numpy=
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]], dtype=float32)>
net1.layers[2](net1.layers[1](net1.layers[0](X))) # 최종출력 10차원, 각각은 확률을 의미하게 된다. 
<tf.Tensor: shape=(60000, 10), dtype=float32, numpy=
array([[2.8995152e-21, 9.9856627e-28, 0.0000000e+00, ..., 1.2420070e-03,
        0.0000000e+00, 9.9541569e-01],
       [4.7701108e-01, 2.6207626e-02, 8.0454284e-03, ..., 2.3365423e-27,
        5.3272030e-04, 4.4156360e-17],
       [4.5070428e-01, 2.1650523e-02, 1.2756436e-02, ..., 6.4327061e-25,
        1.0629607e-03, 1.1437141e-15],
       ...,
       [5.4188430e-01, 5.2440122e-02, 1.0308946e-03, ..., 1.0840898e-37,
        2.5899959e-05, 4.3518392e-23],
       [3.3972868e-01, 1.0773074e-02, 4.7669444e-02, ..., 2.0287354e-17,
        8.0869868e-03, 2.4132330e-11],
       [0.0000000e+00, 0.0000000e+00, 0.0000000e+00, ..., 2.3465420e-05,
        3.3327616e-09, 1.3874462e-04]], dtype=float32)>

- (참고) 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)
Epoch 1/5
1875/1875 [==============================] - 1s 667us/step - loss: 2.5321 - categorical_accuracy: 0.4089
Epoch 2/5
1875/1875 [==============================] - 1s 652us/step - loss: 1.2271 - categorical_accuracy: 0.4976
Epoch 3/5
1875/1875 [==============================] - 1s 662us/step - loss: 1.0478 - categorical_accuracy: 0.5843
Epoch 4/5
1875/1875 [==============================] - 1s 649us/step - loss: 0.9173 - categorical_accuracy: 0.6292
Epoch 5/5
1875/1875 [==============================] - 1s 650us/step - loss: 0.8538 - categorical_accuracy: 0.6457
<keras.callbacks.History at 0x7fdf1075b3a0>
id(tf.metrics.CategoricalAccuracy), id(tf.keras.metrics.CategoricalAccuracy)
(93905843608752, 93905843608752)
  • 주소가 똑같네요, 이게 무슨말인지 알죠?

- 주의사항: 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)
Epoch 1/5
1875/1875 [==============================] - 2s 932us/step - loss: 2.5273 - categorical_accuracy: 0.4117 - recall: 0.3030
Epoch 2/5
1875/1875 [==============================] - 2s 913us/step - loss: 1.2435 - categorical_accuracy: 0.4960 - recall: 0.3858
Epoch 3/5
1875/1875 [==============================] - 2s 937us/step - loss: 1.0755 - categorical_accuracy: 0.5694 - recall: 0.4599
Epoch 4/5
1875/1875 [==============================] - 2s 919us/step - loss: 0.9960 - categorical_accuracy: 0.5975 - recall: 0.4861
Epoch 5/5
1875/1875 [==============================] - 2s 917us/step - loss: 0.8548 - categorical_accuracy: 0.6557 - recall: 0.5258
<keras.callbacks.History at 0x7fdf1066a500>

- 리콜을 추가하면 test set의 성능평가에도 리콜을 볼 수 있다.

net1.evaluate(XX,yy)
313/313 [==============================] - 0s 835us/step - loss: 0.8248 - categorical_accuracy: 0.6813 - recall: 0.5294
[0.8247724771499634, 0.6812999844551086, 0.5293999910354614]

(예제2) X -> Dense(500,relu) -> Dense(500,relu) -> Dense(10,softmax):=>y

- 다른모형으로도 적합시켜보자.

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)
Epoch 1/5
1875/1875 [==============================] - 2s 757us/step - loss: 2.2474 - accuracy: 0.7490
Epoch 2/5
1875/1875 [==============================] - 1s 765us/step - loss: 0.6386 - accuracy: 0.7904
Epoch 3/5
1875/1875 [==============================] - 1s 751us/step - loss: 0.5289 - accuracy: 0.8198
Epoch 4/5
1875/1875 [==============================] - 1s 763us/step - loss: 0.4554 - accuracy: 0.8417
Epoch 5/5
1875/1875 [==============================] - 1s 738us/step - loss: 0.4374 - accuracy: 0.8476
<keras.callbacks.History at 0x7fdf104244c0>
net2.fit(XX,yy)
313/313 [==============================] - 0s 1ms/step - loss: 0.4716 - accuracy: 0.8356
<keras.callbacks.History at 0x7fdf104efdf0>

- 좀 더 돌려보자.

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)
Epoch 1/10
1875/1875 [==============================] - 1s 724us/step - loss: 2.2049 - accuracy: 0.7551
Epoch 2/10
1875/1875 [==============================] - 1s 713us/step - loss: 0.6847 - accuracy: 0.7778
Epoch 3/10
1875/1875 [==============================] - 1s 722us/step - loss: 0.5943 - accuracy: 0.8036
Epoch 4/10
1875/1875 [==============================] - 1s 738us/step - loss: 0.4973 - accuracy: 0.8312
Epoch 5/10
1875/1875 [==============================] - 1s 736us/step - loss: 0.4580 - accuracy: 0.8429
Epoch 6/10
1875/1875 [==============================] - 1s 731us/step - loss: 0.4577 - accuracy: 0.8423
Epoch 7/10
1875/1875 [==============================] - 1s 738us/step - loss: 0.4241 - accuracy: 0.8521
Epoch 8/10
1875/1875 [==============================] - 1s 724us/step - loss: 0.4136 - accuracy: 0.8559
Epoch 9/10
1875/1875 [==============================] - 1s 716us/step - loss: 0.4111 - accuracy: 0.8580
Epoch 10/10
1875/1875 [==============================] - 1s 742us/step - loss: 0.4004 - accuracy: 0.8610
<keras.callbacks.History at 0x7fdf103adb10>
net2.fit(XX,yy)
313/313 [==============================] - 0s 1ms/step - loss: 0.4610 - accuracy: 0.8403
<keras.callbacks.History at 0x7fdf1026bd00>

- 이 이상은 비효율적인듯..

더 좋은 모형을 만들고 싶은데..

(예제3) 아주 복잡한 DNN

- 아 몰라 딥러닝이 해주겠지

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)
Epoch 1/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.9811 - accuracy: 0.7936
Epoch 2/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.4469 - accuracy: 0.8389
Epoch 3/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.4147 - accuracy: 0.8533
Epoch 4/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3944 - accuracy: 0.8603
Epoch 5/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3663 - accuracy: 0.8694
Epoch 6/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3652 - accuracy: 0.8720
Epoch 7/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3467 - accuracy: 0.8777
Epoch 8/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3410 - accuracy: 0.8801
Epoch 9/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3250 - accuracy: 0.8847
Epoch 10/10
1875/1875 [==============================] - 2s 1ms/step - loss: 0.3186 - accuracy: 0.8865
<keras.callbacks.History at 0x7fdf1011a860>
net3.evaluate(XX,yy)
313/313 [==============================] - 0s 867us/step - loss: 0.3635 - accuracy: 0.8767
[0.3634934723377228, 0.8766999840736389]
net2.summary()
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_5 (Flatten)         (None, 784)               0         
                                                                 
 dense_9 (Dense)             (None, 500)               392500    
                                                                 
 dense_10 (Dense)            (None, 500)               250500    
                                                                 
 dense_11 (Dense)            (None, 10)                5010      
                                                                 
=================================================================
Total params: 648,010
Trainable params: 648,010
Non-trainable params: 0
_________________________________________________________________
net3.summary()
Model: "sequential_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_6 (Flatten)         (None, 784)               0         
                                                                 
 dense_12 (Dense)            (None, 500)               392500    
                                                                 
 dense_13 (Dense)            (None, 500)               250500    
                                                                 
 dense_14 (Dense)            (None, 500)               250500    
                                                                 
 dense_15 (Dense)            (None, 500)               250500    
                                                                 
 dense_16 (Dense)            (None, 500)               250500    
                                                                 
 dense_17 (Dense)            (None, 10)                5010      
                                                                 
=================================================================
Total params: 1,399,510
Trainable params: 1,399,510
Non-trainable params: 0
_________________________________________________________________

- 파라메터 증가대비 그닥..

- 왠지 DNN계열로는 한계가 있어보인다.

발상의 전환

- Flattne 레이어를 보면서 느낀점: 생각해보니까 $X \to \hat{y}$를 만드는 과정이 꼭 Full Linear Transform(Dense layer) + Activation(Activation layer)일 필요는 없잖아?

- 뭐가있지?

dir(tf.keras.layers)
['AbstractRNNCell',
 'Activation',
 'ActivityRegularization',
 'Add',
 'AdditiveAttention',
 'AlphaDropout',
 'Attention',
 'Average',
 'AveragePooling1D',
 'AveragePooling2D',
 'AveragePooling3D',
 'AvgPool1D',
 'AvgPool2D',
 'AvgPool3D',
 'BatchNormalization',
 'Bidirectional',
 'CategoryEncoding',
 'CenterCrop',
 'Concatenate',
 'Conv1D',
 'Conv1DTranspose',
 'Conv2D',
 'Conv2DTranspose',
 'Conv3D',
 'Conv3DTranspose',
 'ConvLSTM1D',
 'ConvLSTM2D',
 'ConvLSTM3D',
 'Convolution1D',
 'Convolution1DTranspose',
 'Convolution2D',
 'Convolution2DTranspose',
 'Convolution3D',
 'Convolution3DTranspose',
 'Cropping1D',
 'Cropping2D',
 'Cropping3D',
 'Dense',
 'DenseFeatures',
 'DepthwiseConv1D',
 'DepthwiseConv2D',
 'Discretization',
 'Dot',
 'Dropout',
 'ELU',
 'Embedding',
 'Flatten',
 'GRU',
 'GRUCell',
 'GaussianDropout',
 'GaussianNoise',
 'GlobalAveragePooling1D',
 'GlobalAveragePooling2D',
 'GlobalAveragePooling3D',
 'GlobalAvgPool1D',
 'GlobalAvgPool2D',
 'GlobalAvgPool3D',
 'GlobalMaxPool1D',
 'GlobalMaxPool2D',
 'GlobalMaxPool3D',
 'GlobalMaxPooling1D',
 'GlobalMaxPooling2D',
 'GlobalMaxPooling3D',
 'Hashing',
 'Input',
 'InputLayer',
 'InputSpec',
 'IntegerLookup',
 'LSTM',
 'LSTMCell',
 'Lambda',
 'Layer',
 'LayerNormalization',
 'LeakyReLU',
 'LocallyConnected1D',
 'LocallyConnected2D',
 'Masking',
 'MaxPool1D',
 'MaxPool2D',
 'MaxPool3D',
 'MaxPooling1D',
 'MaxPooling2D',
 'MaxPooling3D',
 'Maximum',
 'Minimum',
 'MultiHeadAttention',
 'Multiply',
 'Normalization',
 'PReLU',
 'Permute',
 'RNN',
 'RandomContrast',
 'RandomCrop',
 'RandomFlip',
 'RandomHeight',
 'RandomRotation',
 'RandomTranslation',
 'RandomWidth',
 'RandomZoom',
 'ReLU',
 'RepeatVector',
 'Rescaling',
 'Reshape',
 'Resizing',
 'SeparableConv1D',
 'SeparableConv2D',
 'SeparableConvolution1D',
 'SeparableConvolution2D',
 'SimpleRNN',
 'SimpleRNNCell',
 'Softmax',
 'SpatialDropout1D',
 'SpatialDropout2D',
 'SpatialDropout3D',
 'StackedRNNCells',
 'StringLookup',
 'Subtract',
 'TextVectorization',
 'ThresholdedReLU',
 'TimeDistributed',
 'UpSampling1D',
 'UpSampling2D',
 'UpSampling3D',
 'Wrapper',
 'ZeroPadding1D',
 'ZeroPadding2D',
 'ZeroPadding3D',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 '_sys',
 'add',
 'average',
 'concatenate',
 'deserialize',
 'dot',
 'experimental',
 'maximum',
 'minimum',
 'multiply',
 'serialize',
 'subtract']

- 엄청많아..

- 우리는 이중에서 2D conv, max pooling 에 관심이 있다! (이번수업은 max pooling 정도만)

MaxPooling2D, MaxPool2D

id(tf.keras.layers.MaxPooling2D), id(tf.keras.layers.MaxPool2D)
(93905842676320, 93905842676320)

- 테스트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
<tf.Tensor: shape=(1, 2, 2, 1), dtype=int64, numpy=
array([[[[0],
         [1]],

        [[2],
         [3]]]])>
XXX.reshape(1,2,2) # 채널때문에 살짝 헷갈리지만 실제로는 이렇게 생긴 이미지! 
<tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
array([[[0, 1],
        [2, 3]]])>
mp(XXX)
<tf.Tensor: shape=(1, 1, 1, 1), dtype=int64, numpy=array([[[[3]]]])>

- 테스트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)
(<tf.Tensor: shape=(1, 4, 4, 1), dtype=int64, numpy=
 array([[[[ 0],
          [ 1],
          [ 2],
          [ 3]],
 
         [[ 4],
          [ 5],
          [ 6],
          [ 7]],
 
         [[ 8],
          [ 9],
          [10],
          [11]],
 
         [[12],
          [13],
          [14],
          [15]]]])>,
 <tf.Tensor: shape=(1, 4, 4), dtype=int64, numpy=
 array([[[ 0,  1,  2,  3],
         [ 4,  5,  6,  7],
         [ 8,  9, 10, 11],
         [12, 13, 14, 15]]])>)
mp(XXX),mp(XXX).reshape(1,2,2)
(<tf.Tensor: shape=(1, 2, 2, 1), dtype=int64, numpy=
 array([[[[ 5],
          [ 7]],
 
         [[13],
          [15]]]])>,
 <tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
 array([[[ 5,  7],
         [13, 15]]])>)

- 테스트3: (6,6) 이미지

XXX = tnp.arange(1*6*6*1).reshape(1,6,6,1)
XXX.reshape(1,6,6)
<tf.Tensor: shape=(1, 6, 6), dtype=int64, numpy=
array([[[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17],
        [18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29],
        [30, 31, 32, 33, 34, 35]]])>
mp(XXX).reshape(1,3,3) # 왜 (2,2)씩...? 
<tf.Tensor: shape=(1, 3, 3), dtype=int64, numpy=
array([[[ 7,  9, 11],
        [19, 21, 23],
        [31, 33, 35]]])>

- 테스트4: (6,6) 이미지 + pool_size=(3, 3)

mp3 = tf.keras.layers.MaxPool2D(pool_size=(3,3))
mp3(XXX).reshape(1,2,2)
<tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
array([[[14, 17],
        [32, 35]]])>

이 부분은 5월11일 강의에서 추가된 내용입니다. 설명은 5월11일 영상을 참고하세요

- 테스트5: 관측치증가

XXX = tnp.arange(2*4*4*1).reshape(2,4,4,1)
XXX.reshape(2,4,4)
<tf.Tensor: shape=(2, 4, 4), dtype=int64, numpy=
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]],

       [[16, 17, 18, 19],
        [20, 21, 22, 23],
        [24, 25, 26, 27],
        [28, 29, 30, 31]]])>
mp(XXX).reshape(2,2,2)
<tf.Tensor: shape=(2, 2, 2), dtype=int64, numpy=
array([[[ 5,  7],
        [13, 15]],

       [[21, 23],
        [29, 31]]])>

- 테스트6: 채널증가

XXX = tnp.arange(1*4*4*3).reshape(1,4,4,3)
XXX
<tf.Tensor: shape=(1, 4, 4, 3), dtype=int64, numpy=
array([[[[ 0,  1,  2],
         [ 3,  4,  5],
         [ 6,  7,  8],
         [ 9, 10, 11]],

        [[12, 13, 14],
         [15, 16, 17],
         [18, 19, 20],
         [21, 22, 23]],

        [[24, 25, 26],
         [27, 28, 29],
         [30, 31, 32],
         [33, 34, 35]],

        [[36, 37, 38],
         [39, 40, 41],
         [42, 43, 44],
         [45, 46, 47]]]])>
XXX[...,0] # XXX[:,:,:,0]
<tf.Tensor: shape=(1, 4, 4), dtype=int64, numpy=
array([[[ 0,  3,  6,  9],
        [12, 15, 18, 21],
        [24, 27, 30, 33],
        [36, 39, 42, 45]]])>
XXX1 = XXX[...,0]
XXX2 = XXX[...,1]
XXX3 = XXX[...,2]
XXX1,XXX2,XXX3
(<tf.Tensor: shape=(1, 4, 4), dtype=int64, numpy=
 array([[[ 0,  3,  6,  9],
         [12, 15, 18, 21],
         [24, 27, 30, 33],
         [36, 39, 42, 45]]])>,
 <tf.Tensor: shape=(1, 4, 4), dtype=int64, numpy=
 array([[[ 1,  4,  7, 10],
         [13, 16, 19, 22],
         [25, 28, 31, 34],
         [37, 40, 43, 46]]])>,
 <tf.Tensor: shape=(1, 4, 4), dtype=int64, numpy=
 array([[[ 2,  5,  8, 11],
         [14, 17, 20, 23],
         [26, 29, 32, 35],
         [38, 41, 44, 47]]])>)
YYY1 = mp(XXX)[...,0]
YYY2 = mp(XXX)[...,1]
YYY3 = mp(XXX)[...,2]
YYY1,YYY2,YYY3
(<tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
 array([[[15, 21],
         [39, 45]]])>,
 <tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
 array([[[16, 22],
         [40, 46]]])>,
 <tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
 array([[[17, 23],
         [41, 47]]])>)
  • 관측치와 채널은 처음에만 따져보고 외울때는 1observation/흑백 버전만 고려해도 무방! (나머지는 복붙이니까)

- 테스트7: 숫자가 좀 안맞으면?...

XXX = - tnp.arange(1*5*5*1).reshape(1,5,5,1)
XXX.reshape(1,5,5)
<tf.Tensor: shape=(1, 5, 5), dtype=int64, numpy=
array([[[  0,  -1,  -2,  -3,  -4],
        [ -5,  -6,  -7,  -8,  -9],
        [-10, -11, -12, -13, -14],
        [-15, -16, -17, -18, -19],
        [-20, -21, -22, -23, -24]]])>
mp(XXX).reshape(1,2,2)
<tf.Tensor: shape=(1, 2, 2), dtype=int64, numpy=
array([[[  0,  -2],
        [-10, -12]]])>
tf.keras.layers.MaxPool2D?
Init signature:
tf.keras.layers.MaxPool2D(
    pool_size=(2, 2),
    strides=None,
    padding='valid',
    data_format=None,
    **kwargs,
)
Docstring:     
Max pooling operation for 2D spatial data.

Downsamples the input along its spatial dimensions (height and width)
by taking the maximum value over an input window
(of size defined by `pool_size`) for each channel of the input.
The window is shifted by `strides` along each dimension.

The resulting output,
when using the `"valid"` padding option, has a spatial shape
(number of rows or columns) of:
`output_shape = math.floor((input_shape - pool_size) / strides) + 1`
(when `input_shape >= pool_size`)

The resulting output shape when using the `"same"` padding option is:
`output_shape = math.floor((input_shape - 1) / strides) + 1`

For example, for `strides=(1, 1)` and `padding="valid"`:

>>> x = tf.constant([[1., 2., 3.],
...                  [4., 5., 6.],
...                  [7., 8., 9.]])
>>> x = tf.reshape(x, [1, 3, 3, 1])
>>> max_pool_2d = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
...    strides=(1, 1), padding='valid')
>>> max_pool_2d(x)
<tf.Tensor: shape=(1, 2, 2, 1), dtype=float32, numpy=
  array([[[[5.],
           [6.]],
          [[8.],
           [9.]]]], dtype=float32)>

For example, for `strides=(2, 2)` and `padding="valid"`:

>>> x = tf.constant([[1., 2., 3., 4.],
...                  [5., 6., 7., 8.],
...                  [9., 10., 11., 12.]])
>>> x = tf.reshape(x, [1, 3, 4, 1])
>>> max_pool_2d = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
...    strides=(2, 2), padding='valid')
>>> max_pool_2d(x)
<tf.Tensor: shape=(1, 1, 2, 1), dtype=float32, numpy=
  array([[[[6.],
           [8.]]]], dtype=float32)>

Usage Example:

>>> input_image = tf.constant([[[[1.], [1.], [2.], [4.]],
...                            [[2.], [2.], [3.], [2.]],
...                            [[4.], [1.], [1.], [1.]],
...                            [[2.], [2.], [1.], [4.]]]])
>>> output = tf.constant([[[[1], [0]],
...                       [[0], [1]]]])
>>> model = tf.keras.models.Sequential()
>>> model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
...    input_shape=(4, 4, 1)))
>>> model.compile('adam', 'mean_squared_error')
>>> model.predict(input_image, steps=1)
array([[[[2.],
         [4.]],
        [[4.],
         [4.]]]], dtype=float32)

For example, for stride=(1, 1) and padding="same":

>>> x = tf.constant([[1., 2., 3.],
...                  [4., 5., 6.],
...                  [7., 8., 9.]])
>>> x = tf.reshape(x, [1, 3, 3, 1])
>>> max_pool_2d = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
...    strides=(1, 1), padding='same')
>>> max_pool_2d(x)
<tf.Tensor: shape=(1, 3, 3, 1), dtype=float32, numpy=
  array([[[[5.],
           [6.],
           [6.]],
          [[8.],
           [9.],
           [9.]],
          [[8.],
           [9.],
           [9.]]]], dtype=float32)>

Args:
  pool_size: integer or tuple of 2 integers,
    window size over which to take the maximum.
    `(2, 2)` will take the max value over a 2x2 pooling window.
    If only one integer is specified, the same window length
    will be used for both dimensions.
  strides: Integer, tuple of 2 integers, or None.
    Strides values.  Specifies how far the pooling window moves
    for each pooling step. If None, it will default to `pool_size`.
  padding: One of `"valid"` or `"same"` (case-insensitive).
    `"valid"` means no padding. `"same"` results in padding evenly to
    the left/right or up/down of the input such that output has the same
    height/width dimension as the input.
  data_format: A string,
    one of `channels_last` (default) or `channels_first`.
    The ordering of the dimensions in the inputs.
    `channels_last` corresponds to inputs with shape
    `(batch, height, width, channels)` while `channels_first`
    corresponds to inputs with shape
    `(batch, channels, height, width)`.
    It defaults to the `image_data_format` value found in your
    Keras config file at `~/.keras/keras.json`.
    If you never set it, then it will be "channels_last".

Input shape:
  - If `data_format='channels_last'`:
    4D tensor with shape `(batch_size, rows, cols, channels)`.
  - If `data_format='channels_first'`:
    4D tensor with shape `(batch_size, channels, rows, cols)`.

Output shape:
  - If `data_format='channels_last'`:
    4D tensor with shape `(batch_size, pooled_rows, pooled_cols, channels)`.
  - If `data_format='channels_first'`:
    4D tensor with shape `(batch_size, channels, pooled_rows, pooled_cols)`.

Returns:
  A tensor of rank 4 representing the maximum pooled values.  See above for
  output shape.
File:           ~/anaconda3/envs/tfgpu/lib/python3.10/site-packages/keras/layers/pooling.py
Type:           type
Subclasses:     MaxPooling2D
mp = tf.keras.layers.MaxPool2D(padding='same')
mp(XXX).reshape(1,3,3)
<tf.Tensor: shape=(1, 3, 3), dtype=int64, numpy=
array([[[  0,  -2,  -4],
        [-10, -12, -14],
        [-20, -22, -24]]])>
XXX.reshape(1,5,5)
<tf.Tensor: shape=(1, 5, 5), dtype=int64, numpy=
array([[[  0,  -1,  -2,  -3,  -4],
        [ -5,  -6,  -7,  -8,  -9],
        [-10, -11, -12, -13, -14],
        [-15, -16, -17, -18, -19],
        [-20, -21, -22, -23, -24]]])>