作者:云朵君 来源:数据STUDIO

今天云朵君给大家总结了四个特征提取及四个特征选择方法,简要介绍了他们的原理,并附上相应代码,希望能够帮助到大家!

  • TF–IDF
  • Word2Vec
  • CountVectorizer
  • FeatureHasher
  • VectorSlicer
  • ChiSqSelector
  • 单变量特征选择器
  • 方差阈值选择器

特征提取

TF–IDF

在信息检索中,tf–idf(也称为TF*IDF、TFIDF、TF–IDF或Tf–idf )是词频-逆文档频率的缩写,TF–IDF是文本挖掘中广泛使用的一种特征矢量化方法,用于反映词汇对语料库中文档的重要性。

TF–IDF中用t表示一个词,用d表示一个文档,用d表示语料库。词频TF(t,d)是单词t在文档d中出现的次数,而文档频率DF(t, d)是包含单词t的文档数量。如果我们只用词频来衡量重要性,就很容易过分强调那些出现频率很高但却很少包含文档信息的词,如“a”、“the”和“of”。如果一个术语在语料库中出现得非常频繁,就意味着它没有携带关于特定文档的特殊信息。

TF–IDF通常用作信息检索、文本挖掘和用户建模搜索中的权重因子。tf-idf 值按比例增加一个单词在文档中出现的次数,并被包含该单词的语料库中的文档数量所抵消,这有助于调整一些单词出现频率更高的事实。tf-idf 是当今最流行的术语加权方案之一。2015 年进行的一项调查显示,数字图书馆中 83% 的基于文本的推荐系统使用 tf-idf。

逆文档频率是一个数值度量一个词提供了多少信息:

其中|D|为语料库中的文档总数。由于使用了对数,如果一个术语出现在所有文档中,它的IDF值就变成0。注意,使用平滑项来避免对语料库之外的项除以零。TF-IDF测度就是TF和IDF的乘积:


术语频率和文档频率的定义有几种变体。在 MLlib 中,我们将 TF 和 IDF 分开以使其灵活。

TF:两者HashingTF都CountVectorizer可以用于生成术语频率向量。

HashingTF是一个Transformer接受术语集并将这些集转换为固定长度特征向量的方法。在文本处理中,“一组术语”可能是一个词袋。HashingTF利用散列技巧,通过应用哈希函数将原始特征映射到索引(术语)。
CountVectorizer将文本文档转换为术语计数向量。
IDF :IDF是Estimator适合数据集并生成IDFModel. IDFModel获取特征向量(通常由或 HashingTF创建CountVectorizer)并缩放每个特征。直观地说,它会降低语料库中频繁出现的特征的权重。

使用Tokenizer将每个句子分成单词,对于每个句子(词袋),使用HashingTF将句子散列成特征向量。IDF用来重新缩放特征向量,然后将特征向量传递给学习算法。

# Author: 数据STUDIO
from pyspark.ml.feature import HashingTF, IDF, Tokenizer
from pyspark.sql import SparkSession

if __name__ == "__main__":
    spark = SparkSession\
        .builder\
        .appName("TfIdfExample")\
        .getOrCreate()

    sentenceData = spark.createDataFrame([
        (0.0, "Hi I heard about PyDataStudio"),
        (0.0, "I wish you will follow us"),
        (1.0, "Logistic regression models are neat")
    ], ["label", "sentence"])

    tokenizer = Tokenizer(inputCol="sentence", 
                          outputCol="words")
    wordsData = tokenizer.transform(sentenceData)

    hashingTF = HashingTF(inputCol="words", 
                          outputCol="rawFeatures",
                          numFeatures=20)
    featurizedData = hashingTF.transform(wordsData)
    # 另外,CountVectorizer也可以用来得到词频矢量

    idf = IDF(inputCol="rawFeatures", 
              outputCol="features")
    idfModel = idf.fit(featurizedData)
    rescaledData = idfModel.transform(featurizedData)

    rescaledData.select("label", "features").show()
    spark.stop()
+-----+--------------------+
|label|            features|
+-----+--------------------+
|  0.0|(20,[6,8,13,16],[...|
|  0.0|(20,[0,2,7,9,13,1...|
|  1.0|(20,[3,4,6,11,19]...|
+-----+--------------------+


Word2Vec

Word2Vec计算单词的分布式向量表示。分布式表示的主要优点是相似词在向量空间中很接近,这使得对新模式的泛化更容易,模型估计更稳健。分布式向量表示在许多自然语言处理应用中被证明是有用的,例如命名实体识别、消歧、解析、标记和机器翻译。

Word2Vec将文本语料库作为输入,并生成词向量作为输出。它首先从训练文本数据中构建一个词汇表,然后学习单词的向量表示。生成的词向量文件可用作许多自然语言处理和机器学习应用程序中的特征。

Word2Vec是一个 Estimator,它采用表示文档的单词序列并训练一个 Word2VecModel。该模型将每个单词映射到一个唯一的固定大小向量。使用Word2VecModel 文档中所有单词的平均值将每个文档转换为向量;然后,此向量可用作预测、文档相似度计算等的特征。

在PySpark ML的 Word2Vec 实现中,使用了 skip-gram 模型。skip-gram 的训练目标是学习擅长预测其在同一个句子中的上下文的词向量表示。在数学上,给定一系列训练词,skip-gram 模型的目标是最大化平均对数似然 ,其中w1,w2,…,wT

k是训练窗口的大小。

在 skip-gram 模型中,每个单词w与两个向量uw和vw相关联,这两个向量分别表示单词和上下文。在给定单词wj的情况下,正确预测单词wj的概率由softmax模型确定


其中V为词汇量。

从一组文档开始,每个文档都表示为一个单词序列。对于每个文档,将其转换为特征向量。然后将该特征向量传递给学习算法。

from pyspark.ml.feature import Word2Vec
from pyspark.sql import SparkSession

if __name__ == "__main__":
    spark = SparkSession\
        .builder\
        .appName("Word2VecExample")\
        .getOrCreate()
    # 输入数据:每一行是一个句子或文档中的词袋。
    documentDF = spark.createDataFrame([
        ("Hi I heard about PyDataStudio".split(" "), ),
        ("I wish you will follow us".split(" "), ),
        ("Logistic regression models are neat".split(" "), )
    ], ["text"])

    # 学习从单词到向量的映射。
    word2Vec = Word2Vec(vectorSize=3, minCount=0, inputCol="text", outputCol="result")
    model = word2Vec.fit(documentDF)

    result = model.transform(documentDF)
    for row in result.collect():
        text, vector = row
        print("Text: [%s] => \nVector: %s\n" % (", ".join(text), str(vector)))
    spark.stop()
Text: [Hi, I, heard, about, PyDataStudio] => 
Vector: [0.012690218258649112,0.015476902015507221,
         -0.015471531823277474]

Text: [I, wish, you, will, follow, us] => 
Vector: [0.054964230551073946,0.06904734919468561,
         -0.028411002596840262]

Text: [Logistic, regression, models, are, neat] => 
Vector: [0.060065926611423494,0.04297176990658045,
         -0.0729493197053671]

CountVectorizer

CountVectorizer 和CountVectorizerModel旨在帮助将文本文档集合转换为 token 计数向量。当先验字典不可用时,CountVectorizer可以用作Estimator来提取词汇表,并生成一个CountVectorizerModel。该模型为词汇表上的文档生成稀疏表示,然后可以将其传递给其他算法,如 LDA。

在拟合过程中,CountVectorizer将选择在vocabSize整个语料库中按词频排序的最高词。可选参数minDF还通过指定术语必须出现在词汇表中的文档的最小数量(或分数,如果 < 1.0)来影响拟合过程。另一个可选的二进制切换参数控制输出向量。如果设置为 true,则所有非零计数都设置为 1。这对于模拟二进制而不是整数计数的离散概率模型特别有用

from pyspark.sql import SparkSession
from pyspark.ml.feature import CountVectorizer

if __name__ == "__main__":
    spark = SparkSession\
        .builder\
        .appName("CountVectorizerExample")\
        .getOrCreate()

    # 输入数据:每一行是一个带 ID 的词袋。
    df = spark.createDataFrame([
        (0, "a b c".split(" ")),
        (1, "a b b c a".split(" "))
    ], ["id", "words"])

    # 从语料库中匹配CountVectorizerModel
    cv = CountVectorizer(inputCol="words", outputCol="features", vocabSize=3, minDF=2.0)
    model = cv.fit(df)
    result = model.transform(df)
    result.show(truncate=False)
    spark.stop()
+---+---------------+-------------------------+
|id |words          |features                 |
+---+---------------+-------------------------+
|0  |[a, b, c]      |(3,[0,1,2],[1.0,1.0,1.0])|
|1  |[a, b, b, c, a]|(3,[0,1,2],[2.0,2.0,1.0])|
+---+---------------+-------------------------+

FeatureHasher

特征散列FeatureHasher将一组分类或数字特征投影到指定维度的特征向量中(通常远小于原始特征空间的特征向量)。这是使用散列技巧将特征映射到特征向量中的索引来完成的。

FeatureHasher transformer 在多列上运行,每列可能包含数字或分类特征。列数据类型的行为和处理如下:

  • 数字列:对于数字特征,列名的哈希值用于将特征值映射到其在特征向量中的索引。默认情况下,数字特征不被视为分类(即使它们是整数)。要将它们视为分类,使用categoricalCols参数指定相关列。
  • 字符串列:对于分类特征,使用字符串“column_name=value”的哈希值映射到向量索引,指标值为1.0。因此,分类特征是“one-hot”编码的(类似于使用 OneHotEncoder 和 dropLast=false)。
  • 布尔列:布尔值的处理方式与字符串列相同。也就是说,布尔特征表示为 “column_name=true” 或 “column_name=false”,指标值为1.0。

Null(缺失)值被忽略(在结果特征向量中隐式为零)。

from pyspark.sql import SparkSession
from pyspark.ml.feature import FeatureHasher

if __name__ == "__main__":
    spark = SparkSession\
        .builder\
        .appName("FeatureHasherExample")\
        .getOrCreate()

    dataset = spark.createDataFrame([
        (2.2, True, "1", "Py"),
        (3.3, False, "2", "Data"),
        (4.4, False, "3", "Studio"),
        (5.5, False, "4", "Py")
    ], ["real", "bool", "stringNum", "string"])

    hasher = FeatureHasher(inputCols=["real", "bool", "stringNum", "string"],
                           outputCol="features")

    featurized = hasher.transform(dataset)
    featurized.show(truncate=False)
    spark.stop()
+----+-----+---------+------+--------------------------------------------------------+
|real|bool |stringNum|string|features                                                |
+----+-----+---------+------+--------------------------------------------------------+
|2.2 |true |1        |Py    |(262144,[168496,174475,247670,262126],[1.0,2.2,1.0,1.0])|
|3.3 |false|2        |Data  |(262144,[70644,89673,174475,221108],[1.0,1.0,3.3,1.0])  |
|4.4 |false|3        |Studio|(262144,[9578,22406,70644,174475],[1.0,1.0,1.0,4.4])    |
|5.5 |false|4        |Py    |(262144,[70644,101499,168496,174475],[1.0,1.0,1.0,5.5]) |
+----+-----+---------+------+--------------------------------------------------------+


特征选择
VectorSlicer
VectorSlicer是一个转换器,它接受一个特征向量并输出一个带有原始特征子数组的新特征向量。它对于从向量列中提取特征很有用。

VectorSlicer接受具有指定索引的向量列,然后输出一个新向量列,其值是通过这些索引选择的。有两种类型的索引,

表示向量索引的整数索引setIndices()。
表示向量中特征名称的字符串索引,setNames()。
输出向量将首先使用所选索引对特征进行排序(按给定顺序),然后是所选名称(按给定顺序)。

from pyspark.ml.feature import VectorSlicer
from pyspark.ml.linalg import Vectors
from pyspark.sql.types import Row
df = spark.createDataFrame([
    Row(userFeatures=Vectors.sparse(3, {0: -2.0, 1: 2.3})),
    Row(userFeatures=Vectors.dense([-2.0, 2.3, 0.0]))])

slicer = VectorSlicer(inputCol="userFeatures", outputCol="features", indices=[1])
output = slicer.transform(df)
output.select("userFeatures", "features").show()
+--------------------+-------------+
|        userFeatures|     features|
+--------------------+-------------+
|(3,[0,1],[-2.0,2.3])|(1,[0],[2.3])|
|      [-2.0,2.3,0.0]|        [2.3]|
+--------------------+-------------+


ChiSqSelector

ChiSqSelector代表卡方特征选择。它对具有分类特征的标记数据进行操作。ChiSqSelector 使用卡方独立性检验来决定选择哪些特征。它支持五种选择方法:numTopFeatures, percentile, fpr, fdr, fwe:numTopFeatures根据卡方检验选择固定数量的顶级特征。这类似于产生具有最大预测能力的特征。
percentile类似于numTopFeatures但选择所有特征的一小部分而不是固定数量。
fpr选择 p 值低于阈值的所有特征,从而控制选择的误报率。
fdr使用Benjamini-Hochberg 过程选择错误发现率低于阈值的所有特征。
fwe选择 p 值低于阈值的所有特征。阈值按 1/numFeatures 缩放,从而控制全族选择错误率。默认情况下,选择方法为numTopFeatures,默认顶部特征数设置为 50。用户可以使用 选择选择方法setSelectorType

from pyspark.ml.feature import ChiSqSelector
from pyspark.ml.linalg import Vectors

df = spark.createDataFrame([
    (7, Vectors.dense([0.0, 0.0, 18.0, 1.0]), 1.0,),
    (8, Vectors.dense([0.0, 1.0, 12.0, 0.0]), 0.0,),
    (9, Vectors.dense([1.0, 0.0, 15.0, 0.1]), 0.0,)], ["id", "features", "clicked"])

selector = ChiSqSelector(numTopFeatures=1, featuresCol="features",
                         outputCol="selectedFeatures", labelCol="clicked")

result = selector.fit(df).transform(df)

print("ChiSqSelector output with top %d features selected" % selector.getNumTopFeatures())
result.show()
ChiSqSelector output with top 1 features selected
+---+------------------+-------+----------------+
| id|          features|clicked|selectedFeatures|
+---+------------------+-------+----------------+
|  7|[0.0,0.0,18.0,1.0]|    1.0|          [18.0]|
|  8|[0.0,1.0,12.0,0.0]|    0.0|          [12.0]|
|  9|[1.0,0.0,15.0,0.1]|    0.0|          [15.0]|
+---+------------------+-------+----------------+


单变量特征选择器
UnivariateFeatureSelector对具有分类/连续特征的分类/连续标签进行操作。用户可以设置featureTypeand labelType,Spark 会根据指定的 featureTypeand选择要使用的评分函数labelType。

featureType |  labelType |score function
------------|------------|--------------
categorical |categorical | chi-squared (chi2)
continuous  |categorical | ANOVATest (f_classif)
continuous  |continuous  | F-value (f_regression)


它支持五种选择模式:numTopFeatures, percentile, fpr, fdr, fwe:numTopFeatures选择固定数量的顶级特征。
percentile类似于numTopFeatures但选择所有特征的一小部分而不是固定数量。
fpr选择 p 值低于阈值的所有特征,从而控制选择的误报率。
fdr使用Benjamini-Hochberg 过程选择错误发现率低于阈值的所有特征。
fwe选择 p 值低于阈值的所有特征。阈值按 1/numFeatures 缩放,从而控制全族选择错误率。

from pyspark.ml.feature import UnivariateFeatureSelector
from pyspark.ml.linalg import Vectors

df = spark.createDataFrame([
    (1, Vectors.dense([1.7, 4.4, 7.6, 5.8, 9.6, 2.3]), 3.0,),
    (2, Vectors.dense([8.8, 7.3, 5.7, 7.3, 2.2, 4.1]), 2.0,),
    (3, Vectors.dense([1.2, 9.5, 2.5, 3.1, 8.7, 2.5]), 3.0,),
    (4, Vectors.dense([3.7, 9.2, 6.1, 4.1, 7.5, 3.8]), 2.0,),
    (5, Vectors.dense([8.9, 5.2, 7.8, 8.3, 5.2, 3.0]), 4.0,),
    (6, Vectors.dense([7.9, 8.5, 9.2, 4.0, 9.4, 2.1]), 4.0,)], ["id", "features", "label"])

selector = UnivariateFeatureSelector(featuresCol="features", 
                                     outputCol="selectedFeatures",
                                     labelCol="label", 
                                     selectionMode="numTopFeatures")

selector.setFeatureType("continuous").setLabelType("categorical").setSelectionThreshold(1)

result = selector.fit(df).transform(df)

print("单变量特征选择器输出,使用 f_classif 选择 top %d 特性"
      % selector.getSelectionThreshold())
result.show()


单变量特征选择器输出,使用 f_classif 选择 top 1 特性

+---+--------------------+-----+----------------+
| id|            features|label|selectedFeatures|
+---+--------------------+-----+----------------+
|  1|[1.7,4.4,7.6,5.8,...|  3.0|           [2.3]|
|  2|[8.8,7.3,5.7,7.3,...|  2.0|           [4.1]|
|  3|[1.2,9.5,2.5,3.1,...|  3.0|           [2.5]|
|  4|[3.7,9.2,6.1,4.1,...|  2.0|           [3.8]|
|  5|[8.9,5.2,7.8,8.3,...|  4.0|           [3.0]|
|  6|[7.9,8.5,9.2,4.0,...|  4.0|           [2.1]|
+---+--------------------+-----+----------------+


方差阈值选择器
VarianceThresholdSelector是一个删除低方差特征的选择器。方差不大于 的特征varianceThreshold将被删除。如果未设置,varianceThreshold 则默认为 0,这意味着只有方差为 0 的特征(即在所有样本中具有相同值的特征)将被删除。

from pyspark.ml.feature import VarianceThresholdSelector
from pyspark.ml.linalg import Vectors

df = spark.createDataFrame([
    (1, Vectors.dense([6.0, 7.0, 0.0, 7.0, 6.0, 0.0])),
    (2, Vectors.dense([0.0, 9.0, 6.0, 0.0, 5.0, 9.0])),
    (3, Vectors.dense([0.0, 9.0, 3.0, 0.0, 5.0, 5.0])),
    (4, Vectors.dense([0.0, 9.0, 8.0, 5.0, 6.0, 4.0])),
    (5, Vectors.dense([8.0, 9.0, 6.0, 5.0, 4.0, 4.0])),
    (6, Vectors.dense([8.0, 9.0, 6.0, 0.0, 0.0, 0.0]))], ["id", "features"])

selector = VarianceThresholdSelector(varianceThreshold=8.0, outputCol="selectedFeatures")

result = selector.fit(df).transform(df)

print("输出: 去掉方差低于 %f 的特征" %
      selector.getVarianceThreshold())
result.show()
输出: 去掉方差低于 8.000000 的特征
+---+--------------------+-----------------+
| id|            features| selectedFeatures|
+---+--------------------+-----------------+
|  1|[6.0,7.0,0.0,7.0,...|[6.0,0.0,7.0,0.0]|
|  2|[0.0,9.0,6.0,0.0,...|[0.0,6.0,0.0,9.0]|
|  3|[0.0,9.0,3.0,0.0,...|[0.0,3.0,0.0,5.0]|
|  4|[0.0,9.0,8.0,5.0,...|[0.0,8.0,5.0,4.0]|
|  5|[8.0,9.0,6.0,5.0,...|[8.0,6.0,5.0,4.0]|
|  6|[8.0,9.0,6.0,0.0,...|[8.0,6.0,0.0,0.0]|
+---+--------------------+-----------------+