페이지

2022년 4월 27일 수요일

라벨 없는 영상 데이터 들로부터 딥러닝을 통해 유사 이미지 탐색해오기 – 2편

 1편에 이어서 3중 손실을 통한 훈련 시행, 이미지 검색을 위한 특징 추출, 유사 이미지 검색 모듈에 대해서 살펴보겠다


3중 손실함수(triplet loss)

실제 프로젝트에서 딥러닝을 통한 이미지 분류를 처리하고자 할 때, 라벨이 부여된 수천 건의 데이터를 준비하는 문제 혹은 분류되는 이미지 종류(class)가 너무 많은 (안면인식과 같은) 경우에 봉착하는 경우가 많다. 2015년 구글에서 발표한 안면인식 FaceNet  논문 에서 컨볼루션 네트워크에서 안면 이미지와 유사 이미지, 그리고 다른 이미지의 3중 손실로 훈련하는 방법이 발표되면서 훈련에 zero shot learning 과 같은 비지도학습 가능성이 소개되었다.


 아래 그림1과 같이 입력이 되는 이미지인 anchor 이미지 A 가 있고, 유사한 positive 이미지 P, 그리고 다른 negative 이미지 N 이 있다. 결국 목적하고자 하는 학습 목표는 그 아래 식 에 표시된 바와 같이, A P 간의 거리 d(A, P) A N 간의 거리 d(A, N) 보다 작아야 하는 것이다. 여기서 +α는 margin 이다. 결국 훈련을 통해 아래 그림의 왼쪽의 거리가 오른쪽의 거리로, 3중 손실 구조는 이미지 간의 유사도를 예측하도록 학습된다.




<그림 1. Triplet loss 함수>


Loss는 아래 그림2의 ①과 같이 Siamese 네트워크의 출력인 ap_distance an_distance 의 차이로 표시된다. ②와 같이 margin 을 두고 0.0 중에 최대치를 취하게 함으로써 음수 값을 갖지 않도록 한다.



<그림 2. 손실 산출>


3중 손실을 통한 훈련 시행

 3중 손실을 통한 훈련 시행은 아래 그림 3과 같이 옵티마이저는 Adam을 채용하고 learning_rate0.0001, 그리고 epoch40으로 하였을 때, validated loss 가 ①과 같이0.0374로 나타났다.



<그림 3. 훈련 시행>


이미지 검색을 위한 특징 추출

컨볼루션 심층망을 통과하여 feature vector 가 생성된 layer를 임베딩(embedding)이라고 칭한다면 비교하고자 하는 모든 이미지들이 이러한 같은(동일한 파라미터 조건하의) 컨볼루션 망을 통과하면 결국 같은 길이의 임베딩으로 이루어질 것이다. 만약 이러한 임베딩들을 고차원의 벡터 공간에 위치시킨다면 같은 구조를 가진 임베딩끼리는 같은 위치에 모일 것이고 다른 구조를 가진 임베딩끼리는 멀리 떨어지게 된다. 우리는 자연어처리에서 word2vec 묘사들을 PCA를 통해 2차원에 투영했을 때 이러한 묘사를 볼 수 있다.

 이전  1편 그림 7에서 정의한 embedding layer를 참고로 다시 보자.  아래 그림 4의 ①과 같이 layer.trainable 상태를 출력해본다. ② 이전 그림에서 설정한데로 154번째 layer 부터 trainable True 로 변경 되어있음을 확인할 수 있다.



<그림 4. layer.trainable 확인>


아래 그림 5와 같이 훈련된 siamese 모델로부터 특징 추출 모델을 생성한다. 3중 손실을 통한 훈련을 통해 생성된 Siamese 모델의 입력으로 embedding inputoutput embeddig,out 즉 그림 4에서 살펴본 256차원의 output 이다.


<그림 5. feature_model 생성>


Feature vector 추출

 아래 그림 6의 ①과 같이 생성된 모델의 model.predict 150 X 150 크기의 입력 값을 통하여 feature_vector 를 얻고 ②로 그 값을 return 한다.



<그림 6. Feature vector 추출>


   feature vector 추출 함수를 이용하여 비교할 대상인 전체 anchor 이미지들을 대상으로 한 데이터프레임 df 를 작성한다. 데이터프레임은 ‘file’‘features’를 컬럼으로 하며 각 row df[‘features’]feature vector가 담기도록 만든다.


유사도 함수

feature vector 를 가지고 유사도를 측정할 경우, cosine_similarity를 사용하며 공식은 아래 그림 7의 ①과 같다. 즉 두 feature vector (그 중 하나는 transpose)의 벡터 곱을 두 feature vector 정규화 값의 곱으로 나눈 것이다.



<그림 7. 코사인 유사도>


함수로 정의된 코드는 아래 그림 8과 같다.



<그림 8. 유사도 함수>


유사 이미지 검색 모듈

유사 이미지 검색 모듈은 아래 그림 9에서 찾고자 하는 입력 이미지 파일인 file, 유사한 이미지를 전체 anchor 이미지들의 feature vector 가 담긴 데이터프레임 feature_df, 그리고 feature_model model_name 을 매개 변수로 전달하여 ①에서 feature vectorimg_features 로 추출하고, ②에서 getCosineSimilarity함수에 img_features와 비교할 numpy array row[‘features’]를 행방향 원소들로 더해서(axis=1) featu_df[‘similarity’] 컬럼에 저장한다. ③에서 ‘similarity’ 컬럼을 내림차순으로 정렬. ④에서 유사도 순서로 5개의 파일을 출력하는 plotSimilarImages를 호출한다.



<그림 9. 유사 이미지 검색 및 출력>


이때 비교할 test 이미지도 순서대로 정렬하여 test_images_path 밑에 위치시킨 test_images 파일로 준비한다.  이러한 test_imagesanchor 이미지 중에 62.jpg 파일을 선택해서 test_images_path 밑에 위치시키고, 아래 그림 10과 같이 실행시키면 이미지 유사도 순서로 5개의 이미지가 출력된다.

 아래 그림 10의 ①에서 test_images에 여러 파일이 있다면 아래 그림과 같은 6개 이미지 블록이 그 수 만큼 표시된다. ②유사이미지 검색 모듈 호출, ③은 test_image 파일, ④는 기존 anchor 이미지들에도 62.jpg 가 있기 때문에 유사도 1.000 로 동일 파일이 나타난다.



<그림 10. 유사 이미지 출력>


결언

지금까지 1편에 이어서 3중 손실을 통한 훈련 시행, 이미지 검색을 위한 특징 추출, 유사 이미지 검색 모듈에 대해서 살펴보았다. 3중 손실을 통한 훈련에서 validated loss 0.037로 낮은 손실을 보여주었고 유사도 검색의 정확도가 높은 feature vector 간의 cosine 유사도 검색으로 라벨 없는 비지도방식으로 높은 유사도 정확도를 보여주었다.

 

라벨 없는 영상 데이터 들로부터 딥러닝을 통해 유사 이미지 탐색해오기-1편

 이전 뉴스레터11편에서 안면 인식시스템을 설명하면서 샴(siamese) 신경망 네트워크, 3(Triplet) 손실 함수, 유사도 함수 등을 다룬 바 있다. 본 프로젝트에서는 Kaggle Flowers Recognition 데이터세트에 있는 600 개 꽃들의 라벨 없는 영상데이터들을 3중 손실함수를 이용하여 샴 네트워크상에서 훈련한다. 통상 딥러닝을 통한 영상 분류에 있어서 수백, 수천 이상의 훈련 샘플/이미지와 매우 큰 데이터세트가 필요하다는 인식과는 다르게 수백 건의 라벨 없는 이미지 데이터들로 3중 손실을 통한 훈련을 시행하여 임베딩끼리의 cosine 유사도로 이미지 유사도 검색이 가능한 지 살펴본다.


비지도 유사 이미지 검색 처리 절차

 3중 손실함수를 통한 비지도 유사 이미지 검색 처리 절차는 아래 그림 1과 같다. 프로젝트 환경은 Nvidia A100 GPU 140GB 메모리와 30GB CEPH 타입 디스크를 사용하였고 Tensorflow 2.6.0, Keras 2.6.0, cuda 11.3, 그리고 python3.8 환경에서 개발하였다.


<그림 1. 유사 이미지 검색 처리절차>


데이터 준비

본 프로젝트에 사용된 데이터세트는 이곳 에서 다운로드 받을 수 있다. 데이터세트 240 X 240 픽셀 크기의 데이지, 민들레, 장미, 해바라기, 튤립의 5가지 종류의 총 4242 개의 꽃 이미지를 보유한다. 본 시스템에서는 각 종류당 120 개만 사용하여 데이지 꽃부터 일련번호를 1번부터 부여하여 튤립까지 총 600번까지의 .jpg 파일을 anchor 디렉토리에 위치시키고 시작한다.

아래 섹션에서 설명하는 3중 손실함수로 훈련하기위해서 우선 anchor600개의 이미지들을 위치시키고, positiveanchor 이미지와 쌍을 이루도록 증식 및 변환을 가하여 위치시키고 negative 이미지들은 임의로(random) 생성된 anchor positive 이미지를 더해서 생성한다. 아래 그림 2anchor 이미지 들로부터 positive image 들을 생성하는 함수이다. ①은 이미지의 중앙 50%를 잘라내는 것이고 ②는 2번에 1번꼴로 이미지를 왼쪽에서 오른쪽으로 뒤집는다. ③은 임의 대비(contrast) 요소의 lower_bound=0.2, upper_bound1.8 로 설정. 이러한 이미지 증식방식은 이전 뉴스레터인 대조학습에서 효과적인 방법으로 언급된 방식을 차용하였다.


<그림 2. positive 이미지 생성 함수>


Data pipeline을 준비하기위해 아래 그림 3과 같이 anchor positive 이미지들을 sort 된 순서로 불러와서 서로 match 되도록 한다.

 


 <그림 3. Anchor, positive 준비>

 

 Negative 이미지들은 아래 그림 4와 같이 anchor positive를 임의로 섞은 다음에 이 둘을 ③과 같이 concatenate 하여 생성한다. 이러한 이미지 들로부터 dataset을 생성한다.


<그림 4. Negative 이미지 생성>


  anchor, positive, negative3가지 이미지가 같은 구조의 샴 네트워크에 입력되므로 이 세가지 dataset zip 한 이후에 shuffle 하고 resize dataset으로 변환한다. 그리고 아래 그림 5와 같이 train valid dataset을 분할한다. ①은 train을 전체 80%로 할당하고 valid 는 그 나머지. ②는 batch 크기를 8로 설정했다.


<그림 5. Train, valid dataset 분할>


 아래 그림 6과 같이 train_dataset을 출력해보니 첫번째가 anchor, 두번째가 positive, 세번째로 negative 이미지가 출력된다.



<그림 6. Triplet 데이터 확인>


 ResNet50 기반 임베딩 준비

 아래 그림 7과 같이 ResNet50 모델 weight를 기반으로 customer layer3개의 dense layer를 추가하여 임베딩 layer 를 만든다. ①에 include_top=False로하고 우리가 원하는 customer layer를 추가한다. ②에 256 벡터크기의 dense2 layer output 이 된다. ③에 inputsbase_cnn.input , outputs=output 으로 embedding을 생성한다. ④와 같이 함으로써, ResNet50 layer155번째인 conv5_block1_out부터 trainable=True, 그 이전 층들은 False 로 선언한다. 전이학습을 통한 파인튜닝에 대한 내용은 이전 뉴스레터를 참고.



<그림 7. 임베딩 준비>


Siamese 네트워크 모델 준비

3중 손실의 훈련을 위한 모델을 수립하는 단계를 살펴보자. (siamese) 네트워크에 관해서는, 이전 뉴스레터논문을 참고하기 바란다. 이를 위하여 anchor, positive, negative의 입력을 받는 3embedding 이 포함된 샴(Siamese) 네트워크를 아래 그림 8과 같이 구성한다. ①은 ResNet50 전이학습을 통한 encoder 파트. ②는 커스텀 layer로 추가한 파인튜닝 층. ③은 각각의 embedding layer. ap_distanceanchor positive 사이의 거리를 나타내고, an_distanceanchornegative 사이의 거리를 출력.

이러한 ap_distance an_distancetuple 로 출력하는 DistanceLayer를 사용한다


<그림 8. Siamese 네트워크>

 

 DistanceLayer 클래스안에 아래 그림 9와 같은 ap_distanceap_distance 값을 tuple return 하는 함수를 사용하여 모델에 [anchor_input, positive_input, negative_input]이 입력되고 output ap_distance an_distance 의 거리가 출력되도록 정의함으로써 Siamese_network 을 구성한다. 이 거리들이 이후 loss 산출에 사용된다.



<그림 9. 거리 측정 layer>


  지금까지 비지도 유사 이미지 검색 절차 중 데이터준비, ResNet50 기반 임베딩 준비, Siamese 네트워크 모델 준비등에 대해서 살펴보았다. 다음 2편에서는 3중 손실을 통한 훈련 시행, 이미지 검색을 위한 특징 추출, 유사 이미지 검색 모듈에 대해서 살펴보겠다.



라벨 데이터가 적은 경우에도 딥러닝을 통한 영상 분류 성능을 보장할 수 있을까? – 3편

 2편에서는 손실 함수와 유사도 함수 그리고 mask를 통한 labels 그리고 similarity matrix 생성, 그리고 8 X 1 행렬의 positives 8 X 6 행렬의 negatives를 결합한 logits 1 X 8positives 샘플들의 인덱스를 포함한 labels를 살펴보았다.

logits labels 생성

Pytorch CrossEntropyLoss에서 targetindex 로 출력하는 예를 보면,

criterion = nn.CrossEntropyLoss(),

loss = criterion(input, target) 

이다. 이때 input에 해당하는 것이 logits, target에 해당하는 것이 labels이다. 아래 그림 10에서, positives negatives의 횡적 결합한 8 X 7 행렬이 logits 이고 labels positives 샘플들의 인덱스를 포함한 1 X 8 행렬로써, labelsCrossEntropy를 각 행의 logits를 보고 0값의 벡터를 보고 positive 쌍이 첫번째 인덱스에 있음을 알려주는 indicator function으로 일반적으로 사용하는 라벨과는 다른 내용이다.

 

<그림 10. Positives, negatives 행렬>


아래 그림 11의 ①을 통해 대조 학습을 위한 온도 계수를 logits에 적용하는 부분이다. 1편의 그림 1에서 Ʈ는 출력 카테고리 간의 구분을 명확 혹은 부드럽게 출력하는 온도 계수라고 설명하였다. ②에서 logits labels를 리턴 한다.

지금까지 살펴본 info_nce_loss 함수는 simclr.py train 함수에서 사용한다.

logits, labels = self.info_nce_loss(features)

loss = self.critetion(logits, labels)


<그림 11. logits labels 반환>


유사 이미지 representation끼리 유사도가 높게, 다른 이미지 representation끼리는 유사도가 낮게 만들어 주기 위해 info NCR loss를 계산하여 최소화한다. positive pair는 자기 자신(주어진 anchor image)와 증식 이미지이다. 이때 원본 이미지끼리, 증식 이미지끼리 의 유사도는 loss 계산 시 배제한다(그림 8 labels[~mask]에서 대각선 1이 제거 됨으로써). 결국 자기 자신(주어진 anchor 이미지)와 다른 이미지의 유사도를 일일이 계산하기보다 자기 자신과 증식된 이미지만을 positive sample로 선택함으로써 손실 함수처리가 된다.

훈련

 대조 학습으로 훈련을 시행하기위해 아래와 같은 명령을 수행한다. datasets 디렉토리가 필요하고 그 밑에 stl10_binary가 생기는데 아래와 같은 명령어에 맞게 stl10 디렉토리와 symbolic link로 맞추어 주던지 하는 것이 필요하다.


100 epoch 훈련이 끝나면 runs 밑에 일자_job_xx 디렉토리가 생기고 그 밑에 아래 그림 12 와 같은 파일들이 생성된다. Nvidia 2080Ti GPU 4대로 ubuntu20.04, Pytorch 1.10, Pthon3.8, CUDA11.3 환경에서 훈련에 4시간 29분 소요되었다.



<그림 12. 훈련 후 생성되는 파일 들>


성능 평가


훈련을 통해 만들어진 분류기의 정확도를 측정하기위해 학습한 encoder를 고정(freeze)시키고 그 위에 linear classifier를 추가하여 정확도를 측정합니다. 코드는 feature_eval 디렉토리 밑에 mini_batch_logistic_regression_evaluator.ipynb 노트북 코드이다.

우선 아래와 같이 stl10 ResNet-18 공식 데이터세트를 ①과 같이 download=True로 하여 다운로드 받는다. 앞에 훈련에서 사용했던 데이터세트를 사용해도 되지만, 사전 학습 모델을 이용하여 모델이나 데이터세트의 차이에 따른 정확도 측정을 위해 주어진 mini_batch_~ 코드를 최대한 활용해본다. 위의 그림 12와 같은 내용이 ./data 디렉토리 밑에 다운로드 된다.



 
아래와 같이 다운로드 된 디렉토리에서 config.yml을 로드 한다. ①은 현재 torch 1.10, cuda 11.3, python 3.8 환경에서 yaml.load 에서 error가 발생해서 unsafe_load로 변경하였다.



사전 훈련 모델은 아래와 같이 resnet18 모델을 정의한다. ①번과 같이 선언함으로써 weight를 불러오지 않는다. 우리는 저장된 체크포인트를 로드해서 사용할 것이다.



 아래 그림 13의 ①번과 같이 저장되었던 체크포인트 파일을 로드 한다.  ②번과 같이 모델 state_dict backbone. 으로 시작하는 접두사를 제거함으로써 fc.wieght fc.bias 를 제거한다. 하지만 model.named_parameters()에는 fc.weightfc.bias 이름과 구조는 보존하고 있다.


<그림 13. state_dict fc.wieght, fc.bias 제거>


 그리고 아래 그림 14의 ①번과 같이 model.named_parameters fc.weight, fc.bias 를 제외한 나머지들을 모두 False 로 선언함으로써 역전파시 gradient 연산이 일어나지 않게 된다. ②는 model.parmaters중에 gradient enable parameter를 뽑아내는 함수이다.


<그림 14. Gradient freeze 이외에 남은 변수를 넘겨 받기>


아래 그림 15의 ①번과 같이 model summary를 호출하면 ②와 같이 ResNet-18  마지막 layer에 완전연결층이 10 class의 출력을 가지고 있음을 볼 수 있다.


<그림 15. 모델 summary>


완전연결층(FC layer) 훈련 및 모델 평가

 아래 그림 16과 같이 훈련될 gradientenable FC layer 를 훈련한다. epoch100으로 정하고 ①과 같이 모델에 x_batch를 넣어주면 logits가 출력된다. ②와 같이 logitsy_batch와 함께 topl=(1,)를 변수로 top1 accuracy를 호출한다. pytorch에서는 이후에 backward를 해줄 때 gradients 값들을 계속 더해주기 때문에 한번의 학습이 완료되면 gradients를 항상 0으로 만들어주어야 한다. ④ 오차를 역전파(backward)하기 위한 설정. ⑤ 옵티마이저는 parameter, learning rate 및 여러 다른 hyper-parameter를 받아 step() method를 통해 업데이트한다.


<그림 16. FC layer 훈련 및 top-1 정확도 산출>


평가 기준 정의: top-k accuracy

 먼저 top-1 accuracy를 설명해보자. , 고양이, 닭 등등의 서로 다른 6개의 동물들 사진이 있다고 치자. 첫번째 분류 예측에서 6개 중에 4개를 맞추면 67% 정확도다. top-2 accuracy는 각각의 경우에 첫번째와 두번째 예측을 수행한 것을 모두 놓고 몇 개를 맞췄는지 평가하므로 당연히 정확도가 올라간다.

테스트 데이터를 통한 Top1, Top5 정확도 평가

 이어서 아래 그림 17과 같이 테스트 데이터를 로드 하여 정확도 성능을 평가한다. ①은 test_loader로부터 batch 단위로 데이터를 로드 한다. ②에서 batch 단위의 테스트 데이터가 모델에 입력되어 logits를 출력. ③에서 topk=(1,5)를 변수로 logits y_batch를 입력하여 top1, top5 결과를 출력한다. ④를 통해 batch 단위의 결과를 평균 내어 ⑤와 같이 화면에 epoch Top1 Test AccuracyTop 5 결과가 나타난다.


<그림 17. 테스트데이터를 통한 top1, top5 정확도 평가>


성능 결과


 라벨 없는 10만개와 5천개의 stl10 데이터세트 훈련데이터를 입력으로 하여 대조 학습을 통한 nce loss 100회 훈련한 사전 모델을 전이학습을 통하여 로드 하여 성능평가를 위해 라벨이 있는 train test 데이터를 이용하여 마지막 FC 층만 제외한 나머지 층을 훈련을 못하도록 freeze 시켜 놓고 FC 층을 100회 훈련시킨 결과는 아래 그림 18의 붉은 선에 표시된 바와 같이 Top1 정확도는 69.28, Top5 정확도는 98.26 이다.



<그림 18. 선형분류기 학습을 통한 성능 평가>

결언

 본 프로젝트의 목적은 라벨 없는 영상데이터들로 이미지 분류가 가능한지와 어느 정도의 성능을 보여주는지를 살펴보는 것이었다. 대조 학습을 위하여 라벨 없는 stl10 데이터를 훈련하였고 성능 평가를 위해 stl10 데이터의 10class를 보유한 train, test 라벨 있는 데이터를 마지막 층에 선형 분류기를 통하여 성능 평가를 하였다. ResNet-50에 비하여 비교적 구조가 간단한 ResNet-18 구조를 채택하여 official record 74.45에는 못 미치지만 69.28 Top1%를 달성하였다.


 <그림 19. 성능 비교>