YOLOv3-Train
- 학습시 입력 값
- 기본 weight파일의 경우에는 [weight파일]에서 설치 가능
./darknet detector train [data파일명] [cfg파일명] [weight파일명]
- 만약 coco데이터와 동일하게 학습할 경우 (80개의 객체에 대해서 학습)
./darknet detector train cfg/coco.data cfg/yolov3.cfg darknet53.conv.74
- 기존 weights(YOLO에서 주어준 학습 데이터)를 사용하여 transfer learning을 하기 위해선
본인이 학습시킬 cfg파일의 max_batches값을 변경해주어야 한다. ( 해당 학습 데이터는 총 500200의 학습을 이미 했기 때문에 수정 안하고 학습을 진행하면 바로 학습이 종료된다. )
- 기존 max_batches값을 600200으로 수정하여 학습하기 위한 방법
./darknet detector train [data파일명] [cfg파일명] [이전에 학습된 weights 파일명 ]으로 학습 가능
- 기본 weight파일의 경우에는 [weight파일]에서 설치 가능
소스 부분
- 학습 부분에서 소스를 봐야 되는 클래스는 examples/darknet.c , detector.c , src/network.c , option_list.c , list.c , utils.c , parser.c , convolutional_layer.c , detector.c , image.c , matrix.c , image_opencv.cpp 와 같은 파일들을 확인하면 좋습니다.
- 소스 흐름도 ( train부분과 detection부분 )은 StarUML5.0을 통해서 확인할 수 있습니다.
여기서는 학습에서 가장 중요한 Anchor Boxes에 관련된 내용을 다룰 예정
- 소스 부분에서 yolo_layer.c클래스의 forward_yolo_layer함수를 확인하시면 좋습니다.
void forward_yolo_layer(const layer l, network net)// forward_yolo_layer() function { // 공부하기 int i,j,b,t,n; memcpy(l.output, net.input, l.outputs*l.batch*sizeof(float)); #ifndef GPU for (b = 0; b < l.batch; ++b){ for(n = 0; n < l.n; ++n){ int index = entry_index(l, b, n*l.w*l.h, 0); activate_array(l.output + index, 2*l.w*l.h, LOGISTIC); index = entry_index(l, b, n*l.w*l.h, 4); activate_array(l.output + index, (1+l.classes)*l.w*l.h, LOGISTIC); } } #endif memset(l.delta, 0, l.outputs * l.batch * sizeof(float)); if(!net.train) return; float avg_iou = 0; float recall = 0; float recall75 = 0; float avg_cat = 0; float avg_obj = 0; float avg_anyobj = 0; int count = 0; int class_count = 0; *(l.cost) = 0; for (b = 0; b < l.batch; ++b) { // batch(4) grid접근 방식 for (j = 0; j < l.h; ++j) { // height for (i = 0; i < l.w; ++i) { // width for (n = 0; n < l.n; ++n) { // anchor's number = 3 int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0); // 몇번째 grid cell인가 //printf(" box_index = %d ",box_index); // 학습하는 이미지의 각각의 이미지를 따로 가져옴. 한번에 4개의 이미지를 읽기 때문에 batch또한 0번부터 3번까지 나눠서 정보를 가져옴 // n*l.w*l.h + j*l.w + i == 이미지 RGB의 모든 값을 가지는 1차원 배열 정보 box pred = get_yolo_box(l.output, l.biases, l.mask[n], box_index, i, j, l.w, l.h, net.w, net.h, l.w*l.h); //3개의 anchor box 확인 float best_iou = 0; int best_t = 0; for(t = 0; t < l.max_boxes; ++t){ // 모든 객체들에 대해서 iou값을 확인 box truth = float_to_box(net.truth + t*(4 + 1) + b*l.truths, 1); // b = 4 if(!truth.x) break;// net.truth + t*(4 + 1) + b*l.truths 이것이 의미하는 것은? float iou = box_iou(pred, truth); // 예측과 실측에 대한 iou값 계산 if (iou > best_iou) { // 최대의 iou값만 남긴다 best_iou = iou; // 가장 높은 iou값을 가진 객체의 값을 저장 best_t = t; // test_t = 무슨 객체인지를 알려줌 여기서 t = 0 이면 // person을 뜻함 } } //하나의 cell에 대해서 모든 객체 점수들을 종합하여 최대의 값을 파악 int obj_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4); // 해당 grid cell 위치에서 objectness값 확인 // objectness score정보를 가져오기 위한 obj_index avg_anyobj += l.output[obj_index]; // 전체 평균값에 obj_index값을 증감 l.delta[obj_index] = 0 - l.output[obj_index]; //best_iou가 0.7보다 작은 경우 l.output[obj_index]는 0에 가까운 값이기에 0 - 로 시작 //하지만 1보다 큰 경우에는 l.output이 1에 근사한 값이기 때문에 1 - 로 시작함 if (best_iou > l.ignore_thresh) { // best_iou > 0.7 //printf("111\n"); l.delta[obj_index] = 0; } if (best_iou > l.truth_thresh){ // best_iou > 1 예외처리 느낌으로 사용 //printf("222\n"); l.delta[obj_index] = 1 - l.output[obj_index]; // make l.delta = 0 ~ 1 int class = net.truth[best_t*(4 + 1) + b*l.truths + 4]; if (l.map) class = l.map[class]; int class_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4 + 1); delta_yolo_class(l.output, l.delta, class_index, class, l.classes, l.w*l.h, 0); //현재 cell에서 가장 알맞은 anchor박스를 통하여 box truth = float_to_box(net.truth + best_t*(4 + 1) + b*l.truths, 1); delta_yolo_box(truth, l.output, l.biases, l.mask[n], box_index, i, j, l.w, l.h, net.w, net.h, l.delta, (2-truth.w*truth.h), l.w*l.h); } } } }해당 부분까지가 각 grid cell에 접근하여 Anchor Box를 통하여 iou값을 얻어서 해당 iou값에 따라서 delta ( loss값 계산할 때 사용되는 변수 )에 feature map을 통하여 추출한 output값을 저장한다. 여기서 iou값이 0.7보다 작으면 0 - output (objectness값은 작아야 되기 때문에) 0.7보다 크면 1 - output (objectness값이 1에 가까워야 하기 때문)을 통하여 delta 값을 결정한다.
for(t = 0; t < l.max_boxes; ++t)
{
box truth = float_to_box(net.truth + t*(4 + 1) + b*l.truths, 1);
//net->truth의 값은 train부분에서 get_next_batch()함수 [이미지에 대한 정보를 가져오는 작업]에서
//실측값(ground truth)를 다음 구조체 변수에 저장한다.
//해당 작업은 if(y) memcpy(y+j*d.y.cols, d.y.vals[index], d.y.cols*sizeof(float));
//와 같은 if문에서 처리
//net->truth = net->truths*net->batch
if(!truth.x) break;
float best_iou = 0;
int best_n = 0;
i = (truth.x * l.w);
j = (truth.y * l.h);
//i,j = 실측값 중심점 좌표 학습 이미지를 resizing 시켰기 때문에 위치 재조정
box truth_shift = truth;
truth_shift.x = truth_shift.y = 0;
for(n = 0; n < l.total; ++n){
box pred = {0};
pred.w = l.biases[2*n]/net.w;
pred.h = l.biases[2*n+1]/net.h;
//pred.w,h = anchor box's w,h
float iou = box_iou(pred, truth_shift);
//truth_shift는 x,y는 0 w,h는 실측값에서 가져온다
//실측값과 anchor box의 iou
if (iou > best_iou){
best_iou = iou;
best_n = n;// 제일 잘 맞는 anchor박스를 검출
}
}
//
int mask_n = int_index(l.mask, best_n, l.n); // best_n = 0 ~ 8
//모든 anchor박스에 하는 이유는 다른 yolo에서도 똑같이 해당 함수를 사용하지만
//해당 l.mask의 값에 따라서 사용할 수 있는 anchor박스는 제한적
//따라서 첫번째 yolo 레이어에서는 6,7,8 anchor box에 대해서만 사용 가능
//printf("mask_n = %d, best_n = %d, l.n = %d\n",mask_n,best_n,l.n);
/* 결과 예시
utils.c 635 line
mask_n = -1, best_n = 5, l.n = 3
mask_n = -1, best_n = 5, l.n = 3
mask_n = -1, best_n = 5, l.n = 3
mask_n = -1, best_n = 3, l.n = 3
mask_n = -1, best_n = 3, l.n = 3
mask_n = -1, best_n = 7, l.n = 3
mask_n = 1, best_n = 1, l.n = 3
mask_n = -1, best_n = 5, l.n = 3
mask_n = 1, best_n = 1, l.n = 3
mask_n = -1, best_n = 5, l.n = 3
mask_n = -1, best_n = 4, l.n = 3
*/
if(mask_n >= 0){ // find something
int box_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 0);
//b = batch 사진 한장
float iou = delta_yolo_box(truth, l.output, l.biases, best_n, box_index, i, j, l.w, l.h, net.w, net.h, l.delta, (2-truth.w*truth.h), l.w*l.h);
//(box truth, float *x, float *biases, int n, int index, int i, int j, int lw, int lh, int w, int h, float *delta, float scale, int stride)
int obj_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 4);
avg_obj += l.output[obj_index];
l.delta[obj_index] = 1 - l.output[obj_index];
// 1 = 정답 - l.output[] = 예측한 값, truth의 확률
int class = net.truth[t*(4 + 1) + b*l.truths + 4];
if (l.map) class = l.map[class];
int class_index = entry_index(l, b, mask_n*l.w*l.h + j*l.w + i, 4 + 1);
delta_yolo_class(l.output, l.delta, class_index, class, l.classes, l.w*l.h, &avg_cat);
++count;
++class_count;
if(iou > .5) recall += 1;
if(iou > .75) recall75 += 1;
avg_iou += iou;
}
}//end t iteration
}//end first iteration
//printf("l.outputs = %d , l.batch = %d\n",l.outputs,l.batch);
*(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2); // 중요
printf("(Yolo)Region %d Avg IOU: %f, Class: %f, Obj: %f, No Obj: %f, .5R: %f, .75R: %f, count: %d\n", net.index, avg_iou/count, avg_cat/class_count, avg_obj/count, avg_anyobj/(l.w*l.h*l.n*l.batch), recall/count, recall75/count, count);
}//end forward_yolo_layer() function
하나의 이미지에 대해서 모든 pixel접근이 완료된 후 실제 라벨링된 위치 (ground truth)정보를 사용하여 학습 모델을 통해서 추출한 output과 실측값을 비교하여 loss를 검출하는 부분이다. 이를 통해서 MSE ( Mean Square Error)을 사용하여 0에 가깝도록 학습시키는 것을 반복한다.
- 위의 부분은 yolo layer에서 loss값을 얻기 위해 forward를 하는 작업이다. 학습 부분에 있어서 보다 다양한 함수들을 접근하여 처리합니다
- 모든 내용을 한번에 다루기에는 너무 양이 많기 때문에 추가적으로 보고 싶은 부분이 있다면
- 흐름도 부분은 StarUML5.0 을 통해서 확인하면 도움이 될 것이며
- 소스 부분은 Darknet을 복제하여 주석처리해놓은 것을 보면 도움이 될 것이라 판단 됩니다.