【技术专题】Scikit-learn Python机器学习 - 特征工程之特征提取

49 阅读27分钟

大家好,我是锋哥。最近连载更新《Scikit-learn Python机器学习》技术专题。 image.png 本课程主要讲解基于Scikit-learn的Python机器学习知识,包括机器学习概述,特征工程(数据集,特征抽取,特征预处理,特征降维等),分类算法(K-临近算法,朴素贝叶斯算法,决策树等),回归与聚类算法(线性回归,欠拟合,逻辑回归与二分类,K-means算法)等。 同时也配套视频教程《Scikit-learn Python机器学习 视频教程》

前面讲'机器学习开发流程',阶段二''数据准备'就需要进行特征工程,特征工程里,就需要进行特征提取,把数据转换成方便我们训练需要的特定格式的数据。

类别特征提取- OneHotEncoder

用于处理离散的、无序的类别特征(如国家、颜色、品牌)。它将每个类别特征转换为一个二进制列(0/1)。

独热编码OneHotEncoder:将一个有 N 个不同取值的类别型特征,转换为 N二进制特征(0或1)。对于原始特征的每一个样本值,只有对应的那个新特征是 1(“热”),其他所有新特征都是 0(“冷”)。

“独热”这个名字非常形象地描述了它的形式:在众多位中,只有一位是“热”的(1),其余都是“冷”的(0)。

简单举例:我们将 "动物" 这一个特征,扩展为三个新的二进制特征:"是否是猫""是否是狗""是否是兔子"

样本动物(原始)是否是猫是否是狗是否是兔子
1100
2010
3兔子001
4010

这样,对于“猫”这个类别,它的向量表示就是 [1, 0, 0];“狗”是 [0, 1, 0];“兔子”是 [0, 0, 1]。这三个向量在数学空间中是相互正交的,完全没有顺序或大小关系,完美地表达了类别之间的独立性。

核心参数详解

  1. categories (默认 ‘auto’)
  • 作用: 指定每个特征(列)有哪些类别(唯一值)。

  • 可选值

    • ‘auto’ (默认): 自动从训练集数据中推断每个特征的类别。这是最常用的模式。

    • ‘list’: 手动指定。需要一个列表的列表,其中每个内部列表对应一个特征的类别。

      • 例如:categories=[['猫', '狗'], ['红', '蓝']] 表示第一个特征有‘猫’和‘狗’两个类别,第二个特征有‘红’和‘蓝’两个类别。
  • 为什么重要

    • 确保训练集和测试集编码的维度一致。如果测试集出现了训练集中从未见过的类别(‘未知’类别),handle_unknown 参数会决定如何处理。
    • 手动指定可以固定编码的维度,即使某些类别在训练集中没有出现。
  1. drop (默认 None)
  • 作用: 指定一种方法来“丢弃”每个特征的一个类别。用于避免多重共线性(即特征向量不是独立的),这对于某些线性模型非常重要。

  • 可选值

    • None (默认): 不丢弃任何类别,保留所有编码后的特征列。
    • ‘first’: 丢弃每个特征的第一个类别。如果某个特征有 k 个类别,则会生成 k-1 列。
    • ‘if_binary’: 仅当特征是二分类(只有两个类别)时,丢弃其中一类。对于多分类特征,则保留所有类别。
    • array: 可以传递一个数组,指定要丢弃的特定类别。例如 drop=[‘猫’] 会丢弃类别‘猫’对应的列。
  • 示例

    • 特征‘颜色’有 [‘红’, ‘蓝’, ‘绿’] 三个类别。
    • drop=None: 生成三列:颜色_红, 颜色_蓝, 颜色_绿
    • drop=‘first’: 生成两列:颜色_蓝, 颜色_绿(丢弃了‘红’)。此时,全为0的向量就代表被丢弃的‘红’类别。
  1. sparse_output (默认 False)
  • 作用: 决定编码后的输出是稀疏矩阵还是密集矩阵(numpy数组)。

  • 可选值

    • True: 返回 scipy.sparse 稀疏矩阵。当类别数量非常多时(例如几万种),这可以极大地节省内存,因为只有0和1,而大部分都是0。
    • False: 返回常规的 numpy.ndarray 密集数组。更易于查看和理解,但类别多时会消耗大量内存。
  • 历史变化: 在 1.2 版本之前,此参数名为 sparse,现已弃用。

  1. dtype (默认 numpy.float64)
  • 作用: 指定输出数组的数据类型。因为One-Hot编码的结果只有0和1,通常使用较小的数据类型来节省内存,例如 np.int8bool
  • 常用值np.float64, np.int8, bool
  1. handle_unknown (默认 ‘error’)
  • 作用: 当转换过程中(例如在预测时处理测试集数据)遇到了训练时未见过的类别(categories 参数中未定义的类别)时,应采取的策略。

  • 可选值

    • ‘error’ (默认): 抛出错误 ValueError
    • ‘ignore’: 将未知类别全部编码为0向量。这是非常危险的做法,因为它会让模型误以为这个样本在所有类别特征上都属于“基准”类别。
    • ‘infrequent_if_exist’ (v1.1新增): 将未知类别视为“不常见”类别进行处理。这需要与 min_frequencymax_categories 参数配合使用。这是最推荐的处理未知类别的方式,因为它更合理。
  1. min_frequencymax_categories (v1.1新增)

这两个参数用于自动将不常见的类别分组到一个“不常见”类别中,从而限制输出维度,防止类别过多导致维度爆炸。

  • min_frequency:

    • 可以是一个整数或浮点数。
    • 如果为整数,表示类别出现次数小于等于这个值的将被归为“不常见”类别。
    • 如果为浮点数(0.0到1.0之间),表示类别出现频率小于等于这个比例的将被归为“不常见”类别。
  • max_categories:

    • 一个整数。
    • 为每个特征保留的最大类别数(不包括“不常见”类别)。频率最高的 n-1 个类别会被保留,剩下的所有类别(第 n 名及以后)都会被归入一个“不常见”类别。

注意: 这两个参数不能同时使用。

我们看一个示例:

from sklearn.preprocessing import OneHotEncoder
import numpy as np
​
# 示例类别数据
data = [['Red'], ['Blue'], ['Green'], ['Blue'], ['Red']]
​
# 初始化 OneHotEncoder
# handle_unknown='ignore' 防止遇到未知类别时报错
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
​
# 学习并转换数据
# fit() :用于从训练数据生成学习模型参数
# transform():从fit()方法生成的参数,应用于模型以生成转换数据集。
# fit_transform():在同一数据集上组合fit()和transform() api
X_encoded = encoder.fit_transform(data)
​
print("编码后的特征矩阵:")
print(X_encoded)
# 输出:
# [[1. 0. 0.]  # Red -> [1, 0, 0]
#  [0. 1. 0.]  # Blue -> [0, 1, 0]
#  [0. 0. 1.]  # Green -> [0, 0, 1]
#  [0. 1. 0.]  # Blue
#  [1. 0. 0.]] # Red
​
print("\n特征名称:")
print(encoder.get_feature_names_out())
# 输出:['x0_Blue' 'x0_Green' 'x0_Red']
​
# 处理新数据(包含未知类别 ‘Yellow’)
new_data = [['Blue'], ['Red'], ['Yellow']]
X_new_encoded = encoder.transform(new_data)
print("\n新数据编码后的特征矩阵:")
print(X_new_encoded)
# 输出:
# [[0. 1. 0. 0.]  # Blue -> [0, 1, 0]
#  [1. 0. 0. 0.]  # Red -> [1, 0, 0]
#  [0. 0. 0. 0.]] # Yellow (未知) -> [0, 0, 0],因为设置了 handle_unknown='ignore'

运行输出:

编码后的特征矩阵:
[[0. 0. 1.]
 [1. 0. 0.]
 [0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 1.]]
​
特征名称:
['x0_Blue' 'x0_Green' 'x0_Red']
​
新数据编码后的特征矩阵:
[[1. 0. 0.]
 [0. 0. 1.]
 [0. 0. 0.]]

字典特征提取-DictVectorizer

将特征名称和值映射为字典的列表转换为数值矩阵,非常适合处理混合类型的特征。

DictVectorizer 是一个非常有用的工具,它用于将特征名称到特征值的映射字典(例如列表)转换为 scikit-learn 模型可以使用的数值矩阵。它的强大之处在于能同时处理分类特征(进行One-Hot编码)和数值特征(保持原值),非常适用于处理混合类型的特征或从类似JSON结构的数据中提取特征。

核心参数详解

  1. dtype (默认 numpy.float64)
  • 作用: 指定输出数组的数据类型。
  • 说明: 为了节省内存,你可以将其设置为更小的数值类型,例如 np.float32np.int32。因为特征矩阵中会包含原始的数值型特征,所以通常不使用 bool 类型。
  1. separator (默认 =)
  • 作用: 用于构造输出矩阵中特征名称的分隔符字符串。当转换分类特征时,DictVectorizer 会生成形如 [特征名][分隔符][类别名] 的新列名。

  • 示例:

    • 有一个特征 ‘animal’,其值为 ‘cat’
    • 默认 separator=‘=’ 会生成新特征名:‘animal=cat’
    • 如果设置 separator=‘_’,则会生成:‘animal_cat’
  • 用途: 主要用于控制输出特征名的可读性和格式。

  1. sparse (默认 True)
  • 作用: 决定转换后的输出是稀疏矩阵还是密集矩阵(numpy数组)。

  • 可选值:

    • True (默认): 返回 scipy.sparse 稀疏矩阵。这是默认行为,因为One-Hot编码会产生大量0值,使用稀疏矩阵可以极大地节省内存
    • False: 返回常规的 numpy.ndarray 密集数组。这样更易于查看和理解,但当特征维度很高时,可能会消耗巨大内存。
  • 注意: 这个参数与新版 OneHotEncodersparse_output 功能相同,但名称不同。

  1. sort (默认 True)
  • 作用: 在拟合时是否对特征名称进行排序。

  • 可选值:

    • True (默认): 对生成的特征名称(列名)进行排序。这能保证每次运行的结果一致,可重现性好。
    • False: 不排序,特征列的顺序将由Python字典的随机性决定(在Python 3.6+中,字典是有序的,但顺序取决于插入顺序)。通常不推荐设置为 False,除非你非常关心特征列的原始顺序。

我们来看一个示例:

from sklearn.feature_extraction import DictVectorizer
​
# 示例数据,列表中的每个元素都是一个字典,代表一个样本
data = [    {'age': 25, 'city': 'New York', 'income': 50000},    {'age': 30, 'city': 'Boston', 'income': 65000},    {'age': 35, 'city': 'New York', 'income': 75000}]
​
# 初始化 DictVectorizer
dict_vectorizer = DictVectorizer(sparse=True)
​
# 学习并转换数据
X_dict = dict_vectorizer.fit_transform(data)
​
print("转换后的特征矩阵:")
print(X_dict)
# 稀疏矩阵输出:
# Coords    Values
#   (0, 0)  25.0
#   (0, 2)  1.0
#   (0, 3)  50000.0
#   (1, 0)  30.0
#   (1, 1)  1.0
#   (1, 3)  65000.0
#   (2, 0)  35.0
#   (2, 2)  1.0
#   (2, 3)  75000.0print("\n特征名称:")
print(dict_vectorizer.get_feature_names_out())
# 输出:['age', 'income', 'city=Boston', 'city=New York']
# 它自动将数值型特征 'age', 'income' 保留,将类别型特征 'city' 进行了 One-Hot 编码。

文本特征提取

词袋模型 - CountVectorizer

它将文本文档集合转换为一个令牌计数的矩阵。

我们来深入解析 sklearn.feature_extraction.text.CountVectorizer 的核心参数。CountVectorizer 是文本处理中最基础、最常用的工具之一,它的核心功能是将文本语料库转换为词频计数矩阵(也称为“词袋”模型)。

工作流程概述

理解参数之前,先了解其默认工作流程:

  1. 分词 (Tokenization) : 将每个文档(文本字符串)转换为单词(token)列表。
  2. 构建词表 (Vocabulary Building) : 从所有文档中找出所有唯一的单词,并为其分配一个索引。这个词表也称为“词典”。
  3. 编码与计数 (Encoding & Counting) : 对于每个文档,统计词表中每个单词出现的次数,形成一个向量。

最终输出是一个矩阵,其中:

  • 代表每个文档。
  • 代表词表中的每个单词(特征)。
  • 代表该单词在对应文档中出现的次数。

核心参数详解(按处理流程分组)

  1. 分词器相关参数 (Tokenization)

这些参数控制如何将文本拆分成单词(token)。

  • token_pattern (默认 r"(?u)\b\w\w+\b")

    • 作用: 一个正则表达式字符串,用于定义什么构成一个“token”。只有匹配这个模式的字符串才会被保留为单词。

    • 详解

      • 默认模式 (?u)\b\w\w+\b 的含义:

        • (?u): 启用Unicode匹配模式。
        • \b: 单词边界。
        • \w\w+: 匹配任何字母、数字、下划线(在Unicode模式下包括中文等)字符,且长度至少为2。
      • 示例: 如果你想保留单字母单词(如英文的 “a”, “I”),可以修改为 r"(?u)\b\w+\b"

  • tokenizer (默认 None)

    • 作用: 指定一个可调用对象(函数),用于覆盖默认的分词流程。

    • 用途: 这是处理中文文本的关键参数。默认的分词器基于空格和正则,对中文无效(因为它不按空格分)。你需要使用第三方中文分词库(如 jieba),并传入其分词函数。

    • 示例

      import jieba
      from sklearn.feature_extraction.text import CountVectorizer
      ​
      def chinese_tokenizer(text):
          return list(jieba.cut(text)) # 使用结巴分词
      ​
      vectorizer = CountVectorizer(tokenizer=chinese_tokenizer)
      
  • lowercase (默认 True)

    • 作用: 在分词之前,是否将所有字符转换为小写。
    • 注意: 对于英文等大小写敏感的语言,这非常有用(确保“Apple”和“apple”被视为同一个词)。对于中文,此参数无效。
  • preprocessor (默认 None)

    • 作用: 指定一个可调用对象(函数),用于在分词之前完全覆盖整个预处理步骤。
    • 用途: 如果你想进行非常自定义的文本清洗,例如去除HTML标签、替换特殊字符等,可以在这里定义。
  • strip_accents (默认 None)

    • 作用: 在预处理期间去除重音符号。例如,将 ‘é’ 变为 ‘e’。
    • 可选值‘ascii’, ‘unicode’, None。主要用于欧洲语言。
  1. 停用词相关参数 (Stop Words)

停用词是指在文本中频繁出现但本身没有太多实际意义的词(如英文的 “the”, “and”, “is”)。

  • stop_words (默认 None)

    • 作用: 指定停用词列表。这些词将被从词表中剔除,不会作为特征出现。

    • 可选值

      • ‘english’: 使用scikit-learn内置的英语停用词列表。
      • None (默认): 不使用任何停用词。
      • list: 可以传入一个自定义的停用词列表。
    • 好处: 移除停用词可以减少特征维度,提高模型训练速度,并有时能提升模型性能(减少噪音)。

  1. N-Gram 相关参数

N-Gram 是指由N个连续的单词组成的序列。CountVectorizer 不仅可以处理单个词(unigram),还可以处理词组。

  • ngram_range (默认 (1, 1))

    • 作用: 要提取的n-gram的上下限。

    • 详解

      • (1, 1): 只提取单个词(unigram)。
      • (1, 2): 提取单个词和二元词组(bigram)。例如,“this is” 和 “is good” 也会被作为特征。
      • (2, 2): 只提取二元词组。
    • 用途: 使用bigram或更高阶的gram可以捕获一些上下文信息,但会极大地增加特征维度

  • analyzer (默认 ‘word’)

    • 作用: 定义特征应该基于“单词”还是“字符”级别生成。

    • 可选值

      • ‘word’: 按单词生成n-gram。
      • ‘char’: 按字符生成n-gram。例如,可以提取字符级别的n-gram(如 ‘ab’, ‘bc’)作为特征,适用于某些特定领域(如基因序列分析、拼写错误检测)。
  1. 词表特征选择参数 (Vocabulary Filtering)

这些参数用于在构建词表时自动过滤掉一些不重要的词,是控制特征维度、防止“维度爆炸” 最重要的手段。

  • max_df (默认 1.0)

    • 作用: 忽略那些文档频率高于给定阈值的词。用于剔除“过于常见”的词(可能包括停用词或特定领域的常见词)。

    • 可选值

      • float (0.0到1.0之间): 表示比例。例如 0.95 表示忽略出现在 95% 以上文档中的词。
      • int: 表示绝对频数。例如 10 表示忽略出现在超过10个文档中的词。
  • min_df (默认 1)

    • 作用: 忽略那些文档频率低于给定阈值的词。用于剔除“过于罕见”的词(可能是拼写错误或没有统计意义的词)。
    • 可选值: 同 max_df,可以是比例或绝对频数。
    • 示例min_df=2 会忽略所有只在一个文档中出现过的词。
  • max_features (默认 None)

    • 作用: 限制词表的大小,只取整个语料中词频最高的 max_features 个词。
    • 用途: 这是一种简单粗暴但非常有效的降维方法。例如,设置 max_features=5000 将只保留最常见的5000个词作为特征。
  • vocabulary (默认 None)

    • 作用: 可以手动传入一个预先定义好的词表(字典:{word: index} 或列表)。
    • 用途: 如果你已经有一个固定的词表,希望 CountVectorizer 只基于这些词进行转换,而不从数据中学习。
  1. 输出格式参数
  • binary (默认 False)

    • 作用: 如果为 True,所有非零计数都设置为1。这意味着特征不再表示“词频”,而只表示“是否出现”(即二进制指示特征)。
    • 用途: 在一些算法(如 Bernoulli Naive Bayes)或当词频本身不是很重要时,使用二进制输出可能更有效。
  • dtype (默认 numpy.int64)

    • 作用: 指定输出矩阵的数据类型。为了节省内存,有时会使用 np.int32

我们看一个示例:

from sklearn.feature_extraction.text import CountVectorizer
​
# 示例文本数据
corpus = [    'I love love machine learning',    'Machine learning is fun',    'I love coding in Python']
​
# 初始化 CountVectorizer
# min_df: 忽略文档频率低于此值的词
# stop_words: 移除停用词(如 'the', 'is', 'and')
vectorizer = CountVectorizer(min_df=1, stop_words='english')
​
# 学习词汇字典并转换文本数据
X = vectorizer.fit_transform(corpus)
​
# 查看生成的词汇表
print("词汇表(特征名):")
print(vectorizer.get_feature_names_out())
# 输出:['coding' 'fun' 'learning' 'love' 'machine' 'python']
​
# 查看稠密矩阵(实际应用中矩阵通常非常稀疏,用.toarray()查看,但用X直接计算)
print("\n特征向量矩阵:")
print(X.toarray())
print("\n稀疏矩阵:")
print(X)
# 输出:
# [[0 0 1 2 1 0]
#  [0 1 1 0 1 0]
#  [1 0 0 1 0 1]]

如果进行中文文本特征提取,我们首先要做的是中文分词,我们使用jieba库。

pip install jieba -i http://mirrors.aliyun.com/pypi/simple/  --trusted-host mirrors.aliyun.com

先对语料库进行中文分词处理,然后再进行文本提取。注意我们这里的停用词,是开源网站主流的停用词库-stopWords.txt,里面包含了所有不需要,不重要的中英文词语包括符号。

我们看一个示例:

import jieba
from sklearn.feature_extraction.text import CountVectorizer
​
# 示例文本数据
corpus = [
    'A股国产软件概念股全线大涨,开普云、正元智慧、君逸数码等好多强势大涨,另有好多只概念股大涨。消息面上,全新一代中国操作系统——银河麒麟操作系统在“2025中国操作系统产业大会”正式发布。',
    '上海壹号院五批次好多开盘,数套房源1小时开盘售罄,劲销好多。至此好多,上海壹号院今年好多累计好多开盘总销售金额超,好多继续保持全国单盘销冠位置。',
    '当日,在江苏南京举行的2025江苏省城市足球联赛好多第九轮比赛中,南京队对阵盐城队。南京市在部分商场、街区等地设置好多观赛“第二现场”,使用大屏幕同步直播赛事,同时好多设有游戏互动区、烟火市集区,让球迷们度过欢乐时光。'
]   
​
def cut_word(text):
    """
    jieba中文分词
    :return:
    """
    new_text = " ".join(list(jieba.cut(text)))
    return new_text
​
# 普通写法
new_corpus = []
for c in corpus:
    new_corpus.append(cut_word(c))
print(new_corpus)
# 高手写法 推导式 一句话搞定
# new_corpus = [' '.join(list(jieba.cut(x))) for x in corpus]
# print(new_corpus)# 初始化 CountVectorizer
# min_df: 忽略文档频率低于此值的词
# stop_words: 移除停用词
vectorizer = CountVectorizer(min_df=1,
                             stop_words=[line.strip() for line in open('stopWords.txt', encoding='UTF-8').readlines()])
​
# 学习词汇字典并转换文本数据
X = vectorizer.fit_transform(new_corpus)
​
# 查看生成的词汇表
print("词汇表(特征名):")
print(vectorizer.get_feature_names_out())
​
# 查看稠密矩阵(实际应用中矩阵通常非常稀疏,用.toarray()查看,但用X直接计算)
print("\n特征向量矩阵:")
print(X.toarray())
print("\n稀疏矩阵:")
print(X)

运行结果:比如'每天'这个词,属于停用词里面的,所以不作为特征。

['A股 国产软件 概念股 全线 大涨 , 开普云 、 正 元 智慧 、 君逸 数码 等 好多 强势 大涨 , 另有 好多 只 概念股 大涨 。 消息 面上 , 全新 一代 中国 操作系统 — — 银河 麒麟 操作系统 在 “ 2025 中国 操作系统 产业 大会 ” 正式 发布 。', '上海 壹号 院五 批次 好多 开盘 , 数 套房 源 1 小时 开盘 售罄 , 劲销 好多 。 至此 好多 , 上海 壹号 院 今年 好多 累计 好多 开盘 总 销售 金额 超 , 好多 继续 保持 全国 单盘 销冠 位置 。', '当日 , 在 江苏 南京 举行 的 2025 江苏省 城市 足球联赛 好多 第九轮 比赛 中 , 南京队 对阵 盐城 队 。 南京市 在 部分 商场 、 街区 等 地 设置 好多 观赛 “ 第二 现场 ” , 使用 大屏幕 同步 直播 赛事 , 同时 好多 设有 游戏 互动 区 、 烟火 市集 区 , 让 球迷 们 度过 欢乐 时光 。']
词汇表(特征名):
['2025' 'a股' '一代' '上海' '中国' '互动' '产业' '位置' '全国' '全新' '全线' '劲销' '单盘' '南京'
 '南京市' '南京队' '发布' '另有' '同步' '君逸' '售罄' '商场' '国产软件' '城市' '壹号' '大会' '大屏幕'
 '大涨' '套房' '好多' '对阵' '小时' '市集' '度过' '开普云' '开盘' '强势' '当日' '批次' '操作系统' '数码'
 '时光' '智慧' '概念股' '欢乐' '正式' '比赛' '江苏' '江苏省' '消息' '游戏' '烟火' '现场' '球迷' '盐城'
 '直播' '第九轮' '累计' '至此' '街区' '观赛' '设有' '设置' '赛事' '足球联赛' '金额' '银河' '销冠' '销售'
 '院五' '面上' '麒麟']
​
特征向量矩阵:
[[1 1 1 0 2 0 1 0 0 1 1 0 0 0 0 0 1 1 0 1 0 0 1 0 0 1 0 3 0 2 0 0 0 0 1 0
  1 0 0 3 1 0 1 2 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1]
 [0 0 0 2 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 2 0 0 0 1 6 0 1 0 0 0 3
  0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 1 1 1 0 0]
 [1 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 0 1 0 0 1 0 1 0 0 1 0 0 3 1 0 1 1 0 0
  0 1 0 0 0 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0]]
​
稀疏矩阵:
<Compressed Sparse Row sparse matrix of dtype 'int64'
    with 75 stored elements and shape (3, 72)>
  Coords    Values
  (0, 1)    1
  (0, 22)   1
  (0, 43)   2
  (0, 10)   1
  (0, 27)   3
  (0, 34)   1
  (0, 42)   1
  (0, 19)   1
  (0, 40)   1
  (0, 29)   2
  (0, 36)   1
  (0, 17)   1
  (0, 49)   1
  (0, 70)   1
  (0, 9)    1
  (0, 2)    1
  (0, 4)    2
  (0, 39)   3
  (0, 66)   1
  (0, 71)   1
  (0, 0)    1
  (0, 6)    1
  (0, 25)   1
  (0, 45)   1
  (0, 16)   1
  : :
  (2, 64)   1
  (2, 56)   1
  (2, 46)   1
  (2, 15)   1
  (2, 30)   1
  (2, 54)   1
  (2, 14)   1
  (2, 21)   1
  (2, 59)   1
  (2, 62)   1
  (2, 60)   1
  (2, 52)   1
  (2, 26)   1
  (2, 18)   1
  (2, 55)   1
  (2, 63)   1
  (2, 61)   1
  (2, 50)   1
  (2, 5)    1
  (2, 51)   1
  (2, 32)   1
  (2, 53)   1
  (2, 33)   1
  (2, 44)   1
  (2, 41)   1

向量的定义:

在数学中,向量(也称为欧几里得向量、几何向量),指具有大小(magnitude)和方向的量

TF-IDF - TfidfVectorizer

要理解 TfidfVectorizer,首先必须理解 TF-IDF 的含义。它是一种用于信息检索和文本挖掘的常用加权技术,用以评估一个词语对于一个语料库中的一份文档的重要程度。

  • TF (Term Frequency - 词频) : 一个词在当前文档中出现的频率。

    • 计算公式:TF(t) = (词t在当前文档中出现的次数) / (当前文档中所有词的总数)
    • 思想: 一个词在文档中出现的次数越多,它对该文档越重要。
  • IDF (Inverse Document Frequency - 逆文档频率) : 一个词在整个语料库中的普遍重要性。

    • 计算公式:IDF(t) = log( (总文档数) / (包含词t的文档数 + 1) ) 备注: log底数是10
    • 思想: 如果一个词在很多文档中都出现(如“的”、“是”),那么它就是一个常见词,其重要性应该被降低(IDF值小)。反之,如果一个词只在少数几篇文档中出现,那么它就能很好地代表那些文档的特点(IDF值大)。
  • TF-IDF: 将两者相乘。

    • TF-IDF(t) = TF(t) * IDF(t)
    • 最终思想一个词的重要性随着它在当前文档中出现的次数成正比增加,但同时会随着它在整个语料库中出现的频率成反比下降。这有效地过滤掉了常见的词语,保留了重要的词语。

TfidfVectorizer 是什么?

TfidfVectorizer 可以看作是 CountVectorizer 的升级版和集成版。它的工作流程分为两步:

  1. 使用 CountVectorizer 的方法: 首先,它将原始文本转换为词频计数矩阵。
  2. 应用 TF-IDF 变换: 然后,它对第一步得到的词频矩阵进行 TF-IDF 加权计算,将原始的计数转换成一个更能体现词语重要性的权重矩阵。

简单来说:TfidfVectorizer = CountVectorizer + TfidfTransformer

核心参数详解

TfidfVectorizer 继承了 CountVectorizer 的所有参数,用于控制分词、构建词表等过程。这意味着你之前为 CountVectorizer 学的所有参数(如 max_df, min_df, stop_words, ngram_range, tokenizer 等)在这里完全适用。

除此之外,它还有自己独有的几个核心参数,主要用于控制 TF-IDF 的计算方式:

  1. norm (默认 'l2')
  • 作用: 对每个文档(每行)进行向量归一化。

  • 可选值

    • 'l2' (默认): 将每个文档的向量归一化为欧几里得范数(L2范数)为1。计算公式:v = v / sqrt(sum(v**2))。这是最常用的设置,使得不同长度的文档之间的向量可以进行比较(计算余弦相似度时直接点积即可)。
    • 'l1': 将每个文档的向量归一化为曼哈顿范数(L1范数)为1。计算公式:v = v / sum(|v|)
    • None: 不进行任何归一化。如果你后续自己进行归一化,或者使用对特征尺度不敏感的模型(如树模型),可以关闭它。
  1. use_idf (默认 True)
  • 作用: 是否启用 IDF 加权。如果设置为 False,则公式中将 IDF(t)=1,相当于只使用词频(TF),退化为一个归一化的 CountVectorizer
  • 用途: 通常保持默认的 True 来利用 IDF 的强大效果。在某些特定场景下可能会关闭。
  1. smooth_idf (默认 True)
  • 作用: 是否平滑 IDF 权重,防止除零错误。

  • 详解

    • True (默认): 使用平滑后的 IDF 公式:idf(t) = log( (1 + 总文档数) / (1 + 包含词t的文档数) ) + 1。这是一种常见的平滑方式。
    • False: 使用原始 IDF 公式:idf(t) = log( 总文档数 / 包含词t的文档数 ) + 1(注意scikit-learn的实现中默认加了1)。
  • 建议: 保持默认的 True,使统计更稳定。

  1. sublinear_tf (默认 False)
  • 作用: 是否对词频(TF)进行亚线性缩放。

  • 详解

    • False (默认): 使用原始词频。
    • True: 将词频 tf 替换为 1 + log(tf)。这样做的目的是减弱高频词出现次数过多带来的线性影响。例如,一个词出现20次,并不意味着它是出现1次的那个词的20倍重要。使用对数缩放可以缓解这种线性关系。

我们看一个示例:

import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
​
# 示例文本数据
corpus = [
    'A股国产软件概念股全线大涨,开普云、正元智慧、君逸数码等好多强势大涨,另有好多只概念股大涨。消息面上,全新一代中国操作系统——银河麒麟操作系统在“2025中国操作系统产业大会”正式发布。',
    '上海壹号院五批次好多开盘,数套房源1小时开盘售罄,劲销好多。至此好多,上海壹号院今年好多累计好多开盘总销售金额超,好多继续保持全国单盘销冠位置。',
    '当日,在江苏南京举行的2025江苏省城市足球联赛好多第九轮比赛中,南京队对阵盐城队。南京市在部分商场、街区等地设置好多观赛“第二现场”,使用大屏幕同步直播赛事,同时好多设有游戏互动区、烟火市集区,让球迷们度过欢乐时光。'
]
​
def cut_word(text):
    """
    jieba中文分词
    :return:
    """
    new_text = " ".join(list(jieba.cut(text)))
    return new_text
​
​
# 普通写法
new_corpus = []
for c in corpus:
    new_corpus.append(cut_word(c))
print(new_corpus)
# 高手写法 推导式 一句话搞定
# new_corpus = [' '.join(list(jieba.cut(x))) for x in corpus]
# print(new_corpus)# 初始化 TfidfVectorizer
# min_df: 忽略文档频率低于此值的词
# stop_words: 移除停用词
vectorizer = TfidfVectorizer(min_df=1,
                             stop_words=[line.strip() for line in open('stopWords.txt', encoding='UTF-8').readlines()])
​
# 学习词汇字典并转换文本数据
X = vectorizer.fit_transform(new_corpus)
​
# 查看生成的词汇表
print("词汇表(特征名):")
print(vectorizer.get_feature_names_out())
​
# 查看稠密矩阵(实际应用中矩阵通常非常稀疏,用.toarray()查看,但用X直接计算)
print("\n特征向量矩阵:")
print(X.toarray())
print("\n稀疏矩阵:")
print(X)
​
# 查看第一句的TF-IDF权重(只显示权重非零的特征)
print(f"\n--- 第一句话的TF-IDF向量 ---")
feature_names = vectorizer.get_feature_names_out()
first_doc_vector = X[0].toarray()[0]  # 取第一行,并转换为密集数组
# 创建一个(特征名, 权重)的列表,并按权重降序排序
sorted_weights = sorted(zip(feature_names, first_doc_vector), key=lambda x: x[1], reverse=True)
# 打印权重大于0.2的特征
for word, weight in sorted_weights:
    if weight > 0.2:
        print(f"{word}: {weight:.4f}")

运行结果:

['A股 国产软件 概念股 全线 大涨 , 开普云 、 正 元 智慧 、 君逸 数码 等 好多 强势 大涨 , 另有 好多 只 概念股 大涨 。 消息 面上 , 全新 一代 中国 操作系统 — — 银河 麒麟 操作系统 在 “ 2025 中国 操作系统 产业 大会 ” 正式 发布 。', '上海 壹号 院五 批次 好多 开盘 , 数 套房 源 1 小时 开盘 售罄 , 劲销 好多 。 至此 好多 , 上海 壹号 院 今年 好多 累计 好多 开盘 总 销售 金额 超 , 好多 继续 保持 全国 单盘 销冠 位置 。', '当日 , 在 江苏 南京 举行 的 2025 江苏省 城市 足球联赛 好多 第九轮 比赛 中 , 南京队 对阵 盐城 队 。 南京市 在 部分 商场 、 街区 等 地 设置 好多 观赛 “ 第二 现场 ” , 使用 大屏幕 同步 直播 赛事 , 同时 好多 设有 游戏 互动 区 、 烟火 市集 区 , 让 球迷 们 度过 欢乐 时光 。']
词汇表(特征名):
['2025' 'a股' '一代' '上海' '中国' '互动' '产业' '位置' '全国' '全新' '全线' '劲销' '单盘' '南京'
 '南京市' '南京队' '发布' '另有' '同步' '君逸' '售罄' '商场' '国产软件' '城市' '壹号' '大会' '大屏幕'
 '大涨' '套房' '好多' '对阵' '小时' '市集' '度过' '开普云' '开盘' '强势' '当日' '批次' '操作系统' '数码'
 '时光' '智慧' '概念股' '欢乐' '正式' '比赛' '江苏' '江苏省' '消息' '游戏' '烟火' '现场' '球迷' '盐城'
 '直播' '第九轮' '累计' '至此' '街区' '观赛' '设有' '设置' '赛事' '足球联赛' '金额' '银河' '销冠' '销售'
 '院五' '面上' '麒麟']
​
特征向量矩阵:
[[0.11096513 0.14590581 0.14590581 0.         0.29181161 0.
  0.14590581 0.         0.         0.14590581 0.14590581 0.
  0.         0.         0.         0.         0.14590581 0.14590581
  0.         0.14590581 0.         0.         0.14590581 0.
  0.         0.14590581 0.         0.43771742 0.         0.17234864
  0.         0.         0.         0.         0.14590581 0.
  0.14590581 0.         0.         0.43771742 0.14590581 0.
  0.14590581 0.29181161 0.         0.14590581 0.         0.
  0.         0.14590581 0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.14590581 0.         0.         0.         0.14590581 0.14590581]
 [0.         0.         0.         0.303038   0.         0.
  0.         0.151519   0.151519   0.         0.         0.151519
  0.151519   0.         0.         0.         0.         0.
  0.         0.         0.151519   0.         0.         0.
  0.303038   0.         0.         0.         0.151519   0.53693738
  0.         0.151519   0.         0.         0.         0.45455701
  0.         0.         0.151519   0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.         0.         0.
  0.         0.         0.         0.151519   0.151519   0.
  0.         0.         0.         0.         0.         0.151519
  0.         0.151519   0.151519   0.151519   0.         0.        ]
 [0.13097368 0.         0.         0.         0.         0.17221465
  0.         0.         0.         0.         0.         0.
  0.         0.17221465 0.17221465 0.17221465 0.         0.
  0.17221465 0.         0.         0.17221465 0.         0.17221465
  0.         0.         0.17221465 0.         0.         0.30513824
  0.17221465 0.         0.17221465 0.17221465 0.         0.
  0.         0.17221465 0.         0.         0.         0.17221465
  0.         0.         0.17221465 0.         0.17221465 0.17221465
  0.17221465 0.         0.17221465 0.17221465 0.17221465 0.17221465
  0.17221465 0.17221465 0.17221465 0.         0.         0.17221465
  0.17221465 0.17221465 0.17221465 0.17221465 0.17221465 0.
  0.         0.         0.         0.         0.         0.        ]]
​
稀疏矩阵:
<Compressed Sparse Row sparse matrix of dtype 'float64'
    with 75 stored elements and shape (3, 72)>
  Coords    Values
  (0, 1)    0.14590580580506213
  (0, 22)   0.14590580580506213
  (0, 43)   0.29181161161012426
  (0, 10)   0.14590580580506213
  (0, 27)   0.4377174174151864
  (0, 34)   0.14590580580506213
  (0, 42)   0.14590580580506213
  (0, 19)   0.14590580580506213
  (0, 40)   0.14590580580506213
  (0, 29)   0.17234863865385786
  (0, 36)   0.14590580580506213
  (0, 17)   0.14590580580506213
  (0, 49)   0.14590580580506213
  (0, 70)   0.14590580580506213
  (0, 9)    0.14590580580506213
  (0, 2)    0.14590580580506213
  (0, 4)    0.29181161161012426
  (0, 39)   0.4377174174151864
  (0, 66)   0.14590580580506213
  (0, 71)   0.14590580580506213
  (0, 0)    0.11096512610302138
  (0, 6)    0.14590580580506213
  (0, 25)   0.14590580580506213
  (0, 45)   0.14590580580506213
  (0, 16)   0.14590580580506213
  : :
  (2, 64)   0.17221464824360078
  (2, 56)   0.17221464824360078
  (2, 46)   0.17221464824360078
  (2, 15)   0.17221464824360078
  (2, 30)   0.17221464824360078
  (2, 54)   0.17221464824360078
  (2, 14)   0.17221464824360078
  (2, 21)   0.17221464824360078
  (2, 59)   0.17221464824360078
  (2, 62)   0.17221464824360078
  (2, 60)   0.17221464824360078
  (2, 52)   0.17221464824360078
  (2, 26)   0.17221464824360078
  (2, 18)   0.17221464824360078
  (2, 55)   0.17221464824360078
  (2, 63)   0.17221464824360078
  (2, 61)   0.17221464824360078
  (2, 50)   0.17221464824360078
  (2, 5)    0.17221464824360078
  (2, 51)   0.17221464824360078
  (2, 32)   0.17221464824360078
  (2, 53)   0.17221464824360078
  (2, 33)   0.17221464824360078
  (2, 44)   0.17221464824360078
  (2, 41)   0.17221464824360078--- 第一句话的TF-IDF向量 ---
大涨: 0.4377
操作系统: 0.4377
中国: 0.2918
概念股: 0.2918

数学知识:

数学里log表示对数。简单理解,log₂8 = 3 对数就是3 反推 2的3次方就是8

image.png