1. batchnorm
1.1 原理
大緻的原理可以參考:https://blog.csdn.net/qq_25737169/article/details/79048516
如果了解個大概的話,就是:(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;}