本章节覆盖特征相关的算法部分,可粗分为以下几组:
1> 抽取: 从原始数据中抽取特征
2> 变换:缩放,转化,或修改特征
3> 选择: 从特征集合中选择子集
4>
Locality Sensitive Hashing (LSH) : 将特征变换和其它算法组合在一起的一类算法。
目录:
1 特征抽取
1.1 TF-IDF
是常用的特征向量化的方法,通过将语料库中文本转化为词在文档中的出现频次向量,反映词在文本中的重要程度,因此广泛应用于文本挖掘。用t表示一个词条,d表示文本,D表示语料库。 词条的频次 TF(t,d) 表示 词条t 在文档 d中出现的次数, DF(t,D)表示出现词条 t的 文本D 的个数。如果 单纯用频次表示词条的重要程度,这样会过分强调某些常用词的重要程度,而乎略了这些词对文本信息量的贡献,如, "a" , "the" 和 "of" 这类小品词。如果一个词条在整个语料库中出现的频次高,那么这个词在识别文本时提供信息量低。反向文本频次(IDF)是数值测度,用于描述词条所提供的信息量
此处 |D| 是语料库中文本的个数。 在log 函数中,如果词条出现在所有文本中,那么该词的IDF值为0 . 注意到光滑因子1 是防止除以0 。 TF-IDF 测度是TF和IDF的向量积
TF-IDF 的定义有多种变体,在MLlibk , 我们将TF 和IDF分开,以保证灵活性。
TF: HashingTF和 countVectorizer 用于生成词条的频次向量。
HashingTF 是一个变换,它将词条的集合转化为固定维度的向量集合。 在文本处理中, “词条集合”表示有很多词目。 HashingTF 实现哈希戏法(hashing trick :
) . 原始特征映射到词条索引,这个索引是通过哈希函数计算得到。此处的哈希函数是MurmurHash 3 (
).
词条的频次是映射到出现词条的唯一索引上, 这样避免了计算全部词条索引的映射, 这样对于大的语料库来说,会消耗很多资源。 但有可能出现不同的原始特征哈希映射到相同的词条。为了降低这种出现概率,我们需要增加目标特征向量的维度,例如, hash 算法的分桶的数目。可以对哈希函数的结果使用简单的求模得到列索引, 但还是建议使用2的极数作为特征向量的维度, 否则可能有特征无法映射到的列上。 默认特征向量的维度是pow(2,18) = 262144 , 一个二开关变量可以控制词条频次计数,当设置成true时, 所有非 0 频次都设置为1 。 这样可以离散二进制概率模型,而不用整型计数。
CountVectorizer 转化文本为词条向量, 详见: CountVectorizer :
IDF : IDF 是一个Estimator , 它将拟合数据集并生成一个IDFModel 模型。 IDFModel 将特征向量(通过 HashingTF 或 CountVectorizer)每列做伸缩变换。直觉来看,它会将语料库中频次为参考,对列值进行权重调整。
注意到: spark.ml 并不支持文本分词, 建议查看 Standford NLP Group :
, 或 scalanlp/chalk :
示例:
在下面的代码中,我们输入一些句子, 然后使用Tokenizer将句子转化为单词,然后对这些句子,我坐着使用HashingTF 将句子转化为特征向量。 再使用IDF来伸缩特征向量;因为使用文本作为特征,这样可以显著提升性能 。 同时可以将特征向量传给学习算法
HashingTF scala doc 和 IDF scala doc 详见 API
import
org.apache.spark.ml.feature.
{
HashingTF
,
IDF
,
Tokenizer
}
val sentenceData
= spark
.createDataFrame
(
Seq
(
(
0.0
,
"Hi I heard about Spark"
),
(
0.0
,
"I wish Java could use case classes"
),
(
1.0
,
"Logistic regression models are neat"
)
)).toDF
(
"label"
,
"sentence"
)
val tokenizer
=
new
Tokenizer
().setInputCol
(
"sentence"
).setOutputCol
(
"words"
)
val wordsData
= tokenizer
.transform
(sentenceData
)
val hashingTF
=
new
HashingTF
()
.setInputCol
(
"words"
).setOutputCol
(
"rawFeatures"
).setNumFeatures
(
20
)
val featurizedData
= hashingTF
.transform
(wordsData
)
// alternatively, CountVectorizer can also be used to get term frequency vectors
val idf
=
new
IDF
().setInputCol
(
"rawFeatures"
).setOutputCol
(
"features"
)
val idfModel
= idf
.fit
(featurizedData
)
val rescaledData
= idfModel
.transform
(featurizedData
)
rescaledData
.select
(
"label"
,
"features"
).show
()
完整的例子详见:
examples/src/main/scala/org/apache/spark/examples/ml/TfIdfExample.scala
1.2 Word2Vec
Word2Vec是一个Estimator , 输入是文档的单词序列, 训练得到Word2VecModel 模型。模型将每个单词映射为唯一固定长度向量。 Word2VecModel 将每个文档变换为一个向量, 这个向量可以作为特征用于预测, 文档相似度计算等。 详细见:MLlib user guide on Word2Vec :
下面代码, 我们输入一些文本, 然后将文本变换为特征向量。 然后将特征向量输入学习算法。
详见: Word2Vec Scala docs :
import
org.apache.spark.ml.feature.Word2Vec
import
org.apache.spark.ml.linalg.Vector
import
org.apache.spark.sql.Row
// Input data: Each row is a bag of words from a sentence or document.
val documentDF
= spark
.createDataFrame
(
Seq
(
"Hi I heard about Spark"
.split
(
" "
),
"I wish Java could use case classes"
.split
(
" "
),
"Logistic regression models are neat"
.split
(
" "
)
).map
(
Tuple1
.apply
)).toDF
(
"text"
)
// Learn a mapping from words to Vectors.
val word2Vec
=
new
Word2Vec
()
.setInputCol
(
"text"
)
.setOutputCol
(
"result"
)
.setVectorSize
(
3
)
.setMinCount
(
0
)
val model
= word2Vec
.fit
(documentDF
)
val result
= model
.transform
(documentDF
)result
.collect
().foreach
{
case
Row
(text
:
Seq
[
_
], features
:
Vector
)
=>
println
(
s"Text: [
${text
.mkString
(
", "
)
}
] => \nVector:
$features
\n"
)
}
完整例子,见:
examples/src/main/scala/org/apache/spark/examples/ml/Word2VecExample.scala