机器学习实验二——使用k-近邻算法改进约会网站的配对效果

目录

一、k-近邻算法

二、使用k-近邻算法改进约会网站的配对效果

2.1  准备数据:从文本文件中解析数据

2.2分析数据:使用Matplotlib创建散点图

2.3  准备数据:归一化数值

2.4  测试算法:作为完整程序验证分类器

2.5   使用算法:构建完整可用系统

三、实验总结


一、k-近邻算法

1.工作原理

存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只能选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

2.k-近邻算法的三个基本要素包括:

(1)k值的选择:k值越小,判断的区域就越小,一旦近邻数据存在噪声或是特征不够一般,模型就有可能出错,类似于过拟合。k值越大,判断的区域就越大,因此对分类的影响也越大,模型的归纳能力就越差,类似欠拟合。k值的选择对分类效果有较大影响,需要结合具体场景和数据特点来选择合适的k值。

(2)距离度量:用于衡量样本之间的相似性。常用的距离度量如欧氏距离:

                                        计算(x1,y1)和(x2,y2)两点间的欧式距离:

                                                d = sqrt( (x1-x2)^2+(y1-y2)^2 )

(3)分类决策规则:在分类问题中,KNN算法通常采用投票法来确定待分类样本的类别,即选择K个邻居中出现最多的类别作为待分类样本的类别

3.实施kNN分类算法:

 对未知类别属性的数据集中的每个点依次执行以下操作:

(1)计算已知类别数据集中的点与当前点之间的距离

(2)按照距离递增次序排序

(3)选取与当前点距离最小的k个点

(4)确定前k个点所在类别的出现频率

(5)返回前k个点出现频率最高的类别作为当前点的预测分类

k-近邻算法代码实现:

创建一个kNN.py文件,将如下代码加入到kNN.py中,之后的相关代码也是加入到这个文件中完成实验。

def classify0(inx,dataset,labels,k): #inx为用于分类的输入量,dataset为训练样本集,labels为标签向量,k表示用于选择最近邻邻居的数目
    datasetsize=dataset.shape[0]  #shape 是 NumPy 数组的属性,用于获取数组的形状,对于二维数组,返回一个元组 (行数, 列数),因此 dataset.shape[0] 返回数据集的行数,即样本数量。
    diffmat=tile(inx,(datasetsize,1))-dataset #这里将输入数据 inx 重复拼接 datasetsize 行,1 列,以便与数据集进行逐元素相减。得到了输入数据与数据集中每个样本的差值矩阵。
    sqdiffmat=diffmat**2  #对差值矩阵中的每个元素进行平方操作,得到平方差值矩阵。
    sqdistances=sqdiffmat.sum(axis=1)  #计算平方欧式距离,即将平方差值矩阵的每一行元素相加,得到每个样本与输入数据的平方欧式距离。指定了沿着每行(axis=1 表示行)的方向进行求和,得到每行元素的总和。这里的每行对应于数据集中的一个样本,因此结果是一个包含了每个样本与输入数据 inx 之间平方欧式距离的数组 sqdistances。
    distances=sqdistances**0.5 #对平方欧式距离进行开方,得到每个样本与输入数据的欧式距离。
    sorteddistindicies=distances.argsort() #对欧式距离进行排序,并返回排序后的索引,索引值表示距离最近的样本在原始数据集中的位置。
    classcount={} #初始化一个空字典,用于统计每个类别的票数。
    for i in range(k):
        voteilabel=labels[sorteddistindicies[i]] #从 labels 列表中获取距离最近的前 k 个样本的标签,并将其存储在 voteilabel 变量中。
        classcount[voteilabel]=classcount.get(voteilabel,0)+1  #统计每个标签的出现次数。如果标签 voteilabel 已经在字典 classcount 中,就将其对应的计数值加一;如果不在,就添加一个新的键值对,值初始化为 0,并加一。
    sortedclasscount=sorted(classcount.items(),key=operator.itemgetter(1),reverse=True)  #对字典 classcount 中的项按照值(票数)从大到小排序,operator.itemgetter(1) 表示按照值进行排序,reverse=True 表示降序排序。
    return sortedclasscount[0][0] #返回票数最多的类别,即距离最近的 k 个样本中所属类别最多的类别作为分类结果。

简单地说,k-近邻算法采用测量不同特征值之间的距离方法进行分类

二、使用k-近邻算法改进约会网站的配对效果

 流程:

(1)收集数据:提供文本文件。

(2)准备数据:使用Python解析文本文件。

(3)分析数据:使用Matplotlib画二维扩散图。

(4)训练算法:此步骤不适用于k-近邻算法。

(5)测试算法:使用海伦提供的部分数据作为测试样本。

         测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际           类别不同,则标记为一个错误。

(6)使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己           喜欢的类型。

2.1  准备数据:从文本文件中解析数据

在文本文件datingTestSet2.txt中存放了海伦收集的约会数据,包含三种特征:每年获得的飞行常客里程数、玩视频游戏所耗时间百分比、每周消费的冰淇淋公升数。

datingTestSet2.txt文件下载链接:https://pan.baidu.com/s/1-EoL3g9aTLH2U6UtchYAJQ?pwd=welw 提取码:welw

文件中每个样本数据占据一行,前三个属性为特征,末尾数字表示喜欢程度,共1000行。

在我们的kNN.py中加入函数file2matrix对datingTestSet文件进行数据解析,代码如下:

def file2matrix(filename) : #filename是我们要解析的文件地址
    fr=open(filename)  
    arrayolines=fr.readlines()  #读取所有行作为字符串并返回存储在列表中
    numberoflines=len(arrayolines)  #获取列表的长度,即文件的行数
    returnmat=zeros((numberoflines,3))  #构建一个numberoflines行,3列的二维数组,其中元素用0填充
    classlabelvector=[]  #创建一个空列表 classlabelvector,用于存储每行数据的类别标签。
    index=0   #初始化索引变量 index,用于迭代数组的行。
    for line in arrayolines:  #遍历每一行
        line=line.strip() #去除字符串 line 的首尾空格和换行符。
        listfromline=line.split('\t')  #将字符串 line 按制表符 \t 进行分割,并将分割后的结果存储在列表 listfromline 中。
        returnmat[index,:]=listfromline[0:3]  #将列表 listfromline 的前3个元素(对应一行数据的特征值)赋值给 returnmat 的第 index 行。
        classlabelvector.append(int(listfromline[-1]))  #将列表 listfromline 的最后一个元素(对应一行数据的类别标签)转换为整数类型,并添加到 classlabelvector 列表中。
        index+=1  #索引变量 index 自增,用于指示下一行的位置。
    return returnmat,classlabelvector #返回构建好的特征矩阵 returnmat(存放三个特征值信息) 和类别标签列表 classlabelvector。

问题与解决:(1)运行过程中会报错,文件无法打开。 解决:datingTestSet2.txt文件要与当前的kNN.py在一个包中filename才能按书上的直接输入相对路径。否则要输入文件的完整路径。

通过这段代码我们可以获取到海伦提供数据对应的特征矩阵和每个样本对应标签构成的列表

接着打开Python命令行环境进行验证,在Python命令行环境中输入下面命令:

我们将得到文件分析后返回的特征值矩阵:

再输入: 

 我们可以得到每个样本对应的标签:

至此,数据解析工作完成。

问题与解决:

(1)代码运行过程中会报错,文件无法打开。

解决:datingTestSet2.txt文件要与当前的kNN.py在一个包中filename才能按书上的直接输入相对路径。否则要输入文件的完整路径。

(2) 打开Python命令行环境有问题。 

解决:Linux/Max OS终端内可以直接输入python,在Windows命令提示符下需要输入自己python所存放的地址路径例如(C:\Python2.6\python.exe),若不知道自己python所存放路径可以通过输入‘where python’进行查看。若不能使用也可以打开一个新的py文件import kNN进行相应操作如:

2.2分析数据:使用Matplotlib创建散点图

使用Matplotlib制作原始数据的散点图,这里以每年获取得的飞行常客里程数和玩视频游戏所耗时间百分比两个特征值为例提供实现代码:

import kNN
import numpy
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  #确保在图中显示中文时不会出现乱码
datingdatamat,datingglabels=kNN.file2matrix('C:\\Users\\86180\\Desktop\\MLiA_SourceCode\\machinelearninginaction\\Ch02\\datingTestSet2.txt')
n = 1000 #样本总数
x1 = []; y1 = []  #存放不喜欢数据点对应的横纵坐标
x2 = []; y2 = []  #存放魅力一般数据点对应的横纵坐标
x3 = []; y3 = []  #存放极具魅力数据的对应的横纵坐标

for i in range(n):  #循环判断每个样本属于哪个类型并存放入相应的横纵坐标
    if(datingglabels[i]==1):  #表示不喜欢的标签类
        x1.append(datingdatamat[i,0])  #在特征矩阵中找到改标签类对应的‘每年获得的飞行常客里程数’对应的特征值放入该类对应的横轴坐标数组中
        y1.append(datingdatamat[i,1])  #在特征矩阵中找到改标签类对应的‘玩视频游戏所耗时间百分比’对应的特征值放入该类对应的横轴坐标数组中

    elif(datingglabels[i]==2):  #表示魅力一般的标签类
        x2.append(datingdatamat[i,0])
        y2.append(datingdatamat[i,1])
    elif(datingglabels[i]==3):  #表示极具魅力的标签类
        x3.append(datingdatamat[i,0])
        y3.append(datingdatamat[i,1])

fig = plt.figure()  #创建一个新的图形
ax = fig.add_subplot(111)  #添加一个子图,参数(111)表示1行1列的子图中的第一个位置
type1 = ax.scatter(x1, y1, s=20, c='red')   #绘制类别1的数据点,颜色为红色,标记大小为20
type2 = ax.scatter(x2, y2, s=30, c='green')  #绘制类别2的数据点,颜色为绿色,标记大小为30
type3 = ax.scatter(x3, y3, s=50, c='blue')  #绘制类别3的数据点,颜色为蓝色,标记大小为50
ax.legend([type1, type2, type3], ["不喜欢", "魅力一般", "极具魅力"])  #添加图例,将每个类别的散点标记与其对应的类别名称关联起来
plt.xlabel('每年获取得的飞行常客里程数')  #添加x轴标签
plt.ylabel('玩视频游戏所耗时间百分比')  #添加y轴标签
plt.show()  #显示绘制的图形

这里的图用每年获取得的飞行常客里程数作为横坐标,玩视频游戏所耗时间百分比作为纵坐标,其他相关代码解释如代码中的注释所示。

运行之后我们可以得到这两个特征值对应的相关散点图:

同理我们也可以得到另外两幅散点图,(每年获得的飞行常客里程数,每周消费的冰淇淋公升数)和(玩视频游戏所耗时间百分比,每周消费的冰淇淋公升数) 图例如下:

                                   (每年获得的飞行常客里程数,每周消费的冰淇淋公升数)图

                        (玩视频游戏所耗时间百分比,每周消费的冰淇淋公升数)图

结论:通过Matplotlib创建散点图我们可以直观的观察到不同数据样本点标签类与数据特征值的关联,过观察散点图的分布,可以发现数据是否存在明显的聚类趋势。不同的类别是否在特征空间中形成了不同的聚类区域,或者它们是否有重叠部分。每个散点图显示两个特征之间的关系,以便更好地理解它们之间的线性或非线性关系。在分类问题中,散点图可以帮助我们直观地理解分类器的决策边界。我们可以将不同类别的数据点绘制在同一张图中,并在图中绘制分类器的决策边界,以便观察分类器对不同类别的区分能力。例如,极具魅力的数据点聚集分布在每年获得的飞行常客里程数范围2000~4000、玩视频游戏所耗时间百分比范围8~13等。

问题与解决:

(1)Python命令行环境中无法使用‘fig = plt.figure()’

解决:重新打卡了一个.py文件,在.py文件中输入相同代码能够正常运行

(2)修改图像的x、y轴标签时出现乱码。 

解决:网络上查询了相关资料信息,发现在开头加上plt.rcParams['font.sans-serif'] = ['SimHei']这段代码后可以确保在图中显示中文时不会出现乱码。

2.3  准备数据:归一化数值

1. 原因:我们很容易发现,在计算各个样本点之间的距离时,数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对应计算结果的影响将远远大于其他两个特征的影响。但这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。

2. 处理方法:在处理这种不同取值范围的特征值时,通常采用将数值归一化的方法,如将取值范围处理为0到1或者-1到1之间。下面的公式(最大最小值归一化)可以将任意取值范围的特征值转化 为0到1区间内的值:

                                        newValue = (oldValue-min) / (max-min)

其中min和max分别是数据集中的最小特征值和最大特征值。

3.代码实现:

def autoNorm(dataset):
    minvals=dataset.min(0) #这一行计算了数据集 dataset 每一列的最小值。min 函数中的参数 0 表示沿着第一个轴(列)计算最小值。
    maxvals=dataset.max(0) #这行计算了数据集 dataset 每一列的最大值
    ranges=maxvals-minvals #这一行计算了每个特征(列)的取值范围,通过将每列的最大值减去最小值得到。  前三句返回的结果都是NumPy数组类型
    normdataset=zeros(shape(dataset))  #这行初始化了一个数组 normdataset,其形状与输入的 dataset 相同,用零填充。这个数组将用来存储归一化后的值。
    m=dataset.shape[0]  #这行获取了 dataset 数组的行数,并将其赋值给变量 m
    normdataset=dataset-tile(minvals,(m,1))  #从 dataset 数组的每个元素中减去对应的最小值 minvals。tile 函数用于创建与 dataset 相同形状的矩阵,其中每行都与 minvals 相同。
    normdataset=normdataset/tile(ranges,(m,1))  #这行将 normdataset 中的每个元素除以对应特征的范围(由 ranges 指定)。同样,tile 函数用于创建与 dataset 相同形状的矩阵,其中每行都与 ranges 相同。
    return normdataset,ranges,minvals  #返回归一化后的数据集 (normdataset)、每个特征的范围 (ranges),以及每个特征的最小值 (minvals)

相关理解如代码注释中所示。

4.效果演示:

在python命令提示符中输入:

可以得到normmat即归一化后的特征值数据集:

再向命令提示符中分别输入ranges、minvals就可以得到每个特征的范围 (ranges),以及每个特征的最小值 (minvals):

5. 结论:数据归一化是将数据按比例缩放,使其落入特定的范围内

优点:

  1. 消除量级影响:使得不同特征具有相同的量级,避免因特征量级差异导致的模型参数难以确定或模型表现不佳的问题。

  2. 提高模型稳定性:归一化可以减少特征值的波动范围,使得模型对数据中的噪声和异常值的影响减弱,提高了模型的稳定性。

缺点:

  1. 信息损失:对数据进行归一化可能会导致一定程度的信息损失,因为归一化后,原始数据的分布和变异性可能发生变化,丢失了部分数据的原始含义。

  2. 对异常值敏感:某些归一化方法对异常值敏感,如最小-最大缩放,如果数据中存在异常值,可能会导致归一化后的结果不准确。

 但在这次机器学习的约会网站改进的实验中,归一化处理帮助我们削弱每年获取的飞行常客里程数对于计算结果的过大影响,使得三个特征值是等权重影响。虽然增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。

2.4  测试算法:作为完整程序验证分类器

1.制作分类器:

机器学习算法一个重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为戌年样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。

代码里我们定义一个计数器变量,每次分类器错误地分类数据,计数器就加1,程序执行完成之后计数器的结果除以数据点总数即是错误率,来检测分类器的性能。

代码实现:

def datingclasstest():
    horatio=0.10 #设置测试数据集的比例,这里设置为 10%
    datingdatamat,datinglabels=file2matrix('C:\\Users\\86180\\Desktop\\MLiA_SourceCode\\machinelearninginaction\\Ch02\\datingTestSet2.txt') #调用 file2matrix 函数读取数据集,并将特征矩阵和标签分别赋值给 datingdatamat 和 datinglabels。
    normat,ranges,minvals=autoNorm(datingdatamat) #对数据集进行归一化处理,并返回归一化后的数据集、范围和最小值。
    m=normat.shape[0] #获取归一化后的数据集的行数
    numtestvecs=int(m*horatio) #计算测试向量的数量,即测试数据集的样本数
    errorcount=0.0 #初始化错误分类计数器。
    for i in range(numtestvecs): #遍历测试数据集。
        classifierresult=classify0(normat[i,:] ,normat[numtestvecs:m,:],datinglabels[numtestvecs:m],3)  #调用 classify0 函数对测试数据进行分类,并返回分类结果。normat[i,:]是测试数据点,normat[numtestvecs:m,:]剩余百分之九十的归一化后数据特征集,datinglabels[numtestvecs:m]是剩余百分之九十的标签,3是3NN.
        print("the classifier came back with: %d, the real answer is : %d" % (classifierresult, datinglabels[i]))
        if(classifierresult!=datinglabels[i]):errorcount+=1.0  #如果分类结果与真实标签不一致,则错误分类计数器加一。
    print("the total error rate is: %f"%(errorcount/float(numtestvecs)))  #错误分类数量与总测试样本数之比作为错误率

相关理解如代码中注释所示。

2. 执行分类器:

在python命令提示符下输入

调用kNN模块中的函数datingclasstest函数执行分类器后,我们将得到对测试样例的预测结果,将之与正确答案进行对比,最终得到我们的错误率。

计算得到我们最终的错误率为5%。

3.结论:

测试分类器取了样本总数的10%作为测试数据,通过对比预测结果和正确答案判断预测是否出错,最后将错误总数除以测试数据总数得到我们最终错误率,5%的错误率是一个不错的结果,这个例子表明我们可以正确地预测分类。

2.5   使用算法:构建完整可用系统

1.编写约会网站预测函数:

编写程序通过用户的输入信息来判断预测对对方的喜欢程度,将代码函数加入到kNN.py文件中,具体代码实现如下:

def classifyperson():
    resultlist=['not at all','in small doses','in large doses']  #创建一个列表,包含了对应三种不同标签的描述。
    percenttats=float(input("percentage of time spent playing video games?"))  #获取用户输入的百分比时间(float 类型)。
    ffmiles=float(input("frequent flier miles earned per year?"))  #获取用户输入的每年飞行里程(float 类型)。
    icecream=float(input("liters of ice cream consumed per year?")) #获取用户输入的每年消费的冰淇淋升数(float 类型)。
    datingdatamat,datinglabels=file2matrix('C:\\Users\\86180\\Desktop\\MLiA_SourceCode\\machinelearninginaction\\Ch02\\datingTestSet2.txt')##加载数据集并解析为特征矩阵和标签列表
    normat,ranges,minvals=autoNorm(datingdatamat)  #对特征矩阵进行归一化处理,返回归一化后的矩阵、范围和最小值。
    inarr=array([ffmiles,percenttats,icecream])  #将用户输入的数据组成一个 NumPy 数组 inarr。
    classifierresult=classify0((inarr-minvals)/ranges,normat,datinglabels,3)  #对用户输入的数据进行分类预测,采用归一化后的数据进行预测。
    print("You will probably like this person:",resultlist[classifierresult-1]) #输出预测的结果,即预测用户是否可能喜欢这个人。

相关理解如代码注释中所示。

2. 效果演示:

如图所示,输入kNN.classifyperson()调用算法,根据提示输入相关信息,最后将返回一个预测结果。

三、实验总结

k最近邻(kNN)算法是一种简单而有效的机器学习算法,常用于分类和回归问题。基本思路就是计算测试数据与样本的距离,取得距离最近的前k个数据的标签类,将其中出现次数最多的标签类作为测试数据的预测结果。

对于k近邻算法有如下结论:

优点:精度高、对异常值不敏感、无数据输入假定。

缺点:计算复杂度高、空间复杂度高。

适用数据范围:数值型和标称型。

本次实验中,我们通过对kNN算法在约会网站数据上的应用进行了深入研究与实践。通过数据准备、分析和预处理等步骤,我们有效地解决了数据处理中的一些常见问题。在实验过程中,我深刻认识到数据质量和特征选择对算法性能的重要影响。合适的数据预处理和特征工程能够显著提高模型的准确性。此外,通过实验也发现了数据可视化在理解和分析数据方面的重要性,散点图等可视化工具能够帮助我们直观地观察数据的分布规律和特征之间的关系,为进一步优化算法提供了重要参考。

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值