数据清洗和特征工程
学习目标
- 了解特征工程在机器学习当中的重要性
- 特征预处理
- 特征提取
- 特征选择和特征的降维
特征工程做一个总结
• 所有一切为了让模型效果变的更好的数据处理方式都可以认为属于特征工程这个范畴中的一个操作;
• 至于需求做不做这个特征工程,需要我们在开发过程中不但的进行尝试。
• 常规的特征工程需要处理的内容:
• 异常数据的处理
• 数据不平衡处理
• 文本处理:词袋法、TF-IDF
• 多项式扩展、哑编码、标准化、归一化、区间缩放法、PCA、特征选择…
• 将均值、方差、协方差等信息作为特征属性,对特征属性进行对数转换、指数转换…
• 结合业务衍生出一些新的特征属性…
特征工程介绍
• 为什么需要特征工程(Feature Engineering)
• 机器学习领域的大神Andrew Ng(吴恩达)老师说“Coming up with features is difficult, time-consuming, requires expert knowledge. ‘Applied machine earning’ is basically feature engineering. ”
• 业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。
• 什么是特征工程
• 特征工程是使用专业背景知识和技巧处理数据,使得特征能在机器学习算法上发挥更好的作用的过程。
• 意义:会直接影响机器学习的效果
特征预处理
数据清洗
• 数据清洗(data cleaning)是在机器学习过程中一个不可缺少的环节,其数据的清洗结果直接关系到模型效果以及最终的结论。在实际的工作中,数据清洗通常占开发过程的30%-50%左右的时间。
数据清洗–预处理
• 在数据预处理过程主要考虑两个方面,如下:
• 选择数据处理工具:关系型数据库或者Python
• 查看数据的元数据以及数据特征:一是查看元数据,包括字段解释、数据来源等一切可以描述数据的信息;另外是抽取一部分数据,通过人工查看的方式,对数据本身做一个比较直观的了解,并且初步发现一些问题,为之后的数据处理做准备。
数据清洗–格式内容错误数据清洗
• 一般情况下,数据是由用户/访客产生的,也就有很大的可能性存在格式和内容上不一致的情况,所以在进行模型构建之前需要先进行数据的格式内容清洗操作。格式内容问题主要有以下几类:
• 时间、日期、数值、半全角等显示格式不一致:直接将数据转换为一类格式即可,该问题一般出现在多个数据源整合的情况下。
• 内容中有不该存在的字符:最典型的就是在头部、中间、尾部的空格等问题,这种情况下,需要以半自动校验加半人工方式来找出问题,并去除不需要的字符。
• 内容与该字段应有的内容不符:比如姓名写成了性别、身份证号写成手机号等问题。
数据清洗–逻辑错误清洗
• 主要是通过简单的逻辑推理发现数据中的问题数据,防止分析结果走偏,主要包含以下几个步骤:
• 数据去重
• 去除/替换不合理的值
• 去除/重构不可靠的字段值(修改矛盾的内容)
数据清洗–去除不需要的数据
• 一般情况下,我们会尽可能多的收集数据,但是不是所有的字段数据都是可以应用到模型构建过程的,也不是说将所有的字段属性都放到构建模型中,最终模型的效果就一定会好,实际上来讲,字段属性越多,模型的构建就会越慢,所以有时候可以考虑将不要的字段进行删除操作。在进行该过程的时候,要注意备份原始数据。
数据清洗–关联性验证
• 如果数据有多个来源,那么有必要进行关联性验证,该过程常应用到多数据源合并的过程中,通过验证数据之间的关联性来选择比较正确的特征属性,比如:汽车的线下购买信息和电话客服问卷信息,两者之间可以通过姓名和手机号进行关联操作,匹配两者之间的车辆信息是否是同一辆,如果不是,那么就需要进行数据调整。
数据清洗——案例:数据缺省值填充
• python科学计算库
• from sklearn.preprocessing import Imputer
• 均值填充
• 中值填充
• 众数填充
归一化和标准化
• 为什么我们要进行归一化/标准化?
• 特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级,容易影响(支配)目标结果,使得一些算法无法学习到其它的特征
• 我们需要用到一些方法进行无量纲化,使不同规格的数据转换到同一规格
归一化
• 通过对原始数据进行变换把数据映射到(默认为[0,1])之间
X
′
=
x
−
m
i
n
m
a
x
−
m
i
n
X
′
′
=
X
′
∗
(
m
x
−
m
i
)
+
m
i
X'=\frac{x-min}{max-min}\\ X''=X'*(mx - mi)+mi
X′=max−minx−minX′′=X′∗(mx−mi)+mi
• 思考:如果数据中异常点较多,会有什么影响?
注意最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性较差,只适合传统精确小数据场景。
标准化
• 通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内
X
′
=
x
−
m
e
a
n
σ
X'=\frac{x-mean}{\sigma}
X′=σx−mean
对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。
• 在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。
from sklearn.impute import SimpleImputer
import numpy as np
X = [
[2, 2, 4, 1],
[np.nan, 3, 4, 4],
[1, 1, 1, np.nan],
[2, 2, np.nan, 3]
]
X2 = [
[2, 6, np.nan, 1],
[np.nan, 5, np.nan, 1],
[4, 1, np.nan, 5],
[np.nan, np.nan, np.nan, 1]]
imp1 = SimpleImputer(missing_values=np.nan, strategy='mean')
imp1.fit(X)
print(imp1.statistics_)
print(imp1.transform(X))
imp2 = SimpleImputer(missing_values=np.nan, strategy='median')
imp3 = SimpleImputer(missing_values=np.nan, strategy='most_frequent')
imp2.fit(X)
imp3.fit(X)
print(imp2.statistics_)
print(imp3.statistics_)
print(X)
print (X2)
print ("--------------------------")
print(imp1.transform(X2))
print ("--------------------------")
print(imp2.transform(X2))
print ("--------------------------")
print(imp3.transform(X2))
from sklearn.preprocessing import MinMaxScaler
import numpy as np
import pandas as pd
X = np.array([
[1, -1, 2, 3],
[2, 0, 0, 3],
[0, 1, -10, 3]
], dtype=np.float64)
scaler = MinMaxScaler(feature_range=(0, 1))
scaler.fit(X)
# 原始特征属性的最大值
print(scaler.data_max_)
# 原始特征属性的最小值
print(scaler.data_min_)
# 原始特征属性的取值范围大小(最大值-最小值)
print(scaler.data_range_)
print(scaler.transform(X))
x_test = [[3, -1, 2, 3]]
print('*' * 50)
print(scaler.transform(x_test))
print(pd.DataFrame(scaler.transform(x_test)).describe())
from sklearn.preprocessing import StandardScaler
X = [
[1, 2, 3, 2],
[7, 8, 9, 2.01],
[4, 8, 2, 2.01],
[9, 5, 2, 1.99],
[7, 5, 3, 1.99],
[1, 4, 9, 2]
]
x_test = [
[12,11,13,12],
[5,6,7,9]
]
ss = StandardScaler(with_mean=True, with_std=True)
ss.fit(X)
# 计算每个特征属性的均值
print(ss.mean_)
print(ss.n_samples_seen_)
# 输出每个特征属性的方差
print(ss.scale_)
print('*' * 50)
print(ss.transform(X))
print('*' * 50)
print(ss.transform(x_test))
print('*' * 50)
import pandas as pd
print(pd.DataFrame(ss.transform(X)).describe())
特征提取
• 将任意数据(如文本或图像)转换为可用于机器学习的数字特征
• 字典特征提取(特征离散化)
• 文本特征提取
• 图像特征提取【一般图片本身就是个数组数据】
from sklearn.feature_extraction import DictVectorizer
data = [{'city': '北京','temperature':0}, {'city': '上海','temperature':1}, {'city': '深圳','temperature':2}]
# 1、实例化一个转换器类
# spares:是否创建副本
transfer = DictVectorizer(sparse=False)
# 1、实例化一个转换器类
data = transfer.fit_transform(data)
print("返回的结果:\n", data)
# 打印特征名字
print('特征名字:\n', transfer.get_feature_names_out())
# 返回的结果:
# [[0. 1. 0. 0.]
# [1. 0. 0. 1.]
# [0. 0. 1. 2.]]
# 特征名字:
# ['city=上海' 'city=北京' 'city=深圳' 'temperature']
字典特征提取(特征离散化)
• 对字典数据进行特征值化
import numpy as np
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(handle_unknown='ignore')
X = [['Male', 1],
['Female', 3],
['Female', 2]]
enc.fit(X)
print(enc.transform(X))
# (0, 1) 1.0
# (0, 2) 1.0
# (1, 0) 1.0
# (1, 4) 1.0
# (2, 0) 1.0
# (2, 3) 1.0
print(enc.transform(X).toarray())
# [[0. 1. 1. 0. 0.]
# [1. 0. 0. 0. 1.]
# [1. 0. 0. 1. 0.]]
print(enc.categories_)
# [array(['Female', 'Male'], dtype=object), array([1, 2, 3], dtype=object)]
print(enc.transform([['Female', 1], ['Male', 5]]).toarray())
# [[1. 0. 1. 0. 0.]
# [0. 1. 0. 0. 0.]]
print(enc.inverse_transform([[0, 1, 1, 0, 0], [0, 0, 0, 1, 0]]))
# [['Male' 1]
# [None 2]]
print(enc.get_feature_names_out(['gender', 'group']))
# ['gender_Female' 'gender_Male' 'group_1' 'group_2' 'group_3']
drop_enp = OneHotEncoder(drop='first').fit(X)
print(drop_enp.categories_)
print(drop_enp.transform([['Female', 1], ['Male', 3]]).toarray())
# [[0. 0. 0.]
# [1. 0. 1.]]
drop_binary_enc = OneHotEncoder(drop='if_binary', handle_unknown='ignore').fit(X)
print(drop_binary_enc.categories_)
print(drop_binary_enc.transform([['Female', 1], ['Male', 4]]).toarray())
# [[0. 1. 0. 0.]
# [1. 0. 0. 0.]]
import pandas as pd
a = pd.DataFrame([
['a', 1, 2],
['b', 1, 1],
['a', 2, 1],
['c', 1, 2],
['c', 1, 2]
], columns=['c1', 'c2', 'c3'])
a = pd.get_dummies(a)
print(a)
文本特征提取
• 中文jieba分词处理:pip3 install jieba
#引入jieba模块, 默认安装pip install jieba
# 集成百度LAC
import jieba
jieba.initialize() # 初始化分词模型
words_a='上海自来水来自海上,所以
# seg_a = jieba.cut(words_a, cut_all=True)
# print("全模式:","/".join(seg_a))
# # 全模式: 上海/自来/自来水/来自/海上/,/所以/吃/葡萄/不/吐/葡萄/吃葡萄不吐葡萄皮'皮
seg_b =jieba.cut(words_a)
print("精确模式:","/".join(seg_b))
# 精确模式: 上海/自来水/来自/海上/,/所以/吃/葡萄/不吐/葡萄/皮
seg_c = jieba.cut_for_search(words_a)
print("搜索引擎模式","/".join(seg_c))
# 搜索引擎模式 上海/自来/自来水/来自/海上/,/所以/吃/葡萄/不吐/葡萄/皮
seg_b
print(list((seg_b)))
# []
seg_b=jieba.cut(words_a)
print(seg_b)
# <generator object Tokenizer.cut at 0x0000015E32C0E730>
print(list(seg_b))
# ['上海', '自来水', '来自', '海上', ',', '所以', '吃', '葡萄', '不吐', '葡萄', '皮']
seg_b=jieba.cut(words_a)
for i in seg_b:
print(i)
# 上海
# 自来水
# 来自
# 海上
# ,
# 所以
# 吃
# 葡萄
# 不吐
# 葡萄
# 皮
seg_b_l = jieba.lcut(words_a)
print(seg_b_l)
# ['上海', '自来水', '来自', '海上', ',', '所以', '吃', '葡萄', '不吐', '葡萄', '皮']
#添加和删除自定义词汇
words_a1='我为机器学习疯狂打call'
print("自定义前:", "/".join(jieba.cut(words_a1)))
# 自定义前: 我/为/机器/学习/疯狂/打/call
jieba.add_word("打call")
print("加入打call后:", '/'.join(jieba.cut(words_a1)))
# 加入打call后: 我/为/机器/学习/疯狂/打call
jieba.del_word("打call")
print("删除打call后:",'/'.join(jieba.cut(words_a1)))
# 删除打call后: 我/为/机器/学习/疯狂/打/call
jieba.add_word("机器学习")
print("加入‘机器学习’后:","/".join(jieba.cut(words_a1)))
# 加入‘机器学习’后: 我/为/机器学习/疯狂/打/call
import jieba
jieba.initialize()
words_a1='我为机器学习疯狂打call'
print("加入‘机器学习’后:","/".join(jieba.cut(words_a1)))
# 加入‘机器学习’后: 我/为/机器学习/疯狂/打/call
jieba.add_word("《打call》") # 这里的add_word没有生效
res = jieba.lcut("我为机器学习疯狂《打call》")
print(res)
# ['我', '为', '机器学习', '疯狂', '《', '打', 'call', '》']
jieba.add_word("打call")
res = jieba.lcut("我为机器学习疯狂《打call》")
print(res)
# ['我', '为', '机器学习', '疯狂', '《', '打call', '》']
jieba.add_word(r"《打call》")
res = jieba.lcut("我为机器学习疯狂《打call》",cut_all=True)
print(res)
# ['我', '为', '机器', '机器学习', '学习', '疯狂', '《', '打call', '》']
导入自定义词典
jieba.del_word("打call")#删除之前添加的词汇
words_a2='在正义者联盟的电影里,嘻哈侠和蝙蝠侠联手打败了大boss,我高喊666,为他们疯狂打call'
print("加载自定义词库前:","/".join(jieba.cut(words_a2)))
jieba.load_userdict("./datas/01mydict.txt")
print("--------VS--------")
print("加载自定义词库后:","/".join(jieba.cut(words_a2)))
# 加载自定义词库前: 在/正义者/联盟/的/电影/里/,/嘻哈侠/和/蝙蝠侠/联手/打败/了/大/boss/,/我/高喊/666/,/为/他们/疯狂/打/call
# --------VS--------
# 加载自定义词库后: 在/正义者联盟/的/电影/里/,/嘻哈侠/和/蝙蝠侠/联手/打败/了/大boss/,/我/高喊/666/,/为/他们/疯狂/打call
#获得切词后的数据
ls1 = []
for item in jieba.cut(words_a2):
ls1.append(item)
print(ls1)
##用lcut直接获得切词后的list列表数据
ls2=jieba.lcut(words_a2)
print(ls2)
# 调整词典,关闭HMM发现新词功能(主要在开发过程中使用)
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=True))) # 又发现新词的可能性的,不代表一定能发现
# 如果/放到/post/中将/出错/。
# 如果/放到/post/中将/出错/。
jieba.suggest_freq(('中', '将'), True)
print("--------------------------")
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=True)))
# 如果/放到/post/中/将/出错/。
# 如果/放到/post/中/将/出错/。
jieba.suggest_freq(('台', '中'), True)
print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))
jieba.suggest_freq('台中', True)
print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))
# 「/台/中/」/正确/应该/不会/被/切开
# 「/台中/」/正确/应该/不会/被/切开
# 关键词提取
# 获取TF-IDF最大的5个单词
import jieba.analyse
words_a2='在正义者联盟的电影里,嘻哈侠和蝙蝠侠联手打败了大boss,我高喊666,为他们疯狂打call'
print(jieba.analyse.extract_tags(words_a2,topK=5,withWeight=True))
# [('蝙蝠侠', 1.0692828963076924), ('嘻哈侠', 0.9195975002230768), ('boss', 0.9195975002230768), ('666', 0.9195975002230768), ('call', 0.9195975002230768)]
• 词袋法
• TF-IDF
词频(
T
F
)
=
某个词在文章中出现的次数
标准化
词频(
T
F
)
=
某个词在文章中出现的次数
文章的总次数
逆文档频率(
I
D
F
)
=
l
o
g
(
词料库的文档总数
包含该词的文档总数
+
1
)
如果一个词越常见,那么分母就越大,逆文档频率就越小越接近
0
。
分母之所以要加
1
,是为了避免分母为
0
(即所有文档都不包含该词)。
l
o
g
表示对得到的值取对数。
T
F
−
I
D
F
=
词频(
T
F
)
∗
逆文档频率(
I
D
F
)
词频(TF)=某个词在文章中出现的次数\\ 标准化\\ 词频(TF)=\frac{某个词在文章中出现的次数}{文章的总次数}\\ 逆文档频率(IDF)=log(\frac{词料库的文档总数}{包含该词的文档总数+1})\\ 如果一个词越常见,那么分母就越大,逆文档频率就越小越接近0。\\分母之所以要加1,是为了避免分母为0(即所有文档都不包含该词)。log表示对得到的值取对数。\\ TF-IDF = 词频(TF)* 逆文档频率(IDF)
词频(TF)=某个词在文章中出现的次数标准化词频(TF)=文章的总次数某个词在文章中出现的次数逆文档频率(IDF)=log(包含该词的文档总数+1词料库的文档总数)如果一个词越常见,那么分母就越大,逆文档频率就越小越接近0。分母之所以要加1,是为了避免分母为0(即所有文档都不包含该词)。log表示对得到的值取对数。TF−IDF=词频(TF)∗逆文档频率(IDF)
TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语言中的出现次数成反比。所以,自动提取关键词的算法就很清楚了,就是计算出文档的每个词的TF-IDF值,然后按降序排列,取排在最前面的几个词。
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer, TfidfTransformer
arr1 = [
"This is spark, spark sql a every good",
"Spark Hadoop Hbase",
"This is sample",
"This is anthor example anthor example",
"spark hbase hadoop spark hive hbase hue oozie",
"hue oozie spark"
]
arr2 = [
"this is a sample a example",
"this c ccc cd is another another sample example example",
"spark Hbase hadoop Spark hive hbase"
]
df = arr2
# 相当于词袋法
count = CountVectorizer(min_df=0.1, dtype=np.float64, ngram_range=(0,1))
df1 = count.fit_transform(df)
print(df1.toarray())
# [[0. 0. 0. 1. 0. 0. 0. 1. 1. 0. 1.]
# [2. 1. 1. 2. 0. 0. 0. 1. 1. 0. 1.]
# [0. 0. 0. 0. 1. 2. 1. 0. 0. 2. 0.]]
print (count.get_stop_words())
# None
print (count.get_feature_names_out())
# ['another' 'ccc' 'cd' 'example' 'hadoop' 'hbase' 'hive' 'is' 'sample' 'spark' 'this']
print ("转换另外的文档数据")
print (count.transform(arr1).toarray())
# [[0. 0. 0. 0. 0. 0. 0. 1. 0. 2. 1.]
# [0. 0. 0. 0. 1. 1. 0. 0. 0. 1. 0.]
# [0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 1.]
# [0. 0. 0. 2. 0. 0. 0. 1. 0. 0. 1.]
# [0. 0. 0. 0. 1. 2. 1. 0. 0. 2. 0.]
# [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]]
print(df1)
# (0, 10) 1.0
# (0, 7) 1.0
# (0, 8) 1.0
# (0, 3) 1.0
# (1, 10) 1.0
# (1, 7) 1.0
# (1, 8) 1.0
# (1, 3) 2.0
# (1, 1) 1.0
# (1, 2) 1.0
# (1, 0) 2.0
# (2, 9) 2.0
# (2, 5) 2.0
# (2, 4) 1.0
# (2, 6) 1.0
# 基于TF的值(词袋法),做一个IDF的转换
tfidf_t = TfidfTransformer()
df2 = tfidf_t.fit_transform(df1)
print(df2.toarray())
print ("转换另外的文档数据")
print (tfidf_t.transform(count.transform(arr1)).toarray())
# [[0. 0. 0. 0. 0. 0.
# 0. 0.3349067 0. 0.88072413 0.3349067 ]
# [0. 0. 0. 0. 0.57735027 0.57735027
# 0. 0. 0. 0.57735027 0. ]
# [0. 0. 0. 0. 0. 0.
# 0. 0.57735027 0.57735027 0. 0.57735027]
# [0. 0. 0. 0.81649658 0. 0.
# 0. 0.40824829 0. 0. 0.40824829]
# [0. 0. 0. 0. 0.31622777 0.63245553
# 0.31622777 0. 0. 0.63245553 0. ]
# [0. 0. 0. 0. 0. 0.
# 0. 0. 0. 1. 0. ]]
## 相当TF+IDF(先做词袋法再做IDF转换)
tfidf_v = TfidfVectorizer(min_df=0.0, dtype=np.float64)
df3 = tfidf_v.fit_transform(df)
print(df3.toarray())
# [[0. 0. 0. 0.5 0. 0.
# 0. 0.5 0.5 0. 0.5 ]
# [0.63091809 0.31545904 0.31545904 0.47982947 0. 0.
# 0. 0.23991473 0.23991473 0. 0.23991473]
# [0. 0. 0. 0. 0.31622777 0.63245553
# 0.31622777 0. 0. 0.63245553 0. ]]
print(tfidf_v.get_feature_names_out())
# ['another' 'ccc' 'cd' 'example' 'hadoop' 'hbase' 'hive' 'is' 'sample'
# 'spark' 'this']
print (tfidf_v.get_stop_words())
# None
print ("转换另外的文档数据")
print (tfidf_v.transform(arr1).toarray())
# [[0. 0. 0. 0. 0. 0.
# 0. 0.3349067 0. 0.88072413 0.3349067 ]
# [0. 0. 0. 0. 0.57735027 0.57735027
# 0. 0. 0. 0.57735027 0. ]
# [0. 0. 0. 0. 0. 0.
# 0. 0.57735027 0.57735027 0. 0.57735027]
# [0. 0. 0. 0.81649658 0. 0.
# 0. 0.40824829 0. 0. 0.40824829]
# [0. 0. 0. 0. 0.31622777 0.63245553
# 0.31622777 0. 0. 0.63245553 0. ]
# [0. 0. 0. 0. 0. 0.
# 0. 0. 0. 1. 0. ]]
特征降维
• 降维是指在某些限定条件下,降低随机变量(特征)个数,得到一组“不相关”主变量的过程
• 正是因为在进行训练的时候,我们都是使用特征进行学习。如果特征本身存在问题或者特征之间相关性较强,对于算法学习预测会影响较大
• 降维的两种方式
• 特征选择
• 主成分分析(可以理解一种特征提取的方式)
什么是特征选择
• 数据中包含冗余或无关变量(或称特征、属性、指标等),旨在从原有特征中找出主要特征。
特征选择
• 特征选择的方法主要有以下三种:
• Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,从而选择特征;常用方法包括方差选择法、相关系数法、卡方检验、互信息法等。
• Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征或者排除若干特征;常用方法主要是递归特征消除法。
• Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权重系数,根据系数从大到小选择特征;常用方法主要是基于惩罚项的特征选择法。
特征选择-方差选择法
• 方差选择法:先计算各个特征属性的方差值,然后根据阈值,获取方差大于阈值的特征。
X = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0.1, 1, 1, 3],
[1, 2, 3, 1],
[2, 3, 4, 3]
], dtype=np.float32)
Y = np.array([1,2,1,2,1])
## 方差选择法
# 基于方差选择最优的特征属性
variance = VarianceThreshold(threshold=0.6)
print(variance)
variance.fit(X)
print("各个特征属性的方差为:")
print(variance.variances_)
# [0.6176 0.56 2.64 0.64 ]
print('-----------------')
print(variance.transform(X))
# [[0. 0. 3. ]
# [0. 4. 3. ]
# [0.1 1. 3. ]
# [1. 3. 1. ]
# [2. 4. 3. ]]
特征选择-相关系数法
• 相关系数法:先计算各个特征属性对于目标值的相关系数以及阈值K,然后获取K个相关系数最大的特征属性。(备注:根据目标属性y的类别选择不同的方式)
## 相关系数法
sk1 = SelectKBest(f_regression, k=2)
sk1.fit(X, Y)
print(sk1)
print('-----------------')
print(sk1.scores_)
# [0.04736842 0.36 1.32 1.8 ]
print('-----------------')
print(sk1.transform(X))
# [[0. 3.]
# [4. 3.]
# [1. 3.]
# [3. 1.]
# [4. 3.]]
特征选择-卡方检验
• 卡方检验:检查定性自变量对定性因变量的相关性。
## 卡方检验
# 使用chi2的时候要求特征属性的取值为非负数
sk2 = SelectKBest(chi2, k=2)
sk2.fit(X, Y)
print(sk2)
print(sk2.scores_)
# [0.07741936 0.16666667 1.68055556 0.46153846]
print(sk2.transform(X))
# [[0. 3.]
# [4. 3.]
# [1. 3.]
# [3. 1.]
# [4. 3.]]
特征选择-递归特征消除法
• 递归特征消除法:使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。
## Wrapper-递归特征消除法
# 基于特征消去法做的特征选择
estimmator = LogisticRegression()
selector = RFE(estimmator, step=2, n_features_to_select=3)
selector = selector.fit(X, Y)
print(selector.support_)
# [False True True True]
print(selector.n_features_)
# 3
print(selector.ranking_)
# [2 1 1 1]
print(selector.transform(X))
# [[2. 0. 3.]
# [1. 4. 3.]
# [1. 1. 3.]
# [2. 3. 1.]
# [3. 4. 3.]]
特征选择 嵌入法-基于惩罚项的特征选择法
• 使用基于惩罚项的基模型,进行特征选择操作。
## Embedded【嵌入法】-基于惩罚项的特征选择法
X2 = np.array([
[ 5.1, 3.5, 1.4, 0.2],
[ 4.9, 3. , 1.4, 0.2],
[ -6.2, 0.4, 5.4, 2.3],
[ -5.9, 0. , 5.1, 1.8]
], dtype=np.float64)
Y2 = np.array([0, 0, 2, 2])
estimator = LogisticRegression(penalty='l1', C=0.1, solver='liblinear')
sfm = SelectFromModel(estimator)
sfm.fit(X2, Y2)
print(sfm.transform(X2))
# [[ 5.1]
# [ 4.9]
# [-6.2]
# [-5.9]]
print("系数:")
print(sfm.estimator_.coef_)
# [[-0.03417754 0. 0. 0. ]]
特征降维
• 当特征选择完成后,可以直接可以进行训练模型了,但是可能由于特征矩阵过大,导致计算量比较大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的。常见的降维方法除了基于L1的惩罚模型外,还有主成分析法(PCA)和线性判别分析法(LDA),这两种方法的本质都是将原始数据映射到维度更低的样本空间中;但是采用的方式不同,PCA是为了让映射后的样本具有更大的发散性,PCA是无监督的学习算法**,LDA是为了让映射后的样本有最好的分类性能,LDA是有监督学习算法。
PCA原理
• PCA(Principal Component Analysis)是常用的线性降维方法,是一种无监督的降维算法。算法目标是通过某种线性投影,将高维的数据映射到低维的空间中表示,并且期望在所投影的维度上数据的方差最大(最大方差理论),以此使用较少的数据维度,同时保留较多的原数据点的特性。
• 在PCA降维中,数据从原来的坐标系转换为新的坐标系,新坐标系的选择由数据本身的特性决定。第一个坐标轴选择原始数据中方差最大的方向,从统计角度来讲,这个方向是最重要的方向;第二个坐标轴选择和第一个坐标轴垂直或者正交的方向;第三个坐标轴选择和第一个、第二个坐标轴都垂直或者正交的方向;该过程一直重复,直到新坐标系的维度和原始坐标系维度数目一致的时候结束计算。而这些方向所表示的数据特征就被称为“主成分” 。
特征选取/降维-PCA
• 主成分析(PCA):将高纬的特征向量合并称为低纬度的特征属性,是一种无监督的降维方法。
LDA原理
• LDA的全称是Linear Discriminant Analysis(线性判别分析),是一种有监督学习算法。
• LDA的原理是,将带上标签的数据(点),通过投影的方法,投影到维度更低的空间中,使得投影后的点,会形成按类别区分,一簇一簇的情况,相同类别的点,将会在投影后的空间中更接近。用一句话概括就是:“投影后类内方差最小,类间方差最大”
PCA和LDA
• 相同点:
• 两者均可以对数据完成降维操作
• 两者在降维时候均使用矩阵分解的思想
• 两者都假设数据符合高斯分布
• 不同点:
• LDA是监督降维算法,PCA是无监督降维算法
• LDA降维最多降到类别数目k-1的维数,而PCA没有限制
• LDA除了降维外,还可以应用于分类
• LDA选择的是分类性能最好的投影,而PCA选择样本点投影具有最大方差的方向
特征选择/降维
• 在实际的机器学习项目中,特征选择/降维是必须进行的,因为在数据中存在以下几个方面的问题:
• 数据的多重共线性:特征属性之间存在着相互关联关系。多重共线性会导致解的空间不稳定,从而导致模型的泛化能力弱;
• 高纬空间样本具有稀疏性,导致模型比较难找到数据特征;
• 过多的变量会妨碍模型查找规律;
• 仅仅考虑单个变量对于目标属性的影响可能忽略变量之间的潜在关系。
• 通过降维的目的是:
• 减少特征属性的个数
• 确保特征属性之间是相互独立的
import numpy as np
from sklearn.feature_selection import VarianceThreshold, SelectKBest, f_regression, chi2, RFE, SelectFromModel
from sklearn.linear_model import LogisticRegression
## PCA降维
from sklearn.decomposition import PCA
X2 = np.array([
[ 5.1, 3.5, 1.4, 0.2, 1, 23],
[ 4.9, 3. , 1.4, 0.2, 2.3, 2.1],
[ -6.2, 0.4, 5.4, 2.3, 2, 23],
[ -5.9, 0. , 5.1, 1.8, 2, 3]
], dtype=np.float64)
# n_components: 给定降低到多少维度,但是要求该值必须小于等于样本数目/特征数目,如果给定的值大于,那么会选择样本数目/特征数目中最小的那个作为最终的特征数目
# whiten:是否做一个白化的操作,在PCA的基础上,对于特征属性是否做一个标准化
pca = PCA(n_components=0.5, whiten=False)
pca.fit(X2)
print(pca.mean_)
# [-0.525 1.725 3.325 1.125 1.825 12.775]
print(pca.components_)
# [[ 0.02038178 -0.01698103 -0.01350052 -0.0149724 0.03184796 -0.99893718]]
print(pca.transform(X2))
# [[-10.11606313]
# [ 10.80754053]
# [-10.34733219]
# [ 9.65585479]]
## LDA降维
import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
X = np.array([
[-1, -1, 3, 1],
[-2, -1, 2, 4],
[-3, -2, 4, 5],
[1, 1, 5, 4],
[2, 1, 6, -5],
[3, 2, 1, 5]])
y = np.array([1, 1, 2, 2,0, 1])
# n_components:给定降低到多少维度,要求给定的这个值和y的取值数量有关,不能超过n_class-1
clf = LinearDiscriminantAnalysis(n_components=2)
clf.fit(X, y)
print(clf.transform(X))
# [[-3.2688434 -0.38911349]
# [-1.25507558 -1.78088569]
# [ 5.26064254 -0.49688862]
# [ 6.34385833 1.16134391]
# [-4.05800618 3.58297801]
# [-3.02257571 -2.07743411]]