DarkNet 시리즈 - Convolutional Layer
convolutional_layer
Convolutional Layer 란?
Convolution은 합성곱으로 2가지 연산을 사용합니다.
- 각 원소끼리 곱합니다. (element wise multiplication)
- 각 원소를 더합니다.
이해를 돕기위해 그림으로 살펴보겠습니다. 아래 그림과 같이 각 원소를 곱하고 합한 값을 활성화 함수를 통과하여 최종적으로 값을 만듭니다.(아래 그림은 활성화 함수를 생략한 그림입니다.)
Convolutional Layer는 Feature Maps과 Filters의 Convolution 연산을 통해 그 다음 Feature Maps을 만들어 내는 작업을 반복합니다. 여기서 filters가 학습 파라미터 입니다.
입력 이미지
-> Filters(kernel)
-> Feature Maps(Channels)
-> Filters(kernel)
-> Feature Maps(Channels)
-> ...
Convolutional Layer는 설정 가능한 파라미터가 있습니다.
stride
: filter가 움직이는 간격입니다.padding
: Feature Map의 테두리 부분의 정보 손실을 줄이기 위해서 테두리를 특정한 값(보통 0)으로 채워 넣는 방법입니다. padding은 몇개의 테두리를 채울지에 대한 값입니다.- filter의 수(가중치의 수) : \(k \times k \times C_1 \times C_2\)
- \[W_2 = \frac{W_1 - k + 2 \times padding}{stride_w} + 1\]
- \[H_2 = \frac{H_1 - k + 2 \times padding}{stride_h} + 1\]
Convolutional Layer 역전파는 쉽게 표현하는 경우 아래 그림과 같습니다.
output을 계산하기 위해서 각자의 id를 가지고 있는 weight가 사용된 곳을 보시면 이해하기 쉽습니다. 예를 들어서 \(w_11\)은 \(h_11, h_12, h_21, h_22\)를 연산하는데 각각 사용되었기 때문에 이들의 미분 값의 합으로 최종적으로 업데이트 할 기울기를 만듭니다. 역전파는 Layer 따로 따로 간단하게 어떻게 동작하는지를 전부 살펴보고 마지막에 보면 더 쉬운거 같습니다.
forward_convolutional_layer
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
void forward_convolutional_layer(convolutional_layer l, network net)
{
int i, j;
fill_cpu(l.outputs*l.batch, 0, l.output, 1); /// output을 0으로 초기화
if(l.xnor){
binarize_weights(l.weights, l.n, l.c/l.groups*l.size*l.size, l.binary_weights);
swap_binary(&l);
binarize_cpu(net.input, l.c*l.h*l.w*l.batch, l.binary_input);
net.input = l.binary_input;
}
int m = l.n/l.groups; /// filter 개수
int k = l.size*l.size*l.c/l.groups; /// filter 크기
int n = l.out_w*l.out_h; /// output feature map 크기
for(i = 0; i < l.batch; ++i){
for(j = 0; j < l.groups; ++j){
float *a = l.weights + j*l.nweights/l.groups; /// 학습 시작 포인터
float *b = net.workspace;
float *c = l.output + (i*l.groups + j)*n*m; /// output 시작 포인터
float *im = net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w; /// input 시작 포인터
if (l.size == 1) {
b = im;
} else {
im2col_cpu(im, l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, b); /// 이미지를 columns로 변환
}
gemm(0,0,m,n,k,1,a,k,b,n,1,c,n); /// 컨볼루션 연산
}
}
if(l.batch_normalize){
forward_batchnorm_layer(l, net);
} else {
add_bias(l.output, l.biases, l.batch, l.n, l.out_h*l.out_w);
}
activate_array(l.output, l.outputs*l.batch, l.activation);
if(l.binary || l.xnor) swap_binary(&l);
}
함수 이름: forward_convolutional_layer
입력:
- convolutional_layer l: 컨볼루션 레이어 구조체
- network net: 네트워크 구조체
동작:
- 컨볼루션 연산을 수행하여 l.output에 결과값을 저장
- 배치 정규화를 사용하는 경우, forward_batchnorm_layer 함수를 호출하여 배치 정규화를 수행
- 활성화 함수를 수행하여 l.output을 업데이트
- 이진 컨볼루션 또는 XNOR-Networks를 사용하는 경우, swap_binary 함수를 호출하여 가중치와 입력값을 이진화
설명:
- 컨볼루션 레이어에 대한 forward 연산을 수행하는 함수이다.
- l.weights, l.biases, l.activation 등 컨볼루션 레이어의 필수 구성 요소들을 사용하여 입력값을 컨볼루션 연산하여 출력값을 계산한다.
- 컨볼루션 연산을 수행하기 위해 입력값을 이미지를 columns로 변환한다.
- l.batch_normalize가 true인 경우, forward_batchnorm_layer 함수를 호출하여 배치 정규화를 수행한다.
- activate_array 함수를 사용하여 활성화 함수를 수행하여 l.output을 업데이트한다.
- l.binary 또는 l.xnor가 true인 경우, swap_binary 함수를 호출하여 가중치와 입력값을 이진화한다.
backward_convolutional_layer
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
void backward_convolutional_layer(convolutional_layer l, network net)
{
int i, j;
int m = l.n/l.groups; /// filter 개수
int k = l.size*l.size*l.c/l.groups; /// filter 크기
int n = l.out_w*l.out_h; /// output feature map 크기
gradient_array(l.output, l.outputs*l.batch, l.activation, l.delta); /// activation function 역전파
if(l.batch_normalize){
backward_batchnorm_layer(l, net); /// batch normalize 역전파
} else {
backward_bias(l.bias_updates, l.delta, l.batch, l.n, k); /// bias 역전파
}
for(i = 0; i < l.batch; ++i){
for(j = 0; j < l.groups; ++j){
float *a = l.delta + (i*l.groups + j)*m*k; /// gradient 포인터 이동
float *b = net.workspace;
float *c = l.weight_updates + j*l.nweights/l.groups; /// update 포인터 이동
float *im = net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w; /// 이미지 포인터
float *imd = net.delta + (i*l.groups + j)*l.c/l.groups*l.h*l.w;
if(l.size == 1){
b = im;
} else {
im2col_cpu(im, l.c/l.groups, l.h, l.w,
l.size, l.stride, l.pad, b); /// 이미지를 columns로 변환
}
gemm(0,1,m,n,k,1,a,k,b,k,1,c,n); /// b(image)를 전치행렬로 컨볼루션 연산
if (net.delta) {
a = l.weights + j*l.nweights/l.groups; /// weight 포인터 이동
b = l.delta + (i*l.groups + j)*m*k; /// gradient 포인터 이동
c = net.workspace;
if (l.size == 1) {
c = imd;
}
gemm(1,0,n,k,m,1,a,n,b,k,0,c,k); /// a(weight)를 전치행렬로 컨볼루션 연산
if (l.size != 1) {
col2im_cpu(net.workspace, l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, imd); /// columns을 이미지로 변환
}
}
}
}
}
함수 이름: backward_convolutional_layer
입력:
- convolutional_layer l: 컨볼루션 레이어 구조체
- network net: 네트워크 구조체
동작:
- activation function의 gradient를 계산한다.
- batch normalization이 적용되었다면 batch normalization의 gradient를 계산하고, 그렇지 않으면 bias의 gradient를 계산한다.
- 각 배치에 대해, 각 그룹에서 gradient와 weight를 곱해 weight update를 계산한다.
- 이미지를 columns로 변환하여, 각 그룹에서 weight와 gradient를 곱해 input delta를 계산한다.
- columns을 이미지로 변환하여 input delta를 저장한다.
설명:
- convolutional layer에서는 이미지와 weight를 컨볼루션 연산하여 output feature map을 계산하고, 이후 activation function을 적용한다.
- 역전파에서는 output feature map의 gradient를 계산하고, 이를 이용하여 input delta와 weight update를 계산한다.
- 이때, gradient와 weight를 곱해 weight update를 계산할 때는 해당 그룹의 weight를 모두 사용하며, 각 그룹마다 input delta를 계산하여 누적하여 저장한다.
- 이후, columns를 이미지로 변환하여 input delta를 계산한다.
- 만약 batch normalization이 적용된 convolutional layer라면, 이전 layer에서 전달받은 error를 이용하여 batch normalization의 gradient를 계산하고, 이를 이용하여 gamma와 beta를 업데이트한다.
update_convolutional_layer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void update_convolutional_layer(convolutional_layer l, update_args a)
{
float learning_rate = a.learning_rate*l.learning_rate_scale;
float momentum = a.momentum;
float decay = a.decay;
int batch = a.batch;
axpy_cpu(l.n, learning_rate/batch, l.bias_updates, 1, l.biases, 1);
scal_cpu(l.n, momentum, l.bias_updates, 1);
if(l.scales){
axpy_cpu(l.n, learning_rate/batch, l.scale_updates, 1, l.scales, 1);
scal_cpu(l.n, momentum, l.scale_updates, 1);
}
axpy_cpu(l.nweights, -decay*batch, l.weights, 1, l.weight_updates, 1);
axpy_cpu(l.nweights, learning_rate/batch, l.weight_updates, 1, l.weights, 1);
scal_cpu(l.nweights, momentum, l.weight_updates, 1);
}
함수 이름: update_convolutional_layer
입력:
- convolutional_layer l: 업데이트할 convolutional layer 구조체
- update_args a: 업데이트에 필요한 인자들을 담은 구조체. learning_rate, momentum, decay, batch 값을 가짐
동작:
- Convolutional layer의 bias와 weight를 업데이트함
- learning_rate, momentum, decay, batch 값을 사용하여 업데이트에 필요한 계산 수행
설명:
- axpy_cpu 함수를 사용하여 bias와 scale을 업데이트 함. axpy_cpu 함수는 y = a*x + y 연산을 수행함
- scal_cpu 함수를 사용하여 momentum을 적용함. scal_cpu 함수는 벡터의 모든 원소에 스칼라 값을 곱해줌
- weight의 경우 decay를 적용하고, axpy_cpu 함수를 사용하여 weight를 업데이트 함. 이때 batch 값을 사용하여 mini-batch gradient descent를 수행함
resize_convolutional_layer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void resize_convolutional_layer(convolutional_layer *l, int w, int h)
{
l->w = w;
l->h = h;
int out_w = convolutional_out_width(*l);
int out_h = convolutional_out_height(*l);
l->out_w = out_w;
l->out_h = out_h;
l->outputs = l->out_h * l->out_w * l->out_c;
l->inputs = l->w * l->h * l->c;
l->output = realloc(l->output, l->batch*l->outputs*sizeof(float));
l->delta = realloc(l->delta, l->batch*l->outputs*sizeof(float));
if(l->batch_normalize){
l->x = realloc(l->x, l->batch*l->outputs*sizeof(float));
l->x_norm = realloc(l->x_norm, l->batch*l->outputs*sizeof(float));
}
l->workspace_size = get_workspace_size(*l);
}
함수 이름: resize_convolutional_layer
입력:
- convolutional_layer *l: 크기를 조절할 컨볼루션 레이어
- int w: 조절할 가로 크기
- int h: 조절할 세로 크기
동작:
- 주어진 가로 크기와 세로 크기에 따라 컨볼루션 레이어의 크기를 조정합니다.
- 이때, 출력 크기(out_w, out_h)도 계산하고, 컨볼루션 레이어의 출력, 델타, x, x_norm, workspace의 크기를 새로운 크기에 맞게 재할당합니다.
설명:
- 컨볼루션 레이어의 크기를 조절하는 함수입니다.
- 입력으로 주어진 컨볼루션 레이어의 가로와 세로 크기를 주어진 w와 h 값으로 각각 바꾸어주며, 출력 크기(out_w, out_h)도 이에 맞게 다시 계산합니다.
- 그리고 출력, 델타, x, x_norm, workspace의 크기를 새로운 크기에 맞게 realloc 함수를 사용하여 재할당합니다.
- 이때, batch_normalize가 사용되는 경우에는 x와 x_norm도 재할당합니다.
- 마지막으로 workspace_size를 get_workspace_size 함수를 통해 다시 계산하여 저장합니다.
make_convolutional_layer
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
convolutional_layer make_convolutional_layer(int batch, int h, int w, int c, int n, int groups, int size, int stride, int padding, ACTIVATION activation, int batch_normalize, int binary, int xnor, int adam)
{
int i;
convolutional_layer l = {0};
l.type = CONVOLUTIONAL;
l.groups = groups;
l.h = h;
l.w = w;
l.c = c;
l.n = n;
l.binary = binary;
l.xnor = xnor;
l.batch = batch;
l.stride = stride;
l.size = size;
l.pad = padding;
l.batch_normalize = batch_normalize;
l.weights = calloc(c/groups*n*size*size, sizeof(float));
l.weight_updates = calloc(c/groups*n*size*size, sizeof(float));
l.biases = calloc(n, sizeof(float));
l.bias_updates = calloc(n, sizeof(float));
l.nweights = c/groups*n*size*size;
l.nbiases = n;
// float scale = 1./sqrt(size*size*c);
float scale = sqrt(2./(size*size*c/l.groups));
//printf("convscale %f\n", scale);
//scale = .02;
//for(i = 0; i < c*n*size*size; ++i) l.weights[i] = scale*rand_uniform(-1, 1);
for(i = 0; i < l.nweights; ++i) l.weights[i] = scale*rand_normal();
int out_w = convolutional_out_width(l);
int out_h = convolutional_out_height(l);
l.out_h = out_h;
l.out_w = out_w;
l.out_c = n;
l.outputs = l.out_h * l.out_w * l.out_c;
l.inputs = l.w * l.h * l.c;
l.output = calloc(l.batch*l.outputs, sizeof(float));
l.delta = calloc(l.batch*l.outputs, sizeof(float));
l.forward = forward_convolutional_layer;
l.backward = backward_convolutional_layer;
l.update = update_convolutional_layer;
if(binary){
l.binary_weights = calloc(l.nweights, sizeof(float));
l.cweights = calloc(l.nweights, sizeof(char));
l.scales = calloc(n, sizeof(float));
}
if(xnor){
l.binary_weights = calloc(l.nweights, sizeof(float));
l.binary_input = calloc(l.inputs*l.batch, sizeof(float));
}
if(batch_normalize){
l.scales = calloc(n, sizeof(float));
l.scale_updates = calloc(n, sizeof(float));
for(i = 0; i < n; ++i){
l.scales[i] = 1;
}
l.mean = calloc(n, sizeof(float));
l.variance = calloc(n, sizeof(float));
l.mean_delta = calloc(n, sizeof(float));
l.variance_delta = calloc(n, sizeof(float));
l.rolling_mean = calloc(n, sizeof(float));
l.rolling_variance = calloc(n, sizeof(float));
l.x = calloc(l.batch*l.outputs, sizeof(float));
l.x_norm = calloc(l.batch*l.outputs, sizeof(float));
}
if(adam){
l.m = calloc(l.nweights, sizeof(float));
l.v = calloc(l.nweights, sizeof(float));
l.bias_m = calloc(n, sizeof(float));
l.scale_m = calloc(n, sizeof(float));
l.bias_v = calloc(n, sizeof(float));
l.scale_v = calloc(n, sizeof(float));
}
l.workspace_size = get_workspace_size(l);
l.activation = activation;
fprintf(stderr, "conv %5d %2d x%2d /%2d %4d x%4d x%4d -> %4d x%4d x%4d %5.3f BFLOPs\n", n, size, size, stride, w, h, c, l.out_w, l.out_h, l.out_c, (2.0 * l.n * l.size*l.size*l.c/l.groups * l.out_h*l.out_w)/1000000000.);
return l;
}
함수 이름: make_convolutional_layer
입력:
- batch: 배치 크기
- h: 입력 이미지의 높이
- w: 입력 이미지의 너비
- c: 입력 이미지의 채널 수
- n: 필터 개수
- groups: 그룹 수
- size: 필터 크기
- stride: 스트라이드
- padding: 패딩 크기
- activation: 활성화 함수
- batch_normalize: 배치 정규화 여부
- binary: 이진화 여부
- xnor: XNOR 여부
- adam: Adam 옵티마이저 사용 여부
동작:
- 입력 이미지와 필터를 합성곱 연산하여 출력을 계산하는 합성곱 레이어를 생성합니다.
- 필요한 메모리를 동적 할당합니다.
- 가중치(weight)와 편향(bias)을 초기화합니다.
- 활성화 함수, 배치 정규화, 이진화, XNOR, Adam 옵티마이저를 사용하는 경우 필요한 메모리와 변수를 할당하고 초기화합니다.
- 생성된 레이어의 출력 크기와 필요한 메모리 크기를 계산합니다.
- 생성된 레이어와 연관된 함수 포인터를 설정합니다.
- 생성된 레이어를 반환합니다.
설명:
- 이 함수는 입력 이미지와 필터의 합성곱 연산을 수행하는 합성곱 레이어를 생성하는 함수입니다.
- 입력으로 받은 파라미터를 사용하여 필요한 메모리를 동적 할당하고 초기화합니다.
- 필터(weight)는 랜덤한 값으로 초기화하며, Xavier 초기화 방법을 사용합니다.
- 활성화 함수는 ReLU, LeakyReLU, linear 함수를 사용할 수 있습니다.
- 배치 정규화는 입력 데이터의 배치 단위로 정규화를 수행하여 학습을 안정화시키는 방법입니다.
- 이진화는 모델의 가중치를 이진 형태로 변환하여 모델의 크기를 줄이고 연산 속도를 높이는 방법입니다.
- XNOR는 이진화된 가중치와 이진화된 입력을 사용하여 합성곱 연산을 수행하는 방법으로, 이진화보다 더 큰 모델 압축과 빠른 연산 속도를 제공합니다.
- Adam 옵티마이저는 경사 하강법을 사용하는 옵티마이저 중 하나로, 모멘텀과 RMSProp을 결합한 방법입니다. 학습 속도를 자동으로 조절하여 더 빠르게 수렴하는 특징이 있습니다.
denormalize_convolutional_layer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void denormalize_convolutional_layer(convolutional_layer l)
{
int i, j;
for(i = 0; i < l.n; ++i){
float scale = l.scales[i]/sqrt(l.rolling_variance[i] + .00001);
for(j = 0; j < l.c/l.groups*l.size*l.size; ++j){
l.weights[i*l.c/l.groups*l.size*l.size + j] *= scale;
}
l.biases[i] -= l.rolling_mean[i] * scale;
l.scales[i] = 1;
l.rolling_mean[i] = 0;
l.rolling_variance[i] = 1;
}
}
함수 이름: denormalize_convolutional_layer
입력:
- convolutional_layer l: denormalization이 필요한 convolutional layer
동작:
- batch normalization을 적용한 convolutional layer를 denormalize 함
- convolutional layer의 weights, biases, scales, rolling_mean, rolling_variance 값을 수정함
설명:
- batch normalization은 데이터의 분포를 조절해 학습을 안정화시키는 기술이다.
- 학습 과정에서 이전 미니배치의 평균과 분산을 이용해 현재 미니배치의 데이터를 normalize한다.
- 하지만, 학습이 끝난 모델을 사용할 때는 이전 미니배치의 평균과 분산 대신 전체 데이터셋의 평균과 분산을 이용해 denormalize해야 한다.
- 이 함수는 그 역할을 수행하는 함수이다.
- 각 채널마다 denormalize에 필요한 값을 계산하고, weights와 biases 값을 수정한다.
- scales 값은 1로, rolling_mean과 rolling_variance 값은 0과 1로 초기화한다.
add_bias
1
2
3
4
5
6
7
8
9
10
11
void add_bias(float *output, float *biases, int batch, int n, int size)
{
int i,j,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
for(j = 0; j < size; ++j){
output[(b*n + i)*size + j] += biases[i];
}
}
}
}
함수 이름: add_bias
입력:
- output: float 형식의 출력 값 포인터
- biases: float 형식의 bias 값 포인터
- batch: int 형식의 batch 크기
- n: int 형식의 필터 개수
- size: int 형식의 출력 값 크기
동작:
- 출력 값에 bias 값을 더함
- 출력 값의 크기는 batch x n x size
설명:
- 각 필터마다 bias 값을 더해주는 함수
- 출력 값의 각 요소에 biases[i] 값을 더해줌으로써 bias 값을 적용함
- 출력 값의 크기는 batch x n x size 이므로, 반복문을 통해 각 요소에 biases[i] 값을 더해줌
scale_bias
1
2
3
4
5
6
7
8
9
10
11
void scale_bias(float *output, float *scales, int batch, int n, int size)
{
int i,j,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
for(j = 0; j < size; ++j){
output[(b*n + i)*size + j] *= scales[i];
}
}
}
}
함수 이름: scale_bias
입력:
- float *output: 출력값 포인터
- float *scales: 스케일값 포인터
- int batch: 배치 크기
- int n: 출력 채널 수
- int size: 출력값 크기 (가로, 세로)
동작:
- 각 배치별로 출력값(output)의 각 채널에 대해, scales 배열의 해당 채널 값으로 출력값을 스케일링(scale)합니다.
설명:
- 이 함수는 출력값(output)에 대해 각 채널에 대한 스케일링 작업을 수행하는 함수입니다.
- 배치(batch) 크기만큼의 데이터를 처리하며, 각 배치에 대해 출력값(output)의 n개 채널에 대해 스케일링 작업을 수행합니다.
- 출력값(output)은 배치, 채널, 가로, 세로의 4차원 배열 구조로 이루어져 있으며, 스케일(scales)도 출력 채널 수(n)만큼의 1차원 배열로 주어집니다.
- 이 함수는 각 배치(b), 채널(i), 가로(j), 세로(k)에 대한 반복문을 수행하며, 출력값(output)의 (b*n+i)*size+j 위치에 해당하는 값을 스케일(scales) 배열의 i번째 값으로 곱해주는 작업을 수행합니다.
- 이렇게 스케일링된 값을 출력값(output)에 저장합니다.
backward_bias
1
2
3
4
5
6
7
8
9
void backward_bias(float *bias_updates, float *delta, int batch, int n, int size)
{
int i,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
bias_updates[i] += sum_array(delta+size*(i+b*n), size);
}
}
}
함수 이름: backward_bias
입력:
- bias_updates: 각 레이어의 편향(bias) 업데이트 값을 저장할 배열
- delta: 각 뉴런의 오차 값(error) 배열
- batch: 미니배치(batch) 크기
- n: 레이어 내 뉴런 개수
- size: 뉴런이 가지고 있는 입력(input) 데이터 크기
동작:
- 이 함수는 convolutional neural network에서 편향(bias) 업데이트를 계산합니다.
- delta는 현재 레이어의 뉴런에서의 오차 값입니다.
- bias_updates 배열은 각 레이어의 편향 값을 업데이트할 때 사용됩니다.
설명:
- 이 함수는 미니배치(batch) 내의 모든 뉴런에 대해 bias_updates 배열에 대한 업데이트 값을 계산합니다.
- 먼저 for문을 이용하여 batch 내 각 뉴런에 대한 bias_updates 값을 계산합니다.
- 그리고 sum_array 함수를 사용하여 delta 배열의 해당 뉴런의 오차 값을 계산하고, bias_updates 배열에 더합니다.
- 따라서 이 함수는 backward propagation 과정에서 편향 업데이트를 수행합니다.
swap_binary
1
2
3
4
5
6
void swap_binary(convolutional_layer *l)
{
float *swap = l->weights;
l->weights = l->binary_weights;
l->binary_weights = swap;
}
함수 이름: swap_binary
입력:
- convolutional_layer *l: convolutional_layer 구조체 포인터
동작:
- 이 함수는 convolutional layer의 가중치(weights)와 binary weights를 교환(swap)합니다.
설명:
- 이 함수는 convolutional layer의 가중치를 binary weights로 교환합니다.
- 이는 forward pass에서 기존의 가중치를 사용하여 연산을 수행하는 대신, 이진 형태의 가중치(binary weights)를 사용하여 더욱 빠르게 연산을 수행할 수 있도록 하기 위한 것입니다.
- 이 함수에서는 포인터를 사용하여 가중치와 binary weights를 교환합니다.
binarize_weights
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void binarize_weights(float *weights, int n, int size, float *binary)
{
int i, f;
for(f = 0; f < n; ++f){
float mean = 0;
for(i = 0; i < size; ++i){
mean += fabs(weights[f*size + i]);
}
mean = mean / size;
for(i = 0; i < size; ++i){
binary[f*size + i] = (weights[f*size + i] > 0) ? mean : -mean;
}
}
}
함수 이름: binarize_weights
입력:
- float *weights: 이진화할 가중치 배열
- int n: 가중치의 채널 수
- int size: 가중치의 크기
- float *binary: 이진화된 가중치를 저장할 배열
동작:
- 주어진 가중치 배열(weights)을 이진화하여 이진화된 가중치(binary)를 계산합니다.
- 이진화된 가중치는 각 가중치의 절댓값 평균(mean)으로 계산됩니다.
- 가중치 값이 평균보다 크면 이진화된 가중치 값은 평균이고, 작으면 -평균입니다.
설명:
- Convolutional neural network에서 이진화된 가중치는 더 적은 메모리 공간을 차지하고 빠른 연산이 가능하여 모델의 실행 속도를 향상시키는 데 도움을 줍니다.
- 이진화된 가중치를 사용하면 계산 복잡도를 줄이는 동시에 정확도를 유지할 수 있습니다.
binarize_cpu
1
2
3
4
5
6
7
void binarize_cpu(float *input, int n, float *binary)
{
int i;
for(i = 0; i < n; ++i){
binary[i] = (input[i] > 0) ? 1 : -1;
}
}
함수 이름: binarize_cpu
입력:
- input: float 형태의 1차원 배열
- n: input 배열의 길이
- binary: float 형태의 1차원 배열
동작:
- input 배열의 요소를 0을 기준으로 1 또는 -1로 이진화하여 binary 배열에 저장함.
설명:
- 이진화란, 입력값을 0과 1 또는 -1과 1과 같은 이진수 형태로 바꾸는 작업을 의미함.
- 이 함수에서는 입력된 input 배열의 요소를 0을 기준으로 1 또는 -1로 이진화하여 binary 배열에 저장함.
- 이진화된 값은 다음과 같은 조건문으로 결정됨:
- input[i] > 0 이면, binary[i] = 1
- input[i] <= 0 이면, binary[i] = -1
binarize_input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void binarize_input(float *input, int n, int size, float *binary)
{
int i, s;
for(s = 0; s < size; ++s){
float mean = 0;
for(i = 0; i < n; ++i){
mean += fabs(input[i*size + s]);
}
mean = mean / n;
for(i = 0; i < n; ++i){
binary[i*size + s] = (input[i*size + s] > 0) ? mean : -mean;
}
}
}
함수 이름: binarize_input
입력:
- float *input: 이진화할 입력 배열
- int n: 입력 배열의 채널 수
- int size: 입력 배열의 크기 (가로 또는 세로 한 변의 길이)
- float *binary: 이진화된 값을 저장할 출력 배열
동작:
- 입력 배열을 이진화하여 출력 배열에 저장합니다.
설명:
- 입력 배열의 각 채널과 위치에 대해 평균값을 계산하고, 입력 값이 평균보다 크면 1, 작으면 -1을 출력 배열에 저장합니다.
- 평균값은 각 채널과 위치마다 다르게 계산됩니다.
convolutional_out_height
1
2
3
4
int convolutional_out_height(convolutional_layer l)
{
return (l.h + 2*l.pad - l.size) / l.stride + 1;
}
함수 이름: convolutional_out_height
입력:
- convolutional_layer l: 합성곱 레이어 구조체
동작:
- 합성곱 레이어의 입력 이미지 높이(l.h), 패딩 크기(l.pad), 필터 크기(l.size), 및 스트라이드 크기(l.stride)를 고려하여 출력 이미지 높이를 계산한다.
설명:
- 합성곱 레이어에서 필터와 입력 이미지의 합성곱 연산을 수행하면 출력 이미지가 생성된다.
- 이때, 출력 이미지의 높이를 계산하는 함수이다.
- 합성곱 레이어 구조체에서 입력 이미지의 높이(l.h), 패딩 크기(l.pad), 필터 크기(l.size), 및 스트라이드 크기(l.stride)를 이용하여 출력 이미지의 높이를 계산하고 반환한다.
convolutional_out_width
1
2
3
4
int convolutional_out_width(convolutional_layer l)
{
return (l.w + 2*l.pad - l.size) / l.stride + 1;
}
함수 이름: convolutional_out_width
입력:
- convolutional_layer l: 컨볼루션 레이어 구조체
동작:
- 입력 이미지의 너비, 패딩, 스트라이드, 필터 크기를 고려하여 컨볼루션 레이어의 출력 너비를 계산한다.
설명:
- 컨볼루션 레이어의 출력 너비를 반환하는 함수이다.
- 입력 이미지의 너비, 패딩, 스트라이드, 필터 크기를 고려하여 컨볼루션 레이어의 출력 너비를 계산하고 반환한다.
- 출력 너비는 아래의 공식을 따른다: (입력 너비 + 2 x 패딩 - 필터 크기) / 스트라이드 + 1
get_convolutional_image
1
2
3
4
image get_convolutional_image(convolutional_layer l)
{
return float_to_image(l.out_w,l.out_h,l.out_c,l.output);
}
함수 이름: get_convolutional_image
입력:
- convolutional_layer 구조체
동작:
- convolutional_layer의 출력을 float_to_image 함수를 사용하여 image 구조체로 변환
설명:
- convolutional_layer의 출력을 이미지 형식으로 변환하여 반환하는 함수이다.
- 반환된 이미지는 float_to_image 함수를 사용하여 변환된다.
get_convolutional_delta
1
2
3
4
image get_convolutional_delta(convolutional_layer l)
{
return float_to_image(l.out_w,l.out_h,l.out_c,l.delta);
}
함수 이름: get_convolutional_delta
입력:
- convolutional_layer 구조체
동작:
- convolutional_layer 구조체 내 delta 배열을 out_w, out_h, out_c 크기의 이미지 구조체 형태로 변환
설명:
- convolutional_layer 구조체 내 delta 배열은 해당 층에서 역전파(backpropagation)를 통해 계산된 출력 값의 오차 값을 저장하고 있다.
- 이 함수는 해당 delta 배열을 이미지 형태로 변환하여 반환한다.
- 이 때, 변환된 이미지의 크기는 해당 층의 출력 값 크기(out_w, out_h, out_c)와 동일하다.
get_convolutional_weight
1
2
3
4
5
6
7
image get_convolutional_weight(convolutional_layer l, int i)
{
int h = l.size;
int w = l.size;
int c = l.c/l.groups;
return float_to_image(w,h,c,l.weights+i*h*w*c);
}
함수 이름: get_convolutional_weight
입력:
- convolutional_layer l: 컨볼루션 레이어 구조체
- int i: 가져올 가중치의 인덱스
동작:
- 컨볼루션 레이어에서 i번째 가중치의 크기를 이용하여 float_to_image 함수를 호출해 가중치 이미지를 생성한다.
설명:
- 컨볼루션 레이어에서 i번째 가중치의 값을 이용하여 해당 가중치를 시각화할 수 있는 이미지를 생성한다.
- 생성된 이미지는 float_to_image 함수를 이용하여 생성되며, 이미지의 크기는 가중치의 크기와 동일하다.
rgbgr_weights
1
2
3
4
5
6
7
8
9
10
void rgbgr_weights(convolutional_layer l)
{
int i;
for(i = 0; i < l.n; ++i){
image im = get_convolutional_weight(l, i);
if (im.c == 3) {
rgbgr_image(im);
}
}
}
함수 이름: rgbgr_weights
입력:
- convolutional_layer l: 컨볼루션 레이어
동작:
- 각 컨볼루션 레이어의 가중치 이미지를 가져온다.
- 이미지의 채널이 3인 경우, rgbgr_image 함수를 사용하여 RGB 이미지를 BGR 이미지로 변환한다.
- 모든 가중치 이미지에 대해 위 동작을 수행한다.
설명:
- 이 함수는 컨볼루션 레이어의 가중치 이미지를 BGR 채널 순서로 변환하는 기능을 수행한다.
- 컬러 이미지에서는 색상 채널의 순서가 RGB가 일반적이지만, OpenCV 라이브러리에서는 BGR 채널 순서를 사용하므로 이러한 변환이 필요하다.
- 이 함수는 주로 딥러닝 모델을 OpenCV와 같은 라이브러리에서 사용할 때 유용하게 사용된다. 하위 설명:
- for 문에서 i 변수를 초기화하고, l.n만큼 반복한다.
- get_convolutional_weight 함수를 사용하여 i번째 가중치 이미지를 가져온다.
- 이미지의 채널이 3인 경우에만 rgbgr_image 함수를 사용하여 이미지를 변환한다.
rescale_weights
1
2
3
4
5
6
7
8
9
10
11
12
void rescale_weights(convolutional_layer l, float scale, float trans)
{
int i;
for(i = 0; i < l.n; ++i){
image im = get_convolutional_weight(l, i);
if (im.c == 3) {
scale_image(im, scale);
float sum = sum_array(im.data, im.w*im.h*im.c);
l.biases[i] += sum*trans;
}
}
}
함수 이름: rescale_weights
입력:
- convolutional_layer l : rescale할 레이어
- float scale : 가중치를 곱할 스케일 값
- float trans : 편향(bias) 값을 조절할 값
동작:
- convolutional layer에서 가중치(weight)를 가져와서 이미지를 rescale하고, 이미지의 총 합(sum)을 구한 후, 편향(bias) 값을 trans만큼 더해줌
설명:
- convolutional_layer l에서 가중치(weight) 이미지를 가져와서 이미지의 채널 수(c)가 3일 때(rescale하고자 하는 이미지가 RGB 이미지일 때), 이미지를 scale 값만큼 곱해줌
- 이미지의 모든 픽셀 값의 합(sum)을 구함
- l.biases[i]에 sum * trans 값을 더해줌으로써, 편향(bias) 값을 trans만큼 더해줌
get_weights
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
image *get_weights(convolutional_layer l)
{
image *weights = calloc(l.n, sizeof(image));
int i;
for(i = 0; i < l.n; ++i){
weights[i] = copy_image(get_convolutional_weight(l, i));
normalize_image(weights[i]);
/*
char buff[256];
sprintf(buff, "filter%d", i);
save_image(weights[i], buff);
*/
}
//error("hey");
return weights;
}
함수 이름: get_weights
입력:
- convolutional_layer l: 합성곱 레이어 객체
동작:
- 합성곱 레이어의 가중치를 가져와서, 가중치 이미지들을 생성
- 생성된 가중치 이미지들을 정규화(normalize)
- 가중치 이미지들을 반환
설명:
- 함수는 입력으로 받은 합성곱 레이어의 가중치를 가져와 가중치 이미지들을 생성하고, 생성된 가중치 이미지들을 정규화(normalize)합니다.
- 정규화(normalize)란, 이미지 픽셀 값들을 0과 1사이의 값으로 조정하는 것을 말합니다.
- 생성된 가중치 이미지들은 포인터 배열로 반환됩니다.
visualize_convolutional_layer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
image *visualize_convolutional_layer(convolutional_layer l, char *window, image *prev_weights)
{
image *single_weights = get_weights(l);
show_images(single_weights, l.n, window);
image delta = get_convolutional_image(l);
image dc = collapse_image_layers(delta, 1);
char buff[256];
sprintf(buff, "%s: Output", window);
//show_image(dc, buff);
//save_image(dc, buff);
free_image(dc);
return single_weights;
}
함수 이름: visualize_convolutional_layer
입력:
- convolutional_layer l: 시각화할 합성곱 레이어
- char *window: 시각화할 윈도우 이름
- image *prev_weights: 이전 가중치 이미지 포인터
동작:
- 합성곱 레이어의 가중치 이미지와 출력 이미지를 시각화하고,
- 시각화된 가중치 이미지를 반환한다.
설명:
- convolutional_layer 구조체에 저장된 가중치 값들을 이미지로 변환한다.
- 변환된 이미지들을 윈도우에 시각화하여 보여준다.
- 합성곱 레이어의 출력 이미지를 이미지 collapse를 통해 한 장으로 만들어서 보여준다.
- 반환되는 single_weights 포인터는 가중치 이미지를 담고 있는 이미지 배열이다.