DarkNet 시리즈 - Compare
compare
train_compare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void train_compare(char *cfgfile, char *weightfile)
{
srand(time(0));
float avg_loss = -1;
char *base = basecfg(cfgfile);
char *backup_directory = "/home/pjreddie/backup/";
printf("%s\n", base);
network net = parse_network_cfg(cfgfile);
if(weightfile){
load_weights(&net, weightfile);
}
printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net.learning_rate, net.momentum, net.decay);
int imgs = 1024;
list *plist = get_paths("data/compare.train.list");
char **paths = (char **)list_to_array(plist);
int N = plist->size;
printf("%d\n", N);
clock_t time;
pthread_t load_thread;
data train;
data buffer;
load_args args = {0};
args.w = net.w;
args.h = net.h;
args.paths = paths;
args.classes = 20;
args.n = imgs;
args.m = N;
args.d = &buffer;
args.type = COMPARE_DATA;
load_thread = load_data_in_thread(args);
int epoch = *net.seen/N;
int i = 0;
while(1){
++i;
time=clock();
pthread_join(load_thread, 0);
train = buffer;
load_thread = load_data_in_thread(args);
printf("Loaded: %lf seconds\n", sec(clock()-time));
time=clock();
float loss = train_network(net, train);
if(avg_loss == -1) avg_loss = loss;
avg_loss = avg_loss*.9 + loss*.1;
printf("%.3f: %f, %f avg, %lf seconds, %ld images\n", (float)*net.seen/N, loss, avg_loss, sec(clock()-time), *net.seen);
free_data(train);
if(i%100 == 0){
char buff[256];
sprintf(buff, "%s/%s_%d_minor_%d.weights",backup_directory,base, epoch, i);
save_weights(net, buff);
}
if(*net.seen/N > epoch){
epoch = *net.seen/N;
i = 0;
char buff[256];
sprintf(buff, "%s/%s_%d.weights",backup_directory,base, epoch);
save_weights(net, buff);
if(epoch%22 == 0) net.learning_rate *= .1;
}
}
pthread_join(load_thread, 0);
free_data(buffer);
free_network(net);
free_ptrs((void**)paths, plist->size);
free_list(plist);
free(base);
}
함수 이름: train_compare
입력:
- char *cfgfile: 학습을 수행할 YOLO 모델의 구성 파일 경로
- char *weightfile: 학습을 수행할 YOLO 모델의 가중치 파일 경로
동작:
- YOLO 모델을 이용하여 compare_data 형식의 데이터를 학습한다.
- 지정된 횟수(epoch)만큼 학습을 반복하며, 학습 중에는 주기적으로 가중치 파일을 저장한다.
- 학습 종료 후, 학습된 YOLO 모델의 가중치를 저장한다.
설명:
- srand(time(0))은 시간에 따라 랜덤 시드를 설정한다.
- basecfg(cfgfile)은 cfgfile에서 파일 이름만 가져온다.
- network net = parse_network_cfg(cfgfile)은 cfgfile에서 모델 구성 정보를 파싱하여 네트워크 모델을 생성한다.
- load_weights(\&net, weightfile)은 weightfile에서 저장된 가중치 정보를 로드하여 모델에 적용한다.
- imgs는 한 번에 읽을 이미지 파일의 개수이다.
- get_paths(“data/compare.train.list”)는 학습에 사용할 이미지 파일 경로 리스트를 읽어온다.
- load_args 구조체를 초기화하고, load_data_in_thread(args)를 호출하여 이미지와 라벨 데이터를 비동기적으로 읽어온다.
- train_network(net, train)은 모델(net)과 학습 데이터(train)를 이용하여 학습을 수행하고, 학습 손실값을 반환한다.
- save_weights(net, buff)는 모델(net)의 가중치 정보를 buff 경로에 저장한다.
- net.learning_rate *= .1은 학습률(learning_rate)을 10% 감소시킨다.
- free_data(train)은 학습 데이터(train)를 해제한다.
- free_network(net)은 모델(net)을 해제한다.
- free_ptrs((void**)paths, plist->size)은 paths 배열과 plist 리스트를 해제한다.
- free_list(plist)는 plist 리스트를 해제한다.
- free(base)는 base 메모리를 해제한다.
validate_compare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void validate_compare(char *filename, char *weightfile)
{
int i = 0;
network net = parse_network_cfg(filename);
if(weightfile){
load_weights(&net, weightfile);
}
srand(time(0));
list *plist = get_paths("data/compare.val.list");
//list *plist = get_paths("data/compare.val.old");
char **paths = (char **)list_to_array(plist);
int N = plist->size/2;
free_list(plist);
clock_t time;
int correct = 0;
int total = 0;
int splits = 10;
int num = (i+1)*N/splits - i*N/splits;
data val, buffer;
load_args args = {0};
args.w = net.w;
args.h = net.h;
args.paths = paths;
args.classes = 20;
args.n = num;
args.m = 0;
args.d = &buffer;
args.type = COMPARE_DATA;
pthread_t load_thread = load_data_in_thread(args);
for(i = 1; i <= splits; ++i){
time=clock();
pthread_join(load_thread, 0);
val = buffer;
num = (i+1)*N/splits - i*N/splits;
char **part = paths+(i*N/splits);
if(i != splits){
args.paths = part;
load_thread = load_data_in_thread(args);
}
printf("Loaded: %d images in %lf seconds\n", val.X.rows, sec(clock()-time));
time=clock();
matrix pred = network_predict_data(net, val);
int j,k;
for(j = 0; j < val.y.rows; ++j){
for(k = 0; k < 20; ++k){
if(val.y.vals[j][k*2] != val.y.vals[j][k*2+1]){
++total;
if((val.y.vals[j][k*2] < val.y.vals[j][k*2+1]) == (pred.vals[j][k*2] < pred.vals[j][k*2+1])){
++correct;
}
}
}
}
free_matrix(pred);
printf("%d: Acc: %f, %lf seconds, %d images\n", i, (float)correct/total, sec(clock()-time), val.X.rows);
free_data(val);
}
}
함수 이름: validate_compare
입력:
- char* filename : 네트워크 설정 파일 경로
- char* weightfile : 미리 학습된 가중치 파일 경로
동작:
- 네트워크 설정 파일과 미리 학습된 가중치 파일을 이용하여 네트워크를 불러온다.
- 검증 데이터셋 경로를 읽어온다.
- 검증 데이터셋을 10개로 분할하여 각각에 대해 다음을 수행한다.
- 검증 데이터를 읽어온다.
- 네트워크를 이용하여 예측을 수행한다.
- 예측값과 실제값을 비교하여 정확도를 계산한다.
- 분할된 각 검증 데이터셋에 대한 정확도와 소요 시간을 출력한다.
설명:
- 이 함수는 이미 학습된 네트워크를 검증하는 역할을 한다. 검증 데이터셋은 10개로 분할하여 각각에 대해 검증을 수행하고, 최종적으로 분할된 전체 검증 데이터셋에 대한 정확도와 소요 시간을 출력한다.
sortable_bbox
1
2
3
4
5
6
7
8
typedef struct {
network net;
char *filename;
int class;
int classes;
float elo;
float *elos;
} sortable_bbox;
network net
: YOLO 네트워크에 대한 정보가 담긴 구조체로, YOLO 객체 검출 모델에 대한 정보를 저장합니다.char *filename
: 객체 검출이 수행된 이미지 파일의 경로를 저장합니다.int class
: 객체가 속한 클래스의 인덱스를 저장합니다. 예를 들어, 클래스가 80개인 COCO 데이터셋에서는 0부터 79까지의 정수값으로 클래스를 표현합니다.int classes
: 객체 검출 모델이 인식할 수 있는 클래스의 수를 저장합니다.float elo
: 현재 객체 검출 결과에 대한 Elo rating 값을 저장합니다. Elo rating은 체스나 게임 등에서 플레이어의 능력치를 표현하기 위한 수치입니다.float *elos
: 각 클래스에 대한 Elo rating 값을 저장합니다. classes와 같은 크기를 가지는 배열입니다.
이 구조체는 객체 검출 결과를 저장하고, 이를 정렬하기 위한 용도로 사용됩니다. 각 객체는 클래스 인덱스와 Elo rating 값으로 구성된 쌍으로 나타내어지며, Elo rating 값이 높은 객체일수록 상위에 위치하게 됩니다.
elo_comparator
1
2
3
4
5
6
7
8
9
10
11
int total_compares = 0;
int current_class = 0;
int elo_comparator(const void*a, const void *b)
{
sortable_bbox box1 = *(sortable_bbox*)a;
sortable_bbox box2 = *(sortable_bbox*)b;
if(box1.elos[current_class] == box2.elos[current_class]) return 0;
if(box1.elos[current_class] > box2.elos[current_class]) return -1;
return 1;
}
함수 이름: elo_comparator
입력:
- void 포인터 형태로 정렬할 sortable_bbox 구조체의 주소값인 a와 b
동작:
- elo_comparator 함수는 정렬 알고리즘에서 사용되는 비교 함수이다.
- 두 개의 sortable_bbox 구조체를 비교하여 정렬 순서를 결정한다.
- 현재 클래스(current_class)에서의 elos 값을 비교하여 내림차순으로 정렬한다.
설명:
- total_compares: 전체 비교 수를 나타내는 변수
- current_class: 현재 클래스의 인덱스를 나타내는 변수
- elo_comparator 함수는 qsort() 함수에서 사용될 비교 함수이다. qsort()는 일반적으로 C/C++에서 사용되는 정렬 함수로, 오름차순 또는 내림차순으로 배열을 정렬할 수 있다.
- sortable_bbox 구조체는 bbox.c 파일에서 사용되며, 네트워크, 파일 이름, 클래스, 클래스 수, elo 값 등을 저장한다.
- elo_comparator 함수는 두 개의 sortable_bbox 구조체를 받아 각 구조체의 current_class 인덱스에 해당하는 elos 값을 비교한다. elos 값이 높은 것부터 내림차순으로 정렬하며, 만약 elos 값이 같은 경우에는 순서를 바꾸지 않는다.
- 이 함수는 compare_weights() 함수에서 호출되며, 정렬된 결과는 sorted_boxes에 저장된다.
bbox_comparator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int bbox_comparator(const void *a, const void *b)
{
++total_compares;
sortable_bbox box1 = *(sortable_bbox*)a;
sortable_bbox box2 = *(sortable_bbox*)b;
network net = box1.net;
int class = box1.class;
image im1 = load_image_color(box1.filename, net.w, net.h);
image im2 = load_image_color(box2.filename, net.w, net.h);
float *X = calloc(net.w*net.h*net.c, sizeof(float));
memcpy(X, im1.data, im1.w*im1.h*im1.c*sizeof(float));
memcpy(X+im1.w*im1.h*im1.c, im2.data, im2.w*im2.h*im2.c*sizeof(float));
float *predictions = network_predict(net, X);
free_image(im1);
free_image(im2);
free(X);
if (predictions[class*2] > predictions[class*2+1]){
return 1;
}
return -1;
}
함수 이름: bbox_comparator
입력:
- const void 포인터 a: 비교하고자 하는 첫 번째 sortable_bbox 구조체의 포인터
- const void 포인터 b: 비교하고자 하는 두 번째 sortable_bbox 구조체의 포인터
동작:
- total_compares 값을 1 증가시킴
- a와 b를 sortable_bbox 구조체로 변환하여 box1, box2에 저장
- box1에 저장된 network와 class 값을 사용하여 이미지를 로드하고, X 배열에 이미지 데이터를 복사
- box2에 저장된 network와 class 값을 사용하여 이미지를 로드하고, X 배열에 이미지 데이터를 이어붙임
- network_predict 함수를 사용하여 X 배열의 예측 값을 계산하여 predictions에 저장
- 사용한 이미지와 X 배열의 메모리를 해제함
- class에 해당하는 예측 값 비교 결과를 반환함
설명:
- 이 함수는 두 개의 sortable_bbox 구조체를 비교하여 정렬하기 위해 qsort 함수에서 사용됩니다.
- 두 개의 구조체에서 network와 class 값은 같으므로, 이를 사용하여 두 개의 이미지를 로드하고, 예측 값을 계산하여 비교합니다.
- 반환 값은 예측 값이 더 큰 경우 1, 그렇지 않은 경우 -1입니다. 이 함수가 호출될 때마다 total_compares 값을 1 씩 증가시켜, 이 함수가 총 몇 번 호출되었는지를 추적합니다.
bbox_update
1
2
3
4
5
6
7
8
9
10
void bbox_update(sortable_bbox *a, sortable_bbox *b, int class, int result)
{
int k = 32;
float EA = 1./(1+pow(10, (b->elos[class] - a->elos[class])/400.));
float EB = 1./(1+pow(10, (a->elos[class] - b->elos[class])/400.));
float SA = result ? 1 : 0;
float SB = result ? 0 : 1;
a->elos[class] += k*(SA - EA);
b->elos[class] += k*(SB - EB);
}
함수 이름: bbox_update
입력:
- sortable_bbox 타입의 두 개의 포인터 a와 b, int 타입의 class와 result
동작:
- Elo 레이팅 시스템을 사용하여 a와 b의 레이팅을 갱신한다. a와 b는 각각 class와 관련된 Elo 레이팅을 가지고 있으며, result는 a와 b 중 어느 쪽이 승리했는지를 나타낸다. 각각의 레이팅은 SA와 SB로 표현된다.
- 이전 레이팅에 대한 예상 승률 EA와 EB는 현재 레이팅과 이전 레이팅 사이의 차이를 통해 계산된다.
- k는 승리나 패배에 대한 가중치를 조절하는 상수이다.
설명:
- Elo 레이팅 시스템은 체스 선수들의 레이팅을 갱신하기 위해 고안된 시스템으로, 이제는 다양한 분야에서 사용되고 있다.
- 이 함수는 이 시스템을 사용하여 a와 b의 레이팅을 갱신한다.
- 승리한 쪽의 레이팅은 상대방에 비해 더 큰 증가를 하고, 패배한 쪽은 상대방에 비해 더 큰 감소를 한다.
- 이전 레이팅과 예상 승률을 비교하여 새로운 레이팅을 계산한다.
bbox_fight
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void bbox_fight(network net, sortable_bbox *a, sortable_bbox *b, int classes, int class)
{
image im1 = load_image_color(a->filename, net.w, net.h);
image im2 = load_image_color(b->filename, net.w, net.h);
float *X = calloc(net.w*net.h*net.c, sizeof(float));
memcpy(X, im1.data, im1.w*im1.h*im1.c*sizeof(float));
memcpy(X+im1.w*im1.h*im1.c, im2.data, im2.w*im2.h*im2.c*sizeof(float));
float *predictions = network_predict(net, X);
++total_compares;
int i;
for(i = 0; i < classes; ++i){
if(class < 0 || class == i){
int result = predictions[i*2] > predictions[i*2+1];
bbox_update(a, b, i, result);
}
}
free_image(im1);
free_image(im2);
free(X);
}
함수 이름: bbox_fight
입력:
- network net: YOLO 모델
- sortable_bbox *a: 비교 대상 A
- sortable_bbox *b: 비교 대상 B
- int classes: 클래스 수
- int class: 비교할 클래스 인덱스. 음수이면 모든 클래스 비교
동작:
- A와 B를 비교하여 예측값 계산
- 각 클래스별로, 해당 클래스가 선택된 경우 또는 음수이면 모든 클래스에 대해, A와 B의 elo를 갱신
설명:
- 두 이미지 a->filename과 b->filename를 YOLO 모델로 예측한 결과(predictions)를 받음
- 클래스별로 predictions에서 이긴 이미지를 결정하여 bbox_update 함수를 호출하여 elo 값 갱신
- 갱신된 값은 sortable_bbox 구조체 내 elos 배열에 저장
SortMaster3000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void SortMaster3000(char *filename, char *weightfile)
{
int i = 0;
network net = parse_network_cfg(filename);
if(weightfile){
load_weights(&net, weightfile);
}
srand(time(0));
set_batch_network(&net, 1);
list *plist = get_paths("data/compare.sort.list");
//list *plist = get_paths("data/compare.val.old");
char **paths = (char **)list_to_array(plist);
int N = plist->size;
free_list(plist);
sortable_bbox *boxes = calloc(N, sizeof(sortable_bbox));
printf("Sorting %d boxes...\n", N);
for(i = 0; i < N; ++i){
boxes[i].filename = paths[i];
boxes[i].net = net;
boxes[i].class = 7;
boxes[i].elo = 1500;
}
clock_t time=clock();
qsort(boxes, N, sizeof(sortable_bbox), bbox_comparator);
for(i = 0; i < N; ++i){
printf("%s\n", boxes[i].filename);
}
printf("Sorted in %d compares, %f secs\n", total_compares, sec(clock()-time));
}
함수 이름: SortMaster3000
입력:
- filename: 정렬할 네트워크 구성 파일 경로
- weightfile: 가중치 파일 경로
동작:
- filename과 weightfile로부터 네트워크를 파싱하고, 가중치를 불러온다.
- 네트워크를 1개의 배치로 설정하고, 시드를 초기화한다.
- “data/compare.sort.list”에서 파일 경로 리스트를 가져온다.
- 가져온 리스트의 크기만큼 sortable_bbox 구조체를 생성하고, 파일 경로, 네트워크, 클래스, elo 등의 정보를 저장한다.
- qsort를 사용하여 boxes를 정렬한다.
- 정렬된 boxes의 파일 경로를 출력한다.
- 총 비교 횟수와 걸린 시간을 출력한다.
설명:
- 주어진 파일 경로에 있는 네트워크 구성 파일과 가중치 파일을 사용하여 네트워크를 파싱하고, “data/compare.sort.list”에서 파일 경로 리스트를 가져온 후, 해당 경로의 파일들을 sortable_bbox 구조체에 저장합니다.
- 그리고 qsort를 사용하여 이를 정렬한 후, 파일 경로를 출력하며, 총 비교 횟수와 걸린 시간을 출력합니다.
BattleRoyaleWithCheese
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void BattleRoyaleWithCheese(char *filename, char *weightfile)
{
int classes = 20;
int i,j;
network net = parse_network_cfg(filename);
if(weightfile){
load_weights(&net, weightfile);
}
srand(time(0));
set_batch_network(&net, 1);
list *plist = get_paths("data/compare.sort.list");
//list *plist = get_paths("data/compare.small.list");
//list *plist = get_paths("data/compare.cat.list");
//list *plist = get_paths("data/compare.val.old");
char **paths = (char **)list_to_array(plist);
int N = plist->size;
int total = N;
free_list(plist);
sortable_bbox *boxes = calloc(N, sizeof(sortable_bbox));
printf("Battling %d boxes...\n", N);
for(i = 0; i < N; ++i){
boxes[i].filename = paths[i];
boxes[i].net = net;
boxes[i].classes = classes;
boxes[i].elos = calloc(classes, sizeof(float));;
for(j = 0; j < classes; ++j){
boxes[i].elos[j] = 1500;
}
}
int round;
clock_t time=clock();
for(round = 1; round <= 4; ++round){
clock_t round_time=clock();
printf("Round: %d\n", round);
shuffle(boxes, N, sizeof(sortable_bbox));
for(i = 0; i < N/2; ++i){
bbox_fight(net, boxes+i*2, boxes+i*2+1, classes, -1);
}
printf("Round: %f secs, %d remaining\n", sec(clock()-round_time), N);
}
int class;
for (class = 0; class < classes; ++class){
N = total;
current_class = class;
qsort(boxes, N, sizeof(sortable_bbox), elo_comparator);
N /= 2;
for(round = 1; round <= 100; ++round){
clock_t round_time=clock();
printf("Round: %d\n", round);
sorta_shuffle(boxes, N, sizeof(sortable_bbox), 10);
for(i = 0; i < N/2; ++i){
bbox_fight(net, boxes+i*2, boxes+i*2+1, classes, class);
}
qsort(boxes, N, sizeof(sortable_bbox), elo_comparator);
if(round <= 20) N = (N*9/10)/2*2;
printf("Round: %f secs, %d remaining\n", sec(clock()-round_time), N);
}
char buff[256];
sprintf(buff, "results/battle_%d.log", class);
FILE *outfp = fopen(buff, "w");
for(i = 0; i < N; ++i){
fprintf(outfp, "%s %f\n", boxes[i].filename, boxes[i].elos[class]);
}
fclose(outfp);
}
printf("Tournament in %d compares, %f secs\n", total_compares, sec(clock()-time));
}
함수 이름: BattleRoyaleWithCheese
입력:
- char* filename: 구성 파일 경로
- char* weightfile: 사전 학습된 모델 가중치 파일 경로
동작:
- 지정된 구성 파일을 파싱하여 네트워크를 만들고, 사전 학습된 모델 가중치를 로드한 후, 지정된 목록 파일에서 비교할 이미지 경로 목록을 가져옵니다.
- 이미지 경로 목록에서 각 이미지에 대한 Elo Rating을 계산합니다. Elo Rating은 두 객체 간의 상대적인 승률을 나타내는 지수입니다.
- Elo Rating을 계산하면 높은 순서대로 이미지가 정렬됩니다.
- 다음으로 Elo Rating이 가장 높은 이미지와 두 번째로 높은 이미지를 뽑아서 싸웁니다.
- Elo Rating이 가장 높은 이미지는 Elo Rating이 두 번째로 높은 이미지보다 이길 가능성이 높습니다.
- 이 과정을 여러 번 반복합니다. 마지막으로 Elo Rating이 가장 높은 이미지를 출력합니다.
설명:
- Elo Rating이라는 지수를 사용하여 이미지를 정렬하고, 각 이미지가 얼마나 잘 분류되는지 측정합니다.
- 이 프로그램은 이미지 분류 문제를 해결하는 데 매우 효과적입니다.
run_compare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void run_compare(int argc, char **argv)
{
if(argc < 4){
fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
return;
}
char *cfg = argv[3];
char *weights = (argc > 4) ? argv[4] : 0;
//char *filename = (argc > 5) ? argv[5]: 0;
if(0==strcmp(argv[2], "train")) train_compare(cfg, weights);
else if(0==strcmp(argv[2], "valid")) validate_compare(cfg, weights);
else if(0==strcmp(argv[2], "sort")) SortMaster3000(cfg, weights);
else if(0==strcmp(argv[2], "battle")) BattleRoyaleWithCheese(cfg, weights);
/*
else if(0==strcmp(argv[2], "train")) train_coco(cfg, weights);
else if(0==strcmp(argv[2], "extract")) extract_boxes(cfg, weights);
else if(0==strcmp(argv[2], "valid")) validate_recall(cfg, weights);
*/
}
함수 이름: run_compare
입력:
- argc: 실행 인자의 개수
- argv: 실행 인자들을 저장하는 배열
동작:
- 실행 인자 개수가 4개 미만이면, 사용법을 출력하고 함수 종료
- 실행 인자 중에서 cfg와 weights를 설정
- 실행 인자 중에서 두 번째로 들어온 문자열 값에 따라 train, valid, sort, battle 중 하나의 함수를 실행
- 주석 처리된 코드는 실행하지 않음
설명:
- 이 함수는 Darknet의 compare 학습을 위해 사용됨
- 실행 인자로 train, valid, sort, battle 중 하나와 cfg 파일 경로, weights 파일 경로를 받음
- 각각의 인자에 따라 해당하는 함수를 실행하며, weights 파일이 없으면 0으로 설정됨
This post is licensed under CC BY 4.0 by the author.