本文仅供学习使用
所有一切为了让模型效果变的更好的数据处理方式都可以认为属于特征工程这个范畴中的一个操作;
至于需求做不做这个特征工程,需要我们在开发过程中不断的进行尝试。
常规的特征工程需要处理的内容:
为什么需要特征工程(Feature Engineering)
机器学习领域的大神Andrew Ng(吴恩达)老师说“Coming up with features is difficult, time-consuming, requires expert knowledge. ‘Applied machine learning’ is basically feature engineering. ”
业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。
什么是特征工程
特征工程是使用专业背景知识和技巧处理数据,使得特征能在机器学习算法上发挥更好的作用的过程。 意义:会直接影响机器学习的效果
数据清洗(data cleaning)
是在机器学习过程中一个不可缺少的环节,其数据的清洗结果直接关系到模型效果以及最终的结论。在实际的工作中,数据清洗通常占开发过程的30%-50%左右的时间
在数据预处理过程主要考虑两个方面,如下:
一般情况下,数据是由用户/访客产生的,也就有很大的可能性存在格式和内容上不一致的情况,所以在进行模型构建之前需要先进行数据的格式内容清洗操作。格式内容问题主要有以下几类:
主要是通过简单的逻辑推理发现数据中的问题数据,防止分析结果走偏,主要包含以下几个步骤:
一般情况下,我们会尽可能多的收集数据,但是不是所有的字段数据都是可以应用到模型构建过程的,也不是说将所有的字段属性都放到构建模型中,最终模型的效果就一定会好,实际上来讲,字段属性越多,模型的构建就会越慢,所以有时候可以考虑将不要的字段进行删除操作。在进行该过程的时候,要注意备份原始数据。
若增加数据,则对过拟合/欠拟合都有一定的改善作用
如果数据有多个来源,那么有必要进行关联性验证,该过程常应用到多数据源合并的过程中,通过验证数据之间的关联性来选择比较正确的特征属性,比如:汽车的线下购买信息和电话客服问卷信息,两者之间可以通过姓名和手机号进行关联操作,匹配两者之间的车辆信息是否是同一辆,如果不是,那么就需要进行数据调整。
python科学计算库 from sklearn.impute import SimpleImputer
所填充的值并非真实值,但所训练的模型并非一定缺失真实性——好的模型具有鲁棒性
• 均值填充
• 中值填充
• 众数填充
• 0填充
• 常数填充(与具体情况相关)
class sklearn.impute.SimpleImputer(*, missing_values=nan, strategy=‘mean’, fill_value=None, verbose=0, copy=True, add_indicator=False)
missing_values: int, float, str, (默认)np.nan或是None, 即缺失值是什么。
strategy: 空值填充的策略,共四种选择(默认)mean、median、most_frequent、constant。mean表示该列的缺失值由该列的均值填充。median为中位数,most_frequent为众数。constant表示将空值填充为自定义的值,但这个自定义的值要通过fill_value来定义。
fill_value: str或数值,默认为Zone。当strategy == “constant"时,fill_value被用来替换所有出现的缺失值(missing_values)。fill_value为Zone,当处理的是数值数据时,缺失值(missing_values)会替换为0,对于字符串或对象数据类型则替换为"missing_value” 这一字符串。
verbose: int,(默认)0,控制imputer的冗长。
copy: boolean,(默认)True,表示对数据的副本进行处理,False对数据原地修改。
add_indicator: boolean,(默认)False,True则会在数据后面加入n列由0和1构成的同样大小的数据,0表示所在位置非缺失值,1表示所在位置为缺失值。
import numpy as np
# from sklearn.preprocessing import Imputer # 旧版本
from sklearn.impute import SimpleImputer # 新版本
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) # 返回值为SimpleImputer()类
# 当axis=0的时候,获取每个特征的计算出来的填充值
print(imp1.statistics_)
print('\n')
print(imp1.transform(X))
print('\n')
print(imp1.fit_transform(X)) # 相当于fit() + transform()
imp1 = SimpleImputer(missing_values=np.nan, strategy='mean') # 均值
imp2 = SimpleImputer(missing_values=np.nan, strategy='median') # 中位数
imp3 = SimpleImputer(missing_values=np.nan, strategy='most_frequent') # 众数
imp1.fit(X)
imp2.fit(X)
imp3.fit(X)
print(imp1.transform(X))
print(imp2.transform(X))
print(imp3.transform(X))
imp4 = SimpleImputer(missing_values=np.nan, strategy='constant',fill_value=1) # 常数
print(imp4.fit_transform(X))
fit:针对训练集(model)
transform:训练+测试
fit_transform:训练集
特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级, 容易影响(支配)目标结果,使得一些算法无法学习到其它的特征
我们需要用到一些方法进行无量纲化,使不同规格的数据转换到同一规格
from sklearn.preprocessing import MinMaxScaler
通过对原始数据进行变换把数据映射到(默认为[0,1])之间:
X ′ = x − x min x max − x min {X}'=\frac{x-{{x}_{\min }}}{{{x}_{\max }}-{{x}_{\min }}} X′=xmax−xminx−xmin
X ′ ′ = X ′ ⋅ ( max − min ) + min X''=X'\cdot (\max -\min )+\min X′′=X′⋅(max−min)+min (其中,max与min为映射区间的上界和下界)
若最大值等于最小值——该特征数据一致,无意义
思考:如果数据中异常点较多(离群),会有什么影响?
注意最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性较差,只适合传统精确小数据场景。
import numpy as np
from sklearn.preprocessing import MinMaxScaler
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))
import pandas as pd
print(pd.DataFrame(X).describe())
print(pd.DataFrame(scaler.transform(X)).describe())
from sklearn.preprocessing import StandardScaler
通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内:
X ′ = x − m e a n σ {X}'=\frac{x-mean}{\sigma } X′=σx−mean
对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。
在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景
同样,若标准差/方差为零,其数据也无意义。
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(ss.transform(X))
# 相当于对测试集做一个数据转换
print(ss.transform(x_test))
import pandas as pd
print(pd.DataFrame(X).describe())
print(pd.DataFrame(ss.transform(X)).describe())
将任意数据(如文本或图像)转换为可用于机器学习的数字特征
from sklearn.feature_extraction import DictVectorizer
(onehot)from sklearn.feature_extraction import DictVectorizer
data = [{'city': '北京','temperature':10}, {'city': '上海','temperature':20}, {'city': '深圳','temperature':30}]
# 1、实例化一个转换器类
transfer = DictVectorizer(sparse=False)
# 2、调用fit_transform
data = transfer.fit_transform(data)
print("返回的结果:\n", data)
# 打印特征名字
print("特征名字:\n", transfer.get_feature_names())
onehot:from sklearn.preprocessing import OneHotEncoder
class sklearn.preprocessing.OneHotEncoder(*, categories='auto', drop=None, sparse=True, dtype=
, handle_unknown='error')
handle_unknown: {‘error’, ‘ignore’}, default=’error’ 在转换过程中遇到未知分类特征时,是引发错误还是忽略(默认为引发)。当此参数设置为“ignore”并且在转换过程中遇到未知类别时,这一特征的 one-hot 编码列将全置为 0。在逆变换中,未知类别将表示为 None
import numpy as np
from sklearn.preprocessing import OneHotEncoder
# OneHotEncoder要求数据类别必须是数值的
# class sklearn.preprocessing.OneHotEncoder(*, categories='auto', drop=None, sparse=True, dtype=, handle_unknown='error')
## -- categorical_features:给定对那些列的数据进行哑编码操作,默认是对所有类数据做
## -- n_values:明确给定各个特征属性的类别数目,其实是最大值-最小值+1
### ---- enc = OneHotEncoder(categorical_features=[1,2])
### ---- enc = OneHotEncoder(n_values=[3, 3, 4])
# -- handle_unknown='error' / 'ignore'
# -- drop:编码方式 'first' 'if_binary'
enc = OneHotEncoder(drop='first')
a=np.array([[0, 0, 3],
[2, 1, 0],
[0, 2, 1],
[1, 0, 1],
[1, 1, 1]])
enc.fit(a)
print(a)
print("编码类别",enc.categories_)
print(enc.transform(a).toarray())
enc2 = OneHotEncoder(drop='if_binary')
a=np.array([[0, 0, 3],
[2, 1, 0],
[0, 2, 1],
[1, 0, 1],
[1, 1, 1]])
enc2.fit(a)
print("编码类别",enc2.categories_)
print(enc2.transform(a).toarray())
enc3 = OneHotEncoder()
a=np.array([[0, 0, 3],
[2, 1, 0],
[0, 2, 1],
[1, 0, 1],
[1, 1, 1]])
enc3.fit(a)
print("编码类别",enc3.categories_)
print(enc3.transform(a).toarray())
# 如果类别数据是字符串类型的,可以使用pandas的API进行哑编码转换
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)
pip3 install jieba
集成百度LAC:python -m pip install --user paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple
import jieba
# 集成百度LAC
# jieba.enable_paddle()
jieba.initialize() # 初始化分词模型
words_a='上海自来水来自海上,所以吃葡萄不吐葡萄皮'
seg_a=jieba.cut(words_a,cut_all=True)
print("全模式:","/".join(seg_a))
seg_b=jieba.cut(words_a)
print(seg_b) # 可迭代对象
print("精确模式:","/".join(seg_b))
seg_c=jieba.cut_for_search(words_a)
print("搜索引擎模式","/".join(seg_c))
seg_l = jieba.lcut(words_a)
print(seg_l) # 以列表形式输出
seg_l = jieba.lcut(words_a,use_paddle=True) # python -m pip install --user paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple
print(seg_l) # 以列表形式输出
seg_b=jieba.cut(words_a)
for w in seg_b:
print(w)
#添加和删除自定义词汇
words_a1='我为机器学习疯狂打call,大(家好)吗'
jieba.del_word("打call") # 删除单词,在后续的分词的时候,被删除的不会认为是一个单词
print("自定义前:","/".join(jieba.cut(words_a1)))
jieba.add_word("打call") # 添加单词,在后续的分词中,遇到到的时候,会认为是属于同一个单词
print("加入‘打call’后:","/".join(jieba.cut(words_a1)))
jieba.add_word("机器学习")
print(jieba.lcut(words_a1))
jieba.del_word("打call")
print(jieba.lcut(words_a1))
jieba.add_word("大(家好)吗") # 失效 不能带符号
print(jieba.lcut(words_a1))
# #导入自定义词典
# 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)))
#获得切词后的数据
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)))
jieba.suggest_freq(('中', '将'), True)
print("--------------------------")
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=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'
ansy = jieba.analyse.extract_tags(words_a2,topK=5,withWeight=True)
print(ansy)
## 停用词过滤
# 先分词,再过滤
stop_words = ["的","了",","]
words_a2='在正义者联盟的电影里,嘻哈侠和蝙蝠侠联手打败了大boss,我高喊666,为他们疯狂打call'
result_l = jieba.lcut(words_a2)
print(result_l)
result_f = [word for word in result_l if word not in stop_words]
print(result_f)
词袋法 、TF-IDF:from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer,TfidfTransformer
(Gension)
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 c 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))
# CountVectorizer 的作用是将文本文档转换为计数的稀疏矩阵
df1 = count.fit_transform(df)
print (df1.toarray())
print (count.get_stop_words())
print (count.get_feature_names()) # 哪些单词进行了统计
print ("转换另外的文档数据")
print (count.transform(arr1).toarray())
print(df1) # 稀疏矩阵形式
# 基于TF的值(词袋法),做一个IDF的转换
tfidf_t = TfidfTransformer()
df2 = tfidf_t.fit_transform(df1)
print (df2.toarray())
print ("转换另外的文档数据")
print (tfidf_t.transform(count.transform(arr1)).toarray())
## 相当TF+IDF(先做词袋法再做IDF转换)
tfidf_v = TfidfVectorizer(min_df=0, dtype=np.float64)
df3 = tfidf_v.fit_transform(df)
print (df3.toarray())
print (tfidf_v.get_feature_names())
print (tfidf_v.get_stop_words())
print ("转换另外的文档数据")
print (tfidf_v.transform(arr1).toarray())
降维是指在某些限定条件下,降低随机变量(特征)个数,得到一组“不相关”主变量的过程
正是因为在进行训练的时候,我们都是使用特征进行学习。如果特征本身存在问题或者特征之间相关性较强,对于算法学习预测会影响较大。通过特征提取(feature extraction)进行降维的目的就是对原特征集 P original {{P}_{\text{original}}} Poriginal进行变换,变换后的新特征集记为 P new {{P}_{\text{new}}} Pnew,这里 P original > P new {{P}_{\text{original}}}>{{P}_{\text{new}}} Poriginal>Pnew,但是 P new {{P}_{\text{new}}} Pnew保留了原特征集的大部分信息。换句话说,就是通过牺牲一小部分数据信息来减少特征的数量,并且保证还能作出准确的预测。
降维的两种方式: 特征选择 、主成分分析(可以理解一种特征提取的方式)
数据中包含冗余或无关变量(或称特征、属性、指标等),旨在从原有特征中找出主要特征。
抽取——减少列数
特征选择的方法主要有以下三种:(人工取值除外)
Filter: 过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,从而选择特征;常用方法包括方差选择法
、相关系数法
、卡方检验
、互信息法
等。
Wrapper: 包装法,根据目标函数(通常是预测效果评分),每次选择若干特征或者排除若干特征;常用方法主要是递归特征消除法
。
Embedded: 嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权重系数,根据系数从大到小选择特征;常用方法主要是基于惩罚项的特征选择法。
from sklearn.feature_selection import VarianceThreshold
创建VarianceThreshold对象:
thresholder = VarianceThreshold(threshold=0.1)
创建大方差特征矩阵:features_high_variance = thresholder.fit_transform(features)
显示大方差特征矩阵:features_high_variance
可以通过参数variances_来查看每个特征的方差:eatures_high_variance.variances_
方差选择法: 亦称方差阈值化(Variance Thresholding,VT)
先计算各个特征属性的方差值,然后根据阈值,获取方差大于阈值的特征。
import numpy as np
import warnings
from sklearn.feature_selection import VarianceThreshold
X = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0.1, 1, 1, 3]
], dtype=np.float32)
Y = np.array([1,2,1])
# 基于方差选择最优的特征属性
variance = VarianceThreshold(threshold=0.1)
print(variance)
variance.fit(X)
print("各个特征属性的方差为:")
print(variance.variances_)
print('-----------------')
print(variance.transform(X))
采用VT方法使需要注意两点:
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
相关系数法:先计算各个特征属性对于目标值的相关系数以及阈值K,然后获取 K 个相关系数最大的特征属性。(备注:根据目标属性y的类别选择不同的方式)
import numpy as np
import warnings
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
X = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0.1, 1, 1, 3]
], dtype=np.float32)
Y = np.array([1,2,1])
## 相关系数法
sk1 = SelectKBest(f_regression, k=2)
sk1.fit(X, Y)
print(sk1)
print('------------')
print(sk1.scores_)
print('------------')
print(sk1.transform(X))
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
卡方检验:检查定性自变量对定性因变量的相关性。 χ 2 = ∑ ( A − E ) 2 E {{\chi }^{2}}=\sum{\frac{{{(A-E)}^{2}}}{E}} χ2=∑E(A−E)2
import numpy as np
import warnings
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
X = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0.1, 1, 1, 3]
], dtype=np.float32)
Y = np.array([1,2,1])
## 卡方检验
# 使用chi2的时候要求特征属性的取值为非负数
sk2 = SelectKBest(chi2, k=2)
sk2.fit(X, Y)
print(sk2)
print(sk2.scores_)
print(sk2.transform(X))
卡方统计可以检查两个分类向量的相互独立性,也就是说,卡方统计量代表了观察到的样本数量和期望的样本数量(假设特征与目标向量无关)之间的差异;卡方统计的结果是一个数值,通过计算特征和目标向量的卡方统计量,可以得到对两者之间独立性的度量值。SelectKBest方法来选择最好的特征,其中参数k决定了要保留的特征数量。
卡方统计量只能在两个分类数据之间进行计算,所以使用卡方统计进行特征选择时,目标向量和特征都必须时分类数据。对于数值型特征,可以将其转换为分类特征后再使用卡方统计,且所有值必须时非负的。
根据分类的目标向量,删除信息量较低的特征。
前三者为过滤法(1.4.1.1~1.4.1.3)
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
递归特征消除法:使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。
自动选择需要保留的最有特征
Wrapper-递归特征消除法
import numpy as np
import warnings
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
X = np.array([
[0, 2, 0, 3],
[0, 1, 4, 3],
[0.1, 1, 1, 3]
], dtype=np.float32)
Y = np.array([1,2,1])
# 基于特征消去法做的特征选择
estimator = LogisticRegression()
selector = RFE(estimator, 2, step=5)
selector = selector.fit(X, Y)
print(selector.support_)
print(selector.n_features_)
print(selector.ranking_)
print(selector.transform(X))
from sklearn.feature_selection import RFECV
REFCV类通过交叉验证(Crossing Validation,CV) 进行 递归式特征消除(Recursive Feature Elimination,REF)。该方法会重复训练模型,每一次训练一处一个特征,直到模型性能(精度)变差,剩余的特征就是最优特征。REF背后的思想是重复训练一个包含若干参数(也称为权重或系数)的模型,在该方法中假设特征已经进行过标准化处理。
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
使用基于惩罚项的基模型,进行特征选择操作。
import numpy as np
import warnings
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
## 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='l2', C=0.1)
# estimator = LogisticRegression(penalty='l2', C=0.1,solver = 'liblinear')
sfm = SelectFromModel(estimator,threshold=0.1)
sfm.fit(X2, Y2)
print(sfm.transform(X2))
print("系数:")
print(sfm.estimator_.coef_)
对于KNN算法而言,需要假设任意测试样本x附近任意小的δ距离范围内总能找到一个训练样本,即训练样本的采样密度足够大,或称为密采样(dense sample)
。——当属性维度增大时,若样本满足密采样条件,所需的样本数目是无法达到的天文数字(δ=0.001),并且计算内积/距离也会带来很大的麻烦——高维数据样本稀疏——维度灾难(curse of dimensionality)
——降维(dimension reduction)
,亦称维度简约
通过某种数学变换将原始高维属性空间转变为一个低维子空间(subspace)
——高维空间中的一个低维嵌入(embedding)
多维缩放(Multiple Dimensional Scaling,简称MDS)
——某种特征值分解
基于线性变换来进行降维的方法称为线性降维方法,若要求低维子空间对样本具有最大可分性,则将得到一种极为常用的线性降维方法;
对降维效果的评估通常是比较降维前后学习器的性能,若性能有所提高则认为降维起到了作用,若将维数降至二维/三维,则可通过可视化技术来直观地判断降维效果
当特征选择完成后,可以直接可以进行训练模型了,但是可能由于特征矩阵过大,导致计算量比较大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的(牺牲了一些评估指标与特征描述)。常见的降维方法除了基于L1的惩罚模型外,还有主成分析法(PCA)
和线性判别分析法(LDA)
,这两种方法的本质都是将原始数据映射到维度更低的样本空间中(矩阵变化);但是采用的方式不同,PCA是为了让映射后的样本具有更大的发散性,PCA是无监督的学习算法,LDA是为了让映射后的样本有最好的分类性能,LDA是有监督学习算法。
对非负矩阵进行降维:非负矩阵分解法(Non-Negative Matrix Factorization,NMF),是一种无监督的线性降维方法,特征矩阵就不能包含负数值,而且不会告诉我们输出特征中保留了原始数据的信息量,因此需要不断尝试一系列可能的值;
对稀疏特征矩阵进行特征降维操作:截断奇异值分解(Truncated Singular Value Decomposition,TSVD),与PCA相比TSVD的优势在于适用于稀疏矩阵,但是其输出值的符号会在多次拟合中不断变换;
PCA(Principal Component Analysis)
是常用的线性降维方法,是一种无监督的降维算法。算法目标是通过某种线性投影,将高维的数据映射到低维的空间中表示,并且期望在所投影的维度上数据的方差最大(最大方差理论),以此使用较少的数据维度,同时保留较多的原数据点的特性。
通俗来讲的话,如果将所有点映射到一起,那么维度一定降低下去了,但是同时也会将几乎所有的信息(包括点点之间的距离等)都丢失了,而如果映射之后的数据具有比较大的方差,那么可以认为数据点则会比较分散,这样的话,就可以保留更多的信息。从而我们可以看到PCA是一种丢失原始数据信息最少的无监督线性降维方式。
在PCA降维中,数据从原来的坐标系转换为新的坐标系,新坐标系的选择由 数据本身的特性决定。第一个坐标轴选择原始数据中方差最大的方向,从统计角度来讲,这个方向是最重要的方向;第二个坐标轴选择和第一个坐标轴垂直或者正交的方向;第三个坐标轴选择和第一个、第二个坐标轴都垂直或者正交的方向;该过程一直重复,直到新坐标系的维度和原始坐标系维度数 目一致的时候结束计算。而这些方向所表示的数据特征就被称为“主成分”
。
用一个超平面(直线的高维推广)对所有样本进行恰当的表达:
from sklearn.decomposition import PCA
创建可以保留99%信息量(用方差表示)的PCA:
pca = PCA(n_components=0.99,whiten=True)
执行PCA:features_pca = pca.fit_transform(feature)
显示结果:
print('Original number of features:',features.shape[1])
print(Reduced number of features:',features_pca.shape[1])
主成分析(PCA): 是一种流行的线性降维方法。将高纬的特征向量合并称为低纬度的特征属性,将雅本数据映射到特征矩阵的主成分空间,只考虑特征矩阵而不需要目标向量的信息,是一种无监督的降维方法。
降维后低维空间的维度d’通常是有用户事先指定,或通过在d’值不同的低维空间中对k近邻分类器(或其他开销较小的分类器)进行交叉验证来选取较好的d‘值。
## 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的基础上,对于特征属性是否做一个标准化——对每一个主成分都进行转换以保证它们的平均值为0、方差为1
# svd_solver = 'randomized',代表使用随机方法找到第一个主成分(这种方法通常速度很快)
pca = PCA(n_components=0.8,whiten=True)
pca.fit(X2)
print(pca.mean_)
print(pca.components_)
print(pca.transform(X2))
PCA仅需保留W*与样本的均值向量即可通过简单的向量减法和矩阵-向量乘法将新样本投影至低维空间中,显然,低维空间与原始高维空间必有不同,因为对应于最小的 d-d’ 个特征值的特征向量被舍弃了,这是降维导致的结果,但舍弃这部分信息往往是必要的:一方面,舍弃这些部分信息之后能使样本的采样密度增大,这正是降维的重要动机;另一方面,当数据收到噪声影响时,最小的特征值所对应的特征向量往往与噪声有关,将它们舍弃能在一定程度起到去噪的效果
对于线性不可分数据进行降维——Kernel PCA(核主成分分析)
from sklearn.decomposition import PCA,KernelPCA
应用基于径向基函数(Radius Basis Function,RBF)核的Kernel PCA方法:
kpca = KernelPCA(kerbek='rbf', gamma=15, n_components=1)
features_kpca = kpca.fit_transform(features)
标准PCA使用线性映射减少特征的数量。如果数据是线性可分的(也就是说可以用一条直线或者超平面将两类数据分开),那么PCA处理的效果就会很好。然而,如果数据不是线性可分的,那么线性变换的效果就不会很好。如果使用线性PCA对数据进行降维,则两类数据会被线性映射到第一个主成分上,因而会交织在一起;理想情况下,我们希望维度变换既能降低数据的维度,又可以使数据变得线性可分,Kernel PCA 可以做到这两点。
核(kernel,也叫核函数)
能够将线性不可分数据映射到更高的维度,数据在这个维度是线性可分的,我们把这种方式叫做核机制(kernel trick)。常用的核函数是高斯径向基函数(rbf),其他核函数还有多项式核(poly) 和sigmoid核(sigmoid),或者一个线性(linear) 映射
Kernel PCA的一个缺点是需要指定很多参数,且必须指定参数的数量(n_components),且每个核都有自己的超参数需要设置(如径向基函数需要设置gamma)
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
创建并运行LDA,然后用它对特征做变换:
lda = LinearDiscriminantAnalysis(n_components=1)
feature_lda = lda.fit(features, target).transfprm(features)
可以使用参数 explained_variance_ratio_来查看每个成分保留的信息量(即数据的差异)的情况:lda.explained_variance_ratio_
,该排序数组表示每个输出的特征所保留的信息量(用方差表示)
LDA的全称是Linear Discriminant Analysis(线性判别分析)
,是一种有监督学习算法。
LDA的原理是,将带上标签的数据(点),通过投影的方法,投影到维度更低的空间中,使得投影后的点,会形成按类别区分,一簇一簇的情况,相同类别的点,将会在投影后的空间中更接近。用一句话概括就是:“投影后类内方差最小,类间方差最大”
## 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=3)
clf.fit(X, y)
print(clf.transform(X))
相同点:
• 两者均可以对数据完成降维操作
• 两者在降维时候均使用矩阵分解的思想
• 两者都假设数据符合高斯分布
不同点:
• LDA是监督降维算法,PCA是无监督降维算法
• LDA降维最多降到类别数目 k-1 的维数,而PCA没有限制
• LDA除了降维外,还可以应用于分类
• LDA选择的是分类性能最好的投影,而PCA选择样本点投影具有最大方差的方向
在PCA中,只需关注使数据差异最大化的成分轴;LDA中,找到使得类间差异最大的成分轴
在实际的机器学习项目中,特征选择/降维是必须进行的,因为在数据中存在以下几个方面的问题:
通过降维的目的是: