文章目录
Spark MLlib 特征工程系列—特征提取 TF-IDF
TF-IDF是文本挖掘中广泛使用的一种特征向量化方法,用于反映术语对语料库中文档的重要性。
Term Frequency (TF)
TF,即词频,是衡量一个词在文档中出现频率的指标。假设某词在文档中出现了( n )次,而文档总共包含( N )个词,则该词的TF定义为:
Inverse Document Frequency (IDF)
IDF,即逆文档频率,是对词普遍性的度量,反映了词的稀有程度。IDF越高,说明词越独特,对于区分文档具有更大的价值。IDF的计算公式为:
如果我们仅使用词频来衡量重要性,很容易过分强调那些出现频率很高但几乎不包含文档信息的术语,例如“a”、“the”和“of”。如果某个术语在整个语料库中出现的频率很高,则意味着它不包含有关特定文档的特殊信息。逆
由于使用了对数,如果某个术语出现在所有文档中,则其 IDF 值变为 0。请注意,应用了平滑项以避免语料库之外的术语除以零。 通过取对数,可以避免数值过大的问题,同时保证了IDF的单调递减特性。
TF-IDF 度量只是 TF 和 IDF 的乘积:词频和文档频率的定义有多种变体。在 MLlib 中,我们将 TF 和 IDF 分开,以使其更加灵活
分词 Tokenizer
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)
wordsData.show()
TF 实现
特征提取 HashingTF
HashingTF 是 Apache Spark 中用于文本处理的一个特征转换器,属于 Spark MLlib 中的特征工程工具。它的主要作用是将一个词语序列(通常是一个文档中的词汇)转换为一个固定长度的特征向量。
这个方法依赖于“哈希技巧”(hashing trick),这种方法通过将词语或特征映射到一个固定长度的向量空间中来实现特征表示。具体来说,它使用哈希函数将每个词语转换成一个索引位置,并在这个索引位置上累加词语的出现次数,从而生成一个固定长度的稀疏向量。可以在处理高维度、海量词汇表时提供高效的维度缩减。
具体原理:
1. 输入数据:假设我们有一组文本数据,这些文本经过分词后形成一个词语列表(或词袋模型),如 [“spark”, “is”, “great”, “spark”, “is”]。
2. 哈希函数:HashingTF 使用一个哈希函数将每个词语映射到一个特定的整数索引位置。哈希函数可以是任何能够将输入映射到固定大小范围内的函数。
例如,对于词语 “spark”,哈希函数可能会将其映射到特征向量的第 5 个位置(假设使用 20 维特征向量,即 numFeatures = 20),而 “great” 可能被映射到第 12 个位置。
3. 特征向量生成:
• HashingTF 创建一个大小为 numFeatures 的零向量。
• 对于输入的每个词语,通过哈希函数计算其索引位置,并在该位置上累加 1(即词频)。
• 例如,如果 “spark” 被映射到第 5 个位置,而 “is” 被映射到第 8 个位置,则结果向量在这两个位置上分别增加 1。
• 如果同一个词语多次出现(如 “spark” 出现了两次),它的索引位置上的值会进一步增加。
4. **哈希碰撞**:由于哈希函数的性质,多个不同的词语可能会映射到相同的索引位置,这被称为哈希碰撞(hash collision)。当发生碰撞时,这些词语的频率会累加到同一个索引位置。虽然这种情况可能会引入噪声,但在实践中通常可以接受。
val hashingTF = new HashingTF()
.setInputCol("words")
.setOutputCol("rawFeatures")
.setNumFeatures(20)
val featurizedData = hashingTF.transform(wordsData)
featurizedData.select("label", "rawFeatures").show(false)
作用:
1. 固定长度特征向量:HashingTF 可以将任意长度的词语序列(文章)转换为固定长度的特征向量,即使原始词汇表非常庞大,这个特征向量的长度也可以通过参数 numFeatures 来控制。
2. 高效性:由于哈希技巧直接将词语映射到特定的索引位置,HashingTF 能够在处理大规模数据时保持高效的内存和计算性能。
3. 维度缩减:通过将大量的词汇映射到较小的固定维度上,HashingTF 在一定程度上起到了降维的效果。这在需要处理非常大的词汇表时尤为重要。
工作原理:
• 输入:一个词语序列,例如一个文档中的词汇列表。
• 哈希映射:每个词语通过哈希函数映射到特征向量中的一个索引位置。
• 特征向量:生成的特征向量是稀疏向量,在向量中的每个索引位置的值表示映射到该位置的词语的频率。
featurizedData.select("label", "rawFeatures").show(false)
label 列表示原始的标签(如文档的分类)。
rawFeatures 列是由 HashingTF 生成的特征向量,例如 (20,[3,8,15],[1.0,1.0,1.0]) 表示一个 20 维的稀疏向量,索引位置 3、8、15 上的值为 1.0,表示这些位置上的词汇频率。
numFeatures 也就是向量纬度或者是向量空间大小,在 HashingTF 中,特征大小(或特征维度)是指词汇表的大小,也就是将每个词映射到的特征空间的维度。这是 HashingTF 的一个关键参数,它影响到特征向量的维度。
特征大小(numFeatures):指定了输出的特征向量的维度。例如,如果 numFeatures 设置为 1000,则每个文本或文档将被表示为一个 1000 维的向量。这些向量是稀疏的,其中的元素是每个词的出现次数或其他权重。
对于每个词,使用哈希函数计算其哈希值。哈希值会被映射到特征空间的索引上。假设特征空间的大小为 numFeatures,则哈希函数的结果会在 [0, numFeatures-1] 范围内进行取模运算,以确定特征向量中相应的位置。
每个词的出现会在特征向量中对应的索引位置增加其权重(通常是出现次数)。如果词的哈希值映射到特征向量的某个索引位置,那个位置的值就增加词的权重。
HashingTF 实际上并不直接进行降维。它通过哈希函数将高维词汇空间映射到一个固定的低维特征空间中。这种映射自然地将词汇空间的维度“降维”到特征空间的大小。 因为哈希函数的特性,多个不同的词汇可能会映射到相同的特征索引上,这种现象叫做“哈希冲突”(Hash Collisions)。哈希冲突会导致信息丢失,增加了特征向量的稀疏性。
特征提取 CountVectorizer
CountVectorizer 是 Apache Spark MLlib 中的一个工具,常用于将一组文本(文档)转换为词频向量(Term Frequency)。它是一种简单而有效的文本特征提取方法,能够将文本数据转换为机器学习算法可用的数值特征。
工作原理:
• 词汇表构建:CountVectorizer 首先扫描所有输入文本,构建出词汇表(vocabulary),即所有出现过的词语集合。
• 词频统计:然后,它将每个文档转换为一个向量,这个向量的每个元素表示该文档中某个词在词汇表中出现的次数。
import org.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel}
import org.apache.spark.sql.SparkSession
// 创建 SparkSession
val spark = SparkSession.builder.appName("CountVectorizer Example").getOrCreate()
// 创建一个简单的 DataFrame,包含几条文本数据
val sentenceData = spark.createDataFrame(Seq(
(0, Array("I", "love", "Spark")),
(1, Array("Spark", "is", "amazing")),
(2, Array("I", "am", "learning", "Spark"))
)).toDF("label", "words")
// 初始化 CountVectorizerModel,并设置词汇表大小和最小词频
val cvModel: CountVectorizerModel = new CountVectorizer()
.setInputCol("words")
.setOutputCol("features")
.setVocabSize(5) // 设置词汇表大小
.setMinDF(1) // 设置词汇的最小文档频率
.fit(sentenceData)
// 将文档转换为词频向量
val featurizedData = cvModel.transform(sentenceData)
// 展示结果
featurizedData.select("label", "features").show(false)
// 打印词汇表
println("Vocabulary: " + cvModel.vocabulary.mkString(", "))
代码解释:
-
创建数据集:sentenceData 是一个简单的 DataFrame,每一行包含一个标签和一个词语数组。
-
初始化 CountVectorizer:
-
setInputCol(“words”):指定输入列为 words,即文档的词语列表。
-
setOutputCol(“features”):输出列为 features,即生成的词频向量。
-
setVocabSize(5):设置词汇表的最大大小为 5。
-
setMinDF(1):设置词汇在至少 1 个文档中出现才能被纳入词汇表。
-
-
转换文档:cvModel.transform(sentenceData) 将每个文档转换为词频向量,并将结果保存在新的列 features 中。
- 展示结果:使用 show(false) 方法展示 label 和 features 列的内容。
- 打印词汇表:输出由 CountVectorizer 构建的词汇表。
通过 vocabSize 参数来限制词汇表的最大大小。如果设置了 vocabSize,CountVectorizer 将从所有出现的词汇中选择最常见的前 vocabSize 个词汇来构建词汇表。
val cv = new CountVectorizer()
.setInputCol("words")
.setOutputCol("features")
.setVocabSize(1000) // 设置词汇表大小为 1000
在这个例子中,vocabSize 被设置为 1000,这意味着词汇表将包含出现频率最高的 1000 个词语。如果语料库中的词汇量超过 1000,CountVectorizer 只会保留这些词语。
如果没有设置 vocabSize,CountVectorizer 默认会根据语料库中的所有词汇构建词汇表,这意味着词汇表的大小将等于语料库中不同词汇的总数。
可以通过 minDF 参数(最小文档频率)来过滤掉那些出现频率太低的词汇。设置 minDF 可以减少词汇表中的词语数量,从而间接控制词汇表大小。
val cvModel = cv.fit(data)
println("Vocabulary size: " + cvModel.vocabulary.length)
一旦 CountVectorizer 模型训练完成,可以通过 vocabulary 属性来查看词汇表的具体内容及其大小。
对比CountVectorizer和HashingTF
CountVectorizer 和 HashingTF 都是用于文本处理的特征提取工具,主要用于将文本数据转换为机器学习模型可以处理的数值特征(向量)。虽然它们都可以将文本转换为向量,但在实现方式、特性以及适用场景上有一些显著的区别。
基本原理
CountVectorizer:
- 词频统计:它扫描整个文档集合(语料库),构建一个词汇表(vocabulary),其中每个词汇都有一个唯一的索引。然后,它为每个文档生成一个词频向量,这个向量的每个元素表示该文档中相应词汇的出现次数。
- 固定词汇表:词汇表是根据整个语料库的词汇构建的,词汇表的大小可以通过 vocabSize 参数设置。
HashingTF:
- 哈希映射:HashingTF 通过一个哈希函数将词语直接映射到一个固定长度的向量空间(即索引位置),并在该位置累加词语的频率。哈希映射使得词汇表的构建是隐式的,不需要扫描整个语料库。
- 固定维度:输出向量的长度是固定的,由 numFeatures 参数控制,即使词汇表非常大,向量长度仍然是固定的。
优缺点对比
CountVectorizer:
优点:
• 可解释性强:由于每个词汇都明确地映射到词汇表中的某个索引位置,因此生成的向量是可解释的,即可以知道向量的某个位置代表哪个具体的词。
• 避免哈希碰撞:词汇表是显式构建的,因此不存在哈希碰撞的问题,向量更加精确。
缺点:
• 词汇表大小限制:如果语料库非常大,词汇表的大小也会很大,这可能会占用大量内存。
• 扩展性较差:如果需要动态添加新文档或词汇,必须重新构建词汇表,计算代价较高。
HashingTF:
优点:
• 内存高效:由于不需要构建词汇表,直接通过哈希函数进行映射,因此在处理大规模数据时更加节省内存和计算资源。
• 固定维度:输出向量的维度固定,不会随着语料库大小变化,因此对内存和计算的需求是可控的。
• 扩展性强:可以轻松处理新文档和新词汇,不需要重新计算词汇表。
缺点:
• 哈希碰撞:不同的词语可能会映射到同一个索引位置,导致哈希碰撞,从而引入噪声。
• 不易解释:由于使用哈希函数,无法直接知道向量的某个位置对应的是哪个具体的词语。
适用场景
• CountVectorizer 适用于以下场景:
• 当你需要明确的、可解释的词汇与特征之间的对应关系时,比如在某些模型中需要解释特征的重要性。
• 当处理规模相对较小的文本数据时,词汇表大小可以控制在合理范围内。
• 需要利用词汇表进行特征选择(如删除低频词)或后续文本分析时。
• HashingTF 适用于以下场景:
• 当你处理非常大规模的文本数据集,并且需要高效的内存和计算资源使用时。
• 当你希望保持向量的固定维度,以便于处理大规模数据或在线学习任务。
• 当词汇表动态变化、扩展性需求高时,如在流处理或实时数据处理场景中。
CountVectorizer 更加精确和可解释,但在处理超大规模数据时可能存在内存和计算资源的限制。
HashingTF 则更高效,适合处理大规模数据或需要固定维度向量的场景,但可能会牺牲一些精度和可解释性。
两者的选择应根据具体应用场景、数据规模、以及对解释性和计算资源的需求来确定。
IDF 模型
有了TF之后,我们就可以计算IDF 了,当然Spark MLlib的IDF ,是 TF-IDF模型的最终计算结果,并不是我们一开始提到的IDF的值
在 Spark 中,使用 IDF 通常需要以下几个步骤:
1. 计算词频(TF):首先需要使用 HashingTF 或 CountVectorizer 来生成词频向量。
2. 计算 IDF 权重:使用 IDF 类来计算每个词的 IDF 值。
3. 生成 TF-IDF 特征向量:将 TF 和 IDF 结合,得到 TF-IDF 特征向量。
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
val idfModel = idf.fit(featurizedData)
val rescaledData = idfModel.transform(featurizedData)
rescaledData.select("label", "features").show()
下面是我们的输入数据
下面是我们最终的结果输出
(20,[6,8,13,16],[0.28768207245178085,0.6931471805599453,0.28768207245178085,0.5753641449035617])
这里的20 和我们之前说的一样,表示特征向量的维度,[6,8,13,16] 表示词汇在特征向量空间的索引位置,[0.28768207245178085,0.6931471805599453,0.28768207245178085,0.5753641449035617] 表示对应位置词汇的TF-IDF 值
总结
TF-IDF 是文本分类、聚类、信息检索等任务中的一种常见特征提取方法。通过降低常见词汇的权重,TF-IDF 能够更有效地捕捉那些对文档区分度更高的词汇,从而提高模型的效果。
IDF 是 TF-IDF 中的重要组成部分,用于调整词频,以减少常见词对文本分析任务的影响。在 Spark 中,可以结合 HashingTF 或 CountVectorizer 来计算 TF-IDF 特征,进而用于各种机器学习任务。
IDF 的计算规模是取决于词频的计算规模,因为词频是作为IDF的输入的,CountVectorizer和HashingTF 都是用来计算词频的
标签:词汇表,MLlib,TF,文档,哈希,IDF,HashingTF,向量 From: https://blog.csdn.net/2401_84052244/article/details/141135555