通俗话说一说各种Normalization以及用deeplearning4j实现Layer Normalization

原创
2019/09/20 13:09
阅读数 2K

一、Normalization是什么

    Normalization一句话概括来说就是用一种办法,将一组数据压到均值为0,方差为1的正态分布上去,具体做法是数据集的每一个元素减去均值再除以标准差。公式如下:(请忽略参数g,g的问题很诡异,后面说)

    这个公式说的更直白一点就是,把每一个a,经过平移和缩放,得到一个新值。而这样做的一个理由是,平移缩放并不会改变原始数据的分布情况,原来最大的还是最大,原来最小的还是最小。

    Deeplearning中有很多Normalization的方法,有BN、LN、IN、GN等等,每一种Normalization公式都一样,只是沿着的轴不一样,BN就是沿着minibatch方向,LN就是沿着影藏层的output vector维方向,举个例子,对于四维张量[minibatch,depth、height、width],那就是沿着depth方向,把height、width维约简掉。

二、说说Layer Normalization

    论文地址:https://arxiv.org/pdf/1607.06450.pdf

    Layer Normalization对于时间序列数据有奇效,下面截一段论文的原文。这是在RNN上用Layer Normalization

   

        简短的话说一下论文的变量含义,a表示t时刻点rnn的预输出值(还没有经过激活函数哦),h表示rnn某一个隐层t时刻点的输出。

        那么,这里Normalization是哪一个维度呢?假设RNN的输入张量为[minibatch、layerSize、timesteps],这么这里Normalization的就是layerSize这一维。这样可能还是太抽象,请看下图:

    

 

    这里Normalization的就是红色箭头指向的每一个维度的向量,对于一条数据的每个time step而言,就求出一个均值和方差,进行变换,下一个time step类推下去。那么多个time step就有多个均值和方差。

三、重点说说参数g和b

    这里g和b的维度要和h的维度相同,也就是上图的values per time step这一维度,也就是layer size的大小,这里g和b是跟随着网络参数学出来的。开始实现时,g的所有值一般会初始化为1,b的所有值会被初始化为0,随着训练的进行,g和b就会被修改为任意值了。那么Normalization也就没有了正态分布的效果,相当于layer size维乘以了一个随机向量,注意这里是向量点积,那么就等同于给一个随机的噪声,居然也能起作用,也是一个不能解释的问题。有点没有道理,但就是这么难以置信,居然是work的。

四、deeplearning4j的自动微分实现Layer Normalization

import java.util.Map;

import org.deeplearning4j.nn.conf.inputs.InputType;
import org.deeplearning4j.nn.conf.layers.samediff.SDLayerParams;
import org.deeplearning4j.nn.conf.layers.samediff.SameDiffLayer;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.linalg.api.ndarray.INDArray;

public class LayerNormaliztion extends SameDiffLayer {

	// gain * standardize(x) + bias

	private double eps = 1e-5;

	private static String GAIN = "gain";
	private static String BIAS = "bias";

	private int nOut;
	private int timeStep;

	public LayerNormaliztion(int nOut, int timeStep) {
		this.timeStep = timeStep;
		this.nOut = nOut;
	}

	protected LayerNormaliztion() {

	}

	@Override
	public InputType getOutputType(int layerIndex, InputType inputType) {
		return InputType.recurrent(nOut);
	}

	@Override
	public void defineParameters(SDLayerParams params) {
		params.addWeightParam(GAIN, 1, nOut, 1);
		params.addWeightParam(BIAS, 1, nOut, 1);
	}

	@Override
	public SDVariable defineLayer(SameDiff sd, SDVariable layerInput, Map<String, SDVariable> paramTable,
			SDVariable mask) {
		SDVariable gain = paramTable.get(GAIN);//论文中的g
		SDVariable bias = paramTable.get(BIAS);//论文中的b
		SDVariable mean = layerInput.mean("mean", true, 1);//均值
		SDVariable variance = sd.math().square(layerInput.sub(mean)).sum(true, 1).div(layerInput.getShape()[1]);//平方差
		SDVariable standardDeviation = sd.math().sqrt("standardDeviation", variance.add(eps));//标准差,加上eps 防止分母为0
		long[] maskShape = mask.getShape();
		return gain.mul(layerInput.sub(mean).div(standardDeviation)).add(bias)
				.mul(mask.reshape(maskShape[0], 1, timeStep));//掩码掩掉多余长度

	}

	@Override
	public void initializeParameters(Map<String, INDArray> params) {
		params.get(GAIN).assign(1);
		params.get(BIAS).assign(0);
	}

	public int getNOut() {
		return nOut;
	}

	public void setNOut(int nOut) {
		this.nOut = nOut;
	}

	public int getTimeStep() {
		return timeStep;
	}

	public void setTimeStep(int timeStep) {
		this.timeStep = timeStep;
	}

}

    五、实战的结果

    就用RNN做文本分类而言,加上LN,收敛会更平稳,但准确率大大下降了。

    在deeplearning的世界里,任何一种方法被提出来,只是在解当前的问题,对于每一种具体的问题,肯定是case by case。

 

快乐源于分享。

   此博客乃作者原创, 转载请注明出处

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部