有监督算法-KNN/K近邻
一:原理
1.计算o点到所有点的欧氏距离
欧
氏
距
离
:
d
=
(
x
a
−
x
b
)
2
+
(
y
a
−
y
b
)
2
欧氏距离: d = \sqrt{(x_a-x_b)^2+(y_a-y_b)^2}
欧氏距离:d=(xa−xb)2+(ya−yb)2
2.假设 K = 4 ,则选择距离最小的 K 个点
3.统计 K 个点属于哪些类
4.找到 K 个点中所属类最多数量,则把O点分为该类
二:算法优缺点极其变种
2.1 优缺点
算法参数 K 的取值影响模型效果,所以后面需要通过学习曲线去确定K值得取值
注:人为传递的参数叫超参数,所以模型的调参一般都是指的是调整超参数
1.当K值越大,则偏差越大,则有可能会发生欠拟合
2.当K值越小,则方差越大,则有可能发生过拟合
2.2 算法变种
原因:K的取值可能会造成相同个数的类,导致无法分类(因为默认每个点的权重一样)
变种一:针对不同邻居,指定不同的距离权重,比如距离越近权重越大
实现:通过指定算法中的weights参数实现
变种二:使用一定半径内的点,取代距离最近的K个点
在 scikit-learn 中,RadiusNeighborsClassifier 实现了这种算法的变种
该算法更加适用于数据采样不均匀的情况
三:python实现KNN算法
#全部行都能输出
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
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')
#构建数据集
data1 = {
'身高':[170,189,178,165,166,160,156,158,160,155],
'体重':[110,120,126,112,108,100,98,95,110,85],
'性别':[1,1,1,1,1,0,0,0,0,0]
}
data = pd.DataFrame(
data1
)
print(data)
# 画图
x = np.array(data.iloc[:,0:2]) #把身高体重等特征值放在x数组中
y = np.array(data.iloc[:,-1])#把性别放在y数组中
print(x)
print(y)
#1.先画男性的点
x_nan = x[y==1,0]
y_nan = x[y==1,1]
plt.scatter(x=x_nan,y=y_nan,color='red',label='男')
#2.画女性
x_nv = x[y==0,0]
y_nv = x[y==0,1]
plt.scatter(x=x_nv,y=y_nv,color='green',label='女')
#3.给出待确定的点
new_data = np.array([162,99])
plt.scatter(x=new_data[0],y=new_data[1],color='blue',label='未知')
#4.画轴
plt.xlabel('身高')
plt.ylabel('体重')
plt.legend(loc='lower right')
#计算欧式距离
#1.计算每一个点到未知点的距离,因为每个点的x,y轴的数据都在 x数组里面了,所以直接和x数组进行算欧式距离
from math import sqrt
distict = [ sqrt(np.sum((i-new_data)**2)) for i in x ]
print(distict)
#2.通过欧式距离进行升序排序
sort = np.argsort(distict)
print(sort) #[5 7 6 4 8 3 0 9 2 1] 所以index=5的点是最近的
#确定分类
#确定K值
k = 4
top_k = [y[i] for i in sort[0:k]] # 结果就是 index=[5,7,6,4] 对应的性别
top_k #[0, 0, 0, 1] ,所以分类中就是3女一男
#计数,统计
result = pd.Series(top_k).value_counts().index[0]
if result==1:
print('男性')
else:
print('女性')
四:sklean实现KNN算法
scikit-learn,简称 sklearn, 支持了包括分类、回归、降维和聚类四大机器学习算法,以及特征提取、数据预处理和模型评估三大模块
官网: http://scikit-learn.org/stable/index.html
主要设计原则:
- 一致性
所有对象共享一个简单一致的界面(接口)。
估算器:fit()方法。基于数据估算参数的任意对象,使用的参数是一个数据集(对应 X, 有监督算法
还需要一个 y),引导估算过程的任意其他参数称为超参数,必须被设置为实例变量。
转换器:transform()方法。使用估算器转换数据集,转换过程依赖于学习参数。可以使用便捷方
式: fit_transform(),相当于先 fit()再 transform()。(fit_transform 有时被优化过,速度更快)
预测器:predict()方法。使用估算器预测新数据,返回包含预测结果的数据,还有score()方法:用
于度量给定测试集的预测效果的好坏。(连续 y 使用 R 方,分类 y 使用准确率 accuracy) - 监控
检查所有参数,所有估算器的超参数可以通过公共实例变量访问,所有估算器的学习参数都可以通过有
下划线后缀的公共实例变量访问。 - 防止类扩散
对象类型固定,数据集被表示为 Numpy 数组或 Scipy 稀疏矩阵,超参是普通的 Python 字符或数字。 - 合成
现有的构件尽可能重用,可以轻松创建一个流水线 Pipeline。 - 合理默认值
大多数参数提供合理默认值,可以轻松搭建一个基本的工作系统
sklean代码块
#数据集
#构建数据集
import pandas as pd
import numpy as np
data1 = {
'身高':[170,189,178,165,166,160,156,158,160,155],
'体重':[110,120,126,112,108,100,98,95,110,85],
'性别':[1,1,1,1,1,0,0,0,0,0]
}
data = pd.DataFrame(
data1
)
print(data)
#sklean 实现KNN算法
#1.导包
from sklearn.neighbors import KNeighborsClassifier
#2.设置 K 的值,默认是 5
knn = KNeighborsClassifier(n_neighbors=3)
#3.加入特征值和分类值
x_tezheng = data.loc[:,'身高':'体重']
print(x_tezheng)
y_fenlei = data.loc[:,'性别']
print(y_fenlei)
knn = knn.fit(X=x_tezheng,y=y_fenlei)
#4.预测结果
result = knn.predict([[170,100]])
print(result)
#5.模型评估,看返回的分数
# X代表被预测的数据
# y代表实际的分类,这里可以放多个,用于判断准确率
# score代表预测的分类的准确率
score=knn.score(X=[[156,80]],y=[1]) #这个是预测男性的,分准确率分数=0,因为实际上应该是女性
print(score)
#6.预测概率值
knn.predict_proba([[170,100]]) #array([[0.33333333, 0.66666667]]) 返回值代表 array([[0的概率,1的概率]])
五:划分训练集和测试集
一般训练集=80%
测试集=20%
#1.准备数据集
import pandas as pd
import numpy as np
data1 = pd.DataFrame(
data=np.random.randint(0,100,size=(100,3)),
columns=['A','B','C']
)
data2 = pd.DataFrame(
data=np.random.randint(0,2,size=(100,1)),
columns=['sex']
)
data1['y']=data2
print(data1)
#2.划分训练集和测试集
#2.1 导包
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
#2.2 划分数据集,30%作为测试集,70%作为训练集
X = data1.iloc[:,0:-1] #特征数据
y = data1.iloc[:,-1] #标签数据
#函数返回值4个,分别是训练特征,测试特征,训练标签,测试标签
Xtrain,Xtest,ytrain,ytest = train_test_split(X,y,test_size=0.3,random_state=420) #test_size代表测试集大小,random_state类似random.seed 随机种子
#2.3 建模
KNN = KNeighborsClassifier(n_neighbors=4)
#2.4 分类器/训练模型
KNN = KNN.fit(Xtrain,ytrain)
#2.5 使用测试集测试准确度
score = KNN.score(Xtest,ytest)
print(score) #准确率= 0.43333333333333335
六:寻找最优K值
6.1 参数学习曲线
KNN 中的 k 是一个超参数,所谓“超参数”,就是需要人为输入,算法不能通过直接计算得出的参数。
参数学习曲线是一条以不同的参数取值为横坐标,不同参数取值下的模型结果为纵坐标的曲线,我们往往选择模型表现最佳点的参数取值作为这个参数的取值。
#准备数据集
import pandas as pd
import numpy as np
data1 = pd.DataFrame(
data=np.random.randint(0,100,size=(100,3)),
columns=['A','B','C']
)
data2 = pd.DataFrame(
data=np.random.randint(0,2,size=(100,1)),
columns=['sex']
)
data1['y']=data2
print(data1)
#2.1 导包
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
#2.2 划分数据集,30%作为测试集,70%作为训练集
X = data1.iloc[:,0:-1] #特征数据
y = data1.iloc[:,-1] #标签数据
#函数返回值4个,分别是训练特征,测试特征,训练标签,测试标签
Xtrain,Xtest,ytrain,ytest = train_test_split(X,y,test_size=0.3,random_state=420) #test_size代表测试集大小,random_state类似random.seed 随机种子
#2.3 建模
KNN = KNeighborsClassifier(n_neighbors=22)
#2.4 分类器/训练模型
KNN = KNN.fit(Xtrain,ytrain)
#2.5 使用测试集测试准确度
score = KNN.score(Xtest,ytest)
print(score) #准确率= 0.43333333333333335
print('========================================')
############################################################################################################
#画学习曲线
#x轴 K的取值
#y轴 准确率
x = []
y = []
for k in range(1,21): #
x.append(k)
clf = KNeighborsClassifier(n_neighbors=k)
clf = clf.fit(Xtrain,ytrain)
sc = clf.score(Xtest,ytest)
y.append(sc)
print(x)
print(y)
#画图
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.xticks(range(1,21))
plt.show()
6.2 交叉验证
模型需要考虑两个问题:
1.准确率,取决于 K 值
2.稳定性,取决于在不同数据集的高分数(如果在不同数据集的分数差异较大,则说明不稳定)
不稳定的表现:当改变随机种子的时候的模型的最高分K值会发生变化
K 折交叉验证
最常用的交叉验证是 k 折交叉验证。我们知道训练集和测试集的划分会干扰模型的结果,因此用交叉验
证 n 次的结果求出的均值,是对模型效果的一个更好的度量。
原理图:
6.3 带交叉验证的参数学习曲线
目的:找到分数高而且方差小的K值(准确率和稳定性兼具)
K 折交叉验证代码实现:
#准备数据集
import pandas as pd
import numpy as np
data1 = pd.DataFrame(
data=np.random.randint(0,100,size=(100,3)),
columns=['A','B','C']
)
data2 = pd.DataFrame(
data=np.random.randint(0,2,size=(100,1)),
columns=['sex']
)
data1['y']=data2
print(data1)
#2.1 导包
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
#2.2 划分数据集,30%作为测试集,70%作为训练集
X = data1.iloc[:,0:-1] #特征数据
y = data1.iloc[:,-1] #标签数据
#函数返回值4个,分别是训练特征,测试特征,训练标签,测试标签
Xtrain,Xtest,ytrain,ytest = train_test_split(X,y,test_size=0.3,random_state=420) #test_size代表测试集大小,random_state类似random.seed 随机种子
#3.建模
score=[] #用于存放每次K折的分数均值
var=[] #用于存放每次K折的分数方差
K = [] #存放K值的取值
for i in range(1,21):
KNN = KNeighborsClassifier(n_neighbors=i)
#交叉验证
result = cross_val_score(KNN,Xtrain,ytrain,cv=8) # cv=8 代表 K 折的次数
K.append(i)
score.append(result.mean())
var.append(result.var())
print('K值',K)
print('均值',score)
print('方差',var)
#4.画图
import matplotlib.pyplot as plt
#把列表转化成数组,才能进行数学运算
score = np.array(score)
var= np.array(var)
plt.plot(K,score,color='k')
plt.plot(K,score+var,color='r',linestyle='--')
plt.plot(K,score-var,color='b',linestyle='--')
plt.xticks(range(1,21))
模型解读:
测试集验证:
#测试集验证
KNN_18 = KNeighborsClassifier(n_neighbors=18)
#训练模型
fit = KNN_18.fit(Xtrain,ytrain)
#测试集验证得分
sc = fit.score(Xtest,ytest)
sc #0.43333333333333335
注意:
1.K值不要设置太大,否则会出现欠拟合,太小就过拟合
2.cv也就是K的折次数不能太大,也不能太小,函数中默认=5折,经验值=5/6折
折数过大:
运算效率变慢。
预测率方差变大,难以保证在新的数据集达到预期预测率。
其他交叉验证:
所有的交叉验证都是在分割训练集和测试集,只不过侧重的方向不同
“k 折"就是按顺序取训练集和测试集
ShuffleSplit 就侧重于让测试集分布在数据的全方位之内
StratifiedKFold 则是认为训练数据和测试数据必须在每个标签分类中占有相同的比例
七:归一化
当数据(x)按照最小值中心化后,再按极差(最大值-最小值)缩放,数据移动了最小值个单位,并且会被收
敛到[0,1]之间,而这个过程,就称作数据归一化(Normalization,又称 Min-Max Scaling)。
目的:消除量纲的影响
特别是在距离(比如PCA,比如KNN,比如kmeans等等)计算上,要么进行归一化,要么进行标准化
公式:
第
一
种
方
式
:
(
X
i
−
X
m
i
n
)
/
(
X
m
a
x
−
X
m
i
n
)
第
二
种
方
式
:
y
=
l
o
g
10
(
x
)
第一种方式:(X_i-X_min) / (X_max - X_min) \\ 第二种方式:y = log10(x)
第一种方式:(Xi−Xmin)/(Xmax−Xmin)第二种方式:y=log10(x)
什么时候做归一化???
- 先分训练集和测试集,再归一化!
归一化代码实现:
#准备数据集
import pandas as pd
import numpy as np
data1 = pd.DataFrame(
data=np.random.randint(0,100,size=(100,3)),
columns=['A','B','C']
)
data2 = pd.DataFrame(
data=np.random.randint(0,2,size=(100,1)),
columns=['sex']
)
data1['y']=data2
print(data1)
#2.1 导包
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
#2.2 划分数据集,30%作为测试集,70%作为训练集
X = data1.iloc[:,0:-1] #特征数据
y = data1.iloc[:,-1] #标签数据
#函数返回值4个,分别是训练特征,测试特征,训练标签,测试标签
Xtrain,Xtest,ytrain,ytest = train_test_split(X,y,test_size=0.3,random_state=420) #test_size代表测试集大小,random_state类似random.seed 随机种子
#3.归一化(先分数据集再归一化,最后再建模)
from sklearn.preprocessing import MinMaxScaler as mms
Xtrain = mms().fit(Xtrain).transform(Xtrain)#训练集归一化
Xtest = mms().fit(Xtest).transform(Xtest)#测试集归一化
print(Xtrain_mms)
#3.建模
score=[] #用于存放每次K折的分数均值
var=[] #用于存放每次K折的分数方差
K = [] #存放K值的取值
for i in range(1,21):
KNN = KNeighborsClassifier(n_neighbors=i)
#交叉验证
result = cross_val_score(KNN,Xtrain,ytrain,cv=8) # cv=8 代表 K 折的次数
K.append(i)
score.append(result.mean())
var.append(result.var())
print('K值',K)
print('均值',score)
print('方差',var)
#4.画图
import matplotlib.pyplot as plt
#把列表转化成数组,才能进行数学运算
score = np.array(score)
var= np.array(var)
plt.plot(K,score,color='k')
plt.plot(K,score+var,color='r',linestyle='--')
plt.plot(K,score-var,color='b',linestyle='--')
plt.xticks(range(1,21))
八:距离的惩罚
使用场景:样本分布不均匀的时候
即:根据距离远近设置权重
这里需要注意的是,关于模型的优化方法只是在理论上而言进行优化会提升模型判别效力,但实际应用
过程中最终能否发挥作用,本质上还是取决于优化方法和实际数据情况的契合程度,如果数据本身存在
大 量异常值点,则采用距离远近作为惩罚因子则会有较好的效果,反之则不然。
代码实现:
- KNeighborsClassifier(n_neighbors=i,weights=‘distance’) # weights=‘distance’ 代表距离惩罚
#准备数据集
import pandas as pd
import numpy as np
data1 = pd.DataFrame(
data=np.random.randint(0,100,size=(100,3)),
columns=['A','B','C']
)
data2 = pd.DataFrame(
data=np.random.randint(0,2,size=(100,1)),
columns=['sex']
)
data1['y']=data2
print(data1)
#2.1 导包
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
#2.2 划分数据集,30%作为测试集,70%作为训练集
X = data1.iloc[:,0:-1] #特征数据
y = data1.iloc[:,-1] #标签数据
#函数返回值4个,分别是训练特征,测试特征,训练标签,测试标签
Xtrain,Xtest,ytrain,ytest = train_test_split(X,y,test_size=0.3,random_state=420) #test_size代表测试集大小,random_state类似random.seed 随机种子
#3.归一化
from sklearn.preprocessing import MinMaxScaler as mms
Xtrain = mms().fit(Xtrain).transform(Xtrain)#训练集归一化
Xtest = mms().fit(Xtest).transform(Xtest)#测试集归一化
print(Xtrain_mms)
#3.建模 + 距离惩罚
score=[] #用于存放每次K折的分数均值
var=[] #用于存放每次K折的分数方差
K = [] #存放K值的取值
for i in range(1,21):
KNN = KNeighborsClassifier(n_neighbors=i,weights='distance') # weights='distance' 代表距离惩罚
#交叉验证
result = cross_val_score(KNN,Xtrain,ytrain,cv=8) # cv=8 代表 K 折的次数
K.append(i)
score.append(result.mean())
var.append(result.var())
print('K值',K)
print('均值',score)
print('方差',var)
#4.画图
import matplotlib.pyplot as plt
#把列表转化成数组,才能进行数学运算
score = np.array(score)
var= np.array(var)
plt.plot(K,score,color='k')
plt.plot(K,score+var,color='r',linestyle='--')
plt.plot(K,score-var,color='b',linestyle='--')
plt.xticks(range(1,21))
N = KNeighborsClassifier(n_neighbors=i,weights=‘distance’) # weights=‘distance’ 代表距离惩罚
#交叉验证
result = cross_val_score(KNN,Xtrain,ytrain,cv=8) # cv=8 代表 K 折的次数
K.append(i)
score.append(result.mean())
var.append(result.var())
print(‘K值’,K)
print(‘均值’,score)
print(‘方差’,var)
#4.画图
import matplotlib.pyplot as plt
#把列表转化成数组,才能进行数学运算
score = np.array(score)
var= np.array(var)
plt.plot(K,score,color=‘k’)
plt.plot(K,score+var,color=‘r’,linestyle=’–’)
plt.plot(K,score-var,color=‘b’,linestyle=’–’)
plt.xticks(range(1,21))