特征提取场景与常见方法

2019年1月1日

新年第一篇, 先做个机器学习实践总结—几种常见场景下的特征提取方法:

机器学习中最重要的一环就是特征工程,特征工程的好坏直接影响模型的效果,如果做得不好大概率会出现“garbage in,garbage out ”的情况;

特征的提取主要指特征信息的数值化,向量化的过程,下面仅仅根据个人实践经验和理解简要总结常见应用场景下的特征提取方法:

 

1.特征提取的场景&常见方法:

1.2分类变量特征提取

分类变量通常用独热编码(One-of-K or One-Hot Encoding),通过二进制数来表示每个解释变量的特征

1.2.1 二元分类变量编码

比如某列的值只有 yes 和No 两种, 属于2元分类变量, 在Spark中可以通过定义UDF函数的方式转换成数值型,如

val udf_lable2Int = (arg: String) => { if (arg == "yes") 1 else 0 } 
val addLabelValue = udf(udf_lable2Int)
// 增加一列 
val JobValue = df1.withColumn("LabelValue", addLabelValue(df1("y"))) 
addJobValue.show(3, false)

1.2.2 有序分类变量编码

spark中同样可以通过自定义udf的方式做数值化转换

变量education是有序分类变量,影响大小排序为”illiterate”, “basic.4y”, “basic.6y”, “basic.9y”, “high.school”, “professional.course”, “university.degree”, 变量影响由小到大的顺序编码为1、2、3、…

val udf_edu2Int = (arg: String) => { 
if (arg == "illiterate") 1 
else if (arg == "basic.4y") 2
else if (arg == "basic.6y") 3
else if (arg == "basic.9y") 4
else if (arg == "high.school") 5
else if (arg == "professional.course") 6
else if (arg == "university.degree") 7
else 8
} 
val addEduValue = udf(udf_edu2Int)

val EduValue = dfx.withColumn("EduValue", addEduValue(dfx("education"))) 
EduValue.show(3, false)

1.2.3 无序分类变量编码

特征marital为无序分类变量, n个分类需要设置n-1个哑变量。例如,变量marital分为divorced、married、single

marital V1 V2
divorced 0 0
married 1 0
single 0 1

spark中还是可以使用UDF函数实现:

val udf_maritalIsSingel = (arg: String) => { 
if (arg == "single") 1 
else 0
}

val udf_maritalIsMarried = (arg: String) => { 
if (arg == "married") 1 
else 0
}
val isSingle = udf(udf_lable2Int)
val ismarried = udf(udf_lable2Int)

val martiralV1 = dfx.withColumn("martiralV1", ismarried(dfx("marital")))
val martiralV2 = dfx.withColumn("martiralV2", isSingle(dfx("marital")))
martiralV1.show(3, false)

sklearn 中提供DictVectorizer函数实现无序分类变量编码

from sklearn.feature_extraction import DictVectorizer
onehot_encoder = DictVectorizer()
instances = [{'city': 'New York'},{'city': 'San Francisco'}, {'city': 'Chapel Hill'}, {'city': 'BeiJing'}, {'city': 'London'}]
print(onehot_encoder.fit_transform(instances).toarray())

打印结果如下

[[0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0.]]

1.2.4 连续型特征离散化

Spark中提供bucketize 可以实现

    // Double.NegativeInfinity:负无穷;Double.PositiveInfinity:正无穷
    // 分为6个组:[负无穷,-100),[-100,-10),[-10,0),[0,10),[10,90),[90,正无穷)
    val splits = Array(Double.NegativeInfinity, -100, -10, 0.0, 10, 90, Double.PositiveInfinity)

    val data: Array[Double] = Array(-180, -160, -100, -50, -70, -20, -8, -5, -3, 0.0, 1, 3, 7, 10, 30, 60, 90, 100, 120, 150)

    val dataFrame = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

    val bucketizer = new Bucketizer()
      .setInputCol("features")
      .setOutputCol("bucketedFeatures")
      .setSplits(splits)

    // 将原始数据转换为桶索引
    val bucketedData = bucketizer.transform(dataFrame)
    bucketedData.show(50, truncate = false)

分桶打印结果:

+——–+—————-+
|features|bucketedFeatures|
+——–+—————-+
|-180.0 |0.0 |
|-160.0 |0.0 |
|-100.0 |1.0 |
|-50.0 |1.0 |
|-70.0 |1.0 |
|-20.0 |1.0 |
|-8.0 |2.0 |
|-5.0 |2.0 |
|-3.0 |2.0 |
|0.0 |3.0 |
|1.0 |3.0 |
|3.0 |3.0 |
|7.0 |3.0 |
|10.0 |4.0 |
|30.0 |4.0 |
|60.0 |4.0 |
|90.0 |5.0 |
|100.0 |5.0 |
|120.0 |5.0 |
|150.0 |5.0 |
+——–+—————-+

 

1.2文本特征提取

主要用于NLP自然语言处理场景,把一串文字转换成可以量化的特征向量,核心特征一般是词,词频,词的顺序;

1.2.1 词库表示法

对于一个文档,忽略其词序和语法,句法,将其仅仅看做是一个词集合,文档中每个词的出现都是独立的,不依赖于其他词是否出现,可以看成是独热编码的一种扩展,它为每个单词设值一个特征值;

在词库模型中,特征向量的每一个元素是用二进制数表示单词是否在文档中。例如,第一个文档的第一个词是UNC,词汇表的第一个单词是UNC,因此特征向量的第一个元素就是1

文档中单词的频率对文档的意思有重要作用。一个文档中某个词多次出现,相比只出现过一次的单词更能体现反映文档的意思。很多时候将单词频率加入特征向量

sklean中提供CountVectorizer类通过正则表达式用空格分割句子,然后抽取长度大于等于2的字母序列,比如:

from sklearn.feature_extraction.text import CountVectorizer
corpus = [
    'UNC played Duke in basketball',
    'Duke lost the basketball game'
]
vectorizer = CountVectorizer()
print(vectorizer.fit_transform(corpus).todense())#-----句子中词集合向量
print(vectorizer.vocabulary_)#-----词频统计

打印结果:

[[1 1 0 1 0 1 0 1]
 [1 1 1 0 1 0 1 0]]
{'played': 5, 'duke': 1, 'unc': 7, 'basketball': 0, 'in': 3, 'game': 2, 'lost': 4, 'the': 6}

(用得不太多,暂不扩展)

1.2.2 RNN

适合对象的时序为重要特征的场景,比如NLP等应用场景, 同样需要通过深度学习方法实现

(用得不太多,暂不扩展)

 

1.3图片特征提取

对图像来说,一般通过像素值提取特征,一张图片可以看成是一个每个元素都是颜色值的矩阵;表示图像基本特征就是将矩阵每行连起来变成一个行向量;

由于图像的维度非常多,一般使用深度学习CNN,通过神经网络中的多个卷积层(利用卷积核)提取特征,其中全连接层输出的其实就是一个特征向量( 网络最后一层可以加一层分类层用于给图片做分类预测)

tensorflow 部分代码样例:

    # 第一层卷积
    W_conv1 = weight_variable([3, 3, 1, 32], 'W_conv1')
    B_conv1 = bias_variable([32], 'B_conv1')
    conv1 = tf.nn.relu(conv2d(x_input, W_conv1, 'conv1') + B_conv1)//使用relu作为特征函数
    conv1 = max_pool_2X2(conv1, 'conv1-pool')
    conv1 = tf.nn.dropout(conv1, keep_prob)//防止过拟合,增加dropout层,过滤部分特征

    # 第二层卷积
    W_conv2 = weight_variable([3, 3, 32, 64], 'W_conv2')
    B_conv2 = bias_variable([64], 'B_conv2')
    conv2 = tf.nn.relu(conv2d(conv1, W_conv2, 'conv2') + B_conv2)
    conv2 = max_pool_2X2(conv2, 'conv2-pool')
    conv2 = tf.nn.dropout(conv2, keep_prob)

    # 第三层卷积
    W_conv3 = weight_variable([3, 3, 64, 64], 'W_conv3')
    B_conv3 = bias_variable([64], 'B_conv3')
    conv3 = tf.nn.relu(conv2d(conv2, W_conv3, 'conv3') + B_conv3)
    conv3 = max_pool_2X2(conv3, 'conv3-pool')
    conv3 = tf.nn.dropout(conv3, keep_prob)

    # 全链接层
    # 每次池化后,图片的宽度和高度均缩小为原来的一半,经过上面的三次池化,宽度和高度均缩小8倍
    # 如图片大小改变,W_fc前两位应该改为weight/8,hight/8向上取整
    W_fc1 = weight_variable([13 * 6   * 64, 1024], 'W_fc1')
    B_fc1 = bias_variable([1024], 'B_fc1')
    fc1 = tf.reshape(conv3, [-1, W_fc1.get_shape().as_list()[0]])
    fc1 = tf.nn.relu(tf.add(tf.matmul(fc1, W_fc1), B_fc1))
    fc1 = tf.nn.dropout(fc1, keep_prob)
    # 输出层
    W_fc2 = weight_variable([1024, CAPTCHA_LEN * CHAR_SET_LEN], 'W_fc2')
    B_fc2 = bias_variable([CAPTCHA_LEN * CHAR_SET_LEN], 'B_fc2')
    output = tf.add(tf.matmul(fc1, W_fc2), B_fc2, 'output')

上述CNN运行后输出层输出的就是提取的图片特征向量了

2.特征向量标准化处理

评估方法在处理标准化数据集时可以获得更好的效果。标准化数据均值为0,单位方差(Unit Variance)。均值为0的解释变量是关于原点对称的,特征向量的单位方差表示其特征值全身统一单位,统一量级的数据。

例如,假设特征向量由两个解释变量构成,第一个变量值范围[0,1],第二个变量值范围[0,1000000],这时就要把第二个变量的值调整为[0,1],这样才能保证数据是单位方差。如果变量特征值的量级比其他特征值的方差还大,这个特征值就会主导学习算法的方向,导致其他变量的影响被忽略。

sklearn框架种提供preprocessing 方法实现相关功能:

from sklearn import preprocessing
import numpy as np
X = np.array([
    [0., 0., 5., 13., 9., 1.],
    [0., 0., 13., 15., 10., 15.],
    [0., 3., 15., 2., 0., 11.]
])
print(preprocessing.scale(X))

在spark.ml中标准化的实现是StandardScaler

StandardScaler处理的对象是每一列,也就是每一维特征,将特征标准化为单位标准差或是0均值,或是0均值单位标准差。
主要有两个参数可以设置:

withStd: 默认为真。将数据标准化到单位标准差。
withMean: 默认为假。是否变换为0均值。

    //数据标准化
    val scaler = new StandardScaler()
      .setInputCol("features")
      .setOutputCol("scaledFeatures")
      .setWithStd(true)
      .setWithMean(false)
      
    val scalerModel = scaler.fit(output)
    val scaledData = scalerModel.transform(output)

图像识别时是否需要做标准化处理?如何处理了?

在使用CNN训练之前,可以先把图像矩阵中的像素特征除以像素的最大值255 :

    image_array = np.array(img)
    #矩阵转化为一维数组,并对数据归一化处理
    image_data = image_array.flatten()/255 

这样就把图片特征矩阵中的值都缩放到[0,1]的范围内了

图像特征标准化样例2:

image = tf.image.decode_jpeg(image_c,channels = 3)
 #resize
 image = tf.image.resize_image_with_crop_or_pad(image,resize_w,resize_h)
 #(x - mean) / adjusted_stddev
 image = tf.image.per_image_standardization(image)
  
 image_batch,label_batch = tf.train.batch([image,label],
            batch_size = batch_size,
            num_threads = 64,
            capacity = capacity)

使用tensorflow 提供的image.per_image_standardization函数做图片标准化处理

没有评论

发表评论

邮箱地址不会被公开。 必填项已用*标注