在机器学习中,线性回归和逻辑回归算是最基础入门的算法,很多书籍都把他们作为第一个入门算法进行介绍。除了本身的公式之外,逻辑回归和线性回归还有一些必须要了解的内容。一个很常用的知识点就是虚拟变量(也叫做哑变量)—— 用于表示一些无法直接应用到线性公式中的变量(特征)。

举个例子:

通过身高来预测体重,可以简单的通过一个线性公式来表示,y=ax+b。其中x为身高,y为体重。

现在想要多加一些特征(参数),比如性别。

那么问题来了:如何在一个公式中表示性别呢?

这就是哑变量的作用,它可以通过扩展特征值的个数来表示一些无法被直接数值化的参数。

实例演示

下面是一组数据,第一列表示体重,第二列表示身高,第三列表示性别

体重

身高

性别

60

170

F

45

163

M

80

183

F

70

175

F

52

167

M

现在首先需要把第三列转换成数值类型

体重

身高

性别

60

170

1.0

45

163

0.0

80

183

1.0

70

175

1.0

52

167

0.0

然后使用多维的数据表示这个参数

体重

身高

性别男

性别女

60

170

1.0

0.0

45

163

0.0

1.0

80

183

1.0

0.0

70

175

1.0

0.0

52

167

0.0

1.0

即,性别这一列会通过两列来标识。

一般来说,有多少种情况出现,就会出现多少列。当然会有很多不同的表现形式,比如有的是通过N-1列表示(为空时表示一种情况),有的是通过n列表示。

代码实践

在Spark MLlib中已经提供了处理哑变量的方法,叫做OneHotEncoder,翻译过来叫做 一位有效编码,即把可能出现多个值的某列转变成多列,同时只有一列有效。MLlib提供了两个方法一个是StringIndex方法,这个方法可以把不同的字符串转换成数值,比如F``M分别用0.0``1.0表示。还有一个是OneHotEncoder方法,这个方法可以把不同的数值转变成稀疏向量。

什么是稀疏向量

在MLlib中,向量有两种表示方法,一种是密集向量,一种是稀疏向量。

  • 密集向量很好理解,[1,2,3,4],代表这个向量有四个元素,分别是1 2 3 4
  • 稀疏向量则可以根据下表表示,(3,[4,5,6],[1,2,3]),第一个值代表大小,第二个代表下标数组,第二个是下标对应的值。

然后话说回来,OneHotEncoder方法可以把不同的数值变成稀疏向量,这样一列就相当于可以用多列来表示。

下面我们具体的看一下代码吧!

object encoderTest {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("MovieLensALS-Test").setMaster("local[2]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val sqlContext = new SQLContext(sc)
    val df = sqlContext.createDataFrame(Seq(
      (60, 170,"F","长春"),
      (45, 163,"M","长春"),
      (80, 183,"F","沈阳"),
      (70, 175,"F","大连"),
      (52, 167,"M","哈尔滨")
    )).toDF("weight", "height","sex","address")

    //把性别这一列数值化
    val indexer = new StringIndexer()
      .setInputCol("sex")
      .setOutputCol("sexIndex")
      .fit(df)
    val indexed = indexer.transform(df)
    //对性别这列进行 有效位编码
    val encoder = new OneHotEncoder()
      .setInputCol("sexIndex")
      .setOutputCol("sexVec")
    val encoded = encoder.transform(indexed)
    //对地址这一列数值化
    val indexer1 = new StringIndexer()
      .setInputCol("address")
      .setOutputCol("addressIndex")
      .fit(encoded)
    val indexed1 = indexer1.transform(encoded)
    //对地址进行有效位编码
    val encoder1 = new OneHotEncoder()
      .setInputCol("addressIndex")
      .setOutputCol("addressVec")
    val encoded1 = encoder1.transform(indexed1)

    encoded1.show()
  }
}

输出的内容为:

+------+------+---+-------+--------+-------------+------------+-------------+
|weight|height|sex|address|sexIndex|       sexVec|addressIndex|   addressVec|
+------+------+---+-------+--------+-------------+------------+-------------+
|    60|   170|  F|     长春|     0.0|(1,[0],[1.0])|         0.0|(3,[0],[1.0])|
|    45|   163|  M|     长春|     1.0|    (1,[],[])|         0.0|(3,[0],[1.0])|
|    80|   183|  F|     沈阳|     0.0|(1,[0],[1.0])|         3.0|    (3,[],[])|
|    70|   175|  F|     大连|     0.0|(1,[0],[1.0])|         2.0|(3,[2],[1.0])|
|    52|   167|  M|    哈尔滨|     1.0|    (1,[],[])|         1.0|(3,[1],[1.0])|
+------+------+---+-------+--------+-------------+------------+-------------+

这样有什么用呢?
得到了weight``height``sexVec``addressVec,就相当于得到了一组数据,基于这组数据,就可以来训练线性回归,得到模型后,就可以根据一个人的身高、性别、地址来预测这个人的身高了。

参考

1 MLlib OneHotEncoder官方文档 2 虚拟变量定义