⇒ 작은 데이터셋에서도 정확도가 낮고 ImageNet이나 COCO와 같은 큰 데이터셋은 거의 인식 못하는 근본 원인
: 입력 특징 맵 (feature map)에 컨볼루션을 적용해서 얻은 피쳐 맵을 출력함
입력 피쳐 맵 : k개 채널로 구성 → 깊이가 k인 m x n 맵, 즉 m x n x k 모양의 3차원 텐서
필터의 깊이 = 입력 피쳐 맵의 깊이 =k
필터의 크기 = h x h (h : 보통 3 or 5)
필터 - bias 값 1개 추가로 가짐
➡️ 필터는 개의 가중치 가짐 (가중치 때문에 +1)
🌿 필요한 이유
맵의 경계에 필터를 대면 일부 픽셀이 밖으로 나가서 적용 불가능
but 경계를 제외하면 층이 깊어질수록 맵의 크기 점점 작아짐
정리하자면,
⇒ ouput 맵의 크기를 유지하기 위해
⇒ edge 쪽 픽셀 정보를 더 잘 이용하기 위해
🌿 종류
선생님, Padding은 뭔가요? 밸리드 패딩 / 풀 패딩 / 세임 패딩
stride = s 이면
5x5x3 입력 피쳐 맵 - 3x3x3 필터 2개 적용 → 3x3x2 출력 피쳐 맵
0 padding, stride=2
conv 층의 특성
⇒ 학습 알고리즘이 최적화해야 할 가중치 개수 대폭 줄여줌
컨볼루션층 : 개의 h x h x k 필터 ⇒ 최적화할 가중치 개 (bias 노드 땜에 +1함)
+) 보통 max pooling 많이 사용
피쳐 맵의 크기 mxn은 컨볼루션층과 풀링층의 stride에 따라 바뀜
깊이 k 는 컨볼루션층에서는 필터 개수에 따라 바뀌지만, 풀링층에서는 그대로 유지
🌿 컨볼루션 신경망의 구조
1) 신경망의 앞부분 : 컨볼루션층 & 풀링층 번갈아 쌓음 - 피쳐 추출하는 역할
2) 중간부분 : Flatten 연산 - 컨볼루션층의 다차원 구조 → 1차원 구조로 변환해 FC층에 입력
3) 신경망 뒷부분 : FC층을 쌓아 분류 수행
4) 출력층 : 부류 개수 만큼 노드 배치
[그림 8-11(a)]
필기 숫자 인식 목적 → 출력층에 10개 노드
*) 6@28x28 : 28 x 28 피처맵이 6장이라는 뜻
입력층 : MNIST의 28 x 28맵의 상하좌우에 2픽셀씩 padding → 32 x 32맵 입력
숫자 맵 - 명암(채널 1개) → 입력 피처 맵의 깊이 = 1
[그림 8-11(b)]
LeNet-5의 구조 : C-P-C-P-C-FC-FC
가중치 : conv층 & FC층에만 있음
연산단계
1) 입력은 28×28×1 맵
1번째 conv층 : 5x5 필터 6개, stride=1, 0 padding 적용
2) 32×32 맵에 컨볼루션 적용 → 28×28×6 피쳐 맵이 만들어지고 풀링층을 통과하면 14×14×6 특징 맵이 됨
3) 2번째 conv층은 5×5 필터 16개를 쓰고 stride=1, padding 적용X
⇒ 10×10×16 특징 맵을 얻는데 풀링층을 통과하여 5×5×16 피처 맵
4) 3번째 conv층에서 5×5 필터를 120개 적용하여 1×1×120 피처 맵 얻음
5) Flatten 연산을 통해 1차원 구조로 변환하여 FC층에 입력
6) 1번째 FC층의 특징 벡터는 84개 노드를 가진 은닉층을 통과하여 10개 노드를 가진 출력층에 도달
conv층, pooling층, FC층을 쌓아 만들어서 다양한 모양으로 조립 가능
🌿 오토인코더(auto-encoder) : 입력 영상을 그대로 출력으로 내놓는 컨볼루션 신경망
🌿영상 분할을 위한 컨볼루션 신경망 : fc층 제거 후 마지막 conv 층에 레이블된 분할 맵 배치
고전적 컴퓨터 비전 - 특징을 사람이 설계한 방법으로 추출
ex) LBP, SIFT, HOG 등
수작업 특징으로 분류 모델을 학습 → 특징 추출 프로그램 + 분류기 프로그램 결합 → 인식기 완성
통합된 신경망에서 특징 학습 & 분류기 학습을 통째로 진행
import numpy as np
import tensorflow as tf
import tensorflow.keras.datasets as ds
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D,MaxPooling2D,Flatten,Dropout,Dense
from tensorflow.keras.optimizers import Adam
# 데이터 준비; MNIST 데이터셋 읽어오고 형태 변환
(x_train,y_train),(x_test,y_test)=ds.mnist.load_data()
x_train=x_train.reshape(60000,28,28,1)
x_test=x_test.reshape(10000,28,28,1)
x_train=x_train.astype(np.float32)/255.0
x_test=x_test.astype(np.float32)/255.0
y_train=tf.keras.utils.to_categorical(y_train,10)
y_test=tf.keras.utils.to_categorical(y_test,10)
# 모델 선택
cnn=Sequential()
cnn.add(Conv2D(6,(5,5),padding='same',activation='relu',input_shape=(28,28,1))) # conv층(필터개수, 크기, 패딩 종류, 활성화함수 종류, 신경망에 최초로 입력되는 텐서 모양)
cnn.add(MaxPooling2D(pool_size=(2,2),strides=2)) # 풀링층
cnn.add(Conv2D(16,(5,5),padding='valid',activation='relu'))
cnn.add(MaxPooling2D(pool_size=(2,2),strides=2))
cnn.add(Conv2D(120,(5,5),padding='valid',activation='relu'))
cnn.add(Flatten()) # 텐서 1차원 구조로 변환하여 FC층에 입력
cnn.add(Dense(units=84,activation='relu')) # FC층
cnn.add(Dense(units=10,activation='softmax')) # 출력층
# 학습
cnn.compile(loss='categorical_crossentropy',optimizer=Adam(learning_rate=0.001),metrics=['accuracy']) # 정확률을 사용하여 성능 조사하라
cnn.fit(x_train,y_train,batch_size=128,epochs=30,validation_data=(x_test,y_test),verbose=2) # 매 epoch마다 validation 인수로 성능 평가
# 예측 ; 성능 측정
res=cnn.evaluate(x_test,y_test,verbose=0) # 테스트 집합으로 성능 측정
print('정확률=',res[1]*100) # 퍼센트로 산
import numpy as np
import tensorflow as tf
import tensorflow.keras.datasets as ds
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D,MaxPooling2D,Flatten,Dense,Dropout
from tensorflow.keras.optimizers import Adam
(x_train,y_train),(x_test,y_test)=ds.cifar10.load_data()
x_train=x_train.astype(np.float32)/255.0
x_test=x_test.astype(np.float32)/255.0
y_train=tf.keras.utils.to_categorical(y_train,10)
y_test=tf.keras.utils.to_categorical(y_test,10)
cnn=Sequential()
cnn.add(Conv2D(32,(3,3),activation='relu',input_shape=(32,32,3)))
cnn.add(Conv2D(32,(3,3),activation='relu'))
cnn.add(MaxPooling2D(pool_size=(2,2)))
cnn.add(Dropout(0.25))
cnn.add(Conv2D(64,(3,3),activation='relu'))
cnn.add(Conv2D(64,(3,3),activation='relu'))
cnn.add(MaxPooling2D(pool_size=(2,2)))
cnn.add(Dropout(0.25))
cnn.add(Flatten())
cnn.add(Dense(units=512,activation='relu'))
cnn.add(Dropout(0.5))
cnn.add(Dense(units=10,activation='softmax'))
cnn.compile(loss='categorical_crossentropy',optimizer=Adam(learning_rate=0.001),metrics=['accuracy'])
hist=cnn.fit(x_train,y_train,batch_size=128,epochs=100,validation_data=(x_test,y_test),verbose=2)
res=cnn.evaluate(x_test,y_test,verbose=0)
print('정확률=',res[1]*100)
import matplotlib.pyplot as plt
plt.plot(hist.history['accuracy'])
plt.plot(hist.history['val_accuracy'])
plt.title('Accuracy graph')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train','Validation'])
plt.grid()
plt.show()
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Loss graph')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train','Validation'])
plt.grid()
plt.show()
: 모델 생성 시 사용
층을 흐르는 텐서 중간에 접근하여 값 읽거나, 2갈래로 나눠 다른 처리하는 경우 사용
: 신경망 구성하는 층 제공
: 손실 함수
: 8종류 옵티마이저 제공
두 확률 분포가 얼마나 다른지 측정
한 샘플에 대한 cross entropy
c : 부류 개수
⇒ 교차 엔트로피는 평균제곱오차의 불공정성 문제를 해결
ex) 부류가 2개인 경우; 물체 분할 - 물체 & 배경을 구분
=(물체일 확률, 배경일 확률) : 신경망이 출력한 예측 벡터
y : 참값
영역 분할 - 보통 물체가 점유한 영역보다 배경 영역이 훨씬 넓음 ➡️ 부류 불균형이 심함
손실 함수 계산; 모든 화소의 e를 평균함 → 배경 픽셀만 분류를 잘하면 물체 픽셀를 못하더라도 교차 엔트로피가 낮아지는 부작용이 발생
➡️이런 부작용 방지 방법 : p_t가 커지면 빠르게 0에 가깝게 만드는 것
식 (8.4)의 Focal 손실 함수는 이런 조건을 만족
🌿 SGD
신경망 학습의 목적은 손실 함수의 값을 가능한 한 낮추는 매개변수를 찾는 것이다. 이는 매개변수의 최적값을 찾는 문제이며, 이러한 문제를 푸는 것을 최적화(optimization)라고 한다.
우리는 지금까지 매개변수의 기울기(미분)을 이용해 기울어진 방향으로 매개변수 값을 갱신하는 일을 반복해 최적화를 진행했다. 이것이 확률적 경사 하강법(SGD)이라는 방법
⇒ SGD의 단점은 비등방성(anisotropy) 함수(방향에 따라 성질, 즉 여기에서는 기울기가 달라지는 함수)에서는 탐색 경로가 비효율적
SGD 옵티마이저의 한계를 모멘텀 & 적응적 학습률로 극복
모멘텀(momentum) : 이전 운동량을 현재에 반영할 때 사용
속도 V 갱신 후 V로 가중치 W 갱신
α : 오래된 운동량의 영향력을 줄여주는 인자
검은색 화살표 ; 이전 단계에서 누적된 속도 벡터 V
검은 점으로 표시한 W가 현재 점
SGD ; 그레이디언트 방향(빨강) 따라 이동한 빨간색 점이 새로운 W
모멘텀을 적용한 SGD ; 그레이디언트 방향과 속도 벡터를 더한 방향(파랑)을 따라 이동한 파란색 점이 새로운 W
식 (8.5)의 가중치 갱신 규칙은 고정된 학습률 ρ를 사용
그레이디언트 - 손실 함수가 작아지는 방향을 지시, but 얼만큼 이동해야 최소점에 도달하는지에 대한 정보 X
⇒ 0.01 또는 0.001과 같이 작은 학습률을 곱해 조금씩 보수적으로 이동
학습률이 너무 크면 최저점을 중심으로 왔다갔다하는 현상 나타남
너무 작으면 학습 시간이 과다함
🌿 적응적 학습률 (adaptive learning rate) : 세대 & 그레이디언트에 따라 적응적으로 학습률 정함
딥러닝은 신경망 모델의 용량을 크게 유지하면서 여러 규제 기법을 적용해 과잉 적합을 방지하는 전략 사용
과잉 적합 (over fitting) : 훈련에 참여하지 않은 새로운 데이터에 대해 아주 낮은 성능 보임
: 피쳐 맵 구성하는 요소 중 일부를 랜덤 선택하여 0으로 설정
EarlyStopping
클래스 매개변수 monitor
: 어떤 측정치를 기준으로 삼을지 min_delta
: 그것보다 작은 개선은 개선으로 여기지 마삼 patience
: 그 값에 해당하는 epoch 동안 개선이 없으면 지정한 epoch에 도달하지 않았더라도 조기에 학습 stop restore_best_weights
: False - 멈추는 순간의 가중치 / True - 가장 좋았던 세대의 가중치 취전이 학습 (transfer learning) : 어떤 도메인의 데이터로 학습한 모델을 다른 도메인에 적용해 성능 높이는 방법
사전 학습 모델(pre-trained model) : 대용량 데이터로 미리 학습돼 있어 전이 학습에 활용할 수 있는 모델
Keras documentation: Keras Applications
ResNet50 사용
import cv2 as cv
import numpy as np
from tensorflow.keras.applications.resnet50 import ResNet50,preprocess_input,decode_predictions
model=ResNet50(weights='imagenet') # ResNet50 클래스로 사전 학습 모델을 읽어 model 객체에 저장, ImageNet으로 학습된 가중치
## 테스트 영상을 읽고 신경망에 입력할 수 있는 형태로 변환
img=cv.imread('rabbit.jpg')
x=np.reshape(cv.resize(img,(224,224)),(1,224,224,3)) # 224×224×3 텐서를 1×224×224×3 텐서로 변환, predict 함수는 여러 장으로 구성된 미니 배치 단위로 입력 받기 때문
x=preprocess_input(x)
preds=model.predict(x) # predict 함수로 예측을 수행하고 결과를 preds에 저장, preds 객체는 1×1000 배열
top5=decode_predictions(preds,top=5)[0] # 1,000개 확률 중에 가장 큰 5개 확률을 취하고 그들의 부류 이름을 같이 제공
print('예측 결과:',top5)
for i in range(5):
cv.putText(img,top5[i][1]+':'+str(top5[i][2]),(10,20+i*20),cv.FONT_HERSHEY_SIMPLEX,0.5,(255,255,255),1)
cv.imshow('Recognition result',img)
cv.waitKey()
cv.destroyAllWindows()
Stanford Dogs dataset for Fine-Grained Visual Categorization
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten,Dense,Dropout,Rescaling
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.applications.densenet import DenseNet121
from tensorflow.keras.utils import image_dataset_from_directory
import pathlib
data_path=pathlib.Path('datasets/stanford_dogs/images/images')
train_ds=image_dataset_from_directory(data_path,validation_split=0.2,subset='training',seed=123,image_size=(224,224),batch_size=16)
test_ds=image_dataset_from_directory(data_path,validation_split=0.2,subset='validation',seed=123,image_size=(224,224),batch_size=16)
base_model=DenseNet121(weights='imagenet',include_top=False,input_shape=(224,224,3))
cnn=Sequential()
cnn.add(Rescaling(1.0/255.0)) # 입력 텐서를 255로 나누어 [0.1] 범위로 변환
cnn.add(base_model)
cnn.add(Flatten())
cnn.add(Dense(1024,activation='relu'))
cnn.add(Dropout(0.75))
cnn.add(Dense(units=120,activation='softmax'))
# 학습
cnn.compile(loss='sparse_categorical_crossentropy',optimizer=Adam(learning_rate=0.000001),metrics=['accuracy']) #
hist=cnn.fit(train_ds,epochs=200,validation_data=test_ds,verbose=2)
print('정확률=',cnn.evaluate(test_ds,verbose=0)[1]*100)
cnn.save('cnn_for_stanford_dogs.h5') # 미세 조정된 모델을 파일에 저장
import pickle
f=open('dog_species_names.txt','wb')
pickle.dump(train_ds.class_names,f)
f.close()
import matplotlib.pyplot as plt
plt.plot(hist.history['accuracy'])
plt.plot(hist.history['val_accuracy'])
plt.title('Accuracy graph')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train','Validation'])
plt.grid()
plt.show()
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Loss graph')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train','Validation'])
plt.grid()
plt.show()
train_ds=image_dataset_from_directory(data_path,validation_split=0.2,subset='training',seed=123,image_size=(224,224),batch_size=16) test_ds=image_dataset_from_directory(data_path,validation_split=0.2,subset='validation',seed=123,image_size=(224,224),batch_size=16)
인수1 : 데이터가 저장되어 있는 폴더의 경로를 지정 인수2 : validation_split=0.2는 훈련 집합과 검증 집합을 8:2로 분할 인수3 : subset 훈련 집합을 취하라 인수4 : 다시 실행해도 같은 결과를 얻기 위해 난수 씨앗을 123으로 고정 인수5 : 영상을 읽으면서 224×224 크기로 변환 미니 배치 크기를 16으로 설정 sparse_categorical_crossentropy
: 부류 정보를 원핫 코드가 아니라 정수로 표현💡 프로그램 패턴
__init__
설정 : 윈도우 제목&위치&크기 설정, 위젯(버튼,레이블 등) 생성, 버튼 클릭 시 수행할 콜백함수 지정import cv2 as cv
import numpy as np
import tensorflow as tf
import winsound
import pickle
import sys
from PyQt5.QtWidgets import *
cnn=tf.keras.models.load_model('cnn_for_stanford_dogs.h5') # 모델 읽기
dog_species=pickle.load(open('dog_species_names.txt','rb')) # 견종 이름
class DogSpeciesRecognition(QMainWindow):
def __init__(self) :
super().__init__()
self.setWindowTitle('견종 인식')
self.setGeometry(200,200,700,100)
fileButton=QPushButton('강아지 사진 열기',self)
recognitionButton=QPushButton('품종 인식',self)
quitButton=QPushButton('나가기',self)
fileButton.setGeometry(10,10,100,30)
recognitionButton.setGeometry(110,10,100,30)
quitButton.setGeometry(510,10,100,30)
fileButton.clicked.connect(self.pictureOpenFunction)
recognitionButton.clicked.connect(self.recognitionFunction)
quitButton.clicked.connect(self.quitFunction)
def pictureOpenFunction(self):
fname=QFileDialog.getOpenFileName(self,'강아지 사진 읽기','./')
self.img=cv.imread(fname[0])
if self.img is None: sys.exit('파일을 찾을 수 없습니다.')
cv.imshow('Dog image',self.img)
def recognitionFunction(self):
x=np.reshape(cv.resize(self.img,(224,224)),(1,224,224,3))
res=cnn.predict(x)[0] # 예측
top5=np.argsort(-res)[:5]
top5_dog_species_names=[dog_species[i] for i in top5]
for i in range(5):
prob='('+str(res[top5[i]])+')'
name=str(top5_dog_species_names[i]).split('-')[1]
cv.putText(self.img,prob+name,(10,100+i*30),cv.FONT_HERSHEY_SIMPLEX,0.7,(255,255,255),2)
cv.imshow('Dog image',self.img)
winsound.Beep(1000,500)
def quitFunction(self):
cv.destroyAllWindows()
self.close()
app=QApplication(sys.argv)
win=DogSpeciesRecognition()
win.show()
app.exec_()