算法定义
k-近邻算法(k-Nearest Neighbors, k-NN) 是一种简单而常用的 监督学习算法,主要用于分类和回归任务。它的核心思想是:通过计算待分类样本与训练集中所有样本的距离,找到距离最近的 k 个样本,然后根据这 k 个样本的类别或值来预测待分类样本的类别或值。
一、问题引入
海伦一直使用在线约会网站寻找适合自己的约会对象。她曾交往过三种类型的人:
– 不喜欢的人
– 一般喜欢的人
– 非常喜欢的人
这些人包含以下三种特征
1. 每年获得的飞行常客里程数
2. 玩视频游戏所耗时间百分比
3. 每周消费的冰淇淋公升数
该网站现在需要尽可能向海伦推荐她喜欢的人,需要我们设计一个分类器,根据用户的以上三种特征,识别出是否该向海伦推荐。
二,解决思路
(1)写出k近邻算法:
计算待分类样本与训练集中每个样本的距离(可用欧氏距离或曼哈顿距离公式);选择k个最近邻居:根据计算的距离,选择距离最近的 k 个训练样本。投票或平均:分类任务:统计 k 个最近邻居的类别,选择出现次数最多的类别作为预测结果。回归任务:计算 k 个最近邻居的值的平均值作为预测结果。具体代码如下:
def classify0(inX,dataSet,labels,k):
dataSetSize = dataSet.shape[0]#获取样本行数
diffMat = np.tile(inX,(dataSetSize,1))-dataSet#计算输入样本 inX 与数据集中每个样本的差值。
sqDiffMat = diffMat ** 2
sqDistances=sqDiffMat.sum(axis=1)#计算每个样本与输入样本 inX 的欧氏距离的平方。
distances = sqDistances ** 0.5
sortedDistIndicies=distances.argsort()#对距离进行排序,并返回排序后的索引。
classCount={}
for i in range (k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0)+1#统计每个类别的投票数。
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)#对 classCount 字典按投票数从高到低排序。
return sortedClassCount[0][0]#返回投票数最多的类别。
(2)将文本记录转化为numpy的解析程序:
导入必要的库并定义函数 file2matrix 然后打开文件并读取所有行,计算文件的行数和遍历每一行数据并处理初始化特征矩阵和标签向量,最后返回特征矩阵和标签向量。具体代码如下:
def file2matrix(datingTestSet):
fr=open(datingTestSet)
arrayOLines=fr.readlines()
numberOfLines=len(arrayOLines)#计算文件的行数,即列表 arrayOLines 的长度,并将其赋值给变量 numberOfLines。
returnMat = np.zeros((numberOfLines,3))#使用 np.zeros 创建一个大小为 (numberOfLines, 3) 的全零矩阵 returnMat,用于存储特征数据。
classLabelVector=[]
index=0
for line in arrayOLines:
line=line.strip()#使用 strip() 方法去除当前行开头和结尾的空白字符(如空格、换行符等)。
listFromLine=line.split('\\t')#使用 split('\\t') 方法按制表符 \\t 将当前行分割成一个列表 listFromLine。
returnMat[index,:]=listFromLine[0:3]#将 listFromLine 中的前 3 个元素(特征值)赋值给 returnMat 的第 index 行。
classLabelVector.append(listFromLine[-1])#将 listFromLine 的最后一个元素(标签)添加到 classLabelVector 中。
index+=1
return returnMat,classLabelVecto
(3)归一化特征值
主要公式:newValue=(oldValue-min)/(max-min)
定义函数autonorm,计算每列的最小值和最大值并初始化归一化后的数据集用 np.tile(minVals, (m, 1)) 将 minVals 按行复制 m 次,生成一个与 dataSet 形状相同的矩阵。将 dataSet 中的每个值减去对应列的最小值,得到平移后的数据。最后归一化数据并返回。具体代码如下:
def autoNorm(dataSet):
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
ranges=maxVals-minVals
normDataSet=np.zeros(np.shape(dataSet))#创建一个与 dataSet 形状相同的全零矩阵 normDataSet,用于存储归一化后的数据。
m=dataSet.shape[0]#获取数据集的行数(样本数量),赋值给变量 m。
normDataSet=dataSet-np.tile(minVals,(m,1))#将数据集中的每个值减去对应列的最小值,得到平移后的数据。
normDataSet=normDataSet /np.tile(ranges,(m,1))#将平移后的数据除以对应列的范围,得到归一化后的数据。
return normDataSet,ranges,minVals
(4)分类器针对约会网站代码
定义 hoRatio = 0.10,将数据集的 10% 作为测试集,剩余的 90% 作为训练集用 file2matrix 函数从文件 datingTestSet.txt 中加载数据;调用 autoNorm 函数对特征矩阵 datingDataMat 进行归一化处理,得到归一化后的特征矩阵 normMat。;获取归一化后数据集的行数 m。计算测试集的数量 numTestVecs = int(m * hoRatio)。;遍历测试集中的每一个样本:调用 classify0 函数对当前测试样本进行分类,最后计算分类器的总错误率。具体代码如下:
def datingClassTest():
hoRatio=0.10#意味着数据集中前 10% 的样本将作为测试集,剩余的 90% 作为训练集。
datingDataMat,datingLabels=file2matrix('datingTestSet.txt')#调用 file2matrix 函数从文件 datingTestSet.txt 中加载数据。
normMat,ranges,minVals=autoNorm(datingDataMat)#调用 autoNorm 函数对特征矩阵 datingDataMat 进行归一化。
m=normMat.shape[0]
numTestVecs=int(m*hoRatio)
errorCount=0.0
for i in range(numTestVecs):
classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)#调用 classify0 函数对当前测试样本 normMat[i, :] 进行分类
print(f"the classifier came back with :{classifierResult} ,the real answer is :{datingLabels[i]} " )#如果分类器的预测结果与真实标签不一致,则将错误计数器 errorCount 加 1。
if(classifierResult!=datingLabels[i]):
errorCount += 1.0
print (f"the total error rate is :{errorCount/float(numTestVecs)}" )
(5)约会网站预测函数
定义三个结果标签,调用 file2matrix 函数从文件 datingTestSet.txt 中加载数据,然后调用 autoNorm 函数对特征矩阵 datingDataMat 进行归一化处理。将用户输入的特征值组合成一个 numpy 数组 inArr。对 inArr 进行归一化处理,使其与训练数据的范围一致。调用 classify0 函数对归一化后的用户输入向量进行分类。根据 classifierResult 的值,从 resultList 中获取对应的分类结果标签。具体代码如下:
def classifyPerson():
resultList = ['not at all','in small doses','in large doses']
percenTats=float(input("percentage of time spent playing video games?"))
ffMiles=float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per year?"))
datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
normMat,ranges,minVals=autoNorm(datingDataMat)#调用 autoNorm 函数对特征矩阵 datingDataMat 进行归一化。
inArr=np.array([ffMiles,percenTats,iceCream])
classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)#对输入向量 inArr 进行归一化:调用 classify0 函数对归一化后的输入向量进行分类
print(f"you will probably like this person:{resultList[classifierResult-1]}")
(6)main函数(满足题目要求即可)
def main():
print("欢迎使用约会推荐系统!")
print("请选择功能:")
print("1. 测试分类器在约会数据集上的性能。")
print("2. 根据您的喜好预测是否喜欢某人。")
print("3. 退出程序。")
while True:
choice = input("请输入您的选择(1、2 或 3):")
if choice == '1':
# 测试分类器
print("\\n正在测试分类器在约会数据集上的性能…")
datingClassTest()
elif choice == '2':
# 预测约会网站结果
print("\\n正在根据您的喜好预测是否喜欢某人…")
classifyPerson()
elif choice == '3':
# 退出程序
print("\\n正在退出程序。再见!")
break
else:
print("\\n无效的选择,请输入 1、2 或 3。")
# 调用 main 函数
if __name__ == "__main__":
main()
三,输出结果

四,实验总结
1. 数据准备:这包括收集、清洗和预处理数据。**预处理可能包括归一化或标准化特征**,以确保所有特征在计算距离时具有相等的权重。
| | 玩视频游戏所耗时间百分比 | 每年获得的飞行常客里程数 | 每周消费的冰淇淋的公升数 | 样本分类 |
| —- | ———————— | ———————— | ———————— | ——– |
| 1 | 0.8 | 400 | 0.5 | 1 |
| 2 | 12 | 134000 | 0.9 | 3 |
| 3 | 0 | 20000 | 1.1 | 2 |
| 4 | 67 | 32000 | 0.1 | 2 |
我们很容易发现,当计算样本之间的距离时数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于上表中其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。**下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
2. 选择距离度量方法:确定用于比较样本之间相似性的度量方法,常见的如欧几里得距离、曼哈顿距离等。
3. 确定K值:选择一个K值**,即在分类或回归时应考虑的邻居数量。这是一个超参数,可以通过交叉验证等方法来选择最优的K值。
4. 找到K个最近邻居**:对于每一个需要预测的未标记的样本:
– 计算该样本与训练集中所有样本的距离。
– 根据距离对它们进行排序。
– 选择距离最近的K个样本
5. 预测:
– 对于**分类任务**:查看K个最近邻居中最常见的类别,作为预测结果。例如,如果K=3,并且三个最近邻居的类别是[1, 2, 1],那么预测结果就是类别1。
– 对于**回归任务**:预测结果可以是K个最近邻居的平均值或加权平均值。
6. 评估使用适当的评价指标(如准确率、均方误差等)评估模型的性能。
7. 优化:基于性能评估结果,可能需要返回并调整某些参数,如K值、距离度量方法等,以获得更好的性能。
网硕互联帮助中心


评论前必须登录!
注册