特征缩放

  特征缩放是用来统一资料中的自变项或特征范围的方法,在资料处理中,通常会被使用在资料前处理这个步骤。

1 动机

  因为在原始的资料中,各变数的范围大不相同。对于某些机器学习的算法,若没有做过标准化,目标函数会无法适当的运作。举例来说,多数的分类器利用两点间的距离计算两点的差异,
若其中一个特征具有非常广的范围,那两点间的差异就会被该特征左右,因此,所有的特征都该被标准化,这样才能大略的使各特征依比例影响距离。另外一个做特征缩放的理由是他能使加速梯度下降法的收敛。

2 方法

2.1 重新缩放

  最简单的方式是重新缩放特征的范围到[0, 1][-1, 1], 依据原始的资料选择目标范围,通式如下:

3.1

2.2 标准化

  在机器学习中,我们可能要处理不同种类的资料,例如,音讯和图片上的像素值,这些资料可能是高维度的,资料标准化后会使每个特征中的数值平均变为0(将每个特征的值都减掉原始资料中该特征的平均)、标准差变为1,这个方法被广泛的使用在许多机器学习算法中。

3 实例

  1. import org.apache.spark.SparkContext._
  2. import org.apache.spark.mllib.feature.StandardScaler
  3. import org.apache.spark.mllib.linalg.Vectors
  4. import org.apache.spark.mllib.util.MLUtils
  5. val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt")
  6. val scaler1 = new StandardScaler().fit(data.map(x => x.features))
  7. val scaler2 = new StandardScaler(withMean = true, withStd = true).fit(data.map(x => x.features))
  8. // scaler3 is an identical model to scaler2, and will produce identical transformations
  9. val scaler3 = new StandardScalerModel(scaler2.std, scaler2.mean)
  10. // data1 will be unit variance.
  11. val data1 = data.map(x => (x.label, scaler1.transform(x.features)))
  12. // Without converting the features into dense vectors, transformation with zero mean will raise
  13. // exception on sparse vector.
  14. // data2 will be unit variance and zero mean.
  15. val data2 = data.map(x => (x.label, scaler2.transform(Vectors.dense(x.features.toArray))))

4 源代码实现

  在MLlib中,StandardScaler类用于标准化特征。

  1. class StandardScaler @Since("1.1.0") (withMean: Boolean, withStd: Boolean)

  StandardScaler的实现中提供了两个参数withMeanwithStd。在介绍这两个参数之前,我们先了解fit方法的实现。

  1. def fit(data: RDD[Vector]): StandardScalerModel = {
  2. // TODO: skip computation if both withMean and withStd are false
  3. val summary = data.treeAggregate(new MultivariateOnlineSummarizer)(
  4. (aggregator, data) => aggregator.add(data),
  5. (aggregator1, aggregator2) => aggregator1.merge(aggregator2))
  6. new StandardScalerModel(
  7. Vectors.dense(summary.variance.toArray.map(v => math.sqrt(v))),
  8. summary.mean,
  9. withStd,
  10. withMean)
  11. }

  该方法计算数据集的均值和方差(查看概括统计以了解更多信息),并初始化StandardScalerModel。初始化StandardScalerModel之后,我们就可以调用transform方法转换特征了。

  当withMean参数为true时,transform的实现如下。

  1. private lazy val shift: Array[Double] = mean.toArray
  2. val localShift = shift
  3. vector match {
  4. case DenseVector(vs) =>
  5. val values = vs.clone()
  6. val size = values.size
  7. if (withStd) {
  8. var i = 0
  9. while (i < size) {
  10. values(i) = if (std(i) != 0.0) (values(i) - localShift(i)) * (1.0 / std(i)) else 0.0
  11. i += 1
  12. }
  13. } else {
  14. var i = 0
  15. while (i < size) {
  16. values(i) -= localShift(i)
  17. i += 1
  18. }
  19. }
  20. Vectors.dense(values)
  21. case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
  22. }

  以上代码显示,当withMeantruewithStdfalse时,向量中的各元素均减去它相应的均值。当withMeanwithStd均为true时,各元素在减去相应的均值之后,还要除以它们相应的方差。
withMeantrue,程序只能处理稠密的向量,不能处理稀疏向量。

  当withMeanfalse时,transform的实现如下。

  1. vector match {
  2. case DenseVector(vs) =>
  3. val values = vs.clone()
  4. val size = values.size
  5. var i = 0
  6. while(i < size) {
  7. values(i) *= (if (std(i) != 0.0) 1.0 / std(i) else 0.0)
  8. i += 1
  9. }
  10. Vectors.dense(values)
  11. case SparseVector(size, indices, vs) =>
  12. // For sparse vector, the `index` array inside sparse vector object will not be changed,
  13. // so we can re-use it to save memory.
  14. val values = vs.clone()
  15. val nnz = values.size
  16. var i = 0
  17. while (i < nnz) {
  18. values(i) *= (if (std(indices(i)) != 0.0) 1.0 / std(indices(i)) else 0.0)
  19. i += 1
  20. }
  21. Vectors.sparse(size, indices, values)
  22. case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
  23. }

  这里的处理很简单,就是将数据集的列的标准差归一化为1。

参考文献

【1】特征缩放