Skip to the content.

abacus与paddle的参数初始化方式

这里主要讲的是FC(Fully Connected,全连接)层的weight和bias的初始化方式。

1 abacus

1.1 weight初始化

以标准正态分布随机初始化为基础,并乘以相应的scale。具体代码如下:

// in file: dnn/param_layer.cpp

double init_range = conf["init_range"].as<double>() * global_init_range;
if (conf["scale_by_rown"].as<bool>()) {
    init_range /= sqrt(double(conf["rown"].as<int>()));
}
int len = conf["rown"].as<int>() * conf["coln"].as<int>();
std::vector<float> w;
w.clear();
std::normal_distribution<float> ndistr(0.0, 1.0);
for (int i = 0; i < len; ++i) {
    w.push_back(ndistr(abacus::local_random_engine()) * init_range);
}

一般配置有:

init_range: 1
global_init_range: 0.2
scale_by_rown: true

总结公式有:

\[w[i] = \mathcal{N}(0.0, 1.0) \times init\_range \times global\_init\_range \times \sqrt{row\_number}\]

其中,$row_number$为参数的行数。

1.2 bias初始化

直接初始化为1。具体代码如下:

// in learner/dnn_plugin.cpp

Eigen::MatrixXf* bias_mat = get_value_mat(dnn_ins, "bias_input");
if (bias_mat) bias_mat->setOnes(thr->ins_num, 1);

2 paddle

可以在组网中自定义,参考文档fc的使用和create_parameter的使用。上述文档为paddle1.8版本

这里节选部分模型的参数初始化代码,如下:

    # 一跳精排,其中init_range = 0.2
    def common_tower(self, bn_video, bn_news, gate_merge, prefix):
        """common tower"""
        input_orig = paddle.concat([bn_video, bn_news], axis=1)
        input_tmp = paddle.multiply(input_orig, gate_merge) 
        fc_layers_input = [input_tmp]
        fc_layers_size = [128, 1]
        fc_layers_act = ["relu"] * (len(fc_layers_size) - 1)  + [None]
        scales_tmp = [bn_video.shape[1] + bn_news.shape[1]] + fc_layers_size
        scales = []
        for i in range(len(scales_tmp)):
            scales.append(self._config.init_range / (scales_tmp[i] ** 0.5))
        for i in range(len(fc_layers_size)):
            name = prefix + "_fc_" + str(i)
            logits = paddle.static.nn.fc(
                    x = fc_layers_input[-1],
                    size = fc_layers_size[i],
                    weight_attr = paddle.ParamAttr(learning_rate=1.0, \
                        initializer=paddle.nn.initializer.Normal(mean=0.0, std=1.0 * scales[i])),
                    bias_attr = paddle.ParamAttr(learning_rate=1.0, \
                        initializer=paddle.nn.initializer.Normal(mean=0.0, std=1.0 * scales[i])),
                    name=name)
            if fc_layers_act[i]:
                name = prefix + "_ln_" + str(i)
                bn = paddle.static.nn.layer_norm(input=logits, begin_norm_axis=1, name=name)
                output = F.relu(bn, name="ctx_dnn_embedding")
            else:
                output = logits
            fc_layers_input.append(output)
    
    # 统一粗排,其中_init_range = 0.2
    def deep_net_origin(self, input, fc_layers_size, prefix, last_None=False, lr_x=1.0):
        """ 重复函数,待合并 """
        fc_layers_input = [input]
        if last_None:
            fc_layers_act = ["relu"] * (len(fc_layers_size) - 1) + [None]
        else:
            fc_layers_act = ["relu"] * (len(fc_layers_size))
        scales_tmp = [input.shape[1]] + fc_layers_size
        scales = []
        for i in range(len(scales_tmp)):
            scales.append(self._init_range / (scales_tmp[i] ** 0.5))
        for i in range(len(fc_layers_size)):
            name = prefix + "_" + str(i)
            fc = fluid.layers.fc(input = fc_layers_input[-1], size = fc_layers_size[i], act = fc_layers_act[i],
                                param_attr = fluid.ParamAttr(learning_rate=lr_x, \
                                    initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=1.0 * scales[i])),
                                bias_attr = fluid.ParamAttr(learning_rate=lr_x, \
                                    initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=1.0 * scales[i])),
                                name=name)
            fc_layers_input.append(fc)

        return fc_layers_input[-1]

可以看到,在具体实践上,初始化方式基本上都是正态分布,均值为0,标准差做了一些scale。且weight和bias的参数初始化方式相同。可以总结基本公式:

\[w[i] = \mathcal{N}(0.0, 1.0 \times \frac{init\_range}{shape^{0.5}})\]

其中,$shape$为输入的shape、fc的大小组成的数组。具体参见代码

3 异同点

相同点

都是以均值为1的正态分布作为基础。

不同点

abacus是标准正态分布随机值之后进行scale,paddle是对正态分布的标准差进行scale。

具体为啥这么搞?暂时还没弄明白。