深度学习的数据预处理——全局对比度归一化(GCN)

 在深度学习领域中,样本数据输入网络之前一般都做一个normalization预处理,把数据钳制到一定范围,确保不同样本的数据都属于同一量级,这样可加快训练速度,并提升训练模型的泛化能力。

全局对比度归一化(Global contrast normalization, 简称GCN)正是这样一种常用的数据预处理方法。

01

GCN的统计学基础知识

假设有n个数据:

将以上每个数据都减去它们的均值然后除以它们的标准差,所得到的值,统计学上称为标准分数

均值:

标准差:

标准分数:

标准分数组成的一系列数据Y=[y0, y1, y2, …,yn-1]具有均值为0、标准差为1的特点,且保留了原始数据中各数据之间的相对大小和分布。

比如对以下数据标准化,12.3对应的标准分数为-0.322,说明12.3比均值小标准差的0.322倍,18.000对应的标准分数为0.059,说明18.000比均值大标准差的0.059倍。

对一系列数据计算标准分数,称为标准化。通常分析或处理不同量纲、不同取值范围的不同系列数据时,需要对它们分别做标准化。比如一个系列数据的取值范围是0~1,另一个系列数据的取值范围是0~255,如果要把这两个系列数据放在一起对比分析,则通常先分别对它们进行标准化。

GCN正是基于统计学中标准化的思想来计算的。

02

GCN计算原理

在深度学习中,神经网络通常要输入不同的图像数据,不同图像数据的取值范围可能有一定的差别,这时候可以通过GCN来缩小它们的差别。假设有n通道r*c的图像数据,按照以上讲的统计学知识,可以按照下式对图像数据进行标准化:

然而在实际情况中,很有可能遇到图像数据的标准差为0或者很小的情况(图像数据变化很小),导致标准化后的数据会变得很大,这与我们“把数据钳制到一定范围内”的初衷背道而驰了。为解决这个问题,人们在上式中增加了λ和ε两个参数:

在上式基础上,增加一个缩放因子s来控制标准化之后数据的取值范围,可以通过自己得的需求设定s的值(通常s=1):

λ和ε的取值要看情况而定:

1. 如果图像的纹理、信息比较丰富,也即它的标准差比较大,可以将λ设置为一个很小的值、ε设置为一个非0的较小值,比如λ=0,ε=10-8

2. 如果图像的纹理、信息很少,也即它的标准差很小甚至为0,可以将λ设置为大一点的值、ε设置为0,比如λ=10,ε=0。

03

GCN的代码实现

我们使用C++/Opencv来实现3通道RGB图像的GCN。

首先我们讲一下标准差的计算,对于3通道r*c图像的均值和标准差,通常按照下式计算:

如果按照上式实现代码,需要循环遍历图像两次,一次遍历计算均值,一次遍历计算标准差,而且需要多次开根号,这是很耗时的。为了减少遍历图像和开根号的次数,我们对上式做一定的变换。下面我们举例说明这个变换:

假设有x0~x8这9个数据,它们的方差可按下式计算:

上式中<I>为x0,x1,……,x8 的均值,即:

从而有:

上式中:

由以上推导可以知道,对于一组数,由平方的均值减去均值的平方可以得到方差和标准差

根据上式,3通道RGB图像的均值和标准差可按照下式计算:

因此我们可以在一次图像遍历中同时计算x和x2的累加和,结束遍历之后再求均值,代码实现如下:

//输入3通道float型的RGB图像
void cal_gcn(Mat &src)
{
  float m = 0;
  float m_2 = 0;
  
  for (int i = 0; i < src.rows; i++)
  {
    Vec3f *p = src.ptr<Vec3f>(i);
    for (int j = 0; j < src.cols; j++)
    {
      //计算x的累加和
      m += (p[j][0] + p[j][1] + p[j][2]);  
      //计算x^2的累加和
      m_2 += (p[j][0] * p[j][0] + p[j][1] * p[j][1] + p[j][2] * p[j][2]);
    }
  }


  float total_cnt = src.rows * src.cols * 3.0;
  m /= total_cnt;   //求得均值
  m_2 /= total_cnt;  //求得平方的均值


  float std = sqrt(m_2 - m*m);   //方差=平方的均值-均值的平方标准差,再开根号得到标准差


  for (int i = 0; i < src.rows; i++)
  {
    Vec3f *p = src.ptr<Vec3f>(i);
    for (int j = 0; j < src.cols; j++)
    {
      //标准化,取s=1,λ=0,ε=1e-8
      p[j][0] = (p[j][0] - m) / max(1e-8, std);  
      p[j][1] = (p[j][1] - m) / max(1e-8, std);
      p[j][2] = (p[j][2] - m) / max(1e-8, std);
    }
  }
}

欢迎扫码关注本微信公众号,接下来会不定时更新更加精彩的内容,敬请期待~

posted @ 2021-06-11 17:28  萌萌哒程序猴  阅读(399)  评论(0编辑  收藏  举报