数据挖掘(五)
数据挖掘(五)
文章目录
-
-
数据挖掘(五)
-
- 特征抽取
-
- 在模型中表示事实
-
通用的特征创建模式
-
创建好的特征
-
特征选择
-
创建特征
-
创建自己的转换器
-
- 转换器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
- 转换器有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)
- 创建函数和类后,进行单元测试。充分的测试应当在独立于现有代码的前提下进行。
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()
- 使用流水线交叉检验。
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()
