特征工程介绍

使用专业背景知识与技巧处理数据,使特征能在机器学习中起到更好的作用

特征工程包含内容

  • 特征提取
  • 特征预处理
  • 特征降维
特征提取

机器学习算法 = 统计方法 = 数学公式
文本类型 --> 数值
类型 --> 数值

  1. 将任意数据(文本或图像)转换为可用于机器学习的数字特征
  • 字典特征提取(特征离散化)
  • 文本特征提取
  • 图像特征提取(深度学习)
  1. 特征提取API

sklearn.feature_extraction

字典特征提取

作用:将字典数据进行特征值化 (对于特征当中存在类别信息的都叫做one-hot编码处理)

  • sklearn.feature_extraction.DictVectorizer(sparse=True, …)
  • DictVectorizer.fit_transform(X) X:字典或者包含字典的迭代器返回值:返回sparse矩阵
  • DictVectorizer.inverse_transform(X) X:array数组或者sparse矩阵 返回值:转换之前的数据格式
  • DictVectorier.get_feature_names() 返回类别名称
    应用:
对以下数据进行特征提取
[{'city':'北京', 'temperature':100},
{'city':'上海', 'temperature':50},
{'city':'深圳', 'temperature':19}]

结果为
[[0, 1, 0, 100.]
[1, 0, 0, 50.]
[0, 0, 1, 19.]]

对于数据与特征名字以及生成矩阵进行分析:

特征名字为: 上海 北京 深圳

1 0 0 代表上海,每一行的第1个元素代表北京这个特征值是否出现,出现为1,否则为0

0 1 0 代表北京,0 0 1 代表深圳。以此类推,如果特征值更多,则有更多的0出现。

如有4个特征值,则矩阵变为5列,代表北京的这一行为[0 1 0 0 100]

100,50,19代表气温特征值

代码实现

def dict_demo():
    """
    字典特征提取
    :return:
    """
    data = [{'city': '北京', 'temperature': 100},
            {'city': '上海', 'temperature': 50},
            {'city': '深圳', 'temperature': 19}]
    # 1. 实例化一个转换器类
    transfer = DictVectorizer(sparse=False)
    # 2. 调用fit_transform()
    data_new = transfer.fit_transform(data)
    print("特征提取后的数据:\n", data_new)

流程分析:

  1. 实例化一个转换器类
  2. 调用fit_transform() (注意返回格式:观察转换器类中sparse的参数)
  3. 返回一个sparse矩阵或正常矩阵

sparse 稀疏 : 稀疏矩阵会将非零值 按位置表示出来 (可以节省内存,提高加载效率)

应用场景:

  • pclass, sex (数据集当中类别特征较多)
  1. 将数据集特征 -> 字典类型
  2. DictVectorizer转换
  • 本身的数据就是字典类型, 使用字典特征抽取
文本特征提取

将单词、短语、句子、字母 作为特征
特征:由特征词构成
作用:将文本数据进行特征值化

方法1:CountVectorizer

统计每个样本特征词出现的个数

  • sklearn.feature_extraction.text.CountVectorizer(stop_words=[])
  • 返回词频矩阵
  • stop_words=[] 为停用词,如果在其中加上词汇,则在后面需要特征提取的句子中,就不会对停用词进行特征提取
  • CountVectorizer.fit_transform(X) X: 文本或者包含文本字符串的可迭代对象 返回值:sparse矩阵
  • CountVectorizer.inverse_transform(X) X: array数组或者sparse矩阵 返回值:转换之前数据格
  • CountVectorizer.get_feature_names() 返回值:单词列表
  • sklearn.featrue_extraction.text.TfidfVectorizer

应用:

对以下数据进行特征提取
data = ["life is short, i like like python", "life is too long, i dislike python"]

提取结果如下:
data_new:
 [[0 1 1 2 0 1 1 0]
 [1 1 1 0 1 1 0 1]]
特征名字:
 ['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too']

对于数据与特征名字以及生成矩阵进行分析:
矩阵中的两行分别对应原数据的两句话,每一个矩阵中的元素都代表着原数据中的句子中出现了几次特征值。
如:[0 1 1 2 0 1 1 0] 对应 dislike is life like long python short too 在life is short, i like like python中分别出现了几次。
原句中 dislike 没有出现,所以代表dislike特征值的元素为0,is出现了1次,所以代表is特征值的元素为1,life出现了1次,所以代表life特征值的元素为1,like出现了2次,所以代表like特征值的元素为2,以此类推。

代码实现:

def count_demo():
    """
    文本特征抽取 CountVectorizer
    :return:
    """
    data = ["life is short, i like like python", "life is too long, i dislike python"]
    # 1. 实例化一个转换器类
    transfer = CountVectorizer()
    # 2. 调用fit_transform
    data_new = transfer.fit_transform(data)
    print("data:\n", data)
    print("data_new:\n", data_new.toarray())
    print("特征名字:\n", transfer.get_feature_names())

    return None
中文文本特征抽取

将特征名字改为中文,以空格分词或以句子分词:

如果 data = [“我爱北京天安门”, “天安门上太阳升”], 特征名字将为这两个短句: 特征名字 = [“我爱北京天安门”, “天安门上太阳升”]

应用:(转换规则同上面的英文文本特征提取)

对以下数据进行特征提取
data:
 ['我 爱 北京 天安门', '天安门 上 太阳 升']
data_new:
 [[1 1 0]
 [0 1 1]]
特征名字:
 ['北京', '天安门', '太阳']

代码实现:

def count_chinese_demo():
    """
    中文文本特征抽取 CountVectorizer
    :return:
    """
    data = ["我 爱 北京 天安门", "天安门 上 太阳 升"]
    # 1. 实例化一个转换器类
    transfer = CountVectorizer()
    # 2. 调用fit_transform
    data_new = transfer.fit_transform(data)
    print("data:\n", data)
    print("data_new:\n", data_new.toarray())
    print("特征名字:\n", transfer.get_feature_names())

    return None

但是,在进行中文文本分词时,我们往往希望可以由程序来自动分词(上面的示例为手动加空格分词)

此时,需要使用jieba分词处理!

jieba分词处理

  • jieba.cut()
  • 返回词语组成的生成器 (jieba.cut(str))

需要安装 jieba 库

pip3 install jieba

案例分析

对于以下三句话进行特征值化:

今天很残酷,明天更残酷,后天很美好,

但绝大部分人是难以熬过明天,所以每个人都不要放弃今天

我们看到的从很远星系来的光是在几百万年之前发出的,

这样当我们看到宇宙时,我们是在看它的过去

如果只用一种方式了解某样事务,你就不会真正了解它

了解事物真正含义的秘密取决于如何将其于我们所了解的事物相联系

流程分析

  • 准备句子,利用jieba.cut进行分词
  • 实例化CountVectorizer
  • 将分词结果编程字符串当做fit_transform的输入值

应用

将下列三句话进行特征提取与自动分词
data_str = ["今天很残酷,明天更残酷,后天很美好,但绝大部分人是难以熬过明天,所以每个人都不要放弃今天",
            "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去",
            "如果只用一种方式了解某样事务,你就不会真正了解它,了解事物真正含义的秘密取决于如何将其于我们所了解的事物相联系"]
          
结果为:
data:
 ['今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝大部分 人 是 难以 熬过 明天 , 所以 每个 人 都 不要 放弃 今天', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去', '如果 只用 一种 方式 了解 某样 事务 , 你 就 不会 真正 了解 它 , 了解 事物 真正 含义 的 秘密 取决于 如何 将 其于 我们 所 了解 的 事物 相 联系']
data_new:
 [[0 0 0 0 0 2 0 0 0 0 0 1 0 0 0 0 0 1 1 0 2 0 0 2 1 1 0 0 0 1 1 0 0 0 1]
 [0 1 0 0 0 0 1 0 1 1 0 0 0 0 0 1 3 0 0 0 0 1 0 0 0 0 2 0 0 0 0 0 1 1 0]
 [1 0 4 1 2 0 0 1 0 0 1 0 1 1 1 0 1 0 0 1 0 0 1 0 0 0 0 2 1 0 0 1 0 0 0]]
特征名字:
 ['不会', '之前', '了解', '事务', '事物', '今天', '光是在', '其于', '几百万年', '发出', '取决于', '后天', '含义', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '某样', '残酷', '每个', '熬过', '看到', '真正', '秘密', '绝大部分', '美好', '联系', '过去', '这样', '难以']

分词代码

jieba.lcut(X)可以返回一个列表类型

X: 一定为str类型的字符串

使用空格分词 则前面加" "即可,具体代码如下

def cut_word(text):
    """
    进行中文分词: 如: "我爱北京天安门" ---> "我 爱 北京 天安门"
    :param text:
    :return: 分好的文本
    """
    return " ".join(jieba.lcut(text))

代码实现:

其中在实例化转换器时,加入了停用词:一种 不要 只用

def count_chinese_demo2():
    """
    中文文本特征抽取:自动分词
    :return:
    """
    data_str = ["今天很残酷,明天更残酷,后天很美好,但绝大部分人是难以熬过明天,所以每个人都不要放弃今天",
                "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去",
                "如果只用一种方式了解某样事务,你就不会真正了解它,了解事物真正含义的秘密取决于如何将其于我们所了解的事物相联系"]
    data_new = []
    for sent in data_str:
        data_new.append(cut_word(sent))
    # 1. 实例化一个转换器类
    transfer = CountVectorizer(stop_words=["一种", "不要", "只用"])
    # 2. 调用fit_transform
    data_final = transfer.fit_transform(data_new)
    print("data:\n", data_new)
    print("data_new:\n", data_final.toarray())  # 如果不使用data_final.toarray() 则会返回一个稀疏矩阵sparse
    print("特征名字:\n", transfer.get_feature_names())

    return None
方法2:Tf-idf文本特征提取
  • TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
  • TF-IDF作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。
  • TF:term frequency 词频 指的是某一个给定词语在该文件中出现的频率
  • 假设有两个词“经济”与“非常”
  • 在具有1000篇文章的语料库中,有100篇文章中出现了“非常”,10篇文章中出现了“经济”
  • 现在给定两篇文章A和B
  • A(包含100个词)中出现了10次“经济”,B(100个词)中出现了10次“非常”
  • 现在需要计算A中“经济”的TF-IDF值和B中“非常”的TF-IDF值
  • A的TF = 10 / 100 = 0.1
  • B的TF = 10 / 100 = 0.1
  • IDF:inverse document frequency 逆向文档频率 是一个词语普遍重要性的度量。某一个特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到
  • 故A的IDF = log10 1000 / 10 = 2
  • B的IDF = log10 1000 / 100 = 1
  • TF-IDF = tf * idf
  • 故A中“经济”的TF-IDF = 0.2
  • B中“非常”的TF-IDF = 0.1

故而经济对于A的重要程度大于非常对于B的重要程度

API的使用:

  • sklearn.feature_extraction.text.TfidfVectorizer(stop_words=None,…)
  • 返回词的权重矩阵
  • TfidfVectorizer.fit_transform(X)
  • X: 文本或者包含文本字符串的可迭代对象
  • 返回值: 返回sparse(稀疏)矩阵
  • TfidfVectorizer.inverse_transform(X)
  • X: array数组或者sparse矩阵
  • 返回值:转换之前的数据格式
  • TfidfVectorizer.get_feature_names()
  • 返回值:单词列表

案例:

data:
 ['今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝大部分 人 是 难以 熬过 明天 , 所以 每个 人 都 不要 放弃 今天', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去', '如果 只用 一种 方式 了解 某样 事务 , 你 就 不会 真正 了解 它 , 了解 事物 真正 含义 的 秘密 取决于 如何 将 其于 我们 所 了解 的 事物 相 联系']
data_new:
 [[0.         0.         0.         0.         0.         0.4472136
  0.         0.         0.         0.         0.         0.2236068
  0.         0.         0.         0.         0.         0.2236068
  0.2236068  0.         0.4472136  0.         0.         0.4472136
  0.2236068  0.2236068  0.         0.         0.         0.2236068
  0.2236068  0.         0.         0.         0.2236068 ]
 [0.         0.2410822  0.         0.         0.         0.
  0.2410822  0.         0.2410822  0.2410822  0.         0.
  0.         0.         0.         0.2410822  0.55004769 0.
  0.         0.         0.         0.2410822  0.         0.
  0.         0.         0.48216441 0.         0.         0.
  0.         0.         0.2410822  0.2410822  0.        ]
 [0.16765125 0.         0.670605   0.16765125 0.3353025  0.
  0.         0.16765125 0.         0.         0.16765125 0.
  0.16765125 0.16765125 0.16765125 0.         0.1275031  0.
  0.         0.16765125 0.         0.         0.16765125 0.
  0.         0.         0.         0.3353025  0.16765125 0.
  0.         0.16765125 0.         0.         0.        ]]
特征名字:
 ['不会', '之前', '了解', '事务', '事物', '今天', '光是在', '其于', '几百万年', '发出', '取决于', '后天', '含义', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '某样', '残酷', '每个', '熬过', '看到', '真正', '秘密', '绝大部分', '美好', '联系', '过去', '这样', '难以']

代码实现:

其中使用到了上文使用过的jieba分词,与中文文本特征抽取中的案例相同

def tfidf_demo():
    """
    用TfidfVectorizer方法进行文本特征抽取
    :return:
    """
    data_str = ["今天很残酷,明天更残酷,后天很美好,但绝大部分人是难以熬过明天,所以每个人都不要放弃今天",
                "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去",
                "如果只用一种方式了解某样事务,你就不会真正了解它,了解事物真正含义的秘密取决于如何将其于我们所了解的事物相联系"]
    data_new = []
    for sent in data_str:
        data_new.append(cut_word(sent))
    # 1. 实例化一个转换器类
    transfer = TfidfVectorizer(stop_words=["一种", "不要", "只用"])
    # 2. 调用fit_transform
    data_final = transfer.fit_transform(data_new)
    print("data:\n", data_new)
    print("data_new:\n", data_final.toarray())
    print("特征名字:\n", transfer.get_feature_names())

TF-IDF的重要性

分类机器学习算法进行文章分类中前期数据处理方式(常用)

特征工程中另外的特征预处理以及特征降维,将在之后的文章中进行介绍。