目录
数据表示与特征工程是机器学习中非常非常重要的一个环节。什么是特征工程,其实就是寻找到表示数据最好的过程。如同我们前面做的机器学习实例,都是前辈们打包好了数据给我们用,但是在真实的开发中,数据往往是我们自己收集打包好的,如果这一步没做后面就没法做了。
俗话说“磨刀不误砍柴工”,使用合适的方法表示数据对于监督学习模型的影响比选择准确参数的影响更大,而且我们也需要创造我们自己的特征。这也是它为什么那么重要的原因之一。
当我们确定好工作任务后,首先做的应该是采集数据,这个时候我们会先对数据进行数据预处理。
数据预处理
在处理数据的时候最好遵循一定的规则,这一过程就是数据的预处理。特征工程的两种形式,而数据预处理则主要有三种形式:
☺数据格式化:就是针对数据存储成专门的文件格式,而我们的机器算法没办法直接读取时。也就是说数据并不是适合我们直接处理的那种格式。
☺数据清理:数据可能存在无效的或者丢失的条目。这些无效数据、缺胳膊断腿的数据都应该被移除出去,而不应该加入到供算法运算中。
☺数据采样:数据对于我们要做的任务而言如果太大,我们就需要把数据已一种巧妙的方式来采样。
当数据一旦被预处理后,我们就可以进行真正的特征工程了:就是把预处理后的数据转换成满足我们特定机器学习算法需要的格式。一般这个过程需要下面三个步骤中一个或者三步来实现:
①缩放:缩放是将所有的特征(可能是不同的单位,比如人的年龄、体重、身高等不同单位的数据)变成特定范围内的值的一个过程。
②分解:数据常常会拥有比我们所能处理的更多特征。特征分解可以把数据压缩成由少量但具有更多信息的数据成分组合的数据过程。就是说我们采集的数据信息很大很杂,我们找出最有用的信息并组合。
③聚合:有时候我们需要把多个特征聚合成一个更有意义的特征。比如想知道是不是学霸,可能我们要看各科成绩,但是其实我们只要把各科成绩集合成考试排名,看排名就知道是不是学霸,而不要一科科成绩去判断。
深入点理解特征工程
首先,一个机器学习系统可以学习的程度主要由训练数据的质量决定,虽然每个算法都肯定有自身的优点、缺点,但是在系统的性能上谈这个差异,往往来自于数据点准备方式或者表示形式不同。因此特征工程可以理解为数据表示的一个工具。就像我们通常讲的每个人天生都有自己的性格。但是,近朱者赤,近墨者黑,我们学习到(被影响到)的也会受制于环境,环境可以改变一个人。
如果说机器学习的算法是尝试从样本数据中学习解决问题的方法,那特征工程就是——什么样的样本数据表示才是可以让系统学习解决问题的方法的最佳表示。换句话说,从我们之前的实例也知道,算法就是通过数学方程式演算得出结果表示,但是前提特征应该如何以数据形式呈现并供算法去做运算,这就是很关键的了。
特征工程是数据预处理中的一部分:
第一篇我们就提过特征过程的内容就包括了“特征选择”和“特征提取”。
☺特征选择:这是识别数据中的重要属性的过程,一张图片的潜在特征可能就是边缘位置、角的位置(角点)或者脊的位置(山脊点)。在OpenCV中,提供了更加高级的描述符,比如:
①尺度不变特征变换——SIFT(Scale-InvariantFeature Transform),可参考百度百科也挺详细https://baike.baidu.com/item/SIFT/1396275?fr=aladdin;
②加速健壮特征——SURF(Speeded Up Robust Features),我推荐一篇https://www.cnblogs.com/gfgwxw/p/9415218.html;
③方向梯度直方图——HOG(Histogram of Oriented Gradients)等等算子,我推荐一篇简单的https://blog.csdn.net/zhanghenan123/article/details/80853523。
☺特征提取:这一步是把原始数据转换为用于机器学习算法的期望特征空间的实际过程。比如Harris算子,它可以被用来从图像中提取角点(即选择特征)。
数据预处理详细过程
特征标准化
在机器学习中标准化就是把数据放到拥有零均值和单位方差的过程(就是数据预处理中的缩放操作)。对于大部分如果个别特征无法满足这个条件则表现就不太好的算法,一般都要做这个处理过程。
可以手动的标准化我们的数据,每个数据点减去所有数据的平均值(μ)然后除以数据的方差(σ)。也就是说每个特征x,我们将要计算(x-μ)/σ。
手算太麻烦了,那么另一种标准化方式是scikit-learn在preprocessing模块中提供了这个过程的简单实现。假设有一个3*3的数据矩阵X,表示拥有3个任意选定的特征值(列)的3个数据点(行)。
from sklearn import preprocessing
import numpy as np
X = np.array([[ 1., -2., 2.],
[ 3., 0., 0.],
[ 0., 1., -1.]])
# 接着用函数scale完成数据矩阵X的标准化
X_scaled = preprocessing.scale(X)
X_scaled #输出 array([[-0.26726124, -1.33630621, 1.33630621],
[ 1.33630621, 0.26726124, -0.26726124],
[-1.06904497, 1.06904497, -1.06904497]])
我们可以通过均值和方差来验证缩放后的数据矩阵X_scaled,确实已经完成这个标准化操作,一个标准化后的特征矩阵应该每行的均值等于(接近于0)。
# 求均方误差
X_scaled.mean(axis=0) #输出array([ 7.40148683e-17, 0.00000000e+00, 0.00000000e+00])
此外,标准化后的特征矩阵的每一行的方差都为1。
# 求方差
X_scaled.std(axis=0) #输出array([ 1., 1., 1.])
特征归一化
跟标准化一样,归一化是缩放单位样本以使他们拥有单位范数的过程。范数表示的就是一个向量的长度,且可以使用不同的方法来定义,我们在第四篇就谈过L1范数(曼哈顿距离)和L2范数(欧式距离)。
在scikit-learn中,数据矩阵X可以使用normalize函数进行归一化,L1范数通过norm关键字进行设定:
# L1范数归一化
X_normalized_l1 = preprocessing.normalize(X, norm='l1')
X_normalized_l1 #输出array([[ 0.2, -0.4, 0.4],
[ 1. , 0. , 0. ],
[ 0. , 0.5, -0.5]])
一样的,L2范数可以通过指定norm=‘12’来计算:
# L2范数的归一化
X_normalized_l2 = preprocessing.normalize(X, norm='l2')
X_normalized_l2 #输出array([[ 0.33333333, -0.66666667, 0.66666667],
[ 1. , 0. , 0. ],
[ 0. , 0.70710678, -0.70710678]])
特征缩放到一定范围
除了向上述一样将特征缩放到一定零均值和单位方差外,还可以将特征缩放到一个给定的最小值和最大值之间。一般来说这两个值是0和1,这样每个特征的最大绝对数就缩放到单位尺寸了。在scikit-learn中,可以使用MinMaxScaler来完成操作:
min_max_scaler = preprocessing.MinMaxScaler()
X_min_max = min_max_scaler.fit_transform(X)
X_min_max #输出array([[ 0.33333333, 0. , 1. ],
[ 1. , 0.66666667, 0.33333333],
[ 0. , 1. , 0. ]])
默认情况下,数据将会缩放到0和1之间。可以通过传入MinMaxScaler构造函数一个关键字参数feature_range来指定不同范围:
min_max_scaler = preprocessing.MinMaxScaler(feature_range=(-10, 10))
X_min_max2 = min_max_scaler.fit_transform(X)
X_min_max2 #输出array([[ 0.33333333, 0. , 1. ],
[ 1. , 0.66666667, 0.33333333],
[ 0. , 1. , 0. ]])
特征二值化
当我们仅想知道一个特征存在不存在,二值化数据的操作可以通过对特征值设置阈值来完成。
接着上面的代码,有X =array([[ 1., -2., 2.], [ 3., 0., 0.], [ 0., 1., -1.]]),假设这些是以千为单位的美元,如果一个账户超过500美元,我们认为他是有钱人,用1表示,否则是穷人,用0表示。那么阈值就是threshold=0.5。
binarizer = preprocessing.Binarizer(threshold=0.5)
X_binarized = binarizer.transform(X)
X_binarized #输出array([[ 1., 0., 1.],
[ 1., 0., 0.],
[ 0., 1., 0.]])
这样就可以实现二值化了。
缺失数据的处理
另一个特征工程的常见需求就是处理缺失的数据。比如可能有一个数据集:
from numpy import nan
X = np.array([[ nan, 0, 3 ],
[ 2, 9, -8 ],
[ 1, nan, 1 ],
[ 5, 2, 4 ],
[ 7, 6, -3 ]])
一般对于缺失的特征nan,机器学习算法是无法处理的。这个时候我们可能要找一个合适的值来代替这个nan,这就叫做填充缺失值。
scikit-learn提供给我们三种策略来填充缺失值:
①mean:将所有的nan值替换为矩阵指定坐标轴上元素的平均值(默认情况axis=0);
②median:将所有的nan值替换为矩阵坐标轴上元素的中值(默认情况axis=0);
③most_frequest:将所有的nan值替换为矩阵在指定坐标轴上出现频率最高的值(默认axis=0)
# 使用mean处理缺失值
from sklearn.preprocessing import Imputer
imp = Imputer(strategy='mean')
X2 = imp.fit_transform(X)
X2 #输出array([[ 3.75, 0. , 3. ],
[ 2. , 9. , -8. ],
[ 1. , 4.25, 1. ],
[ 5. , 2. , 4. ],
[ 7. , 6. , -3. ]])
这就是将nan值分别替换为其对应列计算得到的平均值。可以通过计算第一列(不计算第一个元素X[0,0])的均值来再次检查这个数学结果,并将计算值与矩阵的第一个X2[0,0]进行对比。
np.mean(X[1:, 0]), X2[0, 0] #输出(3.75, 3.75)
同样的,median也是一样操作:
# 使用median处理缺失值
imp = Imputer(strategy='median')
X3 = imp.fit_transform(X)
X3 #输出array([[ 3.5, 0. , 3. ],
[ 2. , 9. , -8. ],
[ 1. , 4. , 1. ],
[ 5. , 2. , 4. ],
[ 7. , 6. , -3. ]])
我们检查一下结果,这一次我们不再计算第一列的平均值而是计算其中值(不包括X[0,0]),并将结果与X3[0,0]进行对比,发现值是一样的,那就是说明Imputer和预期的一样计算。
np.median(X[1:, 0]), X3[0, 0] #输出(3.5, 3.5)
这些学习笔记是我在拜读了Michael Beyeler著的《机器学习 使用OpenCV和Python进行智能图像处理》的自我理解,感谢。