KNN算法:数据集的分割、超参数与数据归约
- 一、数据集的分割
- 1、什么是数据集的分割
- 2、为什么要进行数据集分割
- 3、怎么进行数据集分割
- 1)自己实现数据集分割
- 矩阵分割与合并
- 使用打乱的元素下标
- 2)使用sklearn封装好的数据集分割
- 二、最有超参数的寻找
- 1、什么是超参数
- 2、超参数有哪些
- 3、怎么寻找超参数
- 1)自己实现最优超参数的寻找
- 2)使用sklearn封装的超参数寻找方法
- 三、KNN算法优化:数据归一化
- 1、什么是数据归一化
- 2、为什么要进行数据归一化
- 3、数据归一化的分类
- 1)最值归一化
- 2)均值方差归一化
- 4、怎么实现数据归一化
- 1)自己实现数据归一化
- 最值归一化
- 均值方差归一化
- 2)使用sklearn的数据归一化
- 均值方差归一化
- 5、KNN算法小结
- 1)KNN 流程
- 2)优点
- 3)缺点
一、数据集的分割
1、什么是数据集的分割
数据集的分割是指将数据集分为两份,一份作为训练数据集,另一份作为测试数据集。
例如我们在KNN的第一篇中使用的如下代码:
将数据的前140条数据作为训练数据(特征值数据与类型,其顺序是一一对应的)
X_data_train = X_data[:-10]
y_data_train = y_data[:-10]
将最后10条数据作为测试数据
X_data_test = X_data[-10:]
y_data_test = y_data[-10:]
2、为什么要进行数据集分割
使用数据集训练模型之后,需要对这个训练过的模型进行评判,检测一下模型的预测结果的准确率
所以,我们在训练模型之前,将数据集分割为训练数据集与测试数据集
使用训练数据集训练模型,使用测试数据集评判模型
根据测试数据集的评判结果的好坏,考虑模型是否需要进行重新训练
3、怎么进行数据集分割
X_data 表示数据集的特征数据
y_data 表示数据集的结果数据
1)自己实现数据集分割
矩阵分割与合并
最简单粗暴的方法就是将 X_data 与 y_data 合并为一个矩阵,打乱矩阵的行,之后再将这个大矩阵分开,这样新的 X_data 与 y_data 就是打乱顺序后的矩阵
使用打乱的元素下标
使用矩阵的分割和合并虽然思想简单,实现起来却不一定简单。
我们还可以使用打乱元素下标的方法:
不对矩阵进行打乱,只打乱矩阵 X_data 或 y_data 的元素下表(只需打乱一个即可)。这样,使用这个打乱后的下标,依然可以同时访问两个矩阵,且他们的对应关系仍然成立。
import numpy as np
def train_test_split(X_train, y_train, test_size=0.2, seed=None):
"""将数据集按比例分割,一份用于训练模型,一份用于评判模型"""
assert X_train.shape[0] == y_train.shape[0], 'the size of X_train must be equal to y_train'
assert 0 <= test_size and test_size <= 1, 'the rate is valid'
if seed:
np.random.seed(seed)
shuffle_indexs = np.random.permutation(len(X_train))
size = int(len(X_train) * test_size)
test_indexs = shuffle_indexs[:size]
train_indexs = shuffle_indexs[size:]
X_train_data = X_train[train_indexs]
y_train_data = y_train[train_indexs]
x_test = X_train[test_indexs]
y_test = y_train[test_indexs]
return X_train_data, x_test,y_train_data, y_test
np.random.permutation(len(X_train))
这里的 len(X_train) = 150
也就是生成一个随机序列,序列的范围为 0~149 (不包括150),这个随机序列的长度为150
2)使用sklearn封装好的数据集分割
sklearn 有个封装好的方法:train_test_split(X_data,y_data)
from sklearn.model_selection import train_test_split
......
X_train, X_test , y_train , y_test = train_test_split(X_data,y_data,test_size=0.2,seed=666)
test_size 表示分割的比例,这里 test_size = 0.2 表示 80%的数据作为训练集,20%的数据作为测试集
seed = 666 指定随机种子,这样每次运行程序生成的打乱后的随机序列一致
二、最有超参数的寻找
1、什么是超参数
训练模型时需要传入的参数称为超参数
在模型的训练过程中,模型学习的参数称为模型参数
KNN使用的参数就是典型的超参数
例如:
下面的 n_neighbor 就是一个超参数,需要在训练模型前必须传入
from sklearn import neighbors
# 根据未知样本点附近的3个样本类型进行分类
knn = neighbors.KNeighborsClassifier(n_neighbors = 3)
2、超参数有哪些
各种算法的超参数各不相同
对于KNN而言,除了我们在上面提到过的 n_neighbor 之外,还有其他超参数
例如:weights (是否指定距离的权重),p(使用哪一种距离)
距离除了我们在第二篇中使用欧氏距离之外,还有明可夫斯基距离,等等等等
常见的距离有曼哈顿距离、欧氏距离、明可夫斯基距离(下图中的距离公式从上到下)
例如:
knn = neighbors.KNeighborsClassifier(n_neighbors = k,weights = "distance",p= 2)
这里的 weights 表示是否考虑距离的权重
默认为 uniform,表示不考虑距离的权重
distance 表示考虑距离的权重
p 表示指定使用哪一种距离,默认为1,表示欧氏距离
p =2 表示曼哈顿距离
p = 3 表示明可夫斯基距离
3、怎么寻找超参数
以寻找KNN算法中最合适的 n_neighbors , weights , p 为例
1)自己实现最优超参数的寻找
best_k = -1
best_score = 0
best_p = -1
for k in range(1,11):
for p in range(1,6):
knn = neighbors.KNeighborsClassifier(n_neighbors = k,weights = "distance",p= p)
knn.fit(X_train,y_train)
score = knn.score(X_test,y_test)
if score > best_score:
best_k = k
best_score = score
best_p = p
print(f"best_k = {best_k}")
print(f"best_score = {best_score}")
print(f"best p = {best_p}")
2)使用sklearn封装的超参数寻找方法
skelarn 中 的 GridSearchCV 类可以用来寻找最优超参数(该类使用交叉验证自动划分数据集)
# 俗称网格搜索
from sklearn.model_selection import GridSearchCV
param_grid = [
{
"weights":["uniform"],
"n_neighbors":[ i for i in range(1,11)]
},
{
"weights":["distance"],
"n_neighbors":[i for i in range(1,11)],
"p" : [ i for i in range(1,6)]
}
]
# 使用网格搜索时不用指定 n_neighbors 参数
knn = neighbors.KNeighborsClassifier()
# 将网格参数传入 knn 中,CV表示交叉验证(明天介绍)
grid_search = GridSearchCV(knn,param_grid)
# 训练模型
grid_search.fit(X_train,y_train)
# 最优的训练结果,返回结果是一个knn对象
knn_clf = grid_search.best_estimator_
# KNeighborsClassifier(n_neighbors=1)
# 最优的超参数
grid_search.best_params_
# {'n_neighbors': 1, 'weights': 'uniform'}
# 最优的评价准确率(这里的准确率使用的是交叉验证方法的评分)
grid_search.best_score_
0.9860820751064653
三、KNN算法优化:数据归一化
1、什么是数据归一化
数据归约化就是数据集中的所有数据转换到某个区间,无论这个数据有多大
例如:我们将 (100,99) 转换为 (1,0.99)
2、为什么要进行数据归一化
在 KNN 算法中,当求两点之间的距离时,我们使用欧氏距离
# 欧几里得距离:
|AB| = sqrt((X1 - X2)**2 + (Y1 - Y2)**2)
这样子没有问题,但是我们看一下这个场景
x = [
[50,2],
[55,1],
[60,3]
]
对于这样的数据,x坐标数据比y坐标的数据大得多,如果我们直接使用上面的公式进行距离的计算,那么这个距离就和 y 轴的数据的关系就很小很小了,这显然不是我们想要的结果。
我们想要的应该是 x 轴数据和 y 轴数据对结果的影响同等重要。
所以,这就是数据归一化的目的。
3、数据归一化的分类
数据归一化有多种方法
例如:最值归一化,均值方差归一化
1)最值归一化
公式:
x = (x-np.mean(x)) / (np.max(x) - np.min(x))
np.mean(x) 表示 x 的平均值
np.max(x) 表示x的最大值
np.min(x) 表示x的最小值
这种方法能够将所有的数据转换为 0~1 之间
最值归一化计算简单,适用于界限明确的情况,例如:考试成绩在 90 ~ 100 的为高分
但是他容易受到极端值的影响。
例如:在收入调查中,大多数人的收入都在 1k ~ 10k,但有极少数人的收入是 50k,这样经过最值归一化处理后,大多数人的收入就成了 10k 以上,这显然是错误的
2)均值方差归一化
公式:
x = (x - np.mean(x)) / np.std(x)
np.std(x) 表示 x 的方差
这种方法不能将数据转换在 0~1 之间,但是能够将所有的数据转换到一个均值为0,方差为1的标准正态分布中。
这种方法适用于没有明显的界限,即使存在极端值,也能起到很好的效果。
4、怎么实现数据归一化
1)自己实现数据归一化
最值归一化
import numpy as np
X = np.random.randint(1,100,(50,2))
# 需要将矩阵的数据格式转换为 float,对于int型,当计算后得到小数时,会直接截取整数部分,舍弃掉小数
X = np.array(X,dtype=float)
X.shape
# (50, 2)
print(X[:10])
# array([[90., 84.],
[25., 47.],
[17., 44.],
[17., 5.],
[94., 40.],
[ 9., 63.],
[42., 35.],
[20., 40.],
[72., 17.],
[32., 66.]])
X[:,0] = (X[:,0] - np.min(X[:,0])) / (np.max(X[:,0]) - np.min(X[:,0]))
X[:,1] = (X[:,1] - np.min(X[:,1])) / (np.max(X[:,1]) - np.min(X[:,1]))
print(X[:10])
# array([[0.91304348, 0.84042553],
[0.20652174, 0.44680851],
[0.11956522, 0.41489362],
[0.11956522, 0. ],
[0.95652174, 0.37234043],
[0.0326087 , 0.61702128],
[0.39130435, 0.31914894],
[0.15217391, 0.37234043],
[0.7173913 , 0.12765957],
[0.2826087 , 0.64893617]])
均值方差归一化
Y = np.random.randint(0,100,size=(50,2))
Y.shape
# (50, 2)
print(Y[:10])
# array([[67, 6],
[54, 56],
[23, 4],
[ 4, 40],
[ 2, 90],
[47, 30],
[24, 72],
[96, 27],
[73, 0],
[94, 44]])
Y = np.array(Y,dtype=float)
Y[:,0] = (Y[:,0] - np.mean(Y[:,0])) / np.std(Y[:,0])
Y[:,1] = (Y[:,1] - np.mean(Y[:,1])) / np.std(Y[:,1])
print(Y[:10])
# array([[ 0.65884314, -1.29837627],
[ 0.26010195, 0.29199153],
[-0.69074243, -1.36199098],
[-1.27351802, -0.21692617],
[-1.33486282, 1.37344163],
[ 0.04539515, -0.53499973],
[-0.66007003, 0.80090922],
[ 1.54834272, -0.63042179],
[ 0.84287753, -1.4892204 ],
[ 1.48699792, -0.08969674]])
我们可以测试一下,打印一下经过均值方差归一化后数据的均值与方差
np.mean(Y[:,0])
# -1.0658141036401502e-16
np.mean(Y[:,1])
# 3.41740524767431e-18
np.std(Y[:,0])
# 0.9999999999999998
np.std(Y[:,1])
# 1.0
2)使用sklearn的数据归一化
均值方差归一化
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
iris = datasets.load_iris()
X_data = iris.data
y_data = iris.target
X_train , X_test , y_train , y_test = train_test_split(X_data , y_data,test_size=0.2,random_state=666)
# 生成一个对象,该对象拥有均值方差归一化方法
standardScaler = StandardScaler()
# 进行均值方差归一化,注意该方法不会改变原数据,而是将处理后的结果作为返回值
X_train_standard = standardScaler.transform(X_train)
print(X_train_standard[:10])
# array([[-0.90616043, 0.93246262, -1.30856471, -1.28788802],
[-1.15301457, -0.19551636, -1.30856471, -1.28788802],
[-0.16559799, -0.64670795, 0.22203084, 0.17260355],
[ 0.45153738, 0.70686683, 0.95898425, 1.50032315],
[-0.90616043, -1.32349533, -0.40154513, -0.09294037],
[ 1.43895396, 0.25567524, 0.56216318, 0.30537551],
[ 0.3281103 , -1.09789954, 1.0723617 , 0.30537551],
[ 2.1795164 , -0.19551636, 1.63924894, 1.23477923],
[-0.78273335, 2.2860374 , -1.25187599, -1.42065998],
[ 0.45153738, -2.00028272, 0.44878573, 0.43814747]])
standardScaler.transform(X_data) 对数据进行均值方差归一化
切记,除了对测试数据进行均值方差归一化之外,还需要对测试数据进行相同的处理
X_test_standard = standardScaler.transform(X_test)
print(X_test_standard[:10])
# array([[-0.28902506, -0.19551636, 0.44878573, 0.43814747],
[-0.04217092, -0.64670795, 0.78891808, 1.63309511],
[-1.0295875 , -1.77468693, -0.23147896, -0.22571233],
[-0.04217092, -0.87230374, 0.78891808, 0.96923531],
[-1.52329579, 0.03007944, -1.25187599, -1.28788802],
[-0.41245214, -1.32349533, 0.16534211, 0.17260355],
[-0.16559799, -0.64670795, 0.44878573, 0.17260355],
[ 0.82181859, -0.19551636, 0.8456068 , 1.10200727],
[ 0.57496445, -1.77468693, 0.39209701, 0.17260355],
[-0.41245214, -1.09789954, 0.39209701, 0.03983159]])
knn = KNeighborsClassifier(3)
knn.fit(X_train_standard,y_train)
y_predict = knn.predict(X_test_standard)
print(y_predict)
# array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
2, 0, 1, 1, 0, 1, 2, 2])
knn.score(X_test_standard,y_test)
# 1.0
5、KNN算法小结
1)KNN 流程
2)优点
- 简单,容易实现
- 天生适合多分类
3)缺点
- 只适合小数据集,当数据量很大时性能不好(因为要和所有样本点比较距离)
- 不太适合回归问题