1. 程式人生 > >YOLOv2原始碼分析(三)

YOLOv2原始碼分析(三)

文章全部YOLOv2原始碼分析

接著上一講沒有講完的make_convolutional_layer函式

0x01 make_convolutional_layer

    //make_convolutional_layer
    l.forward = forward_convolutional_layer;
    l.backward = backward_convolutional_layer;
    l.update = update_convolutional_layer;    
  
  • 1
  • 2
  • 3
  • 4

上來就是三坐大山^_^,我們先從第一個forward_convolutional_layer開始。

0x0101 forward_convolutional_layer

void forward_convolutional_layer(convolutional_layer l, network net)
{//傳入卷積層引數和網路的總引數
    int i, j;

    fill_cpu(l.outputs*l.batch, 0, l.output, 1);
  
  • 1
  • 2
  • 3
  • 4
  • 5

看這個fill_cpu函式

void
fill_cpu(int N, float ALPHA, float *X, int INCX) { int i; for(i = 0; i < N; ++i) X[i*INCX] = ALPHA; }
  • 1
  • 2
  • 3
  • 4
  • 5

輸入的引數N表示一個batch中所有的影象元素個數,x指向n對應大小分配的記憶體空間。整個函式來看就是對輸出影象元素的一個初始化操作。

接著看後面

    //forward_convolutional_layer
    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;
    }    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

判斷是否二值化操作,如果是的話,其中有兩個關鍵的函式binarize_weightsbinarize_cpu

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;
        }
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第一個引數就是指向分配給weight記憶體空間 的指標,第二引數是卷積核個數,第三個引數是一個卷積核weight的個數(這裡應該使用l.nweights/l.n),第四個引數是指向分配給二值化weight記憶體空間 的指標。舉個例子

假設有兩個2x2卷積核
n=2  size=4
權重值總共81 2 3 4 5 6 7 8

第一次迴圈 f=0 
mean = 1+2+3+4 = 10
mean/4 = 2.5
binary[0]=2.5 binary[1]=2.5 binary[2]=2.5 binary[3]=2.5

第二次迴圈 f=1
mean = 5+6+7+8 = 26
mean/4 = 6.5
binary[0]=6.5 binary[1]=6.5 binary[2]=6.5 binary[3]=6.5
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

接著看後面的swap_binary函式

void swap_binary(convolutional_layer *l)
{
    float *swap = l->weights;
    l->weights = l->binary_weights;
    l->binary_weights = swap;

#ifdef GPU
    swap = l->weights_gpu;
    l->weights_gpu = l->binary_weights_gpu;
    l->binary_weights_gpu = swap;
#endif
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

函式的作用很明顯了,就要把以前的權重值替換二值化後的

接著binarize_cpu函式

void binarize_cpu(float *input, int n, float *binary)
{
    int i;
    for(i = 0; i < n; ++i){
        binary[i] = (input[i] > 0) ? 1 : -1;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

函式的第一個引數指向輸入影象記憶體空間的指標,函式第二個引數表示一個batch的影象元素個數,函式第三個引數指向分配給二值化input記憶體空間 的指標。

函式很簡單,總體來看函式的作用就是出入影象的二值化。

最後將得到的二值化輸入影象賦值給原來的輸入影象。

我們接著回到forward_convolutional_layer函式

    //forward_convolutional_layer
    int m = l.n/l.groups;//一個group的卷積核個數
    int k = l.size*l.size*l.c/l.groups;//一個group的卷積核元素個數
    int n = l.out_w*l.out_h;//一個輸出影象的元素個數
    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;

            im2col_cpu(net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w,
                l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, b);
            gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);
        }
    }

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

這裡有兩個非常重要的函式im2col_cpugemm。先看第一個

0x0102 im2col_cpu && gemm

float im2col_get_pixel(float *im, int height, int width, int channels,
                        int row, int col, int channel, int pad)
{
    row -= pad;
    col -= pad;

    if (row < 0 || col < 0 ||
        row >= height || col >= width) return 0;
    return im[col + width*(row + height*channel)];
}

//From Berkeley Vision's Caffe!
//https://github.com/BVLC/caffe/blob/master/LICENSE
void im2col_cpu(float* data_im,
     int channels,  int height,  int width,
     int ksize,  int stride, int pad, float* data_col) 
{
    int c,h,w;
    int height_col = (height + 2*pad - ksize) / stride + 1;//卷積後的高度
    int width_col = (width + 2*pad - ksize) / stride + 1;//卷積後的寬度

    int channels_col = channels * ksize * ksize;
    for (c = 0; c < channels_col; ++c) {
        int w_offset = c % ksize;
        int h_offset = (c / ksize) % ksize;
        int c_im = c / ksize / ksize;
        for (h = 0; h < height_col; ++h) {
            for (w = 0; w < width_col; ++w) {
                int im_row = h_offset + h * stride;
                int im_col = w_offset + w * stride;
                int col_index = (c * height_col + h) * width_col + w;
                data_col[col_index] = im2col_get_pixel(data_im, height, width, channels,
                        im_row, im_col, c_im, pad);
            }
        }
    }
}
  
  • 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

這個函式是參考了早期caffe中的設計,但是現在caffe好像有了新的做法。首先說說這個函式的引數

  • data_im:指向輸入資料的指標
  • channels:一個卷積組的通道數
  • height:輸入影象的高
  • width:輸入影象的寬
  • ksize:卷積核的大小
  • stride:步長大小
  • pad:pad大小
  • data_col:指向資料轉化後的記憶體空間

這個函式比較複雜,還是舉個例子說明

我們假設輸入圖片大小3x3,pad=1,stride=2,卷積核大小3x3,channels=1
0 0 0 0 0
0 1 2 3 0
0 4 5 6 0
0 7 8 9 0
0 0 0 0 0
height_col = (3+2-3)/2+1 = 2
width_col = (3+2-3)/2+1 = 2
channels = 1*3*3 = 9
進入第一個迴圈c = 0
w_offset = 0
h_offset = 0
c_im = 0

h=0    w=0
im_row = 0
im_col = 0
col_index = 0
data_col[0] = 0

h=0    w=1
im_row = 0
im_col = 2
col_index = 1
data_col[1] = 0
...
data_col[2]=0 data_col[3]=5 
data_col[4]=0 data_col[5]=0 data_col[6]=4 data_col[7]=6
...

0 0 0 0 0     
0 1 2 3 0     
0 4 5 6 0 ==>  0 0 0 5 0 0 4 6 0 0 5 0 0 2 0 8 1 3 7 9 2 0 8 0 0 5 0 0 4 6 0 0 5 0 0 0
0 7 8 9 0     
0 0 0 0 0
  
  • 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

翻譯成人能看得懂的就是

0 0 0 5
0 0 4 6
0 0 5 0
0 2 0 8
1 3 7 9
2 0 8 0
0 5 0 0
4 6 0 0
5 0 0 0
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這個矩陣有什麼特殊的含義呢?

我們不難發現,這個矩陣的每一列就表示卷積核對應的一個小視窗,例如第一個視窗0 0 0 0 1 2 0 4 5,很有意思是不是?

接著我們再來看看這個gemm函式

void gemm(int TA, int TB, int M, int N, int K, float ALPHA, 
        float *A, int lda, 
        float *B, int ldb,
        float BETA,
        float *C, int ldc)
{
    gemm_cpu( TA,  TB,  M, N, K, ALPHA,A,lda, B, ldb,BETA,C,ldc);
}
void gemm_cpu(int TA, int TB, int M, int N, int K, float ALPHA, 
        float *A, int lda, 
        float *B, int ldb,
        float BETA,
        float *C, int ldc)
{
    //printf("cpu: %d %d %d %d %d %f %d %d %f %d\n",TA, TB, M, N, K, ALPHA, lda, ldb, BETA, ldc);
    int i, j;
    for(i = 0; i < M; ++i){
        for(j = 0; j < N; ++j){
            C[i*ldc + j] *= BETA;//因為前面的BETA是1,所以這裡我們也不關心了
        }
    }
    if(!TA && !TB)
        gemm_nn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
    else if(TA && !TB)
        gemm_tn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
    else if(!TA && TB)
        gemm_nt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
    else
        gemm_tt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
}
void gemm_nn(int M, int N, int K, float ALPHA, 
        float *A, int lda, 
        float *B, int ldb,
        float *C, int ldc)
{
    int i,j,k;
    #pragma omp parallel for
    for(i = 0; i < M; ++i){
        for(k = 0; k < K; ++k){
            register float A_PART = ALPHA*A[i*lda+k];
            for(j = 0; j < N; ++j){
                C[i*ldc+j] += A_PART*B[k*ldb+j];
            }
        }
    }
}
  
  • 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

由於gemm前面傳入的引數是0,0,所以我這裡只看gemm_nn這個函式,其他函式操作相似,不再贅述。

我們還是先看看這個函式的引數

  • M: A的行數
  • N: B的列數
  • K: A的列數
  • ALPHA:係數
  • A:指向矩陣a的指標
  • lda: a的列數
  • B:指向矩陣b的指標
  • ldb: b的列數
  • C:指向矩陣c的指標
  • ldc: c的列數

我們知道這裡A就是輸入weight的矩陣,B就是我們前面im2col_cpu中得到的輸出矩陣,C用來儲存我們最後得到的矩陣(其實是一個數組,前面說的矩陣也是)。M 一個group的卷積核個數,K一個group的卷積核元素個數,N 一個輸出影象的元素個數,lda一個group的卷積核元素個數,ldb一個輸出影象的元素個數,ldc一個輸出影象的元素個數。

我們還是舉個例子說明

這裡我們假設卷積核還是3x3
權重矩陣A為
1 2 3
4 5 6  ==> 1 2 3 4 5 6 7 8 9(應該這樣寫)
7 8 9

B為
0 0 0 5
0 0 4 6
0 0 5 0
0 2 0 8
1 3 7 9
2 0 8 0
0 5 0 0
4 6 0 0
5 0 0 0

C初始化後為
1 1 1 1

M=1 K=9 N=4 lda=9 ldb=4 ldb=4
C[0]=ALPHA*A[0]*B[0]+ALPHA*A[1]*B[4]+...+ALPHA*A[8]*B[32]=95
C[1]=107
C[2]=107
C[3]=95
  
  • 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

換成人能看懂的

                           B
                       [0 0 0 5
                        0 0 4 6
                        0 0 5 0
         A              0 2 0 8       C               C
[1 2 3 4 5 6 7 8 9]  *  1 3 7 9 + [1 1 1 1]==> [95 107 107 95]
                        2 0 8 0
                        0 5 0 0
                        4 6 0 0
                        5 0 0 0]
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

所以這兩個函式的意圖很明顯了,就是將卷積變換成了矩陣運算。一些有意思的數學技巧^_^!!!

最後簡要的提一下gemm_nngemm_tngemm_ttgemm_nt他們之間的區別,他們的命名都是有意義的。這裡的n指的是not transposet指的是transpose。例如nn就表示AB都不轉置。

接著我們回到forward_convolutional_layer函式

//forward_convolutional_layer
    if(l.batch_normalize){
        forward_batchnorm_layer(l, net);   
  
  • 1
  • 2
  • 3

這裡有出現一個有用的函式forward_batchnorm_layer

0x0103 forward_batchnorm_layer

void forward_batchnorm_layer(layer l, network net)
{
    if(l.type == BATCHNORM) copy_cpu(l.outputs*l.batch, net.input, 1, l.output, 1);
    copy_cpu(l.outputs*l.batch, l.output, 1, l.x, 1);
  
  • 1
  • 2
  • 3
  • 4

上來就是一個函式copy_cpu

void copy_cpu(int N, float *X, int INCX, float *Y, int INCY)
{
    int i;
    for(i = 0; i < N; ++i) Y[i*INCY] = X[i*INCX];
}
  
  • 1
  • 2
  • 3
  • 4
  • 5

我們先看一下輸入的引數分別表示的是什麼意思。如果我們定義了BATCHNORM,那麼這裡的N表示一個batch中的輸出引數個數,x表示指向輸入引數的指標,y表示指向輸出引數的指標。那函式的目的很簡單,將net中的輸入,複製到layer中的輸出;如果沒有定義BATCHNORM,那麼將layer中的輸出複製到layer中的x。接著看後面(可以參考這篇論文Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift

//forward_batchnorm_layer
    if(net.train){
        mean_cpu(l.output, l.batch, l.out_c, l.out_h*l.out_w, l.mean);
        variance_cpu(l.output, l.mean, l.batch, l.out_c, l.out_h*l.out_w, l.variance);

        scal_cpu(l.out_c, .99, l.rolling_mean, 1);
        axpy_cpu(l.out_c, .01, l.mean, 1, l.rolling_mean, 1);
        scal_cpu(l.out_c, .99, l.rolling_variance, 1);
        axpy_cpu(l.out_c, .01, l.variance, 1, l.rolling_variance, 1);

        normalize_cpu(l.output, l.mean, l.variance, l.batch, l.out_c, l.out_h*l.out_w);   
        copy_cpu(l.outputs*l.batch, l.output, 1, l.x_norm, 1);
    } else {
        normalize_cpu(l.output, l.rolling_mean, l.rolling_variance, l.batch, l.out_c, l.out_h*l.out_w);
    }
    scale_bias(l.output, l.scales, l.batch, l.out_c, l.out_h*l.out_w);
    add_bias(l.output, l.biases, l.batch, l.out_c, l.out_h*l.out_w);
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

我準備把這幾個函式放在一塊解析,因為這幾個函式都不大。先看mean_cpu

void mean_cpu(float *x, int batch, int filters, int spatial, float *mean)
{
    float scale = 1./(batch * spatial);//求分母
    int i,j,k;
    for(i = 0; i < filters; ++i){
        mean[i] = 0;
        for(j = 0; j < batch; ++j){
            for(k = 0; k < spatial; ++k){
                int index = j*filters*spatial + i*spatial + k;
                mean[i] += x[index];
            }
        }
        mean[i] *= scale;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • x:指向layer的輸出
  • batch:一個batch的大小
  • filters:輸出的影象通道數,在這裡同樣可以理解為卷積核個數
  • spatial:輸出圖片的大小
  • mean:指向儲存結果的指標

還是舉個例子

x [95 107 107 95 1 2 3 4]
batch = 1
filters = 2
spatial = 2x2 = 4

scale = 1/(1x4) = 0.25
第一次迴圈
i=0 j=0
mean[0]=0
k=0
index=0
mean[0]=0+x[0]=95
...
mean[0]=101 mean[1]=2.5
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

那麼這個函式的意義就很明晰了。它要求出的是不同通道下所有輸入影象的均值。對應BN論文中的這個公式

  • 1mmi=1xi1m∑i=1mxi //mini-batch mean

接著看variance_cpu函式

void variance_cpu(float *x, float *mean, int batch, int filters, int spatial, float *variance)
{
    float scale = 1./(batch * spatial - 1);//注意這裡的減1操作
    int i,j,k;
    for(i = 0; i < filters; ++i){
        variance[i] = 0;
        for(j = 0; j < batch; ++j){
            for(k = 0; k < spatial; ++k){
                int index = j*filters*spatial + i*spatial + k;
                variance[i] += pow((x[index] - mean[i]), 2);
            }
        }
        variance[i] *= scale;
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • x:指向layer的輸出指標
  • mean:指向上面函式輸出的均值
  • batch:batch大小
  • filters:輸出的影象通道數,在這裡同樣可以理解為卷積核個數
  • spatial:輸出圖片的大小
  • variance:指向儲存結果的指標

舉個例子

x [95 107 107 95 1 2 3 4]
mean [101 25]
batch = 1
filters = 2
spatial = 2x2 = 4  
scale = 1/(1x4 - 1)=0.333
i=0
variance[0]=0
j=0 k=0
index=0
variance[0] = 0+(95-101)^2
...
variance[0]=48 variance[1]=1.66666675
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

那麼這個函式的意義就很明晰了。它要求出的是不同通道下所有輸入影象的樣本方差(對於n個數據,如果n-1個確定了,那麼剩下的那個就確定了(前提知道均值,均值*n - (n-1)數))。對應BN論文中的這個公式

  • 1mmi=1(xiμβ)1m∑i=1m(xi−μβ) //mini-batch variance

接著看scal_cpu函式

void scal_cpu(int N, float ALPHA, float *X, int INCX)
{
    int i;
    for(i = 0; i < N; ++i) X[i*INCX] *= ALPHA;
}
  
  • 1
  • 2
  • 3
  • 4
  • 5

這個函式很簡單,就是將輸入的資料乘以一個係數。

接著看axpy_cpu函式

void axpy_cpu(int N, float ALPHA, float *X, int INCX, float *Y, int INCY)
{
    int i;
    for(i = 0; i < N; ++i) Y[i*INCY] += ALPHA*X[i*INCX];
}
  
  • 1
  • 2
  • 3
  • 4
  • 5

這個函式也很簡單,就是Y =ALPHA*X + Y

接著看normalize_cpu這個函式

void normalize_cpu(float *x, float *mean, float *variance, int batch, int filters, int spatial)
{
    int b, f, i;
    for(b = 0; b < batch; ++b){
        for(f = 0; f < filters; ++f){
            for(i = 0; i < spatial; ++i){
                int index = b*filters*spatial + f*spatial + i;
                x[index] = (x[index] - mean[f])/(sqrt(variance[f]) + .000001f);
            }
        }
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • xlayer的輸出影象
  • mean:前面算的均值
  • variance:前面算的樣本方差
  • batch:batch大小
  • filters:輸出的影象通道數,在這裡同樣可以理解為卷積核個數
  • spatial:輸出圖片的大小

還是舉個例子

x [95 107 107 95 1 2 3 4]
mean [101 25]
variance [48 1.66666675]
batch=1
filters=2
spatial = 2x2 = 4
進入第一層迴圈
b=0 f=0 i=0
index = 0
x[0] = (x[0]-m[0])/(sqrt(variance[0]) + 0.000001f) = -1.44
...
x[0]=-0.866025329 x[0]=0.866025329  x[0]=0.866025329 x[0]=-0.866025329
x[0]=-1.16189408  x[0]=-0.387298018 x[0]=0.387298018 x[0]=1.16189408

  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這個函式的作用就是一個歸一化處理。對應BN論文中的這個公式

  • xiμβσ2β+ϵxi−μβσβ2+ϵ //normalize

接著看scale_biasadd_bias函式

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];
            }
        }
    }
}
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];
            }
        }
    }
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

這兩個函式的意義都很簡單。對應BN論文中的這個公式

  • γxi^+βγxi^+β

接著我們回到forward_convolutional_layer函式

//forward_convolutional_layer
    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);
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果沒有設定batch_normalize,直接新增偏向就完事了。接著是activate_array函式

void activate_array(float *x, const int n, const ACTIVATION a)
{
    int i;
    for(i = 0; i < n; ++i){
        x[i] = activate(x[i], a);
    }
}
float activate(float x, ACTIVATION a)
{
    switch(a){
        case LINEAR:
            return linear_activate(x);
        case LOGISTIC:
            return logistic_activate(x);
        case LOGGY:
            return loggy_activate(x);
        case RELU:
            return relu_activate(x);
        case ELU:
            return elu_activate(x);
        case RELIE:
            return relie_activate(x);
        case RAMP:
            return ramp_activate(x);
        case LEAKY:
            return leaky_activate(x);
        case TANH:
            return tanh_activate(x);
        case PLSE:
            return plse_activate(x);
        case STAIR:
            return stair_activate(x);
        case HARDTAN:
            return hardtan_activate(x);
        case LHTAN:
            return lhtan_activate(x);
    }
    return 0;
}
static inline float linear_activate(float x){return x;}
static inline float logistic_activate(float x){return 1./(1. + exp(-x));}
static inline float loggy_activate(float x){return 2./(1. + exp(-x)) - 1;}
static inline float relu_activate(float x){return x*(x>0);}
static inline float elu_activate(float x){return (x >= 0)*x + (x < 0)*(exp(x)-1);}
static inline float relie_activate(float x){return (x>0) ? x : .01*x;}
static inline float ramp_activate(float x){return x*(x>0)+.1*x;}
static inline float leaky_activate(float x){return (x>0) ? x : .1*x;}
static inline float tanh_activate(float x){return (exp(2*x)-1)/(exp(2*x)+1);}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20