以《简单易懂》的语言带你搞懂有监督学习算法【附Python代码详解】机器学习系列之KNN篇[通俗易懂]

以《简单易懂》的语言带你搞懂有监督学习算法【附Python代码详解】机器学习系列之KNN篇[通俗易懂]机器学习系列往期回顾❤️开始学习机器学习之前你必须要了解的知识有哪些?机器学习系列入门篇在上篇文章中,我们介绍了机器学习的一些理论知识,包括什么是监督学习、无监督学习、过拟合欠拟合等等,那在本篇文章中,我们会以KNN来正式介绍什么是有监督学习,让大家在了解KNN的同时完全掌握什么是有监督学习,以帮助大家更好的理解机器学习。注:本篇文章非常详细,附带简单易懂的文字说明和实现代码,欢迎收藏后慢慢阅读。监督学习算法本文主要介绍的有监督学习算法是KNN,后续会接着介绍决策树、线性回归等算法。

大家好,又见面了,我是你们的朋友全栈君。

大家早上好,本人姓吴,如果觉得文章写得还行的话也可以叫我吴老师。欢迎大家跟我一起走进数据分析的世界,一起学习

感兴趣的朋友可以关注我或者我的数据分析专栏,里面有许多优质的文章跟大家分享哦。


必须要看的前言

本文风格:以❤️简单易懂❤️的语言带你彻底搞懂KNN,了解什么是有监督学习算法。

认真看完这篇文章,彻底了解KNN、了解监督学习算法绝对是一样很简单的事情。

注:本篇文章非常详细,同时我也附加了Python代码,欢迎收藏后慢慢阅读。

监督学习算法

本文主要介绍的有监督学习算法是KNN,后续会接着介绍决策树、线性回归等算法。

KNN/K近邻算法

1 算法原理

首先,第一个也是最主要的问题——KNN是如何对样本进行分类的呢?

它的本质是通过距离判断两个样本是否相似,如果距离够近就认为他们足够相似属于同一类别。

当然只对比一个样本是不够的,误差会很大,我们需要找到离其最近的 k 个样本,并将这些样本称之为「近邻」(nearest neighbor)。对这 k 个近邻,查看它们的都属于何种类别(这些类别我们称作「标签」 (labels))。

然后根据“少数服从多数,一点算一票”原则进行判断,数量最多的的标签类别就是新样本的标签类别。其中涉及到的原理是“越相近越相似”,这也是KNN的基本假设。

1.1 实现过程

假设 X_test 待标记的数据样本,X_train 为已标记的数据集。

  • 遍历已标记数据集中所有的样本,计算每个样本与待标记点的距离,并把距离保存在 Distance 数组中。
  • 对 Distance 数组进行排序,取距离最近的 k 个点,记为 X_knn。
  • 在 X_knn 中统计每个类别的个数,即 class0 在 X_knn 中有几个样本,class1 在 X_knn 中有几个样本等。
  • 待标记样本的类别,就是在 X_knn 中样本个数最多的那个类别。

1.2 距离的确定

该算法的「距离」在二维坐标轴就表示两点之间的距离,计算距离的公式有很多。

我们常用欧拉公式,即“欧氏距离”。回忆一下,一个平面直角坐标系上,如何计算两点之间的距离?

应该不难会想起来吧,公式应该大致如下: d i s t a n c e ( A , B ) = ( x A − x B ) 2 + ( y A − y B ) 2 distance(A, B)=\sqrt[]{(x_A-x_B)^2+(y_A-y_B)^2} distance(A,B)=(xAxB)2+(yAyB)2
那如果不是在一个平面直角坐标系,而是在立体直角坐标系上,怎么计算呢? d i s t a n c e ( A , B ) = ( x A − x B ) 2 + ( y A − y B ) 2 + ( z A − z B ) 2 distance(A, B)=\sqrt[]{(x_A-x_B)^2+(y_A-y_B)^2+(z_A-z_B)^2} distance(A,B)=(xAxB)2+(yAyB)2+(zAzB)2
那如果是n维空间呢? d i s t a n c e ( A , B ) = ( x 1 A − x 1 B ) 2 + ( x 2 A − x 2 B ) 2 + ( x 3 A − x 3 B ) 2 + . . . . . . + ( x n A − x n B ) 2 = ∑ i = 1 n ( x i A − x i B ) 2 distance(A, B)=\sqrt[]{(x_{1A}-x_{1B})^2+(x_{2A}-x_{2B})^2+(x_{3A}-x_{3B})^2+……+(x_{nA}-x_{nB})^2}=\sqrt[]{\sum_{i=1}^{n} {(x_{iA}-x_{iB})^2}} distance(A,B)=(x1Ax1B)2+(x2Ax2B)2+(x3Ax3B)2+......+(xnAxnB)2
=
i=1n(xiAxiB)2
而在我们的机器学习中,坐标轴上的值 x 1 x_1 x1, x 2 x_2 x2 , x 3 x_3 x3 ,…… x n x_n xn正是我们样本数据上的 n 个特征。

2 算法的优缺点

算法参数是 k,k 可以理解为标记数据周围几个数作为参考对象,参数选择需要根据数据来决定。

  • k 值越大,模型的偏差越大,对噪声数据越不敏感。
  • k 值很大时,可能造成模型欠拟合。
  • k 值越小,模型的方差就会越大。
  • 但是 k 值太小,容易过拟合。

3 算法的变种

3.1 变种一

默认情况下,在计算距离时,权重都是相同的,但实际上我们可以针对不同的邻居指定不同的距。离权重,比如距离越近权重越高。

  • 这个可以通过指定算法的 weights 参数来实现。

3.2 变种二

使用一定半径内的点取代距离最近的 k 个点。

  • 在 scikit-learn 中,RadiusNeighborsClassifier 实现了这种算法的变种。
  • 当数据采样不均匀时,该算法变种可以取得更好的性能。

4 Python代码实现

这里我还是先以上篇文章讲到的红酒分类为例子,待会还会有其他实例。

4.1 导入模块

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False

# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']

plt.style.use('ggplot')
# plt.figure(figsize=(2,3),dpi=720)

4.2 构建已经分类好的原始数据集

首先随机设置十个样本点表示十杯酒,这里取了部分样本点。

为了方便验证,这里使用 Python 的字典 dict 构建数据集,然后再将其转化成 DataFrame 格式。

rowdata = { 
   '颜色深度': [14.23,13.2,13.16,14.37,13.24,12.07,12.43,11.79,12.37,12.04],
           '酒精浓度': [5.64,4.38,5.68,4.80,4.32,2.76,3.94,3.1,2.12,2.6],
           '品种':     [0,0,0,0,0,1,1,1,1,1]}        

# 0 代表 “黑皮诺”,1 代表 “赤霞珠” 

wine_data = pd.DataFrame(rowdata)

wine_data

在这里插入图片描述

X = np.array(wine_data.iloc[:,0:2]) #我们把特征(酒的属性)放在X 
y = np.array(wine_data.iloc[:,-1]) #把标签(酒的类别)放在Y

我们先来画一下图。

#探索数据,假如我们给出新数据[12.03,4.1] ,你能猜出这杯红酒是什么类别么? 
new_data = np.array([12.03,4.1]) 

plt.scatter(X[y==1,0], X[y==1,1], color='red', label='赤霞珠') #画出标签y为1 的、关于“赤霞珠”的散点 
plt.scatter(X[y==0,0], X[y==0,1], color='purple', label='黑皮诺') #画出标签y为0 的、关于“黑皮诺”的散点 
plt.scatter(new_data[0],new_data[1], color='yellow') # 新数据点 new_data 

plt.xlabel('酒精浓度') 
plt.ylabel('颜色深度') plt.legend(loc='lower right') plt.savefig('葡萄酒样本.png')

在这里插入图片描述
讲道理,你应该一下就能看出来了。不过,如果是计算机,会这么分辨呢?

4.3 计算已知类别数据集中的点与当前点之间的距离。

我们使用欧式距离公式,计算新数据点 new_data 与现存的 X 数据集每一个点的距离:

from math import sqrt 
distance = [sqrt(np.sum((x-new_data)**2)) for x in X ] 

distance

在这里插入图片描述
那现在,我们就已经得到黄点距离其它每个点的距离啦。

4.4 将距离升序排列,然后选取距离最小的k个点。

sort_dist = np.argsort(distance) # 这里是用到了argsort方法,可以返回数据对应的下标,如果直接用sort方法的话是返回打乱的数据,我们也不好区分对应是什么类别。
sort_dist

array([6, 7, 1, 4, 5, 9, 2, 8, 3, 0], dtype=int64)

6、7、4为最近的3个“数据点”的索引值,那么这些索引值对应的原数据的标签是什么?

k = 3 
topK = [y[i] for i in sort_dist[:k]] 
topK

[1,1,0]

这个时候我们就得到了离黄点最近的三个点对应的类别啦。

4.5 确定前k个点所在类别的计数。

# 在numpy中有mean、median方法可以求平均数和中位数,不过没有方法直接求众数。
pd.Series(topK).value_counts().index[0]

1

所以当我们的k取3时,分类结果为1,也就是赤霞珠。大家看一下是不是跟我们人脑分辨的结果是一样的呢?

4.6 封装成函数

那为了后续更好的操作,我们可以将上述过程封装成一个函数。

def KNN(new_data,dataSet,k): 
	''' 函数功能:KNN分类器 参数说明: new_data: 需要预测分类的数据集 dataSet: 已知分类标签的数据集 k: k-近邻算法参数,选择距离最小的k个点 return: result: 分类结果 ''' 
	from math import sqrt 
	from collections import Counter 
	import numpy as np 
	import pandas as pd 

	result = [] 
	distance = [sqrt(np.sum((x-new_data)**2)) for x in np.array(dataSet.iloc[:,0:2])] 
	sort_dist = np.argsort(distance) 
	topK = [dataSet.iloc[:,-1][i] for i in sort_dist[:k]]
	result.append(pd.Series(topK).value_counts().index[0]) 
	return result
# 测试函数的运行结果 
new_data=np.array([12.03,4.1]) 
k = 3 
KNN(new_data,wine_data,k)

[1]

5 SCIKIT-LEARN算法库实现

上述过程是我们自己一个个实现的,而在如今呢,人们更习惯用scikit-learn这一Python的第三方模块。

scikit-learn 自 2007 年发布以来,scikit-learn已经成为 Python 中重要的机器学习库了。scikit-learn,简称 sklearn, 支持了包括分类、回归、降维和聚类四大机器学习算法,以及特征提取、数据预处理和模型评估三大模块。 在工程应用中,用 Python 手写代码来从头实现一个算法的可能性非常低,这样不仅耗时耗力,还不一 定能够写出构架清晰,稳定性强的模型。更多情况下,是分析采集到的数据,根据数据特征选择适合的算法, 在工具包中调用算法,调整算法的参数,获取需要的信息,从而实现算法效率和效果之间的平衡。而 sklearn, 正是这样一个可以帮助我们高效实现算法应用的工具包。

http://scikit-learn.org/stable/index.html

上述链接是sklearn的官方网站,里面详细地介绍了sklearn的使用,感兴趣的朋友可以看一看这个,或者看我下面的简要介绍。

在这里插入图片描述
主要设计原则:

1)一致性
所有对象共享一个简单一致的界面(接口)。

  • 估算器:fit()方法。基于数据估算参数的任意对象,使用的参数是一个数据集(对应 X, 有监督算法还需要一个 y),引导估算过程的任意其他参数称为超参数,必须被设置为实例变量。
  • 转换器:transform()方法。使用估算器转换数据集,转换过程依赖于学习参数。可以使用便捷方式: fit_transform(),相当于先 fit()再 transform()。(fit_transform 有时被优化过,速度更快)
  • 预测器:predict()方法。使用估算器预测新数据,返回包含预测结果的数据,还有score()方法:用于度量给定测试集的预测效果的好坏。(连续 y 使用 R 方,分类 y 使用准确率 accuracy)

2)监控
检查所有参数,所有估算器的超参数可以通过公共实例变量访问,所有估算器的学习参数都可以通过有下划线后缀的公共实例变量访问。

3)防止类扩散
对象类型固定,数据集被表示为 Numpy 数组或 Scipy 稀疏矩阵,超参是普通的 Python 字符或数字。

4)合成
现有的构件尽可能重用,可以轻松创建一个流水线 Pipeline。

5)合理默认值
大多数参数提供合理默认值,可以轻松搭建一个基本的工作系统。

5.1 案例一:红酒

from sklearn.neighbors import KNeighborsClassifier 

# 0 代表 “黑皮诺”,1 代表 “赤霞珠” 
clf = KNeighborsClassifier(n_neighbors = 3) 
clf = clf.fit(wine_data.iloc[:,0:2], wine_data.iloc[:,-1]) 
result = clf.predict([[12.8,4.1]]) # 返回预测的标签 
result

array([0])

# 对模型进行一个评估,接口score返回预测的准确率 
score = clf.score([[12.8,4.1]],[0]) 
score

1.0

clf.predict_proba([[12.8,4.1]])

array([[0.66666667, 0.33333333]])

这里解释一下,0.66666667是标签为0的概率,0.33333333是标签为1的概率。

5.2 案例二:乳腺癌

from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

import pandas as pd
import numpy as np

#读取数据集
data = load_breast_cancer()

#DateFrame格式显示
X = data.data
y = data.target
name = ['平均半径','平均纹理','平均周长','平均面积',
        '平均光滑度','平均紧凑度','平均凹度',
        '平均凹点','平均对称','平均分形维数',
        '半径误差','纹理误差','周长误差','面积误差',
        '平滑度误差','紧凑度误差','凹度误差',
        '凹点误差','对称误差',
        '分形维数误差','最差半径','最差纹理',
        '最差的边界','最差的区域','最差的平滑度',
        '最差的紧凑性','最差的凹陷','最差的凹点',
        '最差的对称性','最差的分形维数','患病否']

data=np.concatenate((X,y.reshape(-1,1)),axis=1)
table=pd.DataFrame(data=data,columns=name)
table.head()

在这里插入图片描述

# 划分训练集(Xtrain,Ytrain)和测试集(Xtest,Ytest)
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420)  

# 建立模型&评估模型
clf = KNeighborsClassifier(n_neighbors=2)
clf = clf.fit(Xtrain,Ytrain)
score = clf.score(Xtest,Ytest)

score

0.9210526315789473

如何用上面分类器拟合结果找出离 Xtest 中第 20 行和第 30 行最近的 4 个“点”?

#查找点的K邻居。返回每个点的邻居的与之的距离和索引值。 
clf.kneighbors(Xtest[[20,30],:],return_distance=True)

(array([[35.70015941, 42.02374599, 81.82147557, 83.06271326],
[11.81126721, 14.5871725 , 17.4734004 , 18.94892695]]),
array([[112, 221, 303, 263], [268, 162, 42, 134]], dtype=int64))

6 选择最优K值

相信大家看到这可能会有疑惑说,这个k值到底怎么取比较好,那么接下来,就正式讲一讲怎么选择k值吧。

KNN 中的 k 是一个超参数,所谓“超参数”,就是需要人为输入,算法不能通过直接计算得出的参数。

KNN 中的 k 代表的是距离需要分类的测试点 x 最近的 k 个样本点,如果不输入这个值,那么算法中重要部分 “选出 k 个最近邻” 就无法实现。

从 KNN 的原理中可见,是否能够确认合适的 k 值对算法有极大的影响。

如果选择的 k 值较小,就相当于较小的邻域中的训练实例进行预测,这时候只有与输入实例较近的(相似的)训练实例才会对预测结果起作用,但缺点是预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰好是噪声,预测就会出错。

相反地,如果选择的 k 值较大,就相当于较大的邻域中的训练实例进行预测。这时与输入实例较远的 (不相似的)训练实例也会对预测起作用,使预测发生错误。因此,超参数 k 的选定是 KNN 的头号问题。

在这里插入图片描述
正如上图中圈红的x,取5和取11的话,结果也会完全不一样。

6.1 学习曲线

那我们怎样选择一个最佳的 k 呢?在这里我们要使用机器学习中的神器:参数学习曲线。参数学习曲线是一条以不同的参数取值为横坐标,不同参数取值下的模型结果为纵坐标的曲线,我们往往选择模型表现最佳点的参数取值作为这个参数的取值。

# 更换不同的n_neighbors参数的取值,观察结果的变化 
clf = KNeighborsClassifier(n_neighbors=7) 
clf = clf.fit(Xtrain,Ytrain) score = 
clf.score(Xtest,Ytest) 

score

0.9385964912280702

绘制学习曲线:

score = [] 
krange = range(1,20) 

for i in krange: 
	clf = KNeighborsClassifier(n_neighbors=i) 
	clf = clf.fit(Xtrain,Ytrain) 
	score.append(clf.score(Xtest,Ytest)) 

plt.plot(krange,score) 
plt.show()

在这里插入图片描述

究竟上图中k为多少的时候分数越高?

score.index(max(score))+1

8

但是这个时候会有个问题,如果随机划分的数据集变化的的话,得分最高的k值也会发生变化:

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=421)

score = [] 
krange = range(1,20) 

for i in krange: 
	clf = KNeighborsClassifier(n_neighbors=i) 
	clf = clf.fit(Xtrain,Ytrain) 
	score.append(clf.score(Xtest,Ytest)) 

plt.plot(krange,score) 
plt.show()

在这里插入图片描述

score.index(max(score))+1

3

这样就无法确定最佳的k值了,就无法进行下面的建模工作,怎么办?

我们接着往下看。

7 交叉验证

确定了 k 之后,我们还能够发现一件事:每次运行的时候学习曲线都在变化,模型的效果时好时坏, 这是为什么呢?

实际上,这是由于「训练集」和「测试集」的划分不同造成的。模型每次都使用不同的训练集进行训练, 不同的测试集进行测试,自然也就会有不同的模型结果。

在业务当中,我们的训练数据往往是已有的历史数据,但我们的测试数据却是新进入系统的一系列还没有标签的未知数据。我们的确追求模型的效果,但我们追求的是模型在未知数据集上的效果,在陌生数据集上表现优秀的能力被称为泛化能力,即我们追求的是模型的泛化能力。

7.1 泛化能力

我们在进行学习算法前, 通常会将一个样本集分成训练集(training set)和测试集(testing set),其中训练集用于模型的学习或训练,而后测试集通常用于评估训练好的模型对于数据的预测性能评估。

**训练误差(training error)**代表模型在训练集上的错分样本比率。

**测试误差(empirical error)**是模型在测试集上的错分样本比率。

训练误差的大小,用来判断给定问题是不是一个容易学习的问题。 测试误差则反映了模型对未知数据的预测能力,测试误差小的学习方法具有很好的预测能力,如果得到的训练集和测试集的数据没有交集,通常将此预测能力称为泛化能力(generalization ability)。 我们认为,如果模型在一套训练集和数据集上表现优秀,那说明不了问题,只有在众多不同的训练集和 测试集上都表现优秀,模型才是一个稳定的模型,模型才具有真正意义上的泛化能力。为此,机器学习领域有发挥神作用的技能:「交叉验证」,来帮助我们认识模型。

7.2 K折交叉验证

最常用的交叉验证是 k 折交叉验证。我们知道训练集和测试集的划分会干扰模型的结果,因此用交叉验证 n 次的结果求出的均值,是对模型效果的一个更好的度量。

在这里插入图片描述
图中绿色部分就是我们分离出来的验证集,可以看到在每一次测试中验证集的位置(也就是数据)都会发生改变,K折交叉验证就是会有K次测试。

7.3 带交叉验证的学习曲线

对于带交叉验证的学习曲线,我们需要观察的就不仅仅是最高的准确率了,而是准确率高且方差还相对较小的点,这样的点泛化能力才是最强的。在交叉验证+学习曲线的作用下,我们选出的超参数能够保证更好的泛化能力。

from sklearn.model_selection import cross_val_score as CVS 

Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420) 

clf = KNeighborsClassifier(n_neighbors=8) 
cvresult = CVS(clf,Xtrain,Ytrain,cv=6) #训练集对折6次,一共6个预测率输出 

cvresult #每次交叉验证运行时估算器得分的数组

array([0.92207792, 0.90789474, 0.97368421, 0.94736842, 0.93333333, 0.92 ])

# 均值:查看模型的平均效果 
cvresult.mean() 

# 方差:查看模型是否稳定 
cvresult.var()

0.934059770638718 0.0004622658270548926

score = [] 
var = [] 
krange=range(1,20) #设置不同的k值,从1到19都看看 

for i in krange: 
	clf = KNeighborsClassifier(n_neighbors=i) 
	cvresult = CVS(clf,Xtrain,Ytrain,cv=5) 
	score.append(cvresult.mean()) # 每次交叉验证返回的得分数组,再求数组均 值 
	var.append(cvresult.var()) 

plt.plot(krange,score,color='k') 
plt.plot(krange,np.array(score)+np.array(var)*2,c='red',linestyle='--') 
plt.plot(krange,np.array(score)-np.array(var)*2,c='red',linestyle='--')

在这里插入图片描述

7.4 是否需要验证集

最标准,最严谨的交叉验证应该有三组数据:训练集、验证集和测试集。 当我们获取一组数据后:

  • 先将数据集分成整体的训练集和测试集。
  • 然后我们把训练集放入交叉验证中。
  • 从训练集中分割更小的训练集(k-1 份)和验证集(1 份)。
  • 返回的交叉验证结果其实是验证集上的结果。
  • 使用验证集寻找最佳参数,确认一个我们认为泛化能力最佳的模型。
  • 将这个模型使用在测试集上,观察模型的表现。

通常来说,我们认为经过验证集找出最终参数后的模型的泛化能力是增强了的,因此模型在未知数据(测试集)上的效果会更好,但尴尬的是,模型经过交叉验证在验证集上的调参之后,在测试集上的结果没有变好的情况时有发生。

原因其实是:

  • 我们自己分的训练集和测试集,会影响模型的效果。
  • 交叉验证后的模型的泛化能力增强了,表示它在未知数据集上方差更小,平均水平更高,但却无法保证它在现在分出来的测试集上预测能力最强。
  • 如此说来,是否有测试集的存在,其实意义不大了。

如果我们相信交叉验证的调整结果是增强了模型的泛化能力的,那即便测试集上的测试结果并没有变 好(甚至变坏了),我们也认为模型是成功的。 如果我们不相信交叉验证的调整结果能够增强模型的泛化能力,而一定要依赖测试集来进行判断,我们完全没有进行交叉验证的必要,直接用测试集上的结果来跑学习曲线就好了。 所以,究竟是否需要验证集,其实是存在争议的,在严谨的情况下,大家还是使用有验证集的方式。

7.5 其他交叉验证

交叉验证的方法不止“k 折” 一种,分割训练集和测试集的方法也不止一种,分门别类的交叉验证占据了sklearn 中非常长的一章。

所有的交叉验证都是在分割训练集和测试集,只不过侧重的方向不同。

  • “k 折”就是按顺序取训练集和测试集。
  • ShuffleSplit 就侧重于让测试集分布在数据的全方位之内。
  • StratifiedKFold 则是认为训练数据和测试数据必须在每个标签分类中占有相同的比例。

各类交叉验证的原理繁琐,大家在机器学习道路上一定会逐渐遇到更难的交叉验证,但是万变不离其宗:本质上交叉验证是为了解决训练集和测试集的划分对模型带来的影响,同时检测模型的泛化能力的。

在这里插入图片描述
当然常用的还是K折交叉验证啦。

另外,交叉验证的折数不可太大,因为折数越大抽出来的数据集越小,训练数据所带的信息量会越小,模型会越来越不稳定。

7.6 避免折数太大

如果你发现不使用交叉验证的时候模型表现很好,一使用交叉验证模型的效果就骤降。

  • 一定要查看你的标签是否有顺序。
  • 然后就是查看你的数据量是否太小,折数是否太高。

如果将上面例题的代码中将 cv 将 5 改成 100:
在这里插入图片描述
折数过大:

  • 运算效率变慢。
  • 预测率方差变大,难以保证在新的数据集达到预期预测率。

8 归一化

8.1 距离类模型归一化的要求

什么是归一化?我们把 X 放到数据框中来看一眼,你是否观察到,每个特征的均值差异很大?有的特征数值很大,有的特征数值很小,这种现象在机器学习中被称为”量纲不统一”。KNN 是距离类模型,欧氏距离的计算公式中存在着特征上的平方和: d i s t a n c e ( A , B ) = ( x 1 A − x 1 B ) 2 + ( x 2 A − x 2 B ) 2 + ( x 3 A − x 3 B ) 2 + . . . . . . + ( x n A − x n B ) 2 = ∑ i = 1 n ( x i A − x i B ) 2 distance(A, B)=\sqrt[]{(x_{1A}-x_{1B})^2+(x_{2A}-x_{2B})^2+(x_{3A}-x_{3B})^2+……+(x_{nA}-x_{nB})^2}=\sqrt[]{\sum_{i=1}^{n} {(x_{iA}-x_{iB})^2}} distance(A,B)=(x1Ax1B)2+(x2Ax2B)2+(x3Ax3B)2+......+(xnAxnB)2
=
i=1n(xiAxiB)2
如果某个特征 的取值非常大,其他特征的取值和它比起来就不算什么,那么距离的大小很大程度都会由这个 来决定,其他的特征之间的距离可能就无法对d(A,B)的大小产生什么影响,这种现象会让KNN这样的距离类模型的效果大打折扣。

然而在实际分析情景当中,绝大多数数据集都会存在各特征值量纲不同的情况,此时若要使用 KNN 分类器,则需要先对数据集进行归一化处理,即是将所有的数据压缩都同一个范围内。

当数据(x)按照最小值中心化后,再按极差(最大值-最小值)缩放,数据移动了最小值个单位,并且会被收敛到[0,1]之间,而这个过程,就称作数据归一化(Normalization,又称 Min-Max Scaling)。 x ∗ = x − m i n ( x ) m a x ( x ) − m i n ( x ) x^*=\frac {x-min(x)} {max(x)-min(x)} x=max(x)min(x)xmin(x)

8.2 先分数据集,再做归一化

直接在全数据集 X 上进行了归一化,然后放入交叉验证绘制学习曲线,这种做法是错误的。

真正正确的方式是,先分训练集和测试集,再归一化!

为什么?想想看归一化的处理手段,我们是使用数据中的最小值和极差在对数据进行压缩处理,如果我们在全数据集上进行归一化,那最小值和极差的选取是会参考测试集中的数据的状况的。因此,当我们归一化后,无论我们如何分割数据,都会由一部分测试集的信息被“泄露”给训练集,这会使得我们的模型效果被高估。

在现实业务中,我们只知道训练集的数据,不了解测试集究竟会长什么样,所以我们要利用训练集上的最小值和极差来归一化测试集。

8.3 通过 python 实现

data = [[-1,2],[-0.5,6],[0,10],[1,18]] 
data=pd.DataFrame(data) 

(data-np.min(data,axis=0))/(np.max(data,axis=0)-np.min(data,axis=0))

在这里插入图片描述

8.4 通过 sklearn 实现

同样的,slearn也封装了Min-Max Scaling方法,具体操作如下:

from sklearn.preprocessing import MinMaxScaler as mms 

Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,y,test_size=0.2,random_state=420) 
#归一化 
MMS_01=mms().fit(Xtrain) #求训练集最大/小值 
MMS_02=mms().fit(Xtest) #求测试集最大/小值 

#转换 
X_train=MMS_01.transform(Xtrain) 
X_test =MMS_02.transform(Xtest) 

score=[] 
var=[] 

for i in range(1,20):
	clf=KNeighborsClassifier(n_neighbors=i) 
	cvresult=CVS(clf,X_train,Ytrain,cv=5) # 交叉验证的每次得分 
	score.append(cvresult.mean()) 
	var.append(cvresult.var()) 

plt.plot(krange,score,color="k")
plt.plot(krange,np.array(score)+np.array(var)*2,c="red",linestyle="--") 
plt.plot(krange,np.array(score)-np.array(var)*2,c="red",linestyle="--") 
plt.show()

在这里插入图片描述

score.index(max(score))+1

8

最终的到 k 最优值为 8,无论 random_state 取什么值,最优 k 值不会相差太多。

把经过交叉验证、归一化处理之后,我们得到最优 k 为 8,放在归一化后的训练集重新建模,然后在归一化后的测试集中查看结果分数:

clf=KNeighborsClassifier(n_neighbors=6,weights='distance').fit(X_train,Ytrain) 
score=clf.score(X_test,Ytest) 
score

0.956140350877193

大家可以尝试着更改random_state,然后再根据学习曲线图更换k值,最终查看分数是否同95.6%有较大出入。

9 距离的惩罚

最近邻点距离远近修正在对未知分类过程中, “一点一票” 的规则是 KNN 模型优化的一个重要步骤。 也就是说,对于原始分类模型而言,在选取最近的 k 个元素之后,将参考这些点的所属类别,并对其进行简单计数,而在计数的过程中这些点 “一点一票” ,这些点每个点对分类目标点的分类过程中影响效力相同。

但这实际上是不公平的,就算是最近邻的 k 个点,每个点的分类目标点的距离仍然有远近之别,而近的点往往和目标分类点有更大的可能性属于同一类别( 该假设也是 KNN 分类模型的基本假设) 。

关于惩罚因子的选取有很多种方法, 最常用的就是根据每个最近邻 ?= 距离的不同对其作加权, 加权方法为设置 权重,该权重计算公式为 w i = 1 d ( x ′ , x i ) w_i=\frac{1}{d(x^{‘},x_i)} wi=d(x,xi)1这里需要注意的是,关于模型的优化方法只是在理论上而言进行优化会提升模型判别效力,但实际应用过程中最终能否发挥作用,本质上还是取决于优化方法和实际数据情况的契合程度,如果数据本身存在大量异常值点,则采用距离远近作为惩罚因子则会有较好的效果,反之则不然。

因此在实际我们进行模型优化的过程当中,是否起到优化效果还是要以最终模型运行结果为准。在sklearn中,我们可以通过参数 weights 来控制是否适用距离作为惩罚因子。

for i in range(1,20): 
	clf=KNeighborsClassifier(n_neighbors=i,weights='distance') 	
	cvresult=CVS(clf,X_train,Ytrain,cv=5) # 交叉验证的每次得分 
	score.append(cvresult.mean()) 
	var.append(cvresult.var()) 

plt.plot(krange,score,color="k") 
plt.plot(krange,np.array(score)+np.array(var)*2,c="red",linestyle="--") 
plt.plot(krange,np.array(score)-np.array(var)*2,c="red",linestyle="--") 
plt.show()

在这里插入图片描述

score.index(max(score))+1

6

clf=KNeighborsClassifier(n_neighbors=6,weights='distance').fit(X_train,Ytrain) 
score=clf.score(X_test,Ytest) 
score

0.9473684210526315

结束语

感谢你能看到这里,我会在后续继续更新机器学习系列的文章,内容风格依旧会是易懂但有用,希望能得到你的点赞支持,感谢!

推荐关注的专栏

?‍?‍?‍? 机器学习:分享机器学习理论基础和常用模型讲解
?‍?‍?‍? 数据分析:分享数据分析实战项目和常用技能整理

机器学习系列往期回顾

❤️ 开始学习机器学习之前你必须要了解的知识有哪些?机器学习系列入门篇
? 统计学习方法第二版 李航

往期内容回顾

? 我和关注我的前1000个粉丝“合影”啦!收集前1000个粉丝进行了一系列数据分析,收获满满
❤️ 分享一个超nice的数据分析实战案例 ⭐ “手把手”教学,收藏等于学会
? 数据分析必须掌握的RFM模型是什么?一文搞懂如何利用RFM对用户进行分类【附实战讲解】
? MySQL必须掌握的技能有哪些?超细长文带你掌握MySQL【建议收藏】
? Hive必须了解的技能有哪些?万字博客带你掌握Hive❤️【建议收藏】
? 一文带你了解Hive【详细介绍】Hive与传统数据库有什么区别?

关注我,了解更多相关知识!

CSDN@报告,今天也有好好学习

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140114.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 前工程师讲解:开关电源设计-LLC电源

    前工程师讲解:开关电源设计-LLC电源很多最初接触电源的朋友,都是从开关电源设计来进行入门学习的。期间不仅要查阅大量的资料,还要对这些资料进行筛选和整理,比较耗费时间和精力。为此,小编将一名前工程师的开关电源设计经验进行了整理,希望能帮助大家加快自学的步伐。      原本在本篇文章当中将为大家讲解关于EMI、尖峰电压处理等方面的知识,但是这些知识的整体思路在开关电源的各类拓扑当中都是互通的,所以转而对主拓扑进行介绍。

  • 银行家算法 C语言实现

    银行家算法 C语言实现C语言实现银行家算法这几天老师要求使用C语言实现银行家算法,数据可以自定义。想来想去还是照着书现成的数据来模拟一下。教材使用的是西安电子科大出版社的《计算机操作系统》汤小丹第四版。模拟数据使用的是P121页第4题的数据。听到老师布置题目的第一时间我还是有点懵,看了下书更懵了,这条条框框的判断条件怎么这么多。。沉下心来慢慢看,其实还是挺简单的算法。/*Author:Cnkizy…

  • Lua语言_lua基本语法

    Lua语言_lua基本语法1、Lua简介1.1Lua是什么1.2特性1.3应用场景2、Lua的安装3、入门程序4、Lua的基础语法4.1注释4.2定义变量4.3Lua中的数据类型4.4流程控制4.5循环4.6函数4.7表4.8模块1、Lua简介1.1Lua是什么Lua[1]是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(PontificalCatholicUniversityofRiodeJaneiro)里的一个由RobertoIerusalimschy、WaldemarCele.

    2022年10月22日
  • 软件测试用例编写方法_接口测试用例设计思路

    软件测试用例编写方法_接口测试用例设计思路编写测试用例HttpRunnerv3.x支持三种测试用例格式pytest,YAML和JSON。官方强烈建议以pytest格式而不是以前的YAML/JSON格式编写和维护测试用例格式关系如下图所示

  • 2021.1 datagrip激活码【2021最新】

    (2021.1 datagrip激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~https://javaforall.cn/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~0U…

  • [弗曼学习法] Study for learning methods

    [弗曼学习法] Study for learning methods第一步 -选择一个你想要理解的概念    选择一个你想要理解的概念, 然后拿出一张白纸, 把这个概念写在白纸的最上边.第二步-设想一种场景,你正要向别人传授这个概念    在白纸上写下你对这个概念的解释, 就好像你正在教导一位新接触这个概念的学生一样. 当你这样做的时候, 你会更清楚地意识到关于这个概念你理解了多少, 以及是否还存在理解不清的地方.第三步-如果你感觉卡壳了…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号