티스토리 뷰
CNN 복습
DNN은 이미지 데이터의 공간/위치 정보를 훼손한다.
컬러 이미지나 현실세계의 문제에서는 취약하다.
그렇기 때문에 CNN은 각 공간의 특징을 보존하고자 필터를 이용하여 특징들을 추출하고, 그것으로 이용한다.
feature map의 개수를 늘린다. 그 과정에서 필터의 개수, 필터 사이즈, 스트라이드, 패딩, 활성화 함수를 지정해주었다.
하드웨어 제약 때문에 풀링(subsampling)이 대두되었다.
풀링은 절반으로 만드는 (2,2) 정도가 거의 모든 상황에서 선택된다.
max_pooling, avg_pooling > 이것은 필터 사이즈와 스트라이드만 지정해주었다.
대립관계 = 풀링이 없어도 된다.
현재 트랜드 => 스트라이드만 이용해서 줄여나간다.
DNN, Dense, Input, Flatten, Batch Normalization, Dropout - 기본적인 것
+ Conv2D, MaxPooling2D
kernel_size는 이전 피처맵의 depth를 따라간다. 자동으로 보정
Batch Nomalization, Dropout은 모델 성능을 향상시키려는 테크닉이다.
Batch Nomalization은 mini-batch를 의미한다.
->개인적 의문 : Batch Nomalization은 fold가 필요 없는가?
답변 : 딥러닝에서는 각 validation이 랜덤하기 때문에, 이미 fold의 효과가 있다.
Batch Nomalization은 어디에 넣어야 하는가? 내부 공변량 변화 논문을 참조하면
Fully connected -> Batch Nomalization -> Activation
Dense(512) -> BN -> Activation을 써주라는 게 원 저자의 의도
이는 논란이 많았다.
Dense(512) -> Activation -> BN이 성능이 더 좋게 나온다.
그래서 저자도 이제는 포기하고 쓰라고 한다.
2018년에 Batch Nomalization에 대해서 비판한 논문도 있다.
원핫 인코딩을 하는 이유
one vs all / one vs rest
시그모이드 함수로 쓰면 전체 클래스에 대해서 확률로 표현 불가
보정해주는 게 softmax
표준화 기법
한꺼번에 적용
x_std,x_mean = train_x.std(), train_x.mean()
x_train = (train_x - x_mean) / x_std
x_test = (test_x - x_mean) / x_std
x_train.mead()
x_train.std() #확인
각 채널별로 적용
tr_r_mean, tr_r_std = train_x[:,:,:, 0].mean(), train_x[:,:,:, 0].std() #빨간색
tr_g_mean, tr_g_std = train_x[:,:,:, 1].mean(), train_x[:,:,:, 1].std() #초록
tr_b_mean, tr_b_std = train_x[:,:,:, 2].mean(), train_x[:,:,:, 2].std() #파랑
train_x_r = (train_x[:,:,:, 0] - tr_r_mean) / tr_r_std
train_x_g = (train_x[:,:,:, 1] - tr_g_mean) / tr_g_std
train_x_b = (train_x[:,:,:, 2] - tr_b_mean) / tr_b_std
np.stack( (train_x_r,train_x_g, train_x_b), axis=3) .shape
# 3,50000,32,32로 나온다.
# axis=3을 줘야 한다.
더 쉽게 하면 이렇게
# 각 채널별 평균과 표준 편차 계산
x_mean = train_x.mean(axis=(0, 1, 2))
x_std = train_x.std(axis=(0, 1, 2))
# 각 채널별로 표준화 적용
x_train_r = (train_x - x_mean) / x_std
x_test_r = (test_x - x_mean) / x_std
실제에서는 x_train_r이 약간 더 많이 오른다.
학습할 때 functional로 쓰는 게 더 깔끔하다.
il = Input(shape=(h,w,c))
hl = Conv2D(32, (3,3), (1,1) , 'same', activation='relu')(il)
hl = Conv2D(32, (3,3), (1,1) , 'same', activation='relu')(hl)
hl = BatchNormalization()(hl)
hl = MaxPool2D( (2,2), (2,2))(hl)
hl = Dropout(0.25)(h1)
hl = Flatten() (hl)
hl = Dense(1024, activation='relu')(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.25)(h1)
ol = Dense(10, activation='softmax')(hl)
model = Model(inputs=il, outputs=ol)
model.comile(optimizer='adam',validation_split=0.2, epochs=1000, callbacks=[es])
model.summary()
train_y = train_y.argmax(axis=1)
test_y = test_y.argmax(axis=1)
train 후에는 원핫 인코딩을 해제해준다.
그리고 나서 각각에 대해서 accuacy를 구해준다.
pred_train = model.predict(x_train)
pred_test = model.predict(x_test)
single_pred_train = pred_train.argmax(axis=1)
single_pred_test = pred_test.argmax(axis=1)
logi_train_accuracy = accuracy_score(train_y, single_pred_train)
logi_test_accuracy = accuracy_score(test_y, single_pred_test)
print('CNN')
print(f'트레이닝 정확도 : {logi_train_accuracy*100:.2f}%')
print(f'테스트 정확도 : {logi_test_accuracy*100:.2f}%')
Image Data Augmentation
실제 문제에서는 데이터가 부족하다.
데이터가 없으면 수집부터 해야 한다.
그러나 수집한 양 보다 저작권을 고려해야 한다.
수집한 것만이라도 이용해서 어떻게든 성능을 늘리는 방법
import keras
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array #array 형태로 변환하는 방법
import numpy as np
import matplotlib.pyplot as plt
image_org = load_img("Batman.webp")
image = img_to_array(image_org) #array 형태로 변환을 한다.
image.shape # height, width, channel
plt.figure(figsize=(12,8))
# plt.imshow(image)
plt.imshow(image/255)
plt.show()
데이터 증강 방법
https://keras.io/api/layers/preprocessing_layers/
공식 문서 참고
aug_layers = [keras.layers.RandomRotation(factor=(-0.3,0.3)),
keras.layers.RandomTranslation(height_factor=(-0.3,0.3), width_factor=(-0.3,0.3)),
keras.layers.RandomZoom(height_factor=(-0.2,0.2), width_factor=(-0.2,0.2)),
keras.layers.RandomFlip(mode='horizontal_and_vertical')
]
aug_layers는 rotation, translation, zoom, flip 등을 적용할 수 있다.
def image_augmentation(images):
for layer in aug_layers:
images = layer(images)
return images
이미지를 넣는다.
해당 이미지로 aug_layer에서 만든 이미지들을 리턴한다.
aug_imgs = image_augmentation(image)
for i in range(4) :
aug_imgs = image_augmentation(image)
# temp = keras.preprocessing.image.array_to_img(aug_imgs)
plt.imshow( aug_imgs[0]/255 )
plt.axis('off')
plt.show()
그렇게 하면 해당 이미지들이 어떤지 살펴볼 수 있다.
전부 다 하기에는 많고, 3번째 풀링 전까지만 사용
## Functional API
# 1. 세션 클리어
clear_session()
# 2. 레이어 엮기
il = Input(shape=(28,28,1))
al = keras.layers.RandomRotation(factor=(-0.1,0.1))(il)
al = keras.layers.RandomTranslation(height_factor=(-0.1,0.1), width_factor=(-0.1,0.1))(al)
al = keras.layers.RandomZoom(height_factor=(-0.1,0.1), width_factor=(-0.1,0.1))(al)
# al = keras.layers.RandomFlip(mode='horizontal_and_vertical')(al)
hl = Conv2D(filters=64, # 새롭게 제작하려는 feature map의 수! 서로 다른 filter의 수!
kernel_size=(3,3), # Conv 필터의 가로세로
strides=(1,1), # Conv 필터의 이동 보폭
padding='same', # 1. 사이즈 유지 | 2. 외곽 정보 더 반영
activation='relu' # 주의!
)(al)
hl = Conv2D(filters=64, # 새롭게 제작하려는 feature map의 수! 서로 다른 filter의 수!
kernel_size=(3,3), # Conv 필터의 가로세로
strides=(1,1), # Conv 필터의 이동 보폭
padding='same', # 1. 사이즈 유지 | 2. 외곽 정보 더 반영
activation='relu' # 주의!
)(hl)
hl = MaxPool2D(pool_size=(2,2), # 풀링 필터의 가로세로
strides=(2,2) # 풀링 필터의 이동 보폭
)(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.5)(hl)
hl = Conv2D(filters=128, # 새롭게 제작하려는 feature map의 수! 서로 다른 filter의 수!
kernel_size=(3,3), # Conv 필터의 가로세로
strides=(1,1), # Conv 필터의 이동 보폭
padding='same', # 1. 사이즈 유지 | 2. 외곽 정보 더 반영
activation='relu' # 주의!
)(hl)
hl = Conv2D(filters=128, # 새롭게 제작하려는 feature map의 수! 서로 다른 filter의 수!
kernel_size=(3,3), # Conv 필터의 가로세로
strides=(1,1), # Conv 필터의 이동 보폭
padding='same', # 1. 사이즈 유지 | 2. 외곽 정보 더 반영
activation='relu' # 주의!
)(hl)
hl = MaxPool2D(pool_size=(2,2), # 풀링 필터의 가로세로
strides=(2,2) # 풀링 필터의 이동 보폭
)(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.5)(hl)
hl = Flatten()(hl)
hl = Dense(1024, activation='relu')(hl)
ol = Dense(10, activation='softmax')(hl)
# 3. 모델의 시작과 끝 지정
model = Model(il, ol)
# 4. 컴파일
model.compile(optimizer='adam', loss='categorical_crossentropy',
metrics=['accuracy'])
## Sequential API
# 1 세션 클리어
clear_session()
# 2 모델 선언
model = Sequential()
# 3 레이어 조립
model.add( Input(shape=(32,32,3)) )
####################################
# Augmentation Layer
model.add( RandomFlip(mode='vertical') )
####################################
model.add( Conv2D(filters=64, # 서로 다른 64개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( Conv2D(filters=64, # 서로 다른 64개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( MaxPool2D(pool_size=(2,2),# pooling filter의 가로세로 사이즈
strides=(2,2), # pooling filter의 이동 보폭
) )
model.add( BatchNormalization() )
model.add( Dropout(0.4) )
model.add( Conv2D(filters=128, # 서로 다른 128개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( Conv2D(filters=128, # 서로 다른 128개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( MaxPool2D(pool_size=(2,2),# pooling filter의 가로세로 사이즈
strides=(2,2), # pooling filter의 이동 보폭
) )
model.add( BatchNormalization() )
model.add( Dropout(0.4) )
model.add( Conv2D(filters=256, # 서로 다른 256개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( Conv2D(filters=256, # 서로 다른 256개의 filter를 사용하여 새로운 feature map을 만들겠다는 의미
kernel_size=(3,3), # Conv2D filter의 가로세로 사이즈
strides=(1,1), # Conv2D filter의 이동 보폭
padding='same', # 1.feature map 크기 유지 및 외곽 정보 더 반영
activation='relu', # 주의!
) )
model.add( MaxPool2D(pool_size=(2,2),# pooling filter의 가로세로 사이즈
strides=(2,2), # pooling filter의 이동 보폭
) )
model.add( BatchNormalization() )
model.add( Dropout(0.4) )
model.add( Flatten() )
model.add( Dense(100, activation='softmax') )
# 4 컴파일
model.compile(optimizer='adam', loss=keras.losses.sparse_categorical_crossentropy,
metrics=['accuracy'])
model.summary()
## Functional API
# 1 세션 클리어
clear_session()
# 2 레이어 연결
il = Input(shape=(32,32,3))
####################################
al = RandomFlip(mode='vertical')(il)
####################################
hl = Conv2D(64, (3,3), (1,1), 'same', activation='relu')(al)
hl = Conv2D(64, (3,3), (1,1), 'same', activation='relu')(hl)
hl = MaxPool2D((2,2), (2,2))(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.4)(hl)
hl = Conv2D(128, (3,3), (1,1), 'same', activation='relu')(hl)
hl = Conv2D(128, (3,3), (1,1), 'same', activation='relu')(hl)
hl = MaxPool2D((2,2), (2,2))(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.4)(hl)
hl = Conv2D(256, (3,3), (1,1), 'same', activation='relu')(hl)
hl = Conv2D(256, (3,3), (1,1), 'same', activation='relu')(hl)
hl = MaxPool2D((2,2), (2,2))(hl)
hl = BatchNormalization()(hl)
hl = Dropout(0.4)(hl)
hl = Flatten()(hl)
ol = Dense(100, activation='softmax')(hl)
# 3 모델 시작 끝 지정
model = Model(il, ol)
# 4 컴파일
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.summary()
모델링의 핵심은 이 부분이다.
전부 0.1 정도로 해주는 게 좋다. 여기서는 실험용
flip은 문자에 미치는 영향이 생각보다 크기 때문에, 바꾸는 것도 괜찮다.
CIFAR-100은 filp만 사용했다.
해당 데이터를 받고, 그러한 데이터를 바꿔준다.
이렇게하면 웃긴 일이 일어난다.
val_accuracy가 더 높다.
train set에는 데이터 증강이 적용되었으나, val_accuracy에는 적용이 되지 않아서 그렇다.
그러나 학습에 있어서 제약조건을 걸어버린 격이 되어버렸다.
만약 y를 원핫 인코딩 하지 않고, sparse_categorical_cross entropy를 썼다면,
y_pred = model.predict(test_x)
# 원핫 인코딩 한 것을 다시 묶어주는 코드
# 평가 지표 및 실제 데이터 확인을 위해 필요
y_pred_arg = np.argmax(y_pred, axis=1)
# test_y_arg = np.argmax(test_y, axis=1)
# y_pred_arg = y_pred
test_y_arg = test_y
from sklearn.metrics import accuracy_score, classification_report
accuracy_score(test_y_arg, y_pred_arg)
print( classification_report(test_y_arg, y_pred_arg, target_names=list(label_dict.values())) )
다음과 같이 해야 classification report가 제대로 나온다.
label_dict는 아래와 같이 만들어놨던 것이다.
label_dict = {0:'apple', 1: 'aquarium_fish', 2: 'baby', 3: 'bear', 4: 'beaver', 5: 'bed', 6: 'bee', 7: 'beetle', 8: 'bicycle', 9: 'bottle',
10: 'bowl', 11: 'boy',12: 'bridge',13: 'bus',14: 'butterfly',15: 'camel',16: 'can',17: 'castle',18: 'caterpillar',19: 'cattle',
20: 'chair',21: 'chimpanzee',22: 'clock',23: 'cloud',24: 'cockroach',25: 'couch',26: 'cra',27: 'crocodile',28: 'cup',29: 'dinosaur',
30: 'dolphin',31: 'elephant',32: 'flatfish',33: 'forest',34: 'fox',35: 'girl',36: 'hamster',37: 'house',38: 'kangaroo',39: 'keyboard',
40: 'lamp',41: 'lawn_mower',42: 'leopard',43: 'lion',44: 'lizard',45: 'lobster',46: 'man',47: 'maple_tree',48: 'motorcycle',49: 'mountain',
50: 'mouse',51: 'mushroom',52: 'oak_tree',53: 'orange',54: 'orchid',55: 'otter',56: 'palm_tree',57: 'pear',58: 'pickup_truck',59: 'pine_tree',
60: 'plain',61: 'plate',62: 'poppy',63: 'porcupine',64: 'possum',65: 'rabbit',66: 'raccoon',67: 'ray',68: 'road',69: 'rocket',
70: 'rose',71: 'sea',72: 'seal',73: 'shark',74: 'shrew',75: 'skunk',76: 'skyscraper',77: 'snail',78: 'snake',79: 'spider',
80: 'squirrel',81: 'streetcar',82: 'sunflower',83: 'sweet_pepper',84: 'table',85: 'tank',86: 'telephone',87: 'television',88: 'tiger',89: 'tractor',
90: 'train',91: 'trout',92: 'tulip',93: 'turtle',94: 'wardrobe',95: 'whale',96: 'willow_tree',97: 'wolf',98: 'woman',99: 'worm'
}
Feature Representation
연결된 것으로부터 기존에 없던 새로운 feature를 재표현, 생성 하는 것
해당 피처에 대해 질문을 던질 수 있다.
선형에서 만들어진 피처의 연산이 간단한가? -> yes
유용한가? -> 성능에서, 설명상
Z라는 피처가 생성되었을 때, sigmoid(z)로 하면, y는 예측값이다.
학습이 잘 되었다고 가정해보자.
- z값이 올라가면 y값도 같이 올라간다 (타깃이 될 확률 up)
- z값이 내려가면, y값도 같이 내려간다(타깃이 될 확률 down)
따라서 학습이 잘 된 상태에서는, 설명도 그럴싸하게 된다.
그런데 중간에 히든 레이어가 들어갔다고 해보자.
노드 하나에 대해서 이런 각각의 계산이 발생한다(각 노드마다 z가 생성된다)
a. 간단한가?
b. 유용한가요? -> 성능상으로는 유용할 수밖에 없다.(학습이 잘 되어 있다고 가정하므로)
그러나 설명상으로는 유용한지 모른다.
해당 노드가 하나만이 아니라 여러개이기 때문이다. 그렇기에 블랙박스 모델이다.
그런데 이 가정도 결국에는 훈련이 잘 되어 있어야 가능하다.
그래서 성능부터 높여보자는 게 딥러닝의 사상 중 하나이다.
딥러닝의 성능
2개의 히든 레이어가 있는 딥러닝 모델이 있다.
1번째 히든 레이어의 노드(feature)를 제거 했는데, 성능이 유지가 된다고 가정하자.
해당 노드의 타겟 예측력, 설명력이 없다는 의미이다.
그러면 이번엔 2번째 히든 레이어에서 노드를 증가시켰을 때, 성능이 올라갔다고 하자.
이때는 히든 레이어의 feature가 부족했었다고 결론을 내릴 수 있다.
다시 이번엔 3번째 히든 레이어를 추가했는데 성능이 올라갔다.
고수준 feature가 부족했다고 결론을 내릴 수 있다.
여기서 고수준 저수준의 의미는, Input으로부터 얼마나 떨어져 있는가이다.
따라서 히든 레이어의 수를 조절하고, 노드의 수를 조절하면서 적절한 크기를 찾는 것(depth, width)이 딥러닝의 성능 향상이다.