天天看點

【darknet】閱讀了解(5)——batchnorm和activation

1. batchnorm

1.1 原理

大緻的原理可以參考:https://blog.csdn.net/qq_25737169/article/details/79048516

【darknet】閱讀了解(5)——batchnorm和activation

如果了解個大概的話,就是:(x-均值)/ 偏差 * 縮放系數 + 一個偏置

1.2 darknet實作

說明:

  • darknet cpu采用C實作的,能更有助于原理的了解
  • 或者也可以用numpy等進階架構實作

總之,darknet的實作和想象中的實作還是有點差別的。關鍵的一句是:對channel之外的所有次元進行平均,是以,一個batch_norm有n個均值,方差,縮放系數,偏移值。n = 卷積核的個數。

1.3 具體實作和注釋

1.batch_norm
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);           // 将l.output copy到l.x
    if(net.train){
        mean_cpu(l.output, l.batch, l.out_c, l.out_h*l.out_w, l.mean);                 // 均值     // 對channel之外的所有次元進行平均,這裡是模仿ANN的batchnorm對每個神經元歸一化,CNN是對把每個卷積核當成了神經元
        variance_cpu(l.output, l.mean, l.batch, l.out_c, l.out_h*l.out_w, l.variance); // 方差     // 可參考https://www.zhihu.com/question/269658514

        scal_cpu(l.out_c, .99, l.rolling_mean, 1);              // 初始化32個rolling_mean,儲存全局的平均值,用于推理時
        axpy_cpu(l.out_c, .01, l.mean, 1, l.rolling_mean, 1);   // 這裡l.mean和l.roling_mean二者的位置是不是反了
        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);      // scale,add,和normalize可以一起做,減少循環
    add_bias(l.output, l.biases, l.batch, l.out_c, l.out_h*l.out_w);        // 偏置
}

2. 求均值
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;
    }
}
           

2. activation

  • 如果用numpy實作的話是一個進階函數的問題

    np.maximum(x, 0)

  • darknet采用了一個for循環+内聯函數實作,内聯函數便于激活函數的同一格式,for循環(難道還有其他實作嗎?)
1. for 循環
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);           // 激活函數
    }
}

2. 内聯函數
static inline float leaky_activate(float x){return (x>0) ? x : .1*x;}
           

繼續閱讀