在数据处理中,不同评价指标往往具有不同的量纲和量纲单位,这样的情况会影响到数据分析的结果,为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之间的可比性。原始数据经过数据标准化处理后,各指标处于同一数量级,适合进行综合对比评价。
通常的数据归一化方法有两种:
把所有数据映射到0-1之间。最值归一化的使用范围是特征的分布具有明显边界的(如分数0-100、灰度0-255),受outlier的影响比较大。
x_scale=(x-x_min)/(x_max-x_min )
把所有的数据归一到均值为0方差为1的分布中。适用于没有明显边界,且有可能存在极端数据值的情况。
x_scale=(x-x_mean)/σ
其中:x_mean为样本数据的均值,σ为样本数据的标准差。
为了了解最值归一化的代码实现,先我们可以创建100个随机数,然后对其进行最值归一化。
import numpy as np
# 创建100个随机数
x = np.random.randint(0,100,size=100)
# 最值归一化(向量)
# 最值归一化公式,映射到0,1之间
(x - np.min(x)) / (np.max(x) - np.min(x))
# 最值归一化(矩阵)
# 0~100范围内的50*2的矩阵
X = np.random.randint(0,100,(50,2))
# 将矩阵改为浮点型
X = np.array(X, dtype=float)
# 最值归一化公式,对于每一个维度(列方向)进行归一化。
# X[:,0]第一列,第一个特征
X[:,0] = (X[:,0] - np.min(X[:,0])) / (np.max(X[:,0]) - np.min(X[:,0]))
# X[:,1]第二列,第二个特征
X[:,1] = (X[:,1] - np.min(X[:,1])) / (np.max(X[:,1]) - np.min(X[:,1]))
# 如果有n个特征,可以写个循环:
for i in range(0,2):
X[:,i] = (X[:,i]-np.min(X[:,i])) / (np.max(X[:,i] - np.min(X[:,i])))
import matplotlib.pyplot as plt
# 简单绘制样本,看横纵坐标
plt.scatter(X[:,0],X[:,1])
plt.show()
同样地,为了了解均值方差归一化的代码实现,我们可以创建100个随机数,然后对其进行均值方差归一化。
X2 = np.array(np.random.randint(0,100,(50,2)),dtype=float)
# 套用公式,对每一列做均值方差归一化
for i in range(0,2):
X2[:,i]=(X2[:,i]-np.mean(X2[:,i])) / np.std(X2[:,i])
plt.scatter(X2[:,0],X2[:,1])
plt.show()
我们在建模时要将数据集划分为训练数据集&测试数据集。
训练数据集进行归一化处理,需要计算出训练数据集的均值mean_train和方差std_train。
问题是:我们在对测试数据集进行归一化时,要计算测试数据的均值和方差么?
答案是否定的。在对测试数据集进行归一化时,仍然要使用训练数据集的均值train_mean和方差std_train。这是因为测试数据是模拟的真实环境,真实环境中可能无法得到均值和方差,对数据进行归一化。只能够使用公式**(x_test - mean_train) / std_train**并且,数据归一化也是算法的一部分,针对后面所有的数据,也应该做同样的处理.
因此我们要保存训练数据集中得到的均值和方差。
在sklearn中专门的用来数据归一化的方法:StandardScaler。
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
iris = datasets.load_iris() #加载鸢尾花数据集
X = iris.data
y = iris.target
X_train,X_test,y_train,y_test = train_test_split(iris.data,iris.target,test_size=0.2,random_state=666)
########以下为数据归一化过程
from sklearn.preprocessing import StandardScaler
standardScaler = StandardScaler()
# 归一化的过程跟训练模型一样
standardScaler.fit(X_train)
standardScaler.mean_
standardScaler.scale_ # 表述数据分布范围的变量,替代std_
# 使用transform
X_train_standard = standardScaler.transform(X_train)
X_test_standard = standardScaler.transform(X_test)
#得到训练集和测试集
仿照sklearn的风格,可以自己实现一下均值方差归一化的方法。
import numpy as np
class StandardScaler:
def __init__(self):
self.mean_ = None
self.scale_ = None
def fit(self, X):
"""根据训练数据集X获得数据的均值和方差"""
assert X.ndim == 2, "The dimension of X must be 2"
# 求出每个列的均值
self.mean_ = np.array([np.mean(X[:,i] for i in range(X.shape[1]))])
self.scale_ = np.array([np.std(X[:, i] for i in range(X.shape[1]))])
return self
def tranform(self, X):
"""将X根据StandardScaler进行均值方差归一化处理"""
assert X.ndim == 2, "The dimension of X must be 2"
assert self.mean_ is not None and self.scale_ is not None, \
"must fit before transform"
assert X.shape[1] == len(self.mean_), \
"the feature number of X must be equal to mean_ and std_"
# 创建一个空的浮点型矩阵,大小和X相同
resX = np.empty(shape=X.shape, dtype=float)
# 对于每一列(维度)都计算
for col in range(X.shape[1]):
resX[:,col] = (X[:,col] - self.mean_[col]) / self.scale_[col]
return resX
维数 | 点到点 | 距离 |
---|---|---|
1维 | 0到1的距离 | 1 |
2维 | (0,0)到(1,1)的距离 | 1.414 |
3维 | (0,0,0)到(1,1,1)的距离 | 1.73 |
64维 | (0,0,…0)到(1,1,…1) | 8 |
10000维 | (0,0,…0)到(1,1,…1) | 100 |
10000维貌似很多,但实际上就是100*100像素的黑白灰图片。
Kd-树是K-dimension tree的缩写。是对数据点在k维空间(如二维(x,y),三维(x,y,z),k维(x1,y,z…))中划分的一种数据结构,主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。本质上说,Kd-树就是一种平衡二叉树。KD树是一种查询索引结构,广泛应用于数据库索引中。
由于K近邻法的重要步骤是对所有的实例点进行快速k近邻搜索。如果采用线性扫描(linear scan),要计算输入点与每一个点的距离,时间复杂度非常高。因此在查询操作时,使用kd树。
kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构,且kd树是一种二叉树,表示对k维空间的一个划分。
k-d tree是每个节点均为k维样本点的二叉树,其上的每个样本点代表一个超平面,该超平面垂直于当前划分维度的坐标轴,并在该维度上将空间划分为两部分,一部分在其左子树,另一部分在其右子树。即若当前节点的划分维度为d,其左子树上所有点在d维的坐标值均小于当前值,右子树上所有点在d维的坐标值均大于等于当前值,本定义对其任意子节点均成立。
常规的k-d tree的构建过程为:
对于构建过程,有两个优化点:
• 选择切分维度:根据数据点在各维度上的分布情况,方差越大,分布越分散,从方差大的维度开始切分,有较好的切分效果和平衡性。
• 确定中值点:预先对原始数据点在所有维度进行一次排序,存储下来,然后在后续的中值选择中,无须每次都对其子集进行排序,提升了性能。也可以从原始数据点中随机选择固定数目的点,然后对其进行排序,每次从这些样本点中取中值,来作为分割超平面。该方式在实践中被证明可以取得很好性能及很好的平衡性。
例子:采用常规的构建方式,以二维平面点(x,y)的集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2) 为例结合下图来说明k-d tree的构建过程:
kd树的检索是KNN算法至关重要的一步,给定点p,查询数据集中与其距离最近点的过程即为最近邻搜索。
如在构建好的k-d tree上搜索(3,5)的最近邻时,对二维空间的最近邻搜索过程作分析。首先从根节点(7,2)出发,将当前最近邻设为(7,2),对该k-d tree作深度优先遍历。以(3,5)为圆心,其到(7,2)的距离为半径画圆(多维空间为超球面),可以看出(8,1)右侧的区域与该圆不相交,所以(8,1)的右子树全部忽略。接着走到(7,2)左子树根节点(5,4),与原最近邻对比距离后,更新当前最近邻为(5,4)。以(3,5)为圆心,其到(5,4)的距离为半径画圆,发现(7,2)右侧的区域与该圆不相交,忽略该侧所有节点,这样(7,2)的整个右子树被标记为已忽略。遍历完(5,4)的左右叶子节点,发现与当前最优距离相等,不更新最近邻。所以(3,5)的最近邻为(5,4)。
Sklearn中有KDTree的实现,仅构建了一个二维空间的k-d tree,然后对其作k近邻搜索及指定半径的范围搜索。多维空间的检索,调用方式与此例相差无多。
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Circle
from sklearn.neighbors import KDTree
np.random.seed(0)
points = np.random.random((100, 2))
tree = KDTree(points)
point = points[0]
# kNN
dists, indices = tree.query([point], k=3)
print(dists, indices)
# query radius
indices = tree.query_radius([point], r=0.2)
print(indices)
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
ax.add_patch(Circle(point, 0.2, color='r', fill=False))
X, Y = [p[0] for p in points], [p[1] for p in points]
plt.scatter(X, Y)
plt.scatter([point[0]], [point[1]], c='r')
plt.show()
特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。
虽然我们也有自动的机器学习框架,但特征工程永不过时,即使对于自动化方法,其中也有一部分经常需要根据数据类型、领域和要解决的问题而设计特殊的特征。
特征工程包含了
我们的数据一般都是有单位的,比如身高的单位有m,cm,这个无量纲化并不是说把m变成cm,而是说,无论是m还是cm,最后都会变成1,也就是没有了单位。
无量纲化使不同规格的数据转换到同一规格。常见的无量纲化方法有标准化和归一化。
数据标准化的原因:
某些算法要求样本具有零均值和单位方差;
需要消除样本不同属性具有不同量级时的影响。
归一化有可能提高精度;
数量级的差异将导致量级较大的属性占据主导地位,从而与实际情况相悖(比如这时实际情况是值域范围小的特征更重要);
数量级的差异将导致迭代收敛速度减慢;
当使用梯度下降法寻求最优解时,很有可能走“之字型”路线(垂直等高线走),从而导致需要迭代很多次才能收敛;
依赖于样本距离的算法对于数据的数量级非常敏感。
标准化的前提是特征值服从正态分布,标准化后,其转换成标准正态分布。
基于原始数据的均值(mean)和标准差(standarddeviation)进行数据的标准化。将A的原始值x使用z-score标准化到x’。z-score标准化方法适用于属性A的最大值和最小值未知的情况,或有超出取值范围的离群数据的情况。
标准化公式:
均值和标准差都是在样本集上定义的,而不是在单个样本上定义的。标准化是针对某个属性的,需要用到所有样本在该属性上的值。
优点:
缺点:
区间缩放法利用了边界值信息,将属性缩放到[0,1]。
实现代码
from sklearn.preprocessing import MinMaxScaler
#区间缩放,返回值为缩放到[0, 1]区间的数据
minMaxScaler = MinMaxScaler().fit(X_train)
minMaxScaler.transform(X_train)
缺点:
这种方法有一个缺陷就是当有新数据加入时,可能导致max和min的变化,需要重新定义;
MinMaxScaler对异常值的存在非常敏感。
单独地缩放和转换每个特征,使得训练集中的每个特征的最大绝对值将为1.0,将属性缩放到[-1,1]。它不会移动/居中数据,因此不会破坏任何稀疏性。
缺点:
这种方法有一个缺陷就是当有新数据加入时,可能导致max和min的变化,需要重新定义;
MaxAbsScaler与先前的缩放器不同,绝对值映射在[0,1]范围内。
在仅有正数据时,该缩放器的行为MinMaxScaler与此类似,因此也存在大的异常值。
实现代码
from sklearn.preprocessing import MaxAbsScaler
maxAbsScaler = MaxAbsScaler().fit(X_train)
maxAbsScaler.transform(X_train)
正则化的过程是将每个样本缩放到单位范数(每个样本的范数为1),如果要使用如二次型(点积)或者其它核方法计算两个样本之间的相似性这个方法会很有用。
该方法是文本分类和聚类分析中经常使用的向量空间模型(Vector Space Model)的基础。
Normalization主要思想是对每个样本计算其p-范数,然后对该样本中每个元素除以该范数,这样处理的结果是使得每个处理后样本的p-范数(l1-norm,l2-norm)等于1。
选定的特征仅具有正值,转换后的数据仅位于正象限中。如果某些原始特征具有正值和负值的混合,则情况并非如此。
from sklearn.preprocessing import Normalizer
#归一化,返回值为归一化后的数据
normalizer = Normalizer(norm='l2').fit(X_train)
normalizer.transform(X_train)
相同点:
它们的相同点在于都能取消由于量纲不同引起的误差;都是一种线性变换,都是对向量X按照比例压缩再进行平移。
不同点:
在分类、聚类算法中,需要使用距离来度量相似性的时候(如SVM、KNN)、或者使用PCA技术进行降维的时候,标准化(Z-score
standardization)表现更好;
在不涉及距离度量、协方差计算、数据不符合正太分布的时候,可以使用第一种方法或其他归一化方法。
比如图像处理中,将RGB图像转换为灰度图像后将其值限定在[0 255]的范围;
基于树的方法不需要进行特征的归一化。
例如随机森林,bagging与boosting等方法。
如果是基于参数的模型或者基于距离的模型,因为需要对参数或者距离进行计算,都需要进行归一化。
一般来说,建议优先使用标准化。对于输出有要求时再尝试别的方法,如归一化或者更加复杂的方法。很多方法都可以将输出范围调整到[0, 1],如果我们对于数据的分布有假设的话,更加有效的方法是使用相对应的概率密度函数来转换。
除了上面介绍的方法外,还有一些相对没这么常用的处理方法:
离散化是数值型特征非常重要的一个处理,其实就是要将数值型数据转化成类别型数据。连续值的取值空间可能是无穷的,为了便于表示和在模型中处理,需要对连续值特征进行离散化处理。
分箱的重要性及其优势:
自定义分箱,是指根据业务经验或者常识等自行设定划分的区间,然后将原始数据归类到各个区间中。
定义
按照相同宽度将数据分成几等份。
从最小值到最大值之间,均分为 N 等份, 这样, 如果 A,B 为最小最大值, 则每个区间的长度为 W=(B−A)/N , 则区间边界值为A+W,A+2W,….A+(N−1)W 。这里只考虑边界,每个等份里面的实例数量可能不等。
缺点是受到异常值的影响比较大
实现程序
import pandas as pd
df = pd.DataFrame([[22,1],[13,1],[33,1],[52,0],[16,0],[42,1],[53,1],[39,1],[26,0],[66,0]],columns=['age','Y'])
df['age_bin_2'] = pd.cut(df['age'],3) #新增一列存储等距划分的分箱特征
display(df)
##结果
age Y age_bin_2
0 22 1 (12.947, 30.667]
1 13 1 (12.947, 30.667]
2 33 1 (30.667, 48.333]
3 52 0 (48.333, 66.0]
4 16 0 (12.947, 30.667]
5 42 1 (30.667, 48.333]
6 53 1 (48.333, 66.0]
7 39 1 (30.667, 48.333]
8 26 0 (12.947, 30.667]
9 66 0 (48.333, 66.0]
定义
将数据分成几等份,每等份数据里面的个数是一样的。
区间的边界值要经过选择,使得每个区间包含大致相等的实例数量。比如说 N=10 ,每个区间应该包含大约10%的实例。
实现程序
import pandas as pd
df = pd.DataFrame([[22,1],[13,1],[33,1],[52,0],[16,0],[42,1],[53,1],[39,1],[26,0],[66,0]],columns=['age','Y'])
df['age_bin_1'] = pd.qcut(df['age'],3) #新增一列存储等频划分的分箱特征
display(df)
## 结果
age Y age_bin
0 22 1 (12.999, 26.0]
1 13 1 (12.999, 26.0]
2 33 1 (26.0, 42.0]
3 52 0 (42.0, 66.0]
4 16 0 (12.999, 26.0]
5 42 1 (26.0, 42.0]
6 53 1 (42.0, 66.0]
7 39 1 (26.0, 42.0]
8 26 0 (12.999, 26.0]
9 66 0 (42.0, 66.0]
定义
基于k均值聚类的分箱:k均值聚类法将观测值聚为k类,但在聚类过程中需要保证分箱的有序性:第一个分箱中所有观测值都要小于第二个分箱中的观测值,第二个分箱中所有观测值都要小于第三个分箱中的观测值,等等。
实现步骤
对预处理后的数据进行归一化处理;
将归一化处理过的数据,应用k-means聚类算法,划分为多个区间:
采用等距法设定k-means聚类算法的初始中心,得到聚类中心;
在得到聚类中心后将相邻的聚类中心的中点作为分类的划分点,将各个对象加入到距离最近的类中,从而将数据划分为多个区间;
重新计算每个聚类中心,然后重新划分数据,直到每个聚类中心不再变化,得到最终的聚类结果。
实现代码
from sklearn.cluster import KMeans
kmodel=KMeans(n_clusters=k) #k为聚成几类
kmodel.fit(data.reshape(len(data),1))) #训练模型
c=pd.DataFrame(kmodel.cluster_centers_) #求聚类中心
c=c.sort_values(by=’列索引') #排序
w=pd.rolling_mean(c,2).iloc[1:] #用滑动窗口求均值的方法求相邻两项求中点,作为边界点
w=[0] +list(w[0] + [ data.max() ] #把首末边界点加上
d3= pd.cut(data,w,labels=range(k)) #cut函数
定义
二值化可以将数值型(numerical)的feature进行阀值化得到boolean型数据。这对于下游的概率估计来说可能很有用(比如:数据分布为Bernoulli分布时)。
公式
定量特征二值化的核心在于设定一个阈值,大于阈值的赋值为1,小于等于阈值的赋值为0,公式如下:
定义
自底向上的(即基于合并的)数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。
基本思想
对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
实现步骤
预先定义一个卡方的阈值;
初始化;
根据要离散的属性对实例进行排序,每个实例属于一个区间;
合并区间;
计算每一对相邻区间的卡方值;
将卡方值最小的一对区间合并;
Aij:第i区间第j类的实例的数量;Eij:Aij的期望频率(=(Ni*Cj)/N),N是总样本数,Ni是第i组的样本数,Cj是第j类样本在全体中的比例;
阈值的意义
类别和属性独立时,有90%的可能性,计算得到的卡方值会小于4.6。大于阈值4.6的卡方值就说明属性和类不是相互独立的,不能合并。如果阈值选的大,区间合并就会进行很多次,离散后的区间数量少、区间大。阈值的选择直接会影响到离散后的区间数量和区间大小。
注意
ChiMerge算法推荐使用0.90、0.95、0.99置信度,最大区间数取10到15之间;
也可以不考虑卡方阈值,此时可以考虑最小区间数或者最大区间数。
指定区间数量的上限和下限,最多几个区间,最少几个区间;
对于类别型变量,需要分箱时需要按照某种方式进行排序。
需要使总熵值达到最小,也就是使分箱能够最大限度地区分因变量的各类别。
总之,
1、为什么要对数值类型数据做归一化处理?
2、标准化和归一化有何异同?
相同点:都对不同特征维度的伸缩变换。
不同点:
3、将数值型数据转化成类别型数据,有何方法?如何实现?
采用数值型特征分箱的方法,常用的无监督分箱法包括:自定义分箱、等距分箱、等频分箱、聚类分箱、二值化分箱等。有监督分箱法包括:卡方分箱法、最小熵法分箱等。
实现的方法是先设定划分的区间,然后将原始数据归类到各个区间,从而完成特征分箱,达到将数值类型数据转换成类别型数据。
4、KNN在进行查询操作时使用何种算法,其原理是什么?
采用KD树算法进行查询,根据给定的点p,在KD树中查询数据集中与其距离最近点的过程即为最近邻搜索。先从根节点做深度优先遍历,然后以点p为圆心,p与根节点的之间的距离做半径,画圆,将不与该圆相交的子树全部忽略。然后再找到与p相邻最近的第二个维度的值的点,采用相同的办法,直至找到最接近的点。然后根据k值来找到参与投票的点的数量,根据其对应的值进行排队,最终根据投票的结果得到p的分类。