《Spark机器学习进阶实战》——2.2.4 特征处理
2.2.4 特征处理
数据预处理对数据进行了初步的抽取和清洗,更进一步,可以从数据中提取有用的特征用于机器学习建模,接下来介绍数据特征处理的方法。
在数据分析中,我们把数据对象称作样本,数据对象拥有一些基本特性,叫作特征或维度。例如,对于一个学生信息的样本数据,每一个样本对应一个学生,而数据集中学生的ID、年级、成绩等则是学生的特征。
1. 特征向量化
除了基本的统计分析之外,机器学习模型要求输入特征向量,原始特征需要转化为特征向量,才能用于机器学习模型的训练,下面介绍各类特征向量化的方法。
常用特征包括数值特征、类别特征、文本特征、统计特征等。
1)数值特征:数值类型的特征,如年龄、温度等,一般可以直接作为特征向量的维度使用,它可以取无穷多的值。
2)类别特征:具有相同特性的特征,如一幅图片的颜色(黑色、棕色、蓝色等)就属于类别特征,类别特征有可穷举的值。类别特征不能直接使用,一般对类别特征进行编号,将其转化为数值特征。
3)文本特征:从文本内容中提取出来的特征,如电影评论,文本特征也不能直接使用,需要进行分词、编码等处理,接下来会具体介绍文本特征的处理方法。
4)统计特征:从原始数据中使用统计方法得到的高级特征,常用的统计特征包括平均值、中位数、求和、最大值、最小值等。统计特征比原始特征包含更多的信息,通常使用统计特征可以得到更好的模型训练效果,统计特征和数值特征一样可以直接作为特征向量的维度使用。
5)其他特征:还有一些特征不属于上述特征的范畴,如音频、视频特征,地理位置特征等。这些特征需要使用特殊的处理方法,如图像需要转化为SIFT特征,音频需要转化为MFCC特征等。
实践中,类别特征的可取值比数值特征要少得多,在进行统计分析时更容易处理,所以我们有时需要通过分段,把数值特征转化为类别特征以便于分析建模,例如我们可以把连续的身高特征分成150cm以下、150cm~180cm、180cm以上三个类别。
2. 文本特征处理
文本特征是一类常见的特征,相比类别特征和数值特征,它的处理要复杂得多,一般对文本特征的处理,需要经过分词、去停用词、词稀疏编码等步骤,MLlib为这些处理步骤提供了相应的方法。
(1)分词
MLlib提供Tokenization方法对文本进行分词,RegexTokenizer基于正则表达式匹配提供了更高级的分词。默认用多个空格(\s+)作为分隔符,可以通过参数pattern指定分隔符,分词的样例代码如下:
import org.apache.spark.ml.feature.{RegexTokenizer, Tokenizer}
val sentenceDataFrame = spark.createDataFrame(Seq(
(0, "Hi I heard about Spark"),
(1, "I wish Java could use case classes"),
(2, "Logistic,regression,models,are,neat")
)).toDF("label", "sentence")
val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val regexTokenizer = new RegexTokenizer()
.setInputCol("sentence")
.setOutputCol("words")
.setPattern("\\W") // alternatively .setPattern("\\w+")
.setGaps(false)
val tokenized = tokenizer.transform(sentenceDataFrame)
tokenized.select("words", "label").take(3).foreach(println)
val regexTokenized = regexTokenizer.transform(sentenceDataFrame)
regexTokenized.select("words", "label").take(3).foreach(println)
(2)去停用词
停用词是那些需要从输入数据中排除掉的词,这些词出现频繁,却并没有携带太多有意义的信息。MLlib提供StopWordsRemover方法实现这一功能。停用词表通过stopWords参数来指定。可以通过调用loadDefaultStopWords(language:string)调用默认的停用词表,默认词表提供Jenglish、french、germon、danish等几种语言的停用词,但对于中文停用词需要自己提供。代码示例如下:
import org.apache.spark.ml.feature.StopWordsRemover
val remover = new StopWordsRemover()
.setInputCol("raw")
.setOutputCol("filtered")
val dataSet = spark.createDataFrame(Seq(
(0, Seq("I", "saw", "the", "red", "baloon")),
(1, Seq("Mary", "had", "a", "little", "lamb"))
)).toDF("id", "raw")
remover.transform(dataSet).show()
(3)词稀疏编码
分词和去停用词之后把一篇文章变成了一个词的集合,现在需要把这个集合用数值来表示,我们使用MLlib提供的StringIndexer方法来实现这一需求。StringIndexer给每个词按照出现频率安排一个编号索引,索引的范围是[0,vocab_size),vocab_size为词表的大小,示例代码如下:
import org.apache.spark.ml.feature.StringIndexer
val df = spark.createDataFrame(
Seq((0, "a"), (1, "b"), (2, "c"), (3, "a"), (4, "a"), (5, "c"))
).toDF("id", "category")
val indexer = new StringIndexer()
.setInputCol("category")
.setOutputCol("categoryIndex")
val indexed = indexer.fit(df).transform(df)
indexed.show()
此外,MLlib还为文本处理提供了Ngram、TF/IDF、word2vec等高级方法,可以在实践中查看相关资料。
3. 特征预处理
在前面的章节中我们介绍了对各类特征进行处理的方法,在使用生成的特征进行训练之前,对特征进行预处理有助于优化模型训练效果,提升模型训练速度。MLlib提供了丰富的特征预处理方法,下面介绍3种最常用的特征预处理方法。
(1)特征归一化
特征归一化是用来统一特征范围的方法,它对特征进行标准化,将特征值的大小映射到一个固定的范围内,从而避免特征量级差距过大影响模型训练的情形,此外特征归一化还能加速训练的收敛。
MLlib提供3种归一化方法:StandardScaler、MinMaxScaler和MaxAbsScaler。Standard-Scaler对所有数据减去均值除以标准差,处理后的数据均值变为0,标准差变为1;MinMax-Scaler将每个特征调整到一个特定的范围(通常是[0,1]),在转化过程中可能把0转化为非0的值,因此可能会破坏数据的稀疏性;MaxAbsScaler转换将每个特征调整到[-1,1]的范围,它通过每个特征内的最大绝对值来划分,不会破坏数据的稀疏性。
其中,StandardScaler是使用最广泛的归一化方法,使用StandardScaler方法进行特征归一化的示例代码如下。
import org.apache.spark.SparkContext._
import org.apache.spark.mllib.feature.StandardScaler
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.util.MLUtils
// Spark程序data文件夹下的测试数据
val data = MLUtils.loadLibSVMFile(sc, "data/MLlib/sample_libsvm_data.txt")
val scaler1 = new StandardScaler().fit(data.map(x => x.features))
val scaler2 = new StandardScaler(withMean = true, withStd = true).fit(data.map(x => x.features))
// scaler3是与scaler2相同的模型,并且会产生相同的转换
val scaler3 = new StandardScalerModel(scaler2.std, scaler2.mean)
// data1是单位方差
val data1 = data.map(x => (x.label, scaler1.transform(x.features)))
//如果不将这些特征转换成密度向量,那么零均值转换就会增加。稀疏向量例外
// data2将是单位方差和零均值
val data2 = data.map(x => (x.label, scaler2.transform(Vectors.dense(x.features.toArray))))
(2)正则化
正则化是指计算某个特征向量的p-范数(p=0,范数是指向量中非零元素的个数;p=1,范数为绝对值之和;p=2,范数是指通常意义上的模;p=无穷,范数是取向量的最大值),然后对每个元素除以p-范数,以将特征值正则化。正则化后不仅可以加快梯度下降求最优解的速度,还能提高模型精度。
MLlib提供Normalizer方法实现特征正则化,示例代码如下:
import org.apache.spark.SparkContext._
import org.apache.spark.MLlib.feature.Normalizer
import org.apache.spark.MLlib.linalg.Vectors
import org.apache.spark.MLlib.util.MLUtils
// Spark程序data文件夹下的测试数据
val data = MLUtils.loadLibSVMFile(sc, "data/MLlib/sample_libsvm_data.txt")
// 默认情况下,p=2,计算2阶范数
val normalizer1 = new Normalizer()
// p正无穷范数
val normalizer2 = new Normalizer(p = Double.PositiveInfinity)
// data1中的每个样本将使用L2范数进行标准化
val data1 = data.map(x => (x.label, normalizer1.transform(x.features)))
// data2中的每个样本将使用无穷范数进行标准化
val data2 = data.map(x => (x.label, normalizer2.transform(x.features)))
(3)二值化
二值化是一个将数值特征转换为二值特征的处理过程,根据一个阈值将数值特征分为两类,值大于阈值的特征二值化为1,否则二值化为0。二值化能够大大减少特征的复杂度,提高训练效率,但在二值化的过程中损失了一些信息,这可能会影响训练的效果,二值化的代码示例如下:
import org.apache.spark.ml.feature.Binarizer
val data = Array((0, 0.1), (1, 0.8), (2, 0.2))
val dataFrame = spark.createDataFrame(data).toDF("label", "feature")
val binarizer: Binarizer = new Binarizer()
.setInputCol("feature")
.setOutputCol("binarized_feature")
.setThreshold(0.5)
val binarizedDataFrame = binarizer.transform(dataFrame)
val binarizedFeatures = binarizedDataFrame.select("binarized_feature")
binarizedFeatures.collect().foreach(println)
若训练数据的指标维数太高,则容易造成模型过拟合,且相互独立的特征维数越高,在测试集上达到相同的效果表现所需要的训练样本的数目就越大。此外,指标数量过多,训练、测试以及存储的压力也都会增大,可运用主成分分析等手段,以Spark为工具对数据集进行计算降维。
2
- 点赞
- 收藏
- 关注作者
评论(0)