此篇博客不再对k近邻算法加以解释,还不了解k近邻算法的读者可以参考一下我的上一篇博客,有通过一个相对简单的关于集美大学建筑物的例子来讲k-近邻算法。机器学习-k近邻算法及简单实现https://blog.csdn.net/m0_52053228/article/details/127527163?spm=1001.2014.3001.5502
上篇博客的基础上,我本打算扩大集美大学建筑物这个数据集的范围和属性,但数据量还是太小,干脆换一个新的数据集拿来实现k-近邻算法。作为一名预备党员,应该时刻关心人民,想了又想,我就拿我们学院2022秋季入学是否申请生源地贷款来做数据集(完全没有歧视任何一位同学的意思!!!)。
目录
数据集读取
我们将住宿费、月平均花费、家庭平均收入(\月)作为数据集的特征,是否申请生源地贷款作为数据集的标签(为了方便算法,我们将0视为未申请生源地贷款,将1视为已申请生源地贷款)下图给出13条数据,实际数据共计77条。
我们定义一个读取数据的函数
将excel数据用二维数组保存
#读取数据集
def read_excel(path):
raw_data = pd.read_excel(path,header=0)
#筛除序号一列,所以是读取后面四列
data = raw_data.values[:,1:5]
return data
数据集处理
对读取到的数据集进行处理,为了达到实验的效果,我们从数据集中随机抽取10%作为训练集,其余的作为测试集,分别返回训练集、测试集的特征和标签
#从数据集中随机抽取10%作为测试集,其余作为训练集
def classify(data):
data_test = np.zeros((1,4),dtype=int)
for i in range(int(data.shape[0]*0.1)):
index = np.random.randint(0,data.shape[0])
data = np.delete(data,index,axis=0)
data_test = np.row_stack((data_test,data[index,:]))
#print(index)
#print(data)
#print(data_test)
#print(data[:,:3].shape,data[:,-1].shape,data_test[1:,:3],data_test[1:,-1])
return data[:,:3],data[:,-1],data_test[1:,:3],data_test[1:,-1]
三维散点图
为了更直观地查看数据
我们将住宿费作x轴,月平均花费做y轴,家庭平均收入做z轴
通过matplotlib将数据集用三维散点图表示出来
#画出三维散点图
def plot(data):
a0 = np.zeros((1,4),dtype=int)
a1 = np.ones((1,4),dtype=int)
#遍历将整个数据集分成两个数据集,来区别是否申请生源地贷款
for i in range(data.shape[0]):
if(data[i,3]==0):
a0 = np.row_stack((a0,data[i,:]))
else:
a1 = np.row_stack((a1,data[i,:]))
#data0为没有申请生源地贷款的学生信息
data0 = a0[1:,:]
x1 = data0[:,0]
y1 = data0[:,1]
z1 = data0[:,2]
#print(data0)
#data1为申请生源地贷款的学生信息
data1 = a1[1:,:]
x2 = data1[:,0]
y2 = data1[:,1]
z2 = data1[:,2]
#print(data1)
fig = plt.figure()
ax = Axes3D(fig,auto_add_to_figure=False)
ax.scatter(x1,y1,z1,c='r',label='未申请生源地贷款')
ax.scatter(x2,y2,z2,c='b',label='已申请生源地贷款')
ax.legend(loc='best')
ax.set_zlabel('家庭月平均收入', fontdict={'size': 15, 'color': 'black'})
ax.set_ylabel('月平均花费', fontdict={'size': 15, 'color': 'black'})
ax.set_xlabel('住宿费', fontdict={'size': 15, 'color': 'black'})
fig.add_axes(ax)
plt.show()
将未申请生源地贷款的点标红
将已申请生源地贷款的点标蓝
效果如下
kNN算法
kNN算法与前一篇博客的算法一样,就不再做过多的解释
#kNN算法
def kNN(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndices = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
主函数
主函数通过循环,对测试集的每一个行向量进行kNN算法的运算,并与该测试数据的真实标签进行对比。为了能够区分取不同k值的测试效果,我们定义一个错误率,测试每次实验的错误率(kNN算法返回的标签与其真实的标签不同即为错误)
if __name__=='__main__':
errorCount = 0.0
train_features,train_labels,test_features,test_labels=classify(read_excel(data_path))
for i in range(test_features.shape[0]):
kNNResult = kNN(test_features[i,:],train_features,train_labels,3)
print(f'kNN算法返回的测试结果为 {kNNResult},其真实标签为 {test_labels[i]}')
if(kNNResult!=test_labels[i]):
errorCount+=1.0
#plot(read_excel(data_path))
print(f'此次测试的错误率为 {errorCount/float(test_features.shape[0])}\n\n')
整体代码
from cProfile import label
import numpy as np
import operator
import pandas as pd
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from pylab import mpl
# 设置显示中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
# 设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False
data_path = r'data.xlsx'
#kNN算法
def kNN(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndices = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
#读取数据集
def read_excel(path):
raw_data = pd.read_excel(path,header=0)
data = raw_data.values[:,1:5]
return data
#从数据集中随机抽取10%作为测试集,其余作为训练集
def classify(data):
data_test = np.zeros((1,4),dtype=int)
for i in range(int(data.shape[0]*0.1)):
index = np.random.randint(0,data.shape[0])
data = np.delete(data,index,axis=0)
data_test = np.row_stack((data_test,data[index,:]))
#print(index)
#print(data)
#print(data_test)
#print(data[:,:3].shape,data[:,-1].shape,data_test[1:,:3],data_test[1:,-1])
return data[:,:3],data[:,-1],data_test[1:,:3],data_test[1:,-1]
#画出三维散点图
def plot(data):
a0 = np.zeros((1,4),dtype=int)
a1 = np.ones((1,4),dtype=int)
for i in range(data.shape[0]):
if(data[i,3]==0):
a0 = np.row_stack((a0,data[i,:]))
else:
a1 = np.row_stack((a1,data[i,:]))
#data0为没有申请生源地贷款的学生信息
data0 = a0[1:,:]
x1 = data0[:,0]
y1 = data0[:,1]
z1 = data0[:,2]
#print(data0)
#data1为申请生源地贷款的学生信息
data1 = a1[1:,:]
x2 = data1[:,0]
y2 = data1[:,1]
z2 = data1[:,2]
#print(data1)
fig = plt.figure()
ax = Axes3D(fig,auto_add_to_figure=False)
ax.scatter(x1,y1,z1,c='r',label='未申请生源地贷款')
ax.scatter(x2,y2,z2,c='b',label='已申请生源地贷款')
ax.legend(loc='best')
ax.set_zlabel('家庭月平均收入', fontdict={'size': 15, 'color': 'black'})
ax.set_ylabel('月平均花费', fontdict={'size': 15, 'color': 'black'})
ax.set_xlabel('住宿费', fontdict={'size': 15, 'color': 'black'})
fig.add_axes(ax)
plt.show()
if __name__=='__main__':
errorCount = 0.0
train_features,train_labels,test_features,test_labels=classify(read_excel(data_path))
for i in range(test_features.shape[0]):
kNNResult = kNN(test_features[i,:],train_features,train_labels,3)
print(f'kNN算法返回的测试结果为 {kNNResult},其真实标签为 {test_labels[i]}')
if(kNNResult!=test_labels[i]):
errorCount+=1.0
#plot(read_excel(data_path))
print(f'此次测试的错误率为 {errorCount/float(test_features.shape[0])}\n\n')
运行结果为
变换k值
想要改变k值来观察错误率的变化,首先我们要明白k值的含义。
给定一个训练集,找到其映射到特征空间中的对应向量,保存下来。当新来了一个测试实例,找到其在特征空间中最近的k个实例,看在这k个实例中属于哪一个类别的最多,就给测试样本赋予对应的标签。
上图结果是在k=3的前提下,错误率为0.57。
下面我们对k值进行修改,观察一下不同k值下错误率的变化。
为了避免k个实例中两种类别的数量相同,我们不讨论k为偶数的情况。
修改,错误率为0.14
修改,错误率为0.0(可能是我数据集的问题,类别太好区分,大佬勿喷)
修改,错误率为0.57
总结
由此可见,k值的选择是k近邻算法的要素之一,因为k取不同的值,测试结果也不同。
不能说k值越小越好,当然也不是越大越好,k值太小,模型容易过拟合,k值太大错误率也会升高。
因此,选择合适的k值对我们的模型会有很大的帮助!