KNN算法:数据集的分割、超参数与数据归约

一、数据集的分割

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)缺点

  • 只适合小数据集,当数据量很大时性能不好(因为要和所有样本点比较距离)
  • 不太适合回归问题
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值