R-CNN 논문 리뷰는 지난 포스팅을 참고하길 바란다: https://hyelimkungkung.tistory.com/25
프로젝트 개요
(0) 서론, 데이터 셋 소개: https://hyelimkungkung.tistory.com/27
(1) selective serarch: https://hyelimkungkung.tistory.com/28?category=935193
(2) fine tuning: https://hyelimkungkung.tistory.com/29
(3) linear svm, (4) 결론: https://hyelimkungkung.tistory.com/30
(3) Linear SVM
(개요)
(1) 앞에서 fine tuning한 Alexnet의 파라미터들을 freeze 시킨다
(2) input 을 만든다: 앞에서 iou 기준이었던 0.5를 0.3으로 바꿔서 새롭게 배치를 형성한다 positive:negative는 1:1비율로
(3) 위의 input으로 마지막 layer 만 학습시킨다
* 아 그리고 원래는 class specific SVM 인데 생각해보니 거기에 집착해서 다음 논문을 너무 오래 못 읽은 것 같다
또 하나에 매몰돼서 다 놓쳐버릴 뻔 이제 R - CNN은 놔주기로 했다 나중에 돌아와서 완벽하게 해보겠다.
linear_svm.py
(1) 앞에서 fine tuning한 Alexnet의 파라미터들을 freeze 시킨다
앞에서 훈련했던 모델을 데려온다
import torch, torchvision
import torch.nn as nn
import torchvision.models as models
device = 'cuda' if torch.cuda.is_available() else 'cpu'
pre_model = models.alexnet()
num_features = pre_model.classifier[6].in_features
pre_model.classifier[6] = nn.Linear(num_features,4)
pre_model = pre_model.to(device)
pre_model.load_state_dict(torch.load('/content/BCCD/fine_tuned.pth'))
앞에서 state_dict로 모델을 저장했는데, 이 경우 가중치를 저장한 것이어서
기본 모델 구조는 불러와서 model을 선언해야 한다.
그 이후에 load_state_dict 함수를 이용해서 이전 모델의 가중치를 넣어준다
앞에서 fine-tuning할 때는 pre_trained = True로 했었는데, 여기서는 없다.
왜냐하면 나중에 load_state_dict 하면서 어차피 다 불러와질 것이기 때문.
한번 모델을 살펴보자.
요로코롬 생겼다.
보면 classifier의 6번째 레이어에서 out_features가 4로 바뀐 걸 잘 볼 수 있다.
for e, param in enumerate(model.parameters()):
param.requires_grad = False
model.classifier[6] = nn.Linear(4096,4)
model.to(device)
그 다음으론 모든 가중치들을 freeze 시킨다.
왜냐하면 논문에서도 나와있듯이, linear_SVM에서 훈련하는 건 마지막 fc7 다음의 SVM layer 뿐이기 때문이다. (다만 본 포스팅에선 SVM 대신에 softmax layer를 이용했다. )
requires_grad = False 면 가중치 업데이트를 하지 않는다. grad가 경사고, 우리는 경사하강법으로 역전파를 하는데, 이게 False라면 당근 업데이트가 되지 않겠지?!
하지만 마지막 레이어는 예외다.
(2) input 을 만든다: 앞에서 iou 기준이었던 0.5를 0.3으로 바꿔서 새롭게 배치를 형성한다 positive:negative는 1:1비율로
앞에서 만들었던 region_proposal 함수를 임포트 함으로써 쉽게 할 수 있다.
모드에 따라서 threshold를 다르게 지정했기 때문에 아래와 같이 인자를 줌으로써 원하는 threshold에 맞게 배치를 구성할 수 있다.
## positive, negative에 대해서 다시 정의해야함
train_images, train_labels = region_proposal('classify')
(3) 위의 input으로 마지막 layer 만 학습시킨다
positive_num = 8
negative_num = 8
batch_size = positive_num + negative_num
dataset = CustomDataset(train_images, train_labels)
sampler = CustomSampler(train_labels, positive_num, negative_num)
dataloader = DataLoader(dataset, batch_size=batch_size, sampler=sampler)
total_batch = sampler.iter
epochs = 15
print("훈련 시작")
for epoch in range(epochs):
avg_cost = 0.0
for num, data in enumerate(dataloader):
imgs, labels = data
imgs = imgs.to(device)
labels = labels.to(device)
model.train()
optimizer.zero_grad()
out = model(imgs.permute(0, 3, 1,
2)) ## pytorch의 경우 인자의 순서가 (batchsize, channel, height, width) 우리는 보통 (batchsize,height,width,channel)이라고 생각하는데
labels = labels.squeeze() ## crossentropy input size 가 (N,C) 배치사이즈, 클래스 수 target size는 (N) 이 돼야해서 squeeze로 차원 변경
loss = criterion(out, labels)
loss.backward()
optimizer.step()
avg_cost += loss / total_batch
print(f'[Epoch:{epoch + 1}] cost = {avg_cost}')
print('Learning Finished!')
torch.save(model.state_dict(), 'data/content/BCCD/linear_svm.pth') ## 가중치들 저장
이걸 끝으로 이제 결과를 보는 py다.
다만 test 에서는 하나의 함수가 더 사용되었는데, non-maximum suppression이다.
nms.py
import numpy as np
from iou import iou
def box_dict(box):
x, y, w, h = box
return {'x1': x, 'x2': x+w,'y1': y,'y2':y+h}
def nms(bb, score, predict):
## argument
## bb : bb[i] = [x,y,w,h] 인 리스트
## score: 각 bb의 score이 담긴 리스트
nms_list = [True] * len(bb)
assert len(bb) == len(score), "bb의 수와 score의 수가 다름"
bb = np.array(bb)
score = np.array(score)
predict = np.array(predict)
order = score.argsort()
bb = bb[order][::-1]
score = score[order][::-1]
predict = predict[order][::-1]
for e, box in enumerate(bb):
if (nms_list[e]):
bb1 = box_dict(box)
predict1 = predict[e]
for i in range(len(bb)-e-1):
if predict[i] == predict1:
bb2 = box_dict(bb[i])
if iou(bb1, bb2) > 0.5:
nms_list[i] = False
return np.asarray(nms_list)[order][::-1]
iou가 0.6 이상이면 가장 높은 score의 박스만 남겨놓고 다 없애버린다.
from selectiveSearch import resize
from nms import nms
import torch
import torchvision.models as models
import torch.nn as nn
import cv2
import matplotlib.pyplot as plt
from PIL import Image
from PIL import ImageDraw
test = 'data/content/BCCD/test/images/BloodImage_00038_jpg.rf.63d04b5c9db95f32fa7669f72e4903ca.jpg'
device = 'cuda' if torch.cuda.is_available() else 'cpu'
image = cv2.imread(test)
## test 이미지로부터 영역 추출
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(image)
ss.switchToSelectiveSearchFast()
ssresults = ss.process()
candidate_images = []
candidate_boxes = []
for e, candidate in enumerate(ssresults):
if e > 2000:
break
x, y, w, h = candidate
img = resize(image, x, y, w, h)
candidate_images.append(img)
candidate_boxes.append(candidate)
## 훈련한 모델 로딩
model = models.alexnet()
num_features = model.classifier[6].in_features
model.classifier[6] = nn.Linear(num_features, 4)
model = model.to(device)
model.load_state_dict(torch.load('data/content/BCCD/linear_svm.pth'))
model.eval()
## 훈련한 모델로 예측
candidate_predict = []
candidate_score = []
with torch.no_grad():
for image in candidate_images:
image = torch.FloatTensor(image).to(device)
output = model(image[None, ...].permute(0, 3, 1, 2)) # 배치가 없으므로 None을 가장 첫 인자로 준다
predict_class = torch.argmax(output).to(device).item()
candidate_predict.append(predict_class)
candidate_score.append(torch.softmax(output[0], dim=0)[predict_class].item())
## 그림으로 그리기
temp = Image.open(test).convert('RGB')
draw = ImageDraw.Draw(temp)
candidate_nms = nms(candidate_boxes, candidate_score, candidate_predict)
for e, yn in enumerate(candidate_nms):
if not yn:
candidate_predict[e] = 0
for e, predict in enumerate(candidate_predict):
if predict == 0:
continue
else:
if candidate_score[e] >= 0.998:
x, y, w, h = candidate_boxes[e]
draw.rectangle(((x, y), (x + w, y + h)), outline='red')
draw.text((x, y), str(predict))
plt.figure(figsize=(20, 20))
plt.imshow(temp)
plt.show()
참고로 1번이 RBC, 2번이 WBC, 3번이 Platelets다.
(4) 결론
혈액 사진 속에서 WBC와 Platelets의 수가 RBC에 비해서 상당히 적었음에도 불구하고, 오히려 본 모델을 전자의 두 객체를 잘 탐지하는 반면에 RBC는 상당히 정확도가 떨어진다. 사진을 통해서 도넛 모양처럼 중간에 구멍이 뚫린 것이 명확한 모양도 있는 반면, 이것이 여러개 겹쳐져 그 구멍이 명확히 보이지 않는 WBC가 존재함을 확인할 수 있다. 따라서 WBC의 모양이 일관되게 나타나지 않았기 때문에 WBC의 예측율이 현저히 떨어짐을 추측할 수 있다.
한편 nms를 수행함으로써 많은 후보들이 살아졌음에도, 왜 여전히 일부는 거의 완벽히 겹치는 데도 사라지지 않았는지 그 이유를 모르겠다. 또, sigmoid를 이용해서 예측 값을 확률값으로 바꾸어주었는데, 상당수가 99%를 넘긴다. 이는 아마도 사진 내에 WBC 개체가 굉장히 많고, 이를 찾은 박스가 많기 때문으로 추정된다.
** 이미지를 추출하는데 만 한시간 반 정도 걸렸다 왜인지 모르겠는데 cv2.resize가 굉장히 오래 걸린다. cv2는 cuda 지원이 안 된다고 해서 코랩도 무진장 느렸던 것 같다. 아무래도 비효율적인 건 확실히 몸소 느꼈다.
** 훈련하는데도 두시간이나 걸린다.
** 그러니까 이미지 2번 추출하고 훈련 2시간 1시간 이케 하면 ,,,,
** bb box regressor 도 언젠가 나중에 구현하도록 하겠다. 그때까지 또 열심히 살아야지 후하
와 까먹고 참고한 곳들 안 올렸다 잡혀갈뻔;;
https://github.com/object-detection-algorithm/R-CNN
https://towardsdatascience.com/step-by-step-r-cnn-implementation-from-scratch-in-python-e97101ccde55
아래 두 주소는 keras 이용한 것이니, torch를 사용하는 사람은 알맞게 교체가 필요할 것이다.
첫번째 주소는 torch를 이용했다.
오래보니까 코드가 눈에 익는다! 다음은 fast r-cnn이다.
'딥러닝 > 프로젝트' 카테고리의 다른 글
[Kaggle] CT Medical Image - (1) DICOM 파일 array로 전환 및 시각화 (0) | 2021.09.26 |
---|---|
[Kaggle] CT Medical Image - (0) 서론, 액션 플랜 (0) | 2021.09.25 |
[개인]R-CNN 을 이용한 BCCD type 구분 -(2) fine tuning (0) | 2021.06.29 |
[개인]R-CNN 을 이용한 BCCD type 구분 -(1) selective search (0) | 2021.06.23 |
[개인]R-CNN 을 이용한 BCCD type 구분 -(0) 서론, 데이터 셋 소개 (0) | 2021.06.22 |
댓글