Advertisement

数据挖掘(五)

阅读量:

数据挖掘(五)

文章目录

    • 数据挖掘(五)

      • 特征抽取
        • 在模型中表示事实
    • 通用的特征创建模式

    • 创建好的特征

      • 特征选择

      • 创建特征

      • 创建自己的转换器

        • 转换器API
      • 完整代码1

      • 完整代码2

数据集可以是用特征来描述的,可以使交易类型的数据,此外还有其他数据集,比如文本、图像、声音、视频甚至是真实的物体。大多数数据挖掘算法都依赖于数值或者类别性特征。这表明在使用数据挖掘算法处理之前,需要找到一种表示它们的方法。

特征抽取

特征抽取是数据挖掘任务最为重要的一环,对最终结果的影响要高于数据挖掘算法本身。关于如何选区好的特征,还没有严格、快捷的规则可寻。创建好的规则离不开直觉,还需要专业领域知识和数据挖掘经验,光有这些还不够,还得不停地尝试、摸索,在试错中前进。

在模型中表示事实

不是所有数据集都是用特征来表示的,数据集可以使一位作家写的全部书籍,也可以是电影胶片,还可以是馆藏的历史文物。我们对以上数据集可以做数据挖掘,对于作家的作品,我们想知道这位作家写过哪些主题;对于电影,我们想知道女性形象怎么塑造的;对于文物,我们想要知道它们是何方产物。我们没办法把实物直接塞进决策树来得到结果。

只有先把现实用特征表示出来,才能借助数据挖掘的力量找到问题的答案。特征可以用于建模,模型以机器挖掘算法能够理解的近似的方式来表示现实。模型描述了客观世界中对象的某些方面。特征选择的优点在于降低真实世界的复杂度,模型比现实更容易操纵。实物的复杂性对目前的算法来说太过复杂,我们可以使用简洁的模型来表示实物。

简化以数据挖掘应用的目标为核心,降低复杂度有好处,但是会忽略很多细节。我们要关注如何用模型来表示现实,多考虑数据挖掘的目标,而不是轻率地用我们过去用过的特征。不是所有的特征必须是数值或类别型值,直接用于文本、图像和其他数据结构的算法已经研究出来了。

用[Adult数据集](Adult - UCI Machine Learning Repository)演示如何借助特征为复杂的世界建模。

复制代码
    import os
    import pandas as pd
    data_folder = os.path.join(os.getcwd(), 'adult')
    adult_filename = os.path.join(data_folder, 'adult.data')
    # 加载数据集文件,文件末尾有两处空行,pandas默认会把倒数第二行空行作为一条有效数据读取
    adult = pd.read_csv(adult_filename, header=None,
    names=["Age", "Work-Class", "fnlwgt", "Education", "Education-Num", 
            "Marital-Status", "Occupation", "Relationship", "Race", "Sex", 
            "Capital-gain", "Capital-loss", "Hours-per-week", "Native-Country", "Earnings-Raw"])
    # 删除包含无效数据的行,inplace=True表示改动当前数据框,而不是新建一个
    adult.dropna(how='all', inplace=True)
    # 查看所有特征的名称
    adult.columns

Index([‘Age’, ‘Work-Class’, ‘fnlwgt’, ‘Education’, ‘Education-Num’, ‘Marital-Status’, ‘Occupation’, ‘Relationship’, ‘Race’, ‘Sex’, ‘Capital-gain’, ‘Capital-loss’, ‘Hours-per-week’, ‘Native-Country’, ‘Earnings-Raw’], dtype=‘object’)

通用的特征创建模式
  • 特征创建方法多种多样,但其中有一些是适用于不同学科的通用模式。选择合适的特征是项技术活,需要考虑特征和最终结果之间的相互关系。一些通用特征关注研究对象的物理属性。空间属性比如对象的长、宽、高,重量和密度,年龄、使用年限或成分,种类,品质。另一些特征可能与对象的使用后历史相关,比如生产商、出版商或创造者,生产历史,使用方法。还有一些特征从组成成分角度。次级成分的频率,比如一本书中某个单词的词频;次级成分的数量或成分种类的数量;次级成分的平均大小,比如平均句长。
  • 序数特征可对其排序、分组,特征可以是数值或类别型特征。数值特征通常是有顺序的。adult数据集包含连续特征和序数特征。比如Hours-per-week特征表示一个人每周的工作时间。这个特征可以用来计算平均工作时间、标准差、最大值和最小值。这些统计方法对其他特征比如受教育程度可能没有意义,但是我们可以找到一种近似的表示方法,比如受教育年限特征,它的取值基本上就是每个教育阶段的年限。
复制代码
    # pandas提供了常见统计量的计算方法
    adult['Hours-per-week'].describe()

count 32561.000000
mean 40.437456
std 12.347429
min 1.000000
25% 40.000000
50% 40.000000
75% 45.000000
max 99.000000
Name: Hours-per-week, dtype: float64

复制代码
    adult['Education-Num'].median()

10.0

  • 特征值也可以是类别型的,比如一个球可以是足球、网球等。类别型特征也叫做名义特征。几个值之间要么相同要么不同。类别型特征二值化后就变成了数值型特征,二值化后可以直接进行数字上的比较。Adult数据集包含了一些类别型特征,比如工作Work-Class,其中它的有些值可以进行量级上的比较,比如不同的就业状况影响收入。数值型特征也可以进行离散化转换为类别型特征。比如高于1.7m的人我们称其为高个子。
复制代码
    #使用unique函数获得所有工作的情况,发现部分数据确实,但是不会影响这里的计算
    adult['Work-Class'].unique()

array([’ State-gov’, ’ Self-emp-not-inc’, ’ Private’, ’ Federal-gov’, ’ Local-gov’, ’ ?‘, ’ Self-emp-inc’, ’ Without-pay’, ’ Never-worked’], dtype=object)

复制代码
    # 创建LongHours时长特征用来表示一个人每周工作时长是否多于40个小时,把连续值转换为类别型特征
    adult['LongHours'] = adult['Hours-per-week'] > 40
创建好的特征

建模过程中需要对真实世界中的对象进行简化,这样会导致信息的丢失,这就是为什么没有一套能够用于任何数据集的通用的数据挖掘方法。数据挖掘的行家手里需要拥有数据来源领域的知识,如果没有就需要去掌握。我们需要在弄清楚问题是什么,有哪些可用数据后,才能创建解决问题所需要的模型。

特征选择

通常特征数量很多,但是我们只想选用其中一小部分,这样可以降低复杂度、降低噪音和增加模型可读性。开展数据挖掘工作前,需要做一些基础性测试,比如确保特征值是不同的。scikit-learn中的VarianceThreshold转换器可以删除特征值的方差达不到最低标准的特征。无论何时,拿到数据后,先做下类似简单直接的分析,对数据的特点做到心中有数。

复制代码
    import numpy as np
    # np.arange(30)返回0~29的连续整数数组
    # reshape方法将数组变成10行3列的矩阵,可以看成10个个体、3个特征的数据集
    X = np.arange(30).reshape((10, 3))
    # 把第二列的数值都为1,那么第一、三列特征值的方差很大,第二列的方差为0
    X[:,1] = 1
    # 创建VarianceThreshold转换器处理数据集后第二列消失了
    from sklearn.feature_selection import VarianceThreshold
    vt = VarianceThreshold()
    Xt = vt.fit_transform(X)
    # 输出每一列的方差
    print(vt.variances_)
  • 选择最佳特征与解决数据挖掘问题自身相关,计算量很大。一个变通方法是不要找表现好的子集,而只是去找表现好的单个特征,依据是它们各自能达到的精确度。分类任务通常是这样的,我们一般只要测试变量和目标类别之间的某种相关性。scikit-learn提供了几个用于选择单变量特征的转换器,SelectKBest返回k个最佳特征,SelectPercentile返回表现最佳的前r%个特征。单个特征和某一类别之间相关性常用的计算方法有卡方检验、互信息和信息熵。
复制代码
    # 从pandas数据框中抽取一部分数据,判断税前收入Earnings-Raw是否达到5万美元,创建目标类别列表,如果达到类别为True
    X = adult[['Age', 'Education-Num', 'Capital-gain', 'Capital-loss', 'Hours-per-week']].values
    y = (adult['Earnings-Raw'] == ' >50K').values
    # 使用SelectKBest转化器类用卡方函数打分
    from sklearn.feature_selection import SelectKBest, chi2
    transformer = SelectKBest(score_func=chi2, k=3)
    # 对相同的数据集进行预处理和转结果为分类效果较好的三个特征
    Xt_chi2 = transformer.fit_transform(X, y)
    # 输出每一列的相关性,相关性最好的是第一、三、五列分别对应着Age年龄、Capital-Gain资本收益和Hours-per-week每周工作时长三个特征
    print(transformer.scores_)

[8.60061182e+03 2.40142178e+03 8.21924671e+07 1.37214589e+06 6.47640900e+03]

  • 我们还可以用其他方法计算相关性,比如皮尔逊相关系数,用于科学计算的SciPy库实现了该方法。其中p值为-1到1之间的任意值,1表示两个变量之间绝对正相关,-1表示绝对负相关,一个变量值越大,另一个变量值越小,这样的特征确实能反映两个变量之间的关系,但是根据大小进行排序,这些纸因为是负数而排在后面可能会被舍弃不用。因此,我们对p值取绝对值后再保存。
复制代码
    # 从SciPy导入personr库
    from scipy.stats import pearsonr
    # 包装器函数处理多维数组
    def multivariate_presonr(X, y):
    scores, pvalues = [], []
    # X.shape得到矩阵的行列元组,X.shape[1]获得列数
    for column in range(X.shape[1]):
        cur_score, cur_p = pearsonr(X[:, column], y)
        scores.append(abs(cur_score))
        pvalues.append(cur_p)
    return (np.array(scores), np.array(pvalues))
    # 根据皮尔逊相关系数对特征进行排序
    transformer = SelectKBest(score_func=multivariate_presonr, k=3)
    Xt_pearson = transformer.fit_transform(X, y)
    print(transformer.scores_)

[0.2340371 0.33515395 0.22332882 0.15052631 0.22968907]

  • 我们在分类器中看看哪个特征集合效果更好,实验结果也只表名对于特定分类器和特征子集效果更好,实验结果卡方检验得到的特征组合效果更好。
复制代码
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.model_selection import cross_val_score
    clf = DecisionTreeClassifier(random_state=14)
    scores_chi2 = cross_val_score(clf, Xt_chi2, y, scoring='accuracy')
    scores_pearson = cross_val_score(clf, Xt_pearson, y, scoring='accuracy')
    print('Chi2性能:{0:.3f}, Pearson性能:{1:.3f}'.format(scores_chi2.mean(), scores_pearson.mean()))

Chi2性能:0.829, Pearson性能:0.771

创建特征

有时仅仅选择已有特征不够用,这时我们可以用不同的方法从已有特征中发掘新特征。我们下载广告数据集到当前目录下。

复制代码
    import os
    import pandas as pd
    import numpy as np
    from collections import defaultdict
    
    # 把字符串转换为数字,如果失败捕获异常返回NaN
    def convert_number(x):
    try:
        return float(x)
    except ValueError:
        return np.nan
    # 数据集有几个问题,加载过程需要做些处理,前三个特征是高度、宽度、宽高比,最后一列是数据的类别,其余特征取值为1表示图像的URL、alt属性值
    # 1.前几个特征是数值,但是pandas会把它们当成字符串,我们需要编写将字符串转换为数字的函数
    # 2.数据集中有些值缺失,缺失的值用问号表示,问号不会被转换为浮点型数据,也可以转换为NaN
    data_folder = os.path.join(os.getcwd(), 'advertisements')
    data_filename = os.path.join(data_folder, 'ad.data')
    # 用字典存储所有特征及其转换结果
    converters = defaultdict(convert_number)
    # 把最后一列的值转化为0或1表示每条数据的类别
    converters[1558] = lambda x: 1 if x.strip() == "ad." else 0
    ads = pd.read_csv(data_filename, header=None, converters=converters)
    # 处理特殊符号和NaN
    ads = ads.replace('?', np.nan)
    ads = ads.replace(' ?', np.nan)
    ads = ads.replace('   ?', np.nan)
    ads = ads.replace('     ?', np.nan)
    ads = ads.replace(np.nan, 0)
    # 查看前5行数据
    print(ads[:5])
  • 我们抽取用于分类算法的x矩阵和y数组,x矩阵为数据框出去最后一列的所有制,y数组包含数据框的最后一列。
复制代码
    X = ads.drop(1558, axis=1).values
    y = ads[1558]
  • 主成分分析算法(PCA)的目的是找到能用较少信息描述数据集的特征组合。它意在发现彼此之间没有相关性、能够描述数据集的特征,确切说这些特征的方差跟整体方差没有多大差距,这样的特征也被称为主成分。借助这种方法就能通过更少的特征捕获到数据集的大部分信息。PCA只有主成分数量这一个参数,默认会返回数据集中的所有特征。然而PCA会对返回结果根据方差大小进行排序,返回的第一个特征方差最大。
复制代码
    from sklearn.decomposition import PCA
    pca = PCA(n_components=5)
    Xd = pca.fit_transform(X)
    # Xd矩阵只要5个特征(3279, 5)
    Xd.shape
    # 查看每个特征的方差
    np.set_printoptions(precision=3, suppress=True)
    # 第一个特征的方差对数据集总体方差的贡献率为87.8%
    pca.explained_variance_ratio_

array([0.878, 0.121, 0.001, 0. , 0. ])

  • 用PCA算法处理数据缺点是得到的成分往往是其他几个特征的复杂组合,比如以上第一个特征是通过原始数据集的1558个特征分别乘以不同权重得到的,经过某种组合得到的特征,如果没有丰富的研究经验,理解起来很困难。用PCA算法得到的数据创建模型,不仅能够近似地表示原始数据集,还能提升分类任务的正确率。
复制代码
    from sklearn.model_selection import cross_val_score
    from sklearn.tree import DecisionTreeClassifier
    clf = DecisionTreeClassifier(random_state=14)
    scores_reduced = cross_val_score(clf, Xd, y, scoring='accuracy')
    print('正确率:{:.4f}'.format(np.mean(scores_reduced)))

正确率:0.9393

  • PCA算法的优点是可以吧数据集绘制成图形,比如把PCA返回的前两个特征做成图形。
复制代码
    from matplotlib import pyplot as plt
    # 获取数据集中类别的所有取值,是广告和不是广告
    classes = set(y)
    # 在图形中用什么颜色表示这两个类别
    colors = {'red', 'green'}
    # zip函数把这两个列表组合起来同时遍历
    for cur_class, color in zip(classes, colors):
    # 给当前类别的个体创建遮罩层
    mask = (y == cur_class).values
    # scatter函数显示位置
    plt.scatter(Xd[mask, 0], Xd[mask, 1], marker='o', color=color, label=int(cur_class))
    # 创建图示显示图像    
    plt.legend()
    plt.show()

创建自己的转换器

转换器接收一种形式的数据,输出另一种形式,用来训练集训练,训练得到的参数可以用来转换测试数据集。

转换器API
  1. 转换器有fit和transform两个核心函数,它们的输入应该是同一种数据类型,但是transform可以返回不同的数据类型。
复制代码
    # 导入用来设置API的TransformerMixin类
    from sklearn.base import TransformerMixin
    from sklearn.utils import as_float_array
    
    # 自定义转换器类继承TransformerMixin类,定义fit和transform方法
    class MeanDiscrete(TransformerMixin):
    def fit(self, X, y=None):
        X = as_float_array(X)
        self.mean = np.mean(X, axis=0)
        # fit函数返回本身确保在转换器中能够进行链式调用
        return self
    
    def transform(self, X):
        X = as_float_array(X)
        # 输入必须是numpy数组,检查输入的数据列数是否一致
        assert X.shape[1] == self.mean.shape[0]
        return X > self.mean
    
    mean_discrete = MeanDiscrete()
    X_mean = mean_discrete.fit_transform(X)
  1. 创建函数和类后,进行单元测试。充分的测试应当在独立于现有代码的前提下进行。
复制代码
    import numpy as np
    from numpy.testing import assert_array_equal
    
    # 测试函数以test_开头便于工具自动查找并运行测试
    def test_meandiscrete():
    X_test = np.array([[ 0,  2],
                        [ 3,  5],
                        [ 6,  8],
                        [ 9, 11],
                        [12, 14],
                        [15, 17],
                        [18, 20],
                        [21, 23],
                        [24, 26],
                        [27, 29]])
    # 创建转换函数用测试数据进行训练
    mean_discrete = MeanDiscrete()
    mean_discrete.fit(X_test)
    assert_array_equal(mean_discrete.mean, np.array([13.5, 15.5]))
    X_transformed = mean_discrete.transform(X_test)
    X_expected = np.array([[ 0,  0],
                            [ 0, 0],
                            [ 0, 0],
                            [ 0, 0],
                            [ 0, 0],
                            [ 1, 1],
                            [ 1, 1],
                            [ 1, 1],
                            [ 1, 1],
                            [ 1, 1]])
    assert_array_equal(X_transformed, X_expected)
    test_meandiscrete()
  1. 使用流水线交叉检验。
复制代码
    from sklearn.pipeline import Pipeline
    pipeline = Pipeline([('mean_discrete', MeanDiscrete()),
                     ('classifier', DecisionTreeClassifier(random_state=14))])
    scores_mean_discrete = cross_val_score(pipeline, X, y, scoring='accuracy')
    print("Mean Discrete performance: {0:.3f}".format(scores_mean_discrete.mean()))

完整代码1

复制代码
    import os
    import pandas as pd
    import numpy as np
    from sklearn.feature_selection import VarianceThreshold, SelectKBest, chi2
    from scipy.stats import pearsonr
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.model_selection import cross_val_score
    
    def multivariate_presonr(X, y):
    scores, pvalues = [], []
    for column in range(X.shape[1]):
        # 接收两个数组作为参数, 第一个参数是一维数组,返回两个特征的皮尔逊相关系数和p值
        cur_score, cur_p = pearsonr(X[:, column], y)
        scores.append(abs(cur_score))
        pvalues.append(cur_p)
    return (np.array(scores), np.array(pvalues)) 
    # X = np.arange(30).reshape((10, 3))
    # X[:, 1] = 1
    # vt = VarianceThreshold()
    # Xt = vt.fit_transform(X)
    # print(vt.variances_)
    data_folder = os.path.join(os.getcwd(), 'adult')
    adult_filename = os.path.join(data_folder, 'adult.data')
    adult = pd.read_csv(adult_filename, header=None,
    names=["Age", "Work-Class", "fnlwgt", "Education", "Education-Num", 
            "Marital-Status", "Occupation", "Relationship", "Race", "Sex", 
            "Capital-gain", "Capital-loss", "Hours-per-week", "Native-Country", "Earnings-Raw"])
    adult.dropna(how='all', inplace=True)
    adult.columns
    adult['Hours-per-week'].describe()
    adult['Education-Num'].median()
    adult['Work-Class'].unique()
    adult['LongHours'] = adult['Hours-per-week'] > 40
    X = adult[['Age', 'Education-Num', 'Capital-gain', 'Capital-loss', 'Hours-per-week']].values
    y = (adult['Earnings-Raw'] == ' >50K').values
    transformer = SelectKBest(score_func=chi2, k=3)
    Xt_chi2 = transformer.fit_transform(X, y)
    #print(transformer.scores_)
    transformer = SelectKBest(score_func=multivariate_presonr, k=3)
    Xt_pearson = transformer.fit_transform(X, y)
    #print(transformer.scores_)
    clf = DecisionTreeClassifier(random_state=14)
    scores_chi2 = cross_val_score(clf, Xt_chi2, y, scoring='accuracy')
    scores_pearson = cross_val_score(clf, Xt_pearson, y, scoring='accuracy')
    print('Chi2性能:{0:.3f}, Pearson性能:{1:.3f}'.format(scores_chi2.mean(), scores_pearson.mean()))

完整代码2

复制代码
    import os
    import pandas as pd
    import numpy as np
    from collections import defaultdict
    from sklearn.decomposition import PCA
    from sklearn.model_selection import cross_val_score
    from sklearn.tree import DecisionTreeClassifier
    from matplotlib import pyplot as plt
    
    def convert_number(x):
    try:                       
        return float(x)
    except ValueError:        
        return np.nan
    
    data_folder = os.path.join(os.getcwd(), 'advertisements')
    data_filename = os.path.join(data_folder, 'ad.data')
    converters = defaultdict(convert_number)
    converters[1558] = lambda x: 1 if x.strip() == "ad." else 0
    ads = pd.read_csv(data_filename, header=None, converters=converters, low_memory=False)
    ads = ads.replace('?', np.nan)
    ads = ads.replace(' ?', np.nan)
    ads = ads.replace('   ?', np.nan)
    ads = ads.replace('     ?', np.nan)
    ads = ads.replace(np.nan, 0)
    #print(ads[:5])
    X = ads.drop(1558, axis=1).values
    y = ads[1558]
    pca = PCA(n_components=5)
    Xd = pca.fit_transform(X)
    #Xd.shape
    np.set_printoptions(precision=3, suppress=True)
    pca.explained_variance_ratio_
    clf = DecisionTreeClassifier(random_state=14)
    scores_reduced = cross_val_score(clf, Xd, y, scoring='accuracy')
    print('正确率:{:.4f}'.format(np.mean(scores_reduced)))
    classes = set(y)
    colors = {'red', 'green'}
    for cur_class, color in zip(classes, colors):
    mask = (y == cur_class).values
    plt.scatter(Xd[mask, 0], Xd[mask, 1], marker='o', color=color, label=int(cur_class))
    plt.legend()
    plt.show()

全部评论 (0)

还没有任何评论哟~